#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright 2019 Hiroshi Murayama <opiopan@gmail.com>

from gerber.utils import inch, metric, write_gerber_value
from gerber.cam import FileSettings

class DxfPath(object):
    def __init__(self, statement, error_range=0):
        self.statements = [statement]
        self.error_range = error_range
    
    @property
    def start(self):
        return self.statements[0].start
    
    @property
    def end(self):
        return self.statements[-1].end

    @property
    def is_closed(self):
        from gerberex.dxf import is_equal_point
        return len(self.statements) > 1 and \
               is_equal_point(self.start, self.end, self.error_range)
    
    def is_equal_to(self, target, error_range=0):
        from gerberex.dxf import is_equal_point
        if not isinstance(target, DxfPath):
            return False
        if len(self.statements) != len(target.statements):
            return False
        if is_equal_point(self.start, target.start, error_range) and \
           is_equal_point(self.end, target.end, error_range):
            for i in range(0, len(self.statements)):
               if not self.statements[i].is_equal_to(target.statements[i], error_range):
                   return False
            return True
        elif is_equal_point(self.start, target.end, error_range) and \
             is_equal_point(self.end, target.start, error_range):
            for i in range(0, len(self.statements)):
               if not self.statements[i].is_equal_to(target.statements[-1 - i], error_range):
                   return False
            return True
        return False

    def to_inch(self):
        self.error_range = inch(self.error_range)

    def to_metric(self):
        self.error_range = metric(self.error_range)

    def reverse(self):
        rlist = []
        for statement in reversed(self.statements):
            statement.reverse()
            rlist.append(statement)
        self.statements = rlist
    
    def merge(self, element, error_range=0):
        from gerberex.dxf import is_equal_point
        if self.is_closed or element.is_closed:
            return False
        if not error_range:
            error_range = self.error_range
        if is_equal_point(self.end, element.start, error_range):
            return self._append_at_end(element, error_range)
        elif is_equal_point(self.end, element.end, error_range):
            element.reverse()
            return self._append_at_end(element, error_range)
        elif is_equal_point(self.start, element.end, error_range):
            return self._insert_on_top(element, error_range)
        elif is_equal_point(self.start, element.start, error_range):
            element.reverse()
            return self._insert_on_top(element, error_range)
        else:
            return False
    
    def _append_at_end(self, element, error_range=0):
        if isinstance(element, DxfPath):
            if self.is_equal_to(element, error_range):
                return False
            for i in range(0, min(len(self.statements), len(element.statements))):
                if not self.statements[-1 - i].is_equal_to(element.statements[i]):
                    break
            for j in range(0, min(len(self.statements), len(element.statements))):
                if not self.statements[j].is_equal_to(element.statements[-1 - j]):
                    break
            if i + j >= len(element.statements):
                return False
            mergee = list(element.statements)
            if i > 0:
                del mergee[0:i]
                del self.statements[-i]
            if j > 0:
                del mergee[-j]
                del self.statements[0:j]
            self.statements.extend(mergee)
            return True
        else:
            if self.statements[-1].is_equal_to(element, error_range) or \
               self.statements[0].is_equal_to(element, error_range):
                return False
            self.statements.appen(element)
            return True

    def _insert_on_top(self, element, error_range=0):
        if isinstance(element, DxfPath):
            if self.is_equal_to(element, error_range):
                return False
            for i in range(0, min(len(self.statements), len(element.statements))):
                if not self.statements[-1 - i].is_equal_to(element.statements[i]):
                    break
            for j in range(0, min(len(self.statements), len(element.statements))):
                if not self.statements[j].is_equal_to(element.statements[-1 - j]):
                    break
            if i + j >= len(element.statements):
                return False
            mergee = list(element.statements)
            if i > 0:
                del mergee[0:i]
                del self.statements[-i]
            if j > 0:
                del mergee[-j]
                del self.statements[0:j]
            self.statements[0:0] = mergee
            return True
        else:
            if self.statements[-1].is_equal_to(element, error_range) or \
               self.statements[0].is_equal_to(element, error_range):
                return False
            self.statements.insert(0, element)
            return True

    def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
        from gerberex.dxf import DxfArcStatement
        if pitch:
            return

        x0, y0 = self.statements[0].start
        gerber = 'G01*\nX{0}Y{1}D02*\nG75*'.format(
            write_gerber_value(x0, settings.format,
                               settings.zero_suppression),
            write_gerber_value(y0, settings.format,
                               settings.zero_suppression),
        )

        for statement in self.statements:
            x0, y0 = statement.start
            x1, y1 = statement.end
            if isinstance(statement, DxfArcStatement):
                xc, yc = statement.center
                gerber += '\nG{0}*\nX{1}Y{2}I{3}J{4}D01*'.format(
                    '03' if statement.end_angle > statement.start_angle else '02',
                    write_gerber_value(x1, settings.format,
                                       settings.zero_suppression),
                    write_gerber_value(y1, settings.format,
                                       settings.zero_suppression),
                    write_gerber_value(xc - x0, settings.format,
                                       settings.zero_suppression),
                    write_gerber_value(yc - y0, settings.format,
                                       settings.zero_suppression)
                )
            else:
                gerber += '\nG01*\nX{0}Y{1}D01*'.format(
                    write_gerber_value(x1, settings.format,
                                       settings.zero_suppression),
                    write_gerber_value(y1, settings.format,
                                       settings.zero_suppression),
                )

        return gerber

def generate_closed_paths(statements, error_range=0):
    from gerberex.dxf import DxfLineStatement, DxfArcStatement

    paths = [DxfPath(s, error_range) \
             for s in filter(lambda s: isinstance(s, DxfLineStatement) or \
                                       isinstance(s, DxfArcStatement), statements)]

    prev_paths_num = 0
    while prev_paths_num != len(paths):
        working = []
        for i in range(len(paths)):
            mergee = paths[i]
            for j in range(i + 1, len(paths)):
                target = paths[j]
                if target.merge(mergee, error_range):
                    break
            else:
                working.append(mergee)
        prev_paths_num = len(paths)
        paths = working
    return list(filter(lambda p: p.is_closed, paths))