import gdspy
import numpy as np

from copy import deepcopy
from spira.core.transforms import *
from spira.yevon import constants
from spira.yevon.gdsii.elem_list import ElementList
from spira.yevon.gdsii.group import Group
from spira.yevon.gdsii.polygon import __ShapeElement__
from spira.yevon.geometry import shapes
from spira.yevon.geometry.shapes import ShapeParameter
from spira.yevon.geometry.coord import Coord
from spira.yevon.gdsii.base import __LayerElement__
from spira.core.parameters.descriptor import Parameter
from spira.yevon.process.process_layer import ProcessParameter
from spira.core.parameters.variables import *
from spira.core.transforms import Stretch
from spira.yevon.process.physical_layer import PLayer
from spira.yevon.process import get_rule_deck


__all__ = [
    'Edge',
    'EdgeAdapter',
    'generate_edges',
]


RDD = get_rule_deck()


class Edge(__ShapeElement__):
    """ Edge elements are object that represents the edge
    of a polygonal shape.

    Example
    -------
    >>> edge Edge()
    """

    width = NumberParameter(default=1, doc='The width of the edge.')
    extend = NumberParameter(default=1, doc='The distance the edge extends from the shape.')
    pid = StringParameter(default='no_pid', doc='A unique polygon ID to which the edge connects.')
    edge_type = IntegerParameter(default=constants.EDGE_TYPE_NORMAL)

    def __init__(self, shape, layer, transformation=None, **kwargs):
        super().__init__(shape=shape, layer=layer, transformation=transformation, **kwargs)

    def __repr__(self):
        if self is None:
            return 'Edge is None!'
        layer = RDD.GDSII.IMPORT_LAYER_MAP[self.layer]
        class_string = "[SPiRA: Edge \'{}\'] (center {}, width {}, extend {}, process {}, purpose {})"
        return class_string.format(self.edge_type, self.center, self.width,
            self.extend, self.layer.process.symbol, self.layer.purpose.symbol)

    def __str__(self):
        return self.__repr__()


def EdgeAdapter(original_edge, edge_type, **kwargs):
    """ Adapter class to modify the edge shape. """

    shape = original_edge.shape
    extend = original_edge.extend
    width = original_edge.width

    if edge_type == constants.EDGE_TYPE_INSIDE:
        shp = shape.move((0,extend/2.0))
    elif edge_type == constants.EDGE_TYPE_OUTSIDE:
        shp = shape.move((0,-extend/2.0))
    elif edge_type == constants.EDGE_TYPE_SQUARE:
        sf = 1 + 2*extend/width
        shp = Stretch(stretch_factor=(sf,1), stretch_center=shape.center_of_mass)(shape)
    elif edge_type == constants.EDGE_TYPE_SIDE_EXTEND:
        if 'side_extend' not in kwargs:
            raise ValueError('No `side_extend` parameter given.')
        side_extend = kwargs['side_extend']
        sf = 1 + 2*side_extend/width
        shp = Stretch(stretch_factor=(sf,1), stretch_center=shape.center_of_mass)(shape)
    elif edge_type == constants.EDGE_TYPE_EUCLIDEAN:
        pass
    elif edge_type == constants.EDGE_TYPE_NORMAL:
        pass

    original_edge = original_edge.copy(shape=shp, edge_type=edge_type)

    return original_edge


def EdgeSymmetric(width=1, extend=1, process=None, transformation=None):
    """  """
    layer = PLayer(process=process, purpose=RDD.PURPOSE.PORT.OUTSIDE_EDGE_DISABLED)
    shape = shapes.BoxShape(width=width, height=2*extend)
    return Edge(shape=shape, layer=layer, width=width, extend=2*extend, transformation=transformation)


class EdgeGenerator(Group, __LayerElement__):
    """ Generates edge objects for each shape segment. """

    shape = ShapeParameter()

    def create_elements(self, elems):

        xpts = list(self.shape.x_coords)
        ypts = list(self.shape.y_coords)

        n = len(xpts)
        xpts.append(xpts[0])
        ypts.append(ypts[0])

        clockwise = 0
        for i in range(0, n):
            clockwise += ((xpts[i+1] - xpts[i]) * (ypts[i+1] + ypts[i]))

        if self.layer.name == 'BBOX': bbox = True
        else: bbox = False

        for i in range(0, n):

            name = '{}_e{}'.format(self.layer.name, i)
            x = np.sign(clockwise) * (xpts[i+1] - xpts[i])
            y = np.sign(clockwise) * (ypts[i] - ypts[i+1])
            orientation = (np.arctan2(x, y) * constants.RAD2DEG) + 90
            midpoint = [(xpts[i+1] + xpts[i])/2, (ypts[i+1] + ypts[i])/2]
            width = np.abs(np.sqrt((xpts[i+1] - xpts[i])**2 + (ypts[i+1]-ypts[i])**2))

            layer = RDD.GDSII.IMPORT_LAYER_MAP[self.layer]
            extend = RDD[layer.process.symbol].MIN_SIZE

            T = Rotation(orientation) + Translation(midpoint)
            layer = PLayer(process=layer.process, purpose=RDD.PURPOSE.PORT.OUTSIDE_EDGE_DISABLED)
            shape = shapes.BoxShape(width=width, height=extend)
            # elems += EdgeSymmetric(width=width, extend=extend, process=layer.process, transformation=T)
            elems += Edge(shape=shape, layer=layer, width=width, extend=extend, transformation=T)

        return elems


def generate_edges(shape, layer):
    """ Method call for edge generator. """
    edge_gen = EdgeGenerator(shape=shape, layer=layer)
    return edge_gen.elements


# def shape_edge_ports(shape, layer, local_pid='None', center=(0,0), loc_name=''):

#     edges = PortList()

#     xpts = list(shape.x_coords)
#     ypts = list(shape.y_coords)

#     n = len(xpts)

#     xpts.append(xpts[0])
#     ypts.append(ypts[0])

#     clockwise = 0
#     for i in range(0, n):
#         clockwise += ((xpts[i+1] - xpts[i]) * (ypts[i+1] + ypts[i]))

#     if layer.name == 'BBOX': bbox = True
#     else: bbox = False

#     layer = RDD.GDSII.IMPORT_LAYER_MAP[layer]

#     for i in range(0, n):
#         # name = 'E{}_{}'.format(i, layer.process.symbol)
#         # name = 'E{}_{}_{}'.format(i, layer.process.symbol, shape.bbox_info.center)
#         name = '{}E{}_{}'.format(loc_name, i, layer.process.symbol)
#         x = np.sign(clockwise) * (xpts[i+1] - xpts[i])
#         y = np.sign(clockwise) * (ypts[i] - ypts[i+1])
#         orientation = (np.arctan2(x, y) * constants.RAD2DEG)
#         midpoint = [(xpts[i+1] + xpts[i])/2, (ypts[i+1] + ypts[i])/2]
#         width = np.abs(np.sqrt((xpts[i+1] - xpts[i])**2 + (ypts[i+1]-ypts[i])**2))
#         P = Port(
#             name=name,
#             process=layer.process,
#             purpose=RDD.PURPOSE.PORT.OUTSIDE_EDGE_DISABLED,
#             midpoint=midpoint,
#             orientation=orientation,
#             width=width,
#             length=0.2,
#             local_pid=local_pid
#         )
#         edges += P
#     return edges

