# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
#   This file is part of CutCraft.
#
#   CutCraft 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 3 of the License, or
#   (at your option) any later version.
#
#   CutCraft 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 CutCraft.  If not, see <http://www.gnu.org/licenses/>.

from .point import Point
from .line import Line
from ..util import isclose, iscloselist

class Trace(object):
    """ List of coordinates that make a boundary. """
    def __init__(self, x=None, y=None):
        self.x = [] if x is None else x
        self.y = [] if y is None else y
        self.closed = False
        return

    def close(self):
        """ Close the trace back to the start. """
        if not self.closed:
            if isclose(self.x[0], self.x[-1]) and isclose(self.y[0], self.y[-1]):
                # Start and end should be the same.
                self.x[-1] = self.x[0]
                self.y[-1] = self.y[0]
            else:
                # Add new end point to close the loop.
                self.x.append(self.x[0])
                self.y.append(self.y[0])
            self.closed = True
        return

    def applykerf(self, kerf):
        """ Apply an offset to allow for the kerf when cutting. """
        self.close()

        # Convert the points to lines.
        lines = [Line(Point(x1, y1), Point(x2, y2)) for x1, y1, x2, y2 in
                 zip(self.x[:-1], self.y[:-1], self.x[1:], self.y[1:])]

        # Add the kerf to the lines.
        for line in lines:
            line.addkerf(kerf)

        # Extract the line intersections as the new points.
        pts = [line1.intersection(line2) for line1, line2 in zip(lines, lines[-1:] + lines[:-1])]
        self.clear()
        self.x += [pt.x for pt in pts]
        self.y += [pt.y for pt in pts]
        self.x += self.x[:1]
        self.y += self.y[:1]
        return

    def offset(self, pt):
        """ Move a trace by an x/y offset. """
        self.x = [x + pt.x for x in self.x]
        self.y = [y + pt.y for y in self.y]

    def clear(self):
        self.x = []
        self.y = []

    def svg(self):
        # Generate SVG string for this trace.
        if len(self.x)<2:
            return ""
        return "M {} {} ".format(self.x[0], self.y[0]) + \
               " ".join(["L {} {}".format(x, y) for x, y in zip(self.x[1:], self.y[1:])])

    def bbox(self):
        return Point(min(self.x), min(self.y)), Point(max(self.x), max(self.y))

    def __len__(self):
        return len(self.x)

    def __eq__(self, other):
        return (iscloselist(self.x,other.x) and iscloselist(self.y, other.y))

    def __ne__(self, other):
        return (not iscloselist(self.x, other.x) or not iscloselist(self.y, other.y))

    def __add__(self, other):
        new = Trace()
        if isinstance(other, Point):
            new.x = self.x + [other.x]
            new.y = self.y + [other.y]
        elif isinstance(other, Trace):
            new.x = self.x + other.x
            new.y = self.y + other.y
        else:
            raise RuntimeError("Can only add a Trace or Point to an existing Trace.")
        return new

    def __iadd__(self, other):
        if isinstance(other, Point):
            self.x.append(other.x)
            self.y.append(other.y)
        elif isinstance(other, Trace):
            self.x += other.x
            self.y += other.y
        else:
            raise RuntimeError("Can only add a Trace or Point to an existing Trace.")
        return self

    def __repr__(self):
        return "Trace(" + str(self.x) + ", " + str(self.y) + ")"

    def __str__(self):
        return "(" + str(self.x) + ", " + str(self.y) + ")"

    def __getitem__(self, key):
        """ Used to override the slice functionality (eg: reversing). """
        new = Trace()
        new.x = self.x[key]
        new.y = self.y[key]
        return new

    def __setitem__(self, key, value):
        """ Used to override the slice functionality. """
        if isinstance(value, Point):
            self.x[key] = value.x
            self.y[key] = value.y
        else:
            raise RuntimeError("Can only update a single item in an existing Trace.")
        return self

    def __delitem__(self, key):
        """ Used to override the slice functionality (eg: reversing). """
        del self.x[key]
        del self.y[key]
        return self

    def __reversed__(self):
        """ Used to override the slice functionality (eg: reversing). """
        new = Trace()
        new.x = list(reversed(self.x))
        new.y = list(reversed(self.y))
        return new