#!/usr/pkg/bin/python3.13
# 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 polar grid in Inkscape.
There is a wide range of options including subdivision and labels.
"""

from math import cos, log, pi, sin

import inkex
from inkex import Group, Circle, TextElement


def draw_circle(r, cx, cy, width, fill, name, parent):
    """Draw an SVG circle"""
    circle = parent.add(Circle(cx=str(cx), cy=str(cy), r=str(r)))
    circle.style = {"stroke": "#000000", "stroke-width": str(width), "fill": fill}
    circle.label = name


def draw_line(x1, y1, x2, y2, width, name, parent):
    """Draw an SVG line"""
    line = parent.add(inkex.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_label(x, y, string, font_size, name, parent):
    """Draw a centered label"""
    label = parent.add(TextElement(x=str(x), y=str(y)))
    label.style = {
        "text-align": "center",
        "vertical-align": "top",
        "text-anchor": "middle",
        "font-size": str(font_size) + "px",
        "fill-opacity": "1.0",
        "stroke": "none",
        "font-weight": "normal",
        "font-style": "normal",
        "fill": "#000000",
    }
    label.text = string
    label.label = name


class GridPolar(inkex.GenerateExtension):
    def add_arguments(self, pars):
        pars.add_argument("--tab")
        pars.add_argument("--r_divs", type=int, default=5, help="Circular Divisions")
        pars.add_argument(
            "--dr", type=float, default=50, help="Circular Division Spacing"
        )
        pars.add_argument(
            "--r_subdivs", type=int, default=3, help="Subdivisions per Major div"
        )
        pars.add_argument(
            "--r_log", type=inkex.Boolean, default=False, help="Logarithmic subdiv"
        )
        pars.add_argument(
            "--r_divs_th", type=float, default=2, help="Major Line thickness"
        )
        pars.add_argument(
            "--r_subdivs_th", type=float, default=1, help="Minor Line thickness"
        )
        pars.add_argument("--a_divs", type=int, default=24, help="Angle Divisions")
        pars.add_argument(
            "--a_divs_cent", type=int, default=4, help="Angle Divisions at Centre"
        )
        pars.add_argument(
            "--a_subdivs", type=int, default=1, help="Angcular Subdivisions"
        )
        pars.add_argument(
            "--a_subdivs_cent", type=int, default=2, help="Angular Subdivisions end"
        )
        pars.add_argument(
            "--a_divs_th", type=float, default=2, help="Major Angular thickness"
        )
        pars.add_argument(
            "--a_subdivs_th", type=float, default=1, help="Minor Angular thickness"
        )
        pars.add_argument(
            "--c_dot_dia", type=float, default=5.0, help="Diameter of Centre Dot"
        )
        pars.add_argument(
            "--a_labels", default="none", help="The kind of labels to apply"
        )
        pars.add_argument(
            "--a_label_size", type=int, default=18, help="Pixel size of the labels"
        )
        pars.add_argument(
            "--a_label_outset", type=float, default=24, help="Label Radial outset"
        )

    def generate(self):
        self.options.dr = self.svg.unittouu(str(self.options.dr) + "px")
        self.options.r_divs_th = self.svg.unittouu(str(self.options.r_divs_th) + "px")
        self.options.r_subdivs_th = self.svg.unittouu(
            str(self.options.r_subdivs_th) + "px"
        )
        self.options.a_divs_th = self.svg.unittouu(str(self.options.a_divs_th) + "px")
        self.options.a_subdivs_th = self.svg.unittouu(
            str(self.options.a_subdivs_th) + "px"
        )
        self.options.c_dot_dia = self.svg.unittouu(str(self.options.c_dot_dia) + "px")
        self.options.a_label_size = self.svg.unittouu(
            str(self.options.a_label_size) + "px"
        )
        self.options.a_label_outset = self.svg.unittouu(
            str(self.options.a_label_outset) + "px"
        )

        # Embed grid in group
        grid = Group.new("GridPolar:R{0.r_divs}:A{0.a_divs}".format(self.options))

        (pos_x, pos_y) = self.svg.namedview.center
        grid.transform.add_translate(pos_x, pos_y)

        dr = self.options.dr  # Distance between neighbouring circles
        dtheta = (
            2 * pi / self.options.a_divs_cent
        )  # Angular change between adjacent radial lines at centre
        rmax = self.options.r_divs * dr

        # Create SVG circles
        for i in range(1, self.options.r_divs + 1):
            draw_circle(
                i * dr,
                0,
                0,  # major div circles
                self.options.r_divs_th,
                "none",
                "MajorDivCircle" + str(i) + ":R" + str(i * dr),
                grid,
            )

            if self.options.r_log:  # logarithmic subdivisions
                for j in range(2, self.options.r_subdivs):
                    draw_circle(
                        i * dr
                        - (1 - log(j, self.options.r_subdivs))
                        * dr,  # minor div circles
                        0,
                        0,
                        self.options.r_subdivs_th,
                        "none",
                        "MinorDivCircle" + str(i) + ":Log" + str(j),
                        grid,
                    )
            else:  # linear subdivs
                for j in range(1, self.options.r_subdivs):
                    draw_circle(
                        i * dr - j * dr / self.options.r_subdivs,  # minor div circles
                        0,
                        0,
                        self.options.r_subdivs_th,
                        "none",
                        "MinorDivCircle" + str(i) + ":R" + str(i * dr),
                        grid,
                    )

        if (
            self.options.a_divs == self.options.a_divs_cent
        ):  # the lines can go from the centre to the edge
            for i in range(0, self.options.a_divs):
                draw_line(
                    0,
                    0,
                    rmax * sin(i * dtheta),
                    rmax * cos(i * dtheta),
                    self.options.a_divs_th,
                    "RadialGridline" + str(i),
                    grid,
                )

        else:  # we need separate lines
            for i in range(
                0, self.options.a_divs_cent
            ):  # lines that go to the first circle
                draw_line(
                    0,
                    0,
                    dr * sin(i * dtheta),
                    dr * cos(i * dtheta),
                    self.options.a_divs_th,
                    "RadialGridline" + str(i),
                    grid,
                )

            dtheta = (
                2 * pi / self.options.a_divs
            )  # work out the angle change for outer lines

            for i in range(
                0, self.options.a_divs
            ):  # lines that go from there to the edge
                draw_line(
                    dr * sin(i * dtheta + pi / 2.0),
                    dr * cos(i * dtheta + pi / 2.0),
                    rmax * sin(i * dtheta + pi / 2.0),
                    rmax * cos(i * dtheta + pi / 2.0),
                    self.options.a_divs_th,
                    "RadialGridline" + str(i),
                    grid,
                )

        if self.options.a_subdivs > 1:  # draw angular subdivs
            for i in range(0, self.options.a_divs):  # for each major division
                for j in range(1, self.options.a_subdivs):  # draw the subdivisions
                    angle = (
                        i * dtheta - j * dtheta / self.options.a_subdivs + pi / 2.0
                    )  # the angle of the subdivion line
                    draw_line(
                        dr * self.options.a_subdivs_cent * sin(angle),
                        dr * self.options.a_subdivs_cent * cos(angle),
                        rmax * sin(angle),
                        rmax * cos(angle),
                        self.options.a_subdivs_th,
                        "RadialMinorGridline" + str(i),
                        grid,
                    )

        if self.options.c_dot_dia != 0:  # if a non-zero diameter, draw the centre dot
            draw_circle(
                self.options.c_dot_dia / 2.0, 0, 0, 0, "#000000", "CentreDot", grid
            )

        if self.options.a_labels == "deg":
            label_radius = rmax + self.options.a_label_outset  # radius of label centres
            label_size = self.options.a_label_size
            numeral_size = (
                0.73 * label_size
            )  # numerals appear to be 0.73 the height of the nominal pixel size of the font in "Sans"

            for i in range(
                0, self.options.a_divs
            ):  # self.options.a_divs): #radial line labels
                draw_label(
                    sin(i * dtheta + pi / 2.0)
                    * label_radius,  # 0 at the RHS, mathematical style
                    cos(i * dtheta + pi / 2.0) * label_radius
                    + numeral_size / 2.0,  # centre the text vertically
                    str(i * 360 / self.options.a_divs),
                    label_size,
                    "Label" + str(i),
                    grid,
                )

        return grid


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