#!/usr/bin/env python

import os
import os.path
import subprocess
import logging as log
import re


class Inkscape2Edc(object):
    cmd = "inkscape --without-gui"
    def __init__(self, infile, outfile, group,
                 relative1_x=None, relative2_x=None,
                 relative1_y=None, relative2_y=None,
                 images_dir="",
                 show_max=True, show_min=True, show_mouse_events=True):
        self.infile = infile
        self.outfile = outfile
        self.group = group
        self.relative1_x = relative1_x
        self.relative2_x = relative2_x
        self.relative1_y = relative1_y
        self.relative2_y = relative2_y
        self.images_dir = images_dir
        self.show_max = show_max
        self.show_min = show_min
        self.show_mouse_events = show_mouse_events

        self.images = {}
        self.sizes = {}
        self.known_ids = tuple()
        self.w = 0
        self.h = 0

        self.out = open(self.outfile, "wb")
        self.basedir = os.path.dirname(self.outfile)

    def _exec_cmd(self, *args):
        s = " ".join(args)
        cmd = "%s --file=%r %s" % (self.cmd, self.infile, s)
        try:
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE, shell=True)
        except Exception, e:
            log.error("cmd=%r exception: %s", cmd, e)
            return ""

        out, err = p.communicate()
        if err:
            log.error("cmd=%r error: %s", cmd, err)

        return out

    def load_sizes(self):
        sizes = self._exec_cmd("--query-all").split('\n')

        self.sizes = {}
        order = []
        for line in sizes:
            if not line:
                continue
            try:
                oid, x, y, w, h = line.split(',')
            except ValueError:
                log.warn("could not parse size line: %r", line)
                continue
            x = float(x)
            y = float(y)
            w = float(w)
            h = float(h)
            self.sizes[oid] = (x, y, w, h)
            order.append(oid)

        self.known_ids = tuple(order)

        self.w = float(self._exec_cmd("--query-width"))
        self.h = float(self._exec_cmd("--query-height"))

    def output_file_header(self):
        self.out.write("""\
collections {
   group {
      name: "%(group)s";
""" % self.__dict__)

        if self.show_min:
            self.out.write("      min: %(w)d %(h)d;\n" % self.__dict__)

        if self.show_max:
            self.out.write("      max: %(w)d %(h)d;\n" % self.__dict__)

    def output_file_section_parts_begin(self):
        self.out.write("      parts {\n")

    def output_file_section_parts_end(self):
        self.out.write("      }\n")

    def output_file_section_images_begin(self):
        self.out.write("      images {\n")

    def output_file_section_images_end(self):
        self.out.write("      }\n")

    def output_file_foot(self):
        self.out.write("""\
   }
}
""")

    def output_image(self, oid):
        img = os.path.join(self.images_dir, oid)
        img += ".png"

        self._exec_cmd("--export-id='%s'" % oid,
                       "--export-id-only",
                       "--export-png='%s'" % os.path.join(self.basedir, img))

        self.out.write('         image: "%s" COMP;\n' % img)
        self.images[oid] = img

    def output_part_desc_rel(self, x, y, w, h):
        def choose_rel(relative, value, value_max):
            if relative is not None:
                return relative
            elif value <= abs(value_max - value):
                return 0.0
            else:
                return 1.0

        x2 = x + w - 1
        y2 = y + h - 1

        rx1 = choose_rel(self.relative1_x, x, w)
        rx2 = choose_rel(self.relative2_x, x2, w)
        ry1 = choose_rel(self.relative1_y, y, h)
        ry2 = choose_rel(self.relative2_y, y2, h)

        ox1 = x - self.w * rx1
        ox2 = x2 - self.w * rx2

        oy1 = y - self.h * ry1
        oy2 = y2 - self.h * ry2

        self.out.write("""\
               rel1 {
                  relative: %03.1f %03.1f;
                  offset: %d %d;
               }
               rel2 {
                  relative: %03.1f %03.1f;
                  offset: %d %d;
               }
""" % (rx1, ry1, ox1, oy1, rx2, ry2, ox2, oy2))


    def output_part(self, oid):
        try:
            x, y, w, h = self.sizes[oid]
        except KeyError:
            log.error("no such object id: %s", oid)
            return

        info = {
            "name": oid,
            "x": x,
            "y": y,
            "w": w,
            "h": h,
            }

        self.out.write("""
         part {
            name: "%(name)s";
            type: IMAGE;
""" % info)

        if self.show_mouse_events:
            self.out.write("            mouse_events: 0;\n")

        self.out.write("""\
            description {
               state: "default" 0.0;
""")

        if self.show_min:
            self.out.write("               min: %(w)d %(h)d;\n" % info)

        if self.show_max:
            self.out.write("               max: %(w)d %(h)d;\n" % info)

        self.output_part_desc_rel(x, y, w, h)
        self.out.write("""\
               image.normal: "%s";
            }
         }
""" % (self.images[oid],))


