#!/usr/pkg/bin/python3.12
# coding=utf-8
#
# Copyright (C) 2006 Aaron Spike, aaron@ekips.org
# Copyright (C) 2010-2012 Nicolas Dufour, nicoduf@yahoo.fr
# (Windows support and various fixes)
#
# 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.
#
"""
Export to Gimp's XCF file format including Grids and Guides.
"""

import os
from collections import OrderedDict

import inkex
from inkex.base import TempDirMixin
from inkex.command import take_snapshot, call
from inkex.localization import inkex_gettext as _


class GimpXcf(inkex.OutputExtension):
    """
    Provide a quick and dirty way of using gimp to output an xcf from Inkscape.

    Both Inkscape and Gimp must be installed for this extension to work.
    """

    dir_prefix = "gimp-out-"

    def add_arguments(self, pars):
        pars.add_argument("--tab", dest="tab")
        pars.add_argument(
            "-d", "--guides", type=inkex.Boolean, help="Save the Guides in the XCF"
        )
        pars.add_argument(
            "-r", "--grid", type=inkex.Boolean, help="Save the Grid with the .XCF"
        )
        pars.add_argument(
            "-b", "--background", type=inkex.Boolean, help="Add background color"
        )
        pars.add_argument(
            "-i", "--dpi", type=float, default=96.0, help="File resolution"
        )

    def get_guides(self):
        """Generate a list of horzontal and vertical only guides"""
        horz_guides = []
        vert_guides = []
        # Grab all guide tags in the namedview tag
        # TODO: compute intersection of guide with page instead of discarding those
        # whose control points are outside of the page
        for guide in self.svg.namedview.get_guides():
            if guide.is_horizontal:
                # GIMP doesn't like guides that are outside of the image
                if 0 < guide.raw_position.y < self.svg.viewbox_height:
                    # The origin is at the top in GIMP land # TODO check this (maybe use position instead of raw_position?)
                    horz_guides.append(str(guide.raw_position.y))
            elif guide.is_vertical:
                # GIMP doesn't like guides that are outside of the image
                if 0 < guide.raw_position.x < self.svg.viewbox_width:
                    vert_guides.append(str(guide.raw_position.x))

        return ("h", " ".join(horz_guides)), ("v", " ".join(vert_guides))

    def get_grid(self):
        """Get the grid if asked for and return as gimpfu script"""
        scale = (self.svg.scale) * (self.options.dpi / 96.0)
        # GIMP only allows one rectangular grid
        xpath = "sodipodi:namedview/inkscape:grid[@type='xygrid' and (not(@units) or @units='px')]"
        if self.svg.xpath(xpath):
            node = self.svg.getElement(xpath)
            for attr, default, target in (
                ("spacing", 1, "spacing"),
                ("origin", 0, "offset"),
            ):
                fmt = {"target": target}
                for dim in "xy":
                    # These attributes could be nonexistent
                    unit = float(node.get(attr + dim, default))
                    unit = self.svg.uutounit(unit, "px") * scale
                    fmt[dim] = int(round(float(unit)))
                yield "(gimp-image-grid-set-{target} img {x} {y})".format(**fmt)

    @property
    def docname(self):
        """Get the document name suitable for export"""
        return self.svg.get("sodipodi:docname") or "document"

    def save(self, stream):
        pngs = OrderedDict()
        valid = False

        for node in self.svg.xpath("/svg:svg/*[name()='g' or @style][@id]"):
            if not len(node):  # pylint: disable=len-as-condition
                # Ignore empty layers
                continue

            valid = True
            node_id = node.get("id")
            name = node.get("inkscape:label", node_id)

            pngs[name] = take_snapshot(
                self.document,
                dirname=self.tempdir,
                name=name,
                dpi=int(self.options.dpi),
                export_id=node_id,
                export_id_only=True,
                export_area_page=True,
                export_background_opacity=int(bool(self.options.background)),
            )

        if not valid:
            inkex.errormsg(_("This extension requires at least one non empty layer."))
            return

        xcf = os.path.join(self.tempdir, "{}.xcf".format(self.docname))
        script_fu = """
(tracing 1)
(define
  (png-to-layer img png_filename layer_name)
  (let*
    (
      (png (car (file-png-load RUN-NONINTERACTIVE png_filename png_filename)))
      (png_layer (car (gimp-image-get-active-layer png)))
      (xcf_layer (car (gimp-layer-new-from-drawable png_layer img)))
    )
    (gimp-image-add-layer img xcf_layer -1)
    (gimp-drawable-set-name xcf_layer layer_name)
  )
)
(let*
  (
    (img (car (gimp-image-new 200 200 RGB)))
  )
  (gimp-image-set-resolution img {dpi} {dpi})
  (gimp-image-undo-disable img)
  (for-each
    (lambda (names)
      (png-to-layer img (car names) (cdr names))
    )
    (map cons '("{files}") '("{names}"))
  )

  (gimp-image-resize-to-layers img)
""".format(
            dpi=self.options.dpi,
            files='" "'.join(pngs.values()),
            names='" "'.join(list(pngs)),
        )

        if self.options.guides:
            for dim, guides in self.get_guides():
                script_fu += """
  (for-each
    (lambda ({d}Guide)
      (gimp-image-add-{d}guide img {d}Guide)
    )
    '({g})
  )""".format(
                    d=dim, g=guides
                )

        # Grid
        if self.options.grid:
            for fu_let in self.get_grid():
                script_fu += "\n" + fu_let + "\n"

        script_fu += """
  (gimp-image-undo-enable img)
  (gimp-file-save RUN-NONINTERACTIVE img (car (gimp-image-get-active-layer img)) "{xcf}" "{xcf}"))
(gimp-quit 0)
            """.format(
            xcf=xcf
        )

        call(
            "gimp",
            "-b",
            "-",
            i=True,
            batch_interpreter="plug-in-script-fu-eval",
            stdin=script_fu,
        )

        with open(xcf, "rb") as fhl:
            stream.write(fhl.read())


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