#!/usr/pkg/bin/python3.11
# coding=utf-8
#
# Copyright (C) 2007 John Beard john.j.beard@gmail.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
"""
This extension allows you to draw a Cartesian grid in Inkscape.

There is a wide range of options including subdivision, subsubdivions and
logarithmic scales. Custom line widths are also possible.

All elements are grouped with similar elements (eg all x-subdivs)
"""

from math import log

import inkex
from inkex import Group, PathElement, Rectangle


def draw_line(x1, y1, x2, y2, width, name, parent):
    """Draw an SVG line"""
    line = parent.add(PathElement())
    line.style = {"stroke": "#000000", "stroke-width": str(width), "fill": "none"}
    line.path = "M {},{} L {},{}".format(x1, y1, x2, y2)
    line.label = name


def draw_rect(x, y, w, h, width, fill, name, parent):
    """Draw an SVG Rectangle"""
    rect = parent.add(Rectangle(x=str(x), y=str(y), width=str(w), height=str(h)))
    rect.style = {"stroke": "#000000", "stroke-width": str(width), "fill": fill}
    rect.label = name


class GridCartesian(inkex.GenerateExtension):
    def add_arguments(self, pars):
        pars.add_argument("--border_th", type=float, default=1.0)
        pars.add_argument("--border_th_unit", default="px")
        pars.add_argument("--tab", default="x_tab")
        pars.add_argument("--x_divs", type=int, default=6)
        pars.add_argument("--dx", type=float, default=5.0)
        pars.add_argument("--dx_unit", default="mm")
        pars.add_argument("--x_subdivs", type=int, default=1)
        pars.add_argument("--x_log", type=inkex.Boolean, default=False)
        pars.add_argument("--x_subsubdivs", type=int, default=1)
        pars.add_argument("--x_half_freq", type=int, default=1)
        pars.add_argument("--x_divs_th", type=float, default=1.0)
        pars.add_argument("--x_subdivs_th", type=float, default=0.5)
        pars.add_argument("--x_subsubdivs_th", type=float, default=0.3)
        pars.add_argument("--x_div_unit", default="px")
        pars.add_argument("--y_divs", type=int, default=5)
        pars.add_argument("--dy", type=float, default=5.0)
        pars.add_argument("--dy_unit", default="mm")
        pars.add_argument("--y_subdivs", type=int, default=1)
        pars.add_argument("--y_log", type=inkex.Boolean, default=False)
        pars.add_argument("--y_subsubdivs", type=int, default=5)
        pars.add_argument("--y_half_freq", type=int, default=1)
        pars.add_argument("--y_divs_th", type=float, default=1.0)
        pars.add_argument("--y_subdivs_th", type=float, default=0.5)
        pars.add_argument("--y_subsubdivs_th", type=float, default=0.3)
        pars.add_argument("--y_div_unit", default="px")

    def generate(self):
        self.options.border_th = self.svg.unittouu(
            str(self.options.border_th) + self.options.border_th_unit
        )

        self.options.dx = self.svg.unittouu(str(self.options.dx) + self.options.dx_unit)
        self.options.x_divs_th = self.svg.unittouu(
            str(self.options.x_divs_th) + self.options.x_div_unit
        )
        self.options.x_subdivs_th = self.svg.unittouu(
            str(self.options.x_subdivs_th) + self.options.x_div_unit
        )
        self.options.x_subsubdivs_th = self.svg.unittouu(
            str(self.options.x_subsubdivs_th) + self.options.x_div_unit
        )

        self.options.dy = self.svg.unittouu(str(self.options.dy) + self.options.dy_unit)
        self.options.y_divs_th = self.svg.unittouu(
            str(self.options.y_divs_th) + self.options.y_div_unit
        )
        self.options.y_subdivs_th = self.svg.unittouu(
            str(self.options.y_subdivs_th) + self.options.y_div_unit
        )
        self.options.y_subsubdivs_th = self.svg.unittouu(
            str(self.options.y_subsubdivs_th) + self.options.y_div_unit
        )

        # find the pixel dimensions of the overall grid
        ymax = self.options.dy * self.options.y_divs
        xmax = self.options.dx * self.options.x_divs

        # Embed grid in group
        # Put in in the centre of the current view

        grid = Group.new("GridCartesian:X{0.x_divs}:Y{0.y_divs}".format(self.options))

        (pos_x, pos_y) = self.svg.namedview.center
        grid.transform.add_translate(pos_x - xmax / 2.0, pos_y - ymax / 2.0)

        # Group for major x gridlines
        majglx = grid.add(Group.new("MajorXGridlines"))
        # Group for major y gridlines
        majgly = grid.add(Group.new("MajorYGridlines"))

        # Group for minor x gridlines
        if self.options.x_subdivs > 1:  # if there are any minor x gridlines
            minglx = grid.add(Group.new("MinorXGridlines"))

        # Group for subminor x gridlines
        if self.options.x_subsubdivs > 1:  # if there are any minor minor x gridlines
            mminglx = grid.add(Group.new("SubMinorXGridlines"))

        # Group for minor y gridlines
        if self.options.y_subdivs > 1:  # if there are any minor y gridlines
            mingly = grid.add(Group.new("MinorYGridlines"))

        # Group for subminor y gridlines
        if self.options.y_subsubdivs > 1:  # if there are any minor minor x gridlines
            mmingly = grid.add(Group.new("SubMinorYGridlines"))

        draw_rect(
            0, 0, xmax, ymax, self.options.border_th, "none", "Border", grid
        )  # border rectangle

        # DO THE X DIVISIONS======================================
        sd = self.options.x_subdivs  # sub divs per div
        ssd = self.options.x_subsubdivs  # subsubdivs per subdiv

        for i in range(0, self.options.x_divs):  # Major x divisions
            if i > 0:  # don't draw first line (we made a proper border)
                draw_line(
                    self.options.dx * i,
                    0,
                    self.options.dx * i,
                    ymax,
                    self.options.x_divs_th,
                    "MajorXDiv" + str(i),
                    majglx,
                )

            if self.options.x_log:  # log x subdivs
                for j in range(1, sd):
                    if j > 1:  # the first loop is only for subsubdivs
                        draw_line(
                            self.options.dx * (i + log(j, sd)),
                            0,
                            self.options.dx * (i + log(j, sd)),
                            ymax,
                            self.options.x_subdivs_th,
                            "MinorXDiv" + str(i) + ":" + str(j),
                            minglx,
                        )

                    for k in range(1, ssd):  # subsub divs
                        if (j <= self.options.x_half_freq) or (
                            k % 2 == 0
                        ):  # only draw half the subsubdivs past the half-freq point
                            if (ssd % 2 > 0) and (
                                j > self.options.y_half_freq
                            ):  # half frequency won't work with odd numbers of subsubdivs,
                                ssd2 = ssd + 1  # make even
                            else:
                                ssd2 = ssd  # no change
                            draw_line(
                                self.options.dx * (i + log(j + k / float(ssd2), sd)),
                                0,
                                self.options.dx * (i + log(j + k / float(ssd2), sd)),
                                ymax,
                                self.options.x_subsubdivs_th,
                                "SubminorXDiv" + str(i) + ":" + str(j) + ":" + str(k),
                                mminglx,
                            )

            else:  # linear x subdivs
                for j in range(0, sd):
                    if (
                        j > 0
                    ):  # not for the first loop (this loop is for the subsubdivs before the first subdiv)
                        draw_line(
                            self.options.dx * (i + j / float(sd)),
                            0,
                            self.options.dx * (i + j / float(sd)),
                            ymax,
                            self.options.x_subdivs_th,
                            "MinorXDiv" + str(i) + ":" + str(j),
                            minglx,
                        )

                    for k in range(1, ssd):  # subsub divs
                        draw_line(
                            self.options.dx * (i + (j * ssd + k) / (float(sd) * ssd)),
                            0,
                            self.options.dx * (i + (j * ssd + k) / (float(sd) * ssd)),
                            ymax,
                            self.options.x_subsubdivs_th,
                            "SubminorXDiv" + str(i) + ":" + str(j) + ":" + str(k),
                            mminglx,
                        )

        # DO THE Y DIVISIONS========================================
        sd = self.options.y_subdivs  # sub divs per div
        ssd = self.options.y_subsubdivs  # subsubdivs per subdiv

        for i in range(0, self.options.y_divs):  # Major y divisions
            if i > 0:  # don't draw first line (we will make a border)
                draw_line(
                    0,
                    self.options.dy * i,
                    xmax,
                    self.options.dy * i,
                    self.options.y_divs_th,
                    "MajorYDiv" + str(i),
                    majgly,
                )

            if self.options.y_log:  # log y subdivs
                for j in range(1, sd):
                    if j > 1:  # the first loop is only for subsubdivs
                        draw_line(
                            0,
                            self.options.dy * (i + 1 - log(j, sd)),
                            xmax,
                            self.options.dy * (i + 1 - log(j, sd)),
                            self.options.y_subdivs_th,
                            "MinorXDiv" + str(i) + ":" + str(j),
                            mingly,
                        )

                    for k in range(1, ssd):  # subsub divs
                        if (j <= self.options.y_half_freq) or (
                            k % 2 == 0
                        ):  # only draw half the subsubdivs past the half-freq point
                            if (ssd % 2 > 0) and (
                                j > self.options.y_half_freq
                            ):  # half frequency won't work with odd numbers of subsubdivs,
                                ssd2 = ssd + 1
                            else:
                                ssd2 = ssd  # no change
                            draw_line(
                                0,
                                self.options.dx
                                * (i + 1 - log(j + k / float(ssd2), sd)),
                                xmax,
                                self.options.dx
                                * (i + 1 - log(j + k / float(ssd2), sd)),
                                self.options.y_subsubdivs_th,
                                "SubminorXDiv" + str(i) + ":" + str(j) + ":" + str(k),
                                mmingly,
                            )
            else:  # linear y subdivs
                for j in range(0, self.options.y_subdivs):
                    if (
                        j > 0
                    ):  # not for the first loop (this loop is for the subsubdivs before the first subdiv)
                        draw_line(
                            0,
                            self.options.dy * (i + j / float(sd)),
                            xmax,
                            self.options.dy * (i + j / float(sd)),
                            self.options.y_subdivs_th,
                            "MinorXYiv" + str(i) + ":" + str(j),
                            mingly,
                        )

                    for k in range(1, ssd):  # subsub divs
                        draw_line(
                            0,
                            self.options.dy * (i + (j * ssd + k) / (float(sd) * ssd)),
                            xmax,
                            self.options.dy * (i + (j * ssd + k) / (float(sd) * ssd)),
                            self.options.y_subsubdivs_th,
                            "SubminorXDiv" + str(i) + ":" + str(j) + ":" + str(k),
                            mmingly,
                        )

        return grid


if __name__ == "__main__":
    GridCartesian().run()
