import sys
from copy import deepcopy
from shapely import geometry as shgeo

from ..i18n import _
from ..utils import cache
from ..svg import PIXELS_PER_MM, convert_length, get_doc_size, apply_transforms
from ..commands import find_commands

# inkscape-provided utilities
import simpletransform
import simplestyle
import cubicsuperpath
from cspsubdiv import cspsubdiv


class Patch:
    """A raw collection of stitches with attached instructions."""

    def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, stitch_as_is=False):
        self.color = color
        self.stitches = stitches or []
        self.trim_after = trim_after
        self.stop_after = stop_after
        self.stitch_as_is = stitch_as_is

    def __add__(self, other):
        if isinstance(other, Patch):
            return Patch(self.color, self.stitches + other.stitches)
        else:
            raise TypeError("Patch can only be added to another Patch")

    def __len__(self):
        # This method allows `len(patch)` and `if patch:
        return len(self.stitches)

    def add_stitch(self, stitch):
        self.stitches.append(stitch)

    def reverse(self):
        return Patch(self.color, self.stitches[::-1])


class Param(object):
    def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False, default=None, tooltip=None, sort_index=0):
        self.name = name
        self.description = description
        self.unit = unit
        self.values = values or [""]
        self.type = type
        self.group = group
        self.inverse = inverse
        self.default = default
        self.tooltip = tooltip
        self.sort_index = sort_index

    def __repr__(self):
        return "Param(%s)" % vars(self)


# Decorate a member function or property with information about
# the embroidery parameter it corresponds to
def param(*args, **kwargs):
    p = Param(*args, **kwargs)

    def decorator(func):
        func.param = p
        return func

    return decorator


class EmbroideryElement(object):
    def __init__(self, node):
        self.node = node

    @property
    def id(self):
        return self.node.get('id')

    @classmethod
    def get_params(cls):
        params = []
        for attr in dir(cls):
            prop = getattr(cls, attr)
            if isinstance(prop, property):
                # The 'param' attribute is set by the 'param' decorator defined above.
                if hasattr(prop.fget, 'param'):
                    params.append(prop.fget.param)

        return params

    @cache
    def get_param(self, param, default):
        value = self.node.get("embroider_" + param, "").strip()

        return value or default

    @cache
    def get_boolean_param(self, param, default=None):
        value = self.get_param(param, default)

        if isinstance(value, bool):
            return value
        else:
            return value and (value.lower() in ('yes', 'y', 'true', 't', '1'))

    @cache
    def get_float_param(self, param, default=None):
        try:
            value = float(self.get_param(param, default))
        except (TypeError, ValueError):
            value = default

        if value is None:
            return value

        if param.endswith('_mm'):
            value = value * PIXELS_PER_MM

        return value

    @cache
    def get_int_param(self, param, default=None):
        try:
            value = int(self.get_param(param, default))
        except (TypeError, ValueError):
            return default

        if param.endswith('_mm'):
            value = int(value * PIXELS_PER_MM)

        return value

    def set_param(self, name, value):
        self.node.set("embroider_%s" % name, str(value))

    @cache
    def get_style(self, style_name, default=None):
        style = simplestyle.parseStyle(self.node.get("style"))
        if (style_name not in style):
            return default
        value = style[style_name]
        if value == 'none':
            return None
        return value

    @cache
    def has_style(self, style_name):
        style = simplestyle.parseStyle(self.node.get("style"))
        return style_name in style

    @property
    @cache
    def stroke_scale(self):
        svg = self.node.getroottree().getroot()
        doc_width, doc_height = get_doc_size(svg)
        viewbox = svg.get('viewBox', '0 0 %s %s' % (doc_width, doc_height))
        viewbox = viewbox.strip().replace(',', ' ').split()
        return  doc_width / float(viewbox[2])

    @property
    @cache
    def stroke_width(self):
        width = self.get_style("stroke-width", "1")

        if width is None:
            return 1.0

        width = convert_length(width)
        return width * self.stroke_scale

    @property
    def path(self):
        # A CSP is a  "cubic superpath".
        #
        # A "path" is a sequence of strung-together bezier curves.
        #
        # A "superpath" is a collection of paths that are all in one object.
        #
        # The "cubic" bit in "cubic superpath" is because the bezier curves
        # inkscape uses involve cubic polynomials.
        #
        # Each path is a collection of tuples, each of the form:
        #
        # (control_before, point, control_after)
        #
        # A bezier curve segment is defined by an endpoint, a control point,
        # a second control point, and a final endpoint.  A path is a bunch of
        # bezier curves strung together.  One could represent a path as a set
        # of four-tuples, but there would be redundancy because the ending
        # point of one bezier is the starting point of the next.  Instead, a
        # path is a set of 3-tuples as shown above, and one must construct
        # each bezier curve by taking the appropriate endpoints and control
        # points.  Bleh. It should be noted that a straight segment is
        # represented by having the control point on each end equal to that
        # end's point.
        #
        # In a path, each element in the 3-tuple is itself a tuple of (x, y).
        # Tuples all the way down.  Hasn't anyone heard of using classes?

        return cubicsuperpath.parsePath(self.node.get("d"))

    @cache
    def parse_path(self):
        return apply_transforms(self.path, self.node)

    @property
    @cache
    def commands(self):
        return find_commands(self.node)

    @cache
    def get_commands(self, command):
        return [c for c in self.commands if c.command == command]

    @cache
    def get_command(self, command):
        commands = self.get_commands(command)

        if len(commands) == 1:
            return commands[0]
        elif len(commands) > 1:
            raise ValueError(_("%(id)s has more than one command of type '%(command)s' linked to it") %
                                dict(id=self.node.get(id), command=command))
        else:
            return None

    def strip_control_points(self, subpath):
        return [point for control_before, point, control_after in subpath]

    def flatten(self, path):
        """approximate a path containing beziers with a series of points"""

        path = deepcopy(path)
        cspsubdiv(path, 0.1)

        return [self.strip_control_points(subpath) for subpath in path]

    @property
    @param('trim_after',
           _('TRIM after'),
           tooltip=_('Trim thread after this object (for supported machines and file formats)'),
           type='boolean',
           default=False,
           sort_index=1000)
    def trim_after(self):
        return self.get_boolean_param('trim_after', False)

    @property
    @param('stop_after',
           _('STOP after'),
           tooltip=_('Add STOP instruction after this object (for supported machines and file formats)'),
           type='boolean',
           default=False,
           sort_index=1000)
    def stop_after(self):
        return self.get_boolean_param('stop_after', False)

    def to_patches(self, last_patch):
        raise NotImplementedError("%s must implement to_patches()" % self.__class__.__name__)

    def embroider(self, last_patch):
        patches = self.to_patches(last_patch)

        if patches:
            patches[-1].trim_after = self.trim_after
            patches[-1].stop_after = self.stop_after

        return patches

    def fatal(self, message):
        print >> sys.stderr, "error:", message
        sys.exit(1)
