#!/usr/bin/python3.11
# coding=utf-8
#
# Copyright (C) 2007 Terry Brown, terry_n_brown@yahoo.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.
#
from __future__ import absolute_import, unicode_literals

import inkex
from inkex import Use, Rectangle
from inkex.base import SvgOutputMixin
from inkex.localization import inkex_gettext as _


class Nup(inkex.OutputExtension, SvgOutputMixin):
    """N-up Layout generator"""

    def add_arguments(self, pars):
        pars.add_argument("--unit", default="px")
        pars.add_argument("--rows", type=int, default=2)
        pars.add_argument("--cols", type=int, default=2)
        pars.add_argument("--paddingTop", type=float)
        pars.add_argument("--paddingBottom", type=float)
        pars.add_argument("--paddingLeft", type=float)
        pars.add_argument("--paddingRight", type=float)
        pars.add_argument("--marginTop", type=float)
        pars.add_argument("--marginBottom", type=float)
        pars.add_argument("--marginLeft", type=float)
        pars.add_argument("--marginRight", type=float)
        pars.add_argument("--pgMarginTop", type=float)
        pars.add_argument("--pgMarginBottom", type=float)
        pars.add_argument("--pgMarginLeft", type=float)
        pars.add_argument("--pgMarginRight", type=float)
        pars.add_argument("--pgSizeX", type=float)
        pars.add_argument("--pgSizeY", type=float)
        pars.add_argument("--sizeX", type=float)
        pars.add_argument("--sizeY", type=float)
        pars.add_argument("--calculateSize", type=inkex.Boolean, default=True)
        pars.add_argument("--showHolder", type=inkex.Boolean, default=True)
        pars.add_argument("--showCrosses", type=inkex.Boolean, default=True)
        pars.add_argument("--showInner", type=inkex.Boolean, default=True)
        pars.add_argument("--showOuter", type=inkex.Boolean, default=False)
        pars.add_argument("--showInnerBox", type=inkex.Boolean, default=False)
        pars.add_argument("--showOuterBox", type=inkex.Boolean, default=False)
        pars.add_argument("--tab")

    def save(self, stream):
        show_list = []
        for i in [
            "showHolder",
            "showCrosses",
            "showInner",
            "showOuter",
            "showInnerBox",
            "showOuterBox",
        ]:
            if getattr(self.options, i):
                show_list.append(i.lower().replace("show", ""))
        opt = self.options
        ret = self.generate_nup(
            unit=opt.unit,
            pgSize=(opt.pgSizeX, opt.pgSizeY),
            pgMargin=(
                opt.pgMarginTop,
                opt.pgMarginRight,
                opt.pgMarginBottom,
                opt.pgMarginLeft,
            ),
            num=(opt.rows, opt.cols),
            calculateSize=opt.calculateSize,
            size=(opt.sizeX, opt.sizeY),
            margin=(opt.marginTop, opt.marginRight, opt.marginBottom, opt.marginLeft),
            padding=(
                opt.paddingTop,
                opt.paddingRight,
                opt.paddingBottom,
                opt.paddingLeft,
            ),
            show=show_list,
        )
        if ret:
            stream.write(ret)

    def expandTuple(self, unit, x, length=4):
        try:
            iter(x)
        except:
            return None

        if len(x) != length:
            x *= 2
        if len(x) != length:
            raise Exception("expandTuple: requires 2 or 4 item tuple")
        try:
            return tuple(
                map(
                    lambda ev: (
                        self.svg.unittouu(str(eval(str(ev))) + unit)
                        / self.svg.unittouu("1px")
                    ),
                    x,
                )
            )
        except:
            return None

    def generate_nup(
        self,
        unit="px",
        pgSize=("8.5*96", "11*96"),
        pgMargin=(0, 0),
        pgPadding=(0, 0),
        num=(2, 2),
        calculateSize=True,
        size=None,
        margin=(0, 0),
        padding=(20, 20),
        show=["default"],
    ):
        """Generate the SVG.  Inputs are run through 'eval(str(x))' so you can use
        '8.5*72' instead of 612.  Margin / padding dimension tuples can be
        (top & bottom, left & right) or (top, right, bottom, left).

        Keyword arguments:
        pgSize -- page size, width x height
        pgMargin -- extra space around each page
        pgPadding -- added to pgMargin
        n -- rows x cols
        size -- override calculated size, width x height
        margin -- white space around each piece
        padding -- inner padding for each piece
        show -- list of keywords indicating what to show
                - 'crosses' - cutting guides
                - 'inner' - inner boundary
                - 'outer' - outer boundary
        """

        if "default" in show:
            show = set(show).union(["inner", "innerbox", "holder", "crosses"])

        pgMargin = self.expandTuple(unit, pgMargin)
        pgPadding = self.expandTuple(unit, pgPadding)
        margin = self.expandTuple(unit, margin)
        padding = self.expandTuple(unit, padding)

        pgSize = self.expandTuple(unit, pgSize, length=2)
        #    num = tuple(map(lambda ev: eval(str(ev)), num))

        if not pgMargin or not pgPadding:
            return inkex.errormsg(_("No padding or margin available."))

        page_edge = list(map(sum, zip(pgMargin, pgPadding)))

        top, right, bottom, left = 0, 1, 2, 3
        width, height = 0, 1
        rows, cols = 0, 1
        size = self.expandTuple(unit, size, length=2)
        if (
            size is None
            or calculateSize
            or len(size) < 2
            or size[0] == 0
            or size[1] == 0
        ):
            size = (
                (
                    pgSize[width]
                    - page_edge[left]
                    - page_edge[right]
                    - num[cols] * (margin[left] + margin[right])
                )
                / num[cols],
                (
                    pgSize[height]
                    - page_edge[top]
                    - page_edge[bottom]
                    - num[rows] * (margin[top] + margin[bottom])
                )
                / num[rows],
            )
        else:
            size = self.expandTuple(unit, size, length=2)

        # sep is separation between same points on pieces
        sep = (
            size[width] + margin[right] + margin[left],
            size[height] + margin[top] + margin[bottom],
        )

        style = "stroke:#000000;stroke-opacity:1;fill:none;fill-opacity:1;"

        padbox = Rectangle(
            x=str(page_edge[left] + margin[left] + padding[left]),
            y=str(page_edge[top] + margin[top] + padding[top]),
            width=str(size[width] - padding[left] - padding[right]),
            height=str(size[height] - padding[top] - padding[bottom]),
            style=style,
        )
        margbox = Rectangle(
            x=str(page_edge[left] + margin[left]),
            y=str(page_edge[top] + margin[top]),
            width=str(size[width]),
            height=str(size[height]),
            style=style,
        )

        doc = self.get_template(width=pgSize[width], height=pgSize[height])
        svg = doc.getroot()

        def make_clones(under, to):
            for row in range(0, num[rows]):
                for col in range(0, num[cols]):
                    if row == 0 and col == 0:
                        continue
                    use = under.add(Use())
                    use.set("xlink:href", "#" + to)
                    use.transform.add_translate(col * sep[width], row * sep[height])

        # guidelayer #####################################################
        if {"inner", "outer"}.intersection(show):
            layer = svg.add(inkex.Layer.new("Guide Layer"))
            if "inner" in show:
                ibox = layer.add(padbox.copy())
                ibox.style["stroke"] = "#8080ff"
                ibox.set("id", "innerguide")
                make_clones(layer, "innerguide")
            if "outer" in show:
                obox = layer.add(margbox.copy())
                obox.style["stroke"] = "#8080ff"
                obox.set("id", "outerguide")
                make_clones(layer, "outerguide")

        # crosslayer #####################################################
        if {"crosses"}.intersection(show):
            layer = svg.add(inkex.Layer.new("Cut Layer"))

            if "crosses" in show:
                crosslen = 12
                group = layer.add(inkex.Group(id="cross"))
                x, y = 0, 0
                path = "M%f %f" % (
                    x + page_edge[left] + margin[left],
                    y + page_edge[top] + margin[top] - crosslen,
                )
                path += " L%f %f" % (
                    x + page_edge[left] + margin[left],
                    y + page_edge[top] + margin[top] + crosslen,
                )
                path += " M%f %f" % (
                    x + page_edge[left] + margin[left] - crosslen,
                    y + page_edge[top] + margin[top],
                )
                path += " L%f %f" % (
                    x + page_edge[left] + margin[left] + crosslen,
                    y + page_edge[top] + margin[top],
                )
                group.add(
                    inkex.PathElement(
                        style=style + "stroke-width:0.05", d=path, id="crossmarker"
                    )
                )
                for row in 0, 1:
                    for col in 0, 1:
                        if row or col:
                            cln = group.add(Use())
                            cln.set("xlink:href", "#crossmarker")
                            cln.transform.add_translate(
                                col * size[width], row * size[height]
                            )
                make_clones(layer, "cross")

        # clonelayer #####################################################
        layer = svg.add(inkex.Layer.new("Clone Layer"))
        make_clones(layer, "main")

        # mainlayer ######################################################
        layer = svg.add(inkex.Layer.new("Main Layer"))
        group = layer.add(inkex.Group(id="main"))

        if "innerbox" in show:
            group.add(padbox)
        if "outerbox" in show:
            group.add(margbox)
        if "holder" in show:
            x, y = (
                page_edge[left] + margin[left] + padding[left],
                page_edge[top] + margin[top] + padding[top],
            )
            w, h = (
                size[width] - padding[left] - padding[right],
                size[height] - padding[top] - padding[bottom],
            )
            path = "M{:f} {:f}".format(x + w / 2.0, y)
            path += " L{:f} {:f}".format(x + w, y + h / 2.0)
            path += " L{:f} {:f}".format(x + w / 2.0, y + h)
            path += " L{:f} {:f}".format(x, y + h / 2.0)
            path += " Z"
            group.add(inkex.PathElement(style=style, d=path))

        return svg.tostring()


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