def foreach_id(inkscape2edc, ids=None, re_exclude=None):
    if ids:
        for oid in inkscape2edc.known_ids:
            if oid in ids:
                yield oid
    else:
        for oid in inkscape2edc.known_ids:
            if re_exclude is not None and re_exclude.match(oid):
                continue
            yield oid


if __name__ == "__main__":
    import optparse

    usage = "usage: %prog [options] <input.svg>"
    parser = optparse.OptionParser(usage=usage)

    parser.add_option("-i", "--id", action="append", default=[],
                      help=("Object ID to use, it will be the part name. "
                            "Multiple usage to use more object ids."))
    parser.add_option("-e", "--exclude", action="store", default=None,
                      help=("Exclude regular expression."
                            "Matching IDs will be ignored."))
    parser.add_option("-o", "--output", action="store", default=None,
                      help="Output file to use")
    parser.add_option("-g", "--group", action="store", default=None,
                      help="Group name")
    parser.add_option("-d", "--images-dir", action="store", default="",
                      help="Directory where to output images.")
    parser.add_option("--no-min", action="store_true",
                      help="Do not output min values")
    parser.add_option("--no-max", action="store_true",
                      help="Do not output max values")
    parser.add_option("--no-mouse_events", action="store_true",
                      help="Do not output mouse_events lines")
    parser.add_option("--relative1-y", action="store",
                      choices=("top", "bottom", "auto"),
                      default="auto",
                      help=("Choose what to use as base for rel1 y values, "
                            "top=0.0, bottom=1.0, auto=nearest"))
    parser.add_option("--relative2-y", action="store",
                      choices=("top", "bottom", "auto"),
                      default="auto",
                      help=("Choose what to use as base for rel2 y values, "
                            "top=0.0, bottom=1.0, auto=nearest"))
    parser.add_option("--relative1-x", action="store",
                      choices=("left", "right", "auto"),
                      default="auto",
                      help=("Choose what to use as base for rel1 x values, "
                            "left=0.0, right=1.0, auto=nearest"))
    parser.add_option("--relative2-x", action="store",
                      choices=("left", "right", "auto"),
                      default="auto",
                      help=("Choose what to use as base for rel2 x values, "
                            "left=0.0, right=1.0, auto=nearest"))


    options, args = parser.parse_args()

    try:
        infile = args[0]
    except IndexError:
        parser.print_help()
        raise SystemExit("missing input file name")

    fname = os.path.splitext(infile)[0]
    if not options.output:
        options.output = fname + ".edc"

    if not options.group:
        options.group = fname

    rx_map = {"left": 0.0, "right": 1.0}
    options.relative1_x = rx_map.get(options.relative1_x, None)
    options.relative2_x = rx_map.get(options.relative2_x, None)

    ry_map = {"top": 0.0, "bottom": 1.0}
    options.relative1_y = ry_map.get(options.relative1_y, None)
    options.relative2_y = ry_map.get(options.relative2_y, None)

    o = Inkscape2Edc(infile, options.output, options.group,
                     relative1_x=options.relative1_x,
                     relative2_x=options.relative2_x,
                     relative1_y=options.relative1_y,
                     relative2_y=options.relative2_y,
                     images_dir=options.images_dir,
                     show_max=not options.no_max, show_min=not options.no_min,
                     show_mouse_events=not options.no_mouse_events)

    re_exclude = None
    if options.exclude:
        re_exclude = re.compile(options.exclude)

    if options.images_dir:
        os.makedirs(options.images_dir)

    o.load_sizes()
    o.output_file_header()

    o.output_file_section_images_begin()
    for oid in foreach_id(o, options.id, re_exclude):
        o.output_image(oid)
    o.output_file_section_images_end()

    o.output_file_section_parts_begin()
    for oid in foreach_id(o, options.id, re_exclude):
        o.output_part(oid)
    o.output_file_section_parts_end()

    o.output_file_foot()
