#! /usr/bin/env python3

import os
from abc import abstractmethod
from lxml import etree
from Path import Path, inkex

class Pattern(inkex.Effect):
    @abstractmethod
    def generate_path_tree(self):
        """ Generate nested list of Path instances 
        Abstract method, must be defined in all child classes
        """
        pass

    def __init__(self):
        inkex.Effect.__init__(self)  # initialize the super class
        self.add_argument = self.arg_parser.add_argument
        self.add_argument("-u", "--units", default='mm', help="Units this dialog is using")
                  
        # self.add_argument("-a", "--add_attachment", type=inkex.Boolean, default=False, help="command line help")
        # self.add_argument("", "--accuracy", type=int, default=0, help="command line help")

        # --------------------------------------------------------------------------------------------------------------
        # mountain options
        self.add_argument('-m', '--mountain_stroke_color', default=4278190335, help='The mountain creases color.')
        self.add_argument('--mountain_stroke_width', type=float, default=0.1, help='Width of mountain strokes.')
        self.add_argument('--mountain_dashes_len', type=float, default=1.0, help='Mountain dash + gap length.')
        self.add_argument('--mountain_dashes_duty', type=float, default=0.5, help='Mountain dash duty cycle.')
        self.add_argument('--mountain_dashes_bool', type=inkex.Boolean, default=True, help='Dashed strokes?')
        self.add_argument('--mountain_bool', type=inkex.Boolean, default=True, help='Draw mountains?')
        self.add_argument('--mountain_bool_only', type=inkex.Boolean, default=False)
        
        # --------------------------------------------------------------------------------------------------------------
        # valley options
        self.add_argument('-v', '--valley_stroke_color', default=65535, help='The valley creases color.')
        self.add_argument('--valley_stroke_width', type=float,  default=0.1, help='Width of valley strokes.')
        self.add_argument('--valley_dashes_len', type=float, default=1.0, help='Valley dash + gap length.')
        self.add_argument('--valley_dashes_duty', type=float, default=0.25, help='Valley dash duty cycle.')
        self.add_argument('--valley_dashes_bool', type=inkex.Boolean, default=True, help='Dashed strokes?')
        self.add_argument('--valley_bool', type=inkex.Boolean, default=True, help='Draw valleys?')
        self.add_argument('--valley_bool_only', type=inkex.Boolean, default=False)

        # --------------------------------------------------------------------------------------------------------------
        # edge options
        self.add_argument('-e', '--edge_stroke_color', default=255, help='The mountain creases color.')
        self.add_argument('--edge_stroke_width', type=float, default=0.1, help='Width of edge strokes.')
        self.add_argument('--edge_dashes_len', type=float, default=1.0, help='Edge dash + gap length.')
        self.add_argument('--edge_dashes_duty', type=float, default=0.25, help='Edge dash duty cycle.')
        self.add_argument('--edge_dashes_bool', type=inkex.Boolean, default=False, help='Dashed strokes?')
        self.add_argument('--edge_bool', type=inkex.Boolean, default=True, help='Draw edges?')
        self.add_argument('--edge_bool_only', type=inkex.Boolean, default=False)
        self.add_argument('--edge_single_path', type=inkex.Boolean, default=True, help='Edges as single path?')

        # --------------------------------------------------------------------------------------------------------------
        # universal crease options
        self.add_argument('--universal_stroke_color', default=4278255615, help='The universal creases color.')
        self.add_argument('--universal_stroke_width', type=float, default=0.1, help='Width of universal strokes.')
        self.add_argument('--universal_dashes_len', type=float, default=1.0, help='Universal dash + gap length.')
        self.add_argument('--universal_dashes_duty', type=float, default=0.25, help='Universal dash duty cycle.')
        self.add_argument('--universal_dashes_bool', type=inkex.Boolean, default=False, help='Dashed strokes?')
        self.add_argument('--universal_bool', type=inkex.Boolean, default=True, help='Draw universal creases?')
        self.add_argument('--universal_bool_only', type=inkex.Boolean, default=False)

        # --------------------------------------------------------------------------------------------------------------
        # semicrease options
        self.add_argument('--semicrease_stroke_color',  default=4294902015, help='The semicrease creases color.')
        self.add_argument('--semicrease_stroke_width', type=float, default=0.1, help='Width of semicrease strokes.')
        self.add_argument('--semicrease_dashes_len',  type=float, default=1.0, help='Semicrease dash + gap length.')
        self.add_argument('--semicrease_dashes_duty', type=float,default=0.25, help='Semicrease dash duty cycle.')
        self.add_argument('--semicrease_dashes_bool', type=inkex.Boolean, default=False, help='Dashed strokes?')
        self.add_argument('--semicrease_bool', type=inkex.Boolean,  default=True, help='Draw semicreases?')
        self.add_argument('--semicrease_bool_only', type=inkex.Boolean, default=False)
         
        # --------------------------------------------------------------------------------------------------------------
        # cut options
        self.add_argument('--cut_stroke_color', default=16711935,  help='The cut creases color.')
        self.add_argument('--cut_stroke_width', type=float, default=0.1, help='Width of cut strokes.')
        self.add_argument('--cut_dashes_len', type=float, default=1.0, help='Cut dash + gap length.')
        self.add_argument('--cut_dashes_duty', type=float, default=0.25, help='Cut dash duty cycle.')
        self.add_argument('--cut_dashes_bool', type=inkex.Boolean, default=False, help='Dashed strokes?')
        self.add_argument('--cut_bool', type=inkex.Boolean, default=True, help='Draw cuts?')
        self.add_argument('--cut_bool_only', type=inkex.Boolean, default=False)

        # --------------------------------------------------------------------------------------------------------------
        # vertex options
        self.add_argument('--vertex_stroke_color', default=255,  help='Vertices\' color.')
        self.add_argument('--vertex_stroke_width', type=float, default=0.1,  help='Width of vertex strokes.')
        self.add_argument('--vertex_radius', type=float, default=0.1, help='Radius of vertices.')
        self.add_argument('--vertex_bool', type=inkex.Boolean, default=True)
        self.add_argument('--vertex_bool_only', type=inkex.Boolean, default=False)
        
        # here so we can have tabs - but we do not use it directly - else error
        self.add_argument('--active-tab', default='title', help="Active tab.")

        self.path_tree = []
        self.edge_points = []
        self.vertex_points = []
        self.translate = (0, 0)

    def effect(self):
        # check if any selected to print only some of the crease types:
        bool_only_list = [self.options.mountain_bool_only,
                          self.options.valley_bool_only,
                          self.options.edge_bool_only,
                          self.options.universal_bool_only,
                          self.options.semicrease_bool_only,
                          self.options.cut_bool_only,
                          self.options.vertex_bool_only]
        if sum(bool_only_list) > 0:
            self.options.mountain_bool = self.options.mountain_bool and self.options.mountain_bool_only
            self.options.valley_bool = self.options.valley_bool and self.options.valley_bool_only
            self.options.edge_bool = self.options.edge_bool and self.options.edge_bool_only
            self.options.universal_bool = self.options.universal_bool and self.options.universal_bool_only
            self.options.semicrease_bool = self.options.semicrease_bool and self.options.semicrease_bool_only
            self.options.cut_bool = self.options.cut_bool and self.options.cut_bool_only
            self.options.vertex_bool = self.options.vertex_bool and self.options.vertex_bool_only
        
        # construct dictionary containing styles
        self.create_styles_dict()

        # get paths for selected origami pattern
        self.generate_path_tree()

        # ~ accuracy = self.options.accuracy
        # ~ unit_factor = self.calc_unit_factor()
        # what page are we on
        # page_id = self.options.active_tab # sometimes wrong the very first time

        # get vertex points and add them to path tree
        vertex_radius = self.options.vertex_radius * self.calc_unit_factor()
        vertices = []
        self.vertex_points = list(set([i for i in self.vertex_points])) # remove duplicates 
        for vertex_point in self.vertex_points:
            vertices.append(Path(vertex_point, style='p', radius=vertex_radius))
        self.path_tree.append(vertices)

        # Translate according to translate attribute
        g_attribs = {inkex.addNS('label', 'inkscape'): '{} Origami pattern'.format(self.options.pattern),
                       # inkex.addNS('transform-center-x','inkscape'): str(-bbox_center[0]),
                       # inkex.addNS('transform-center-y','inkscape'): str(-bbox_center[1]),
                     inkex.addNS('transform-center-x', 'inkscape'): str(0),
                     inkex.addNS('transform-center-y', 'inkscape'): str(0),
                     'transform': 'translate(%s,%s)' % self.translate}

        # add the group to the document's current layer
        if type(self.path_tree) == list and len(self.path_tree) != 1:
            self.topgroup = etree.SubElement(self.get_layer(), 'g', g_attribs)
        else:
            self.topgroup = self.get_layer()

        if len(self.edge_points) == 0:
            Path.draw_paths_recursively(self.path_tree, self.topgroup, self.styles_dict)
        elif self.options.edge_single_path:
            edges = Path(self.edge_points, 'e', closed=True)
            Path.draw_paths_recursively(self.path_tree + [edges], self.topgroup, self.styles_dict)
        else:
            edges = Path.generate_separated_paths(self.edge_points, 'e', closed=True)
            Path.draw_paths_recursively(self.path_tree + edges, self.topgroup, self.styles_dict)

        # self.draw_paths_recursively(self.path_tree, self.topgroup, self.styles_dict)

    # compatibility hack
    def get_layer(self):
        try:
            return self.svg.get_current_layer() # new
        except:
            return self.current_layer # old

    def create_styles_dict(self):
        """ Get stroke style parameters and use them to create the styles dictionary, used for the Path generation
        """
        unit_factor = self.calc_unit_factor()
        
        # define colour and stroke width
        mountain_style = {'draw': self.options.mountain_bool,
                          'stroke': self.get_color_string(self.options.mountain_stroke_color),
                          'fill': 'none',
                          'stroke-width': self.options.mountain_stroke_width*unit_factor}

        valley_style = {'draw': self.options.valley_bool,
                        'stroke': self.get_color_string(self.options.valley_stroke_color),
                        'fill': 'none',
                        'stroke-width': self.options.valley_stroke_width*unit_factor}

        universal_style = {'draw': self.options.universal_bool,
                           'stroke': self.get_color_string(self.options.universal_stroke_color),
                           'fill': 'none',
                           'stroke-width': self.options.universal_stroke_width*unit_factor}

        semicrease_style = {'draw': self.options.semicrease_bool,
                            'stroke': self.get_color_string(self.options.semicrease_stroke_color),
                            'fill': 'none',
                            'stroke-width': self.options.semicrease_stroke_width*unit_factor}

        cut_style = {'draw': self.options.cut_bool,
                     'stroke': self.get_color_string(self.options.cut_stroke_color),
                     'fill': 'none',
                     'stroke-width': self.options.cut_stroke_width*unit_factor}

        edge_style = {'draw': self.options.edge_bool,
                      'stroke': self.get_color_string(self.options.edge_stroke_color),
                      'fill': 'none',
                      'stroke-width': self.options.edge_stroke_width*unit_factor}

        vertex_style = {'draw': self.options.vertex_bool,
                        'stroke': self.get_color_string(self.options.vertex_stroke_color),
                        'fill': 'none',
                        'stroke-width': self.options.vertex_stroke_width*unit_factor}

        # check if dashed option selected
        if self.options.mountain_dashes_bool:
            dash = self.options.mountain_dashes_len*self.options.mountain_dashes_duty*unit_factor
            gap = abs(dash - self.options.mountain_dashes_len*unit_factor)
            mountain_style['stroke-dasharray'] = "{},{}".format(dash, gap)
        if self.options.valley_dashes_bool:
            dash = self.options.valley_dashes_len * self.options.valley_dashes_duty*unit_factor
            gap = abs(dash - self.options.valley_dashes_len*unit_factor)
            valley_style['stroke-dasharray'] = "{},{}".format(dash, gap)
        if self.options.edge_dashes_bool:
            dash = self.options.edge_dashes_len * self.options.edge_dashes_duty*unit_factor
            gap = abs(dash - self.options.edge_dashes_len*unit_factor)
            edge_style['stroke-dasharray'] = "{},{}".format(dash, gap)
        if self.options.universal_dashes_bool:
            dash = self.options.universal_dashes_len * self.options.universal_dashes_duty*unit_factor
            gap = abs(dash - self.options.universal_dashes_len*unit_factor)
            universal_style['stroke-dasharray'] = "{},{}".format(dash, gap)
        if self.options.semicrease_dashes_bool:
            dash = self.options.semicrease_dashes_len * self.options.semicrease_dashes_duty*unit_factor
            gap = abs(dash - self.options.semicrease_dashes_len*unit_factor)
            semicrease_style['stroke-dasharray'] = "{},{}".format(dash, gap)
        if self.options.cut_dashes_bool:
            dash = self.options.cut_dashes_len * self.options.cut_dashes_duty*unit_factor
            gap = abs(dash - self.options.cut_dashes_len*unit_factor)
            cut_style['stroke-dasharray'] = "{},{}".format(dash, gap)

        self.styles_dict = {'m': mountain_style,
                            'v': valley_style,
                            'u': universal_style,
                            's': semicrease_style,
                            'c': cut_style,
                            'e': edge_style,
                            'p': vertex_style}

    def get_color_string(self, longColor, verbose=False):
        """ Convert the long into a #RRGGBB color value
            - verbose=true pops up value for us in defaults
            conversion back is A + B*256^1 + G*256^2 + R*256^3
        """
        # compatibility hack, no "long" in Python 3
        try:
            longColor = long(longColor)
            if longColor < 0: longColor = long(longColor) & 0xFFFFFFFF
            hexColor = hex(longColor)[2:-3]
        except:
            longColor = int(longColor)
            hexColor = hex(longColor)[2:-2]
            inkex.debug = inkex.utils.debug

        hexColor = '#' + hexColor.rjust(6, '0').upper()
        if verbose: inkex.debug("longColor = {}, hex = {}".format(longColor,hexColor))

        return hexColor
    
    def add_text(self, node, text, position, text_height=12):
        """ Create and insert a single line of text into the svg under node.
        """
        line_style = {'font-size': '%dpx' % text_height, 'font-style':'normal', 'font-weight': 'normal',
                     'fill': '#F6921E', 'font-family': 'Bitstream Vera Sans,sans-serif',
                     'text-anchor': 'middle', 'text-align': 'center'}
        line_attribs = {inkex.addNS('label','inkscape'): 'Annotation',
                       'style': str(Inkex.style(line_style)),
                       'x': str(position[0]),
                       'y': str((position[1] + text_height) * 1.2)
                       }
        line = etree.SubElement(node, inkex.addNS('text','svg'), line_attribs)
        line.text = text

           
    def calc_unit_factor(self):
        return self.svg.unittouu(str(1.0) + self.options.units)