import ipkiss3.all as i3
from ipkiss3.pcell.layout.port_list import PortList

from ipkiss3.pcell.wiring.window_trace import ElectricalWindowWire


class ElectricalWindowWire(ElectricalWindowWire):
    def _default_template(self):
        # workaround for OA export: the ElectricalWindoWire in IPKISS does not have a default template
        return MetalWireTemplate()


class MetalWireTemplate(i3.ElectricalWindowWireTemplate):
    """Metal wire template."""

    _doc_properties = []
    _templated_class = ElectricalWindowWire

    class Layout(i3.ElectricalWindowWireTemplate.Layout):
        _doc_properties = ["core_width"]
        core_width = i3.PositiveNumberProperty(doc="Width of the metal [um]")

        def _default_layer(self):
            return i3.TECH.PPLAYER.M1

        def _default_core_width(self):
            return i3.TECH.METAL.LINE_WIDTH

        def _default_width(self):
            return self.core_width

        def _default_windows(self):
            width = self.width

            windows = [i3.PathTraceWindow(layer=self.layer, start_offset=-width / 2.0, end_offset=width / 2.0)]
            return windows


class MetalWire(i3.ElectricalWire):
    """
    Metal wire.
    """

    _name_prefix = "Metal_Wire"

    def _default_trace_template(self):
        return MetalWireTemplate()

    class Layout(i3.ElectricalWire.Layout):
        _doc_properties = ["core_width", "shape"]
        core_width = i3.PositiveNumberProperty(doc="Width of the metal [um]")

        def _default_core_width(self):
            return i3.TECH.METAL.LINE_WIDTH

        def _default_trace_template(self):
            lv = self.cell.trace_template.get_default_view(self)
            lv.set(core_width=self.core_width)
            return lv

        def _default_shape(self):
            return i3.Shape([i3.Coord2(0.0, 0.0), i3.Coord2(50.0, 0.0)])

        def _generate_ports(self, ports):
            from numpy import arctan2
            from ipkiss.constants import RAD2DEG

            default_ports = super(MetalWire.Layout, self)._generate_ports(ports)
            offset = self.trace_template.core_width / 2.0
            ports = PortList()

            points = self.shape.points
            in_vector = points[0] - points[1]
            in_angle = RAD2DEG * arctan2(in_vector[1], in_vector[0])

            out_vector = points[-1] - points[-2]
            out_angle = RAD2DEG * arctan2(out_vector[1], out_vector[0])
            angles = [in_angle, out_angle]
            layer = i3.TECH.PPLAYER.M1

            for idx, p in enumerate(default_ports):
                ports += i3.ElectricalPort(
                    name=p.name,
                    layer=layer,
                    position=p.position.move_polar_copy(offset, angles[idx]),
                    angle=angles[idx],
                    direction=p.direction,
                    trace_template=p.trace_template,
                )

            return ports


class DCTaper(i3.PCell):
    """
    Metal Taper with 45 degree slope.
    """

    _name_prefix = "DC_Taper"

    class Layout(i3.LayoutView):
        _doc_properties = ["in_width", "out_width", "length"]
        in_width = i3.PositiveNumberProperty(default=20.0, doc="Width of the metal at the input[um]")
        out_width = i3.PositiveNumberProperty(
            default=i3.TECH.METAL.LINE_WIDTH, doc="Width of the metal at the output[um]"
        )
        length = i3.NonNegativeNumberProperty(
            doc="Length of the taper based on in_width and out_width for a 45 degree angle by default",
        )

        def _default_length(self):
            return abs(self.out_width - self.in_width) / 2.0

        def _generate_elements(self, elems):
            elems += i3.Wedge(
                layer=i3.TECH.PPLAYER.M1,
                begin_coord=(0.0, 0.0),
                end_coord=(self.length, 0.0),
                begin_width=self.in_width,
                end_width=self.out_width,
            )
            return elems

        def _generate_ports(self, ports):
            el_t_in = MetalWireTemplate()
            el_t_in.Layout(core_width=self.in_width)
            layer = i3.TECH.PPLAYER.M1
            ports += i3.ElectricalPort(
                name="in",
                layer=layer,
                position=(0.0, 0.0),
                angle=180.0,
                trace_template=el_t_in,
            )
            el_t_out = MetalWireTemplate()
            el_t_out.Layout(core_width=self.out_width)
            ports += i3.ElectricalPort(
                name="out",
                layer=layer,
                position=(self.length, 0.0),
                angle=0.0,
                trace_template=el_t_out,
            )
            return ports

    class Netlist(i3.NetlistFromLayout):
        pass


class DCBend(MetalWire):
    """
    A metal bent section that follows the 45 degree Manhattan routing rule.
    The radius can be defined and also the orientation (clockwise, counterclockwise).
    """

    _name_prefix = "DC_Bend"

    class Layout(MetalWire.Layout):
        _doc_properties = ["core_width", "bend_radius", "orientation"]
        bend_radius = i3.PositiveNumberProperty(default=100.0, doc="Radius of the bend[um]")
        orientation = i3.StringProperty(
            default="clockwise",
            restriction=i3.RestrictValueList(allowed_values=["clockwise", "counterclockwise"]),
            doc="Orientation of the bend clockwise/counterclockwise." "Default is clockwise",
        )

        def _default_shape(self):
            radius = self.bend_radius
            orientation = -1.0 if self.orientation == "clockwise" else 1.0
            return i3.Shape(
                [
                    i3.Coord2(0.0, 0.0),
                    i3.Coord2(radius / 2.0, 0.0),
                    i3.Coord2(radius, orientation * radius / 2.0),
                    i3.Coord2(radius, orientation * radius),
                ]
            )
