from ..waveguides.waveguide_traces import WG_TMPL
from ..waveguides.cells import StripWaveguide, ArcPathPcell, EulerBendPcell
import ipkiss3.all as i3
import numpy as np


class SquareSpiralPcell(i3.PCell):
    """
    Square Spiral.
    """

    _doc_properties = []

    _name_prefix = "sq_spiral"
    arc = i3.ChildCellProperty(locked=True)

    def _default_arc(self):
        return ArcPathPcell(name=self.name + "arc")

    class Layout(i3.LayoutView):
        _doc_properties = ["radius", "length", "spacing", "width"]

        radius = i3.PositiveNumberProperty(doc="Radius of the arcs", default=i3.TECH.TRACE.BEND_RADIUS)
        length = i3.PositiveNumberProperty(doc="Total length of the spiral", default=2500.0)
        spacing = i3.PositiveNumberProperty(doc="Spacing b/w the turns of the spiral", default=1.5)
        width = i3.PositiveNumberProperty(doc="Width of the spiral", default=0.6)

        def _default_arc(self):
            lv = self.cell.arc.get_default_view(self)
            lv.set(
                width=self.width,
                arc_angle=90,
                radius=self.radius,
            )
            return lv

        def validate_properties(self):
            radius = self.radius
            gap = self.spacing
            width = self.width
            length = self.length
            total_gap = gap + width

            quad_a = 4 * total_gap
            quad_b = 4 * radius + 2 * np.pi * radius
            min_length = quad_a + quad_b + np.pi * radius
            if length < min_length:
                raise i3.PropertyValidationError(
                    self, "Square Spiral Length should be larger than {}".format(min_length)
                )
            return True

        def _generate_elements(self, elems):
            arc = self.arc
            radius = self.radius
            gap = self.spacing
            width = self.width
            length = self.length
            total_gap = gap + width

            quad_a = 4 * total_gap
            quad_b = 4 * radius + 2 * np.pi * radius
            quad_c = np.pi * radius - length
            N = (-quad_b + np.sqrt(quad_b**2 - 4 * quad_a * quad_c)) / (2.0 * quad_a)

            Nf = int(N)
            Lf = 4 * total_gap * Nf**2 + Nf * (4 * radius + 2 * np.pi * radius) + np.pi * radius
            extraH = (length - Lf) / (2 * Nf + 1)
            baseW = 2 * radius
            baseH = total_gap + extraH
            cx = 0
            cy = 0
            straight_1 = StripWaveguide(name=self.name + "_st1").Layout(
                width=width, shape=[(0.0, 0.0), (0, -extraH / 2.0)]
            )
            insts_dict = {"straight1": straight_1}
            specs_array = [i3.Place("straight1", (cx, cy))]
            cy -= extraH / 2.0
            insts_dict.update(
                [
                    ("arc", arc),
                ]
            )
            specs_array.append(i3.Place("arc", (cx, cy), 270))
            cx += radius
            cy -= radius
            for n in range(1, Nf + 1):
                if n == 1:
                    insts_dict.update(
                        [
                            ("arc_l_1", arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_l_1", (cx, cy)))
                    cx += radius
                    cy += radius
                    straight = StripWaveguide(name=self.name + "_st_l_1").Layout(
                        width=width, shape=[(0, 0), (0, baseH)]
                    )
                    insts_dict.update(
                        [
                            ("straight_1", straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_1", (cx, cy)))
                    cy += baseH
                    insts_dict.update(
                        [
                            ("arc_r_1", arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_r_1", (cx, cy), 90))
                    cx -= radius
                    cy += radius
                    straight = StripWaveguide(name=self.name + "_st_r_1").Layout(
                        width=width, shape=[(0, 0), (-(baseW + total_gap), 0)]
                    )
                    insts_dict.update(
                        [
                            ("straight_r_1", straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_r_1", (cx, cy)))
                    cx -= baseW + total_gap
                elif n % 2 == 1:
                    insts_dict.update([("arc_{}".format(n), arc)])
                    specs_array.append(i3.Place("arc_{}".format(n), (cx, cy)))
                    cx += radius
                    cy += radius
                    straight = StripWaveguide(name=self.name + "_st_r_{}".format(n)).Layout(
                        width=width, shape=[(0, 0), (0, baseH + 2 * (n - 1) * total_gap)]
                    )
                    insts_dict.update(
                        [
                            ("straight_r_{}".format(n), straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_r_{}".format(n), (cx, cy)))
                    cy += baseH + 2 * (n - 1) * total_gap
                    insts_dict.update(
                        [
                            ("arc_l_{}".format(n), arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_l_{}".format(n), (cx, cy), 90))
                    cx -= radius
                    cy += radius
                    straight = StripWaveguide(name=self.name + "_st_t_{}".format(n)).Layout(
                        width=width, shape=[(0, 0), (-(baseW + (2 * n - 1) * total_gap), 0)]
                    )
                    insts_dict.update(
                        [
                            ("straight_t_{}".format(n), straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_t_{}".format(n), (cx, cy)))
                    cx -= baseW + (2 * n - 1) * total_gap
                else:
                    insts_dict.update(
                        [
                            ("arc_l_{}".format(n), arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_l_{}".format(n), (cx, cy), 180))
                    cx -= radius
                    cy -= radius
                    straight = StripWaveguide(name=self.name + "_st_l_{}".format(n)).Layout(
                        width=width, shape=[(0, 0), (0, -(baseH + 2 * (n - 1) * total_gap))]
                    )
                    insts_dict.update(
                        [
                            ("straight_l_{}".format(n), straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_l_{}".format(n), (cx, cy)))
                    cy -= baseH + 2 * (n - 1) * total_gap
                    insts_dict.update(
                        [
                            ("arc_r_{}".format(n), arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_r_{}".format(n), (cx, cy), 270))
                    cx += radius
                    cy -= radius
                    straight = StripWaveguide(name=self.name + "_st_b_{}".format(n)).Layout(
                        width=width, shape=[(0, 0), ((baseW + (2 * n - 1) * total_gap), 0)]
                    )
                    insts_dict.update(
                        [
                            ("straight_b_{}".format(n), straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_b_{}".format(n), (cx, cy)))
                    cx += baseW + (2 * n - 1) * total_gap

            if Nf % 2 == 1:
                straight = StripWaveguide(name=self.name + "_st_end").Layout(
                    width=width, shape=[(0, 0), (-(radius - total_gap + width / 2.0), 0)]
                )
                insts_dict.update(
                    [
                        ("straight_end", straight),
                    ]
                )
                specs_array.append(i3.Place("straight_end", (cx, cy)))
                outport = (cx - (radius - total_gap + width / 2.0), cy)
                cx -= radius - total_gap

            else:
                straight = StripWaveguide(name=self.name + "_st_end").Layout(
                    width=width, shape=[(0, 0), ((radius - total_gap + width / 2.0), 0)]
                )
                insts_dict.update(
                    [
                        ("straight_end", straight),
                    ]
                )
                specs_array.append(i3.Place("straight_end", (cx, cy)))
                outport = (cx + (radius - total_gap + width / 2.0), cy)
                cx += radius - total_gap

            layout = i3.place_and_route(
                insts=insts_dict,
                specs=specs_array,
            )
            for elem in layout:
                elems += layout[elem].flat_copy()
            final_elems = elems.flat_copy()
            if outport[0] < 0:
                outport = (-outport[0], -outport[1])
            self.outport = i3.Coord2(outport).translate(translation=(outport[0], outport[1]))
            final_elems.translate(translation=(outport[0], outport[1]))
            elems.rotate(rotation_center=(0, 0), rotation=180)
            elems.translate(translation=(outport[0], outport[1]))
            final_elems += elems
            return final_elems

        def _generate_ports(self, ports):
            self.elements
            outport = self.outport
            tt = WG_TMPL(name=self.name + "_tt")
            tt.Layout(core_width=self.width)
            ports += i3.OpticalPort(
                name="in0",
                trace_template=tt,
                position=(0, 0),
                angle=180.0,
            )
            ports += i3.OpticalPort(
                name="out0",
                trace_template=tt,
                position=outport,
                angle=0.0,
            )
            return ports

    class Netlist(i3.NetlistView):
        def _generate_terms(self, terms):
            ports = self.cell.get_default_view(i3.LayoutView).ports
            terms += [i3.OpticalTerm(name=p.name) for p in ports]
            return terms


class SquareSpiralEulerPcell(i3.PCell):
    """
    Square Spiral with Euler arcs.
    """

    _doc_properties = []

    _name_prefix = "sq_spiral_euler"
    arc = i3.ChildCellProperty(locked=True)

    def _default_arc(self):
        return EulerBendPcell(name=self.name + "arc")

    class Layout(i3.LayoutView):
        _doc_properties = ["radius", "length", "spacing", "width", "p_factor"]

        radius = i3.PositiveNumberProperty(doc="Radius of the arcs", default=i3.TECH.TRACE.BEND_RADIUS)
        length = i3.PositiveNumberProperty(doc="Total length of the spiral", default=2500.0)
        spacing = i3.PositiveNumberProperty(doc="Spacing b/w the turns of the spiral", default=1.5)
        width = i3.PositiveNumberProperty(doc="Width of the spiral", default=0.6)
        p_factor = i3.PositiveNumberProperty(
            default=0.2,
            doc="Fraction of the bend having linearly increasing curvature",
        )

        def _default_arc(self):
            lv = self.cell.arc.get_default_view(self)
            lv.set(
                width=self.width,
                arc_angle=90,
                min_radius=self.radius,
                p_factor=self.p_factor,
            )
            return lv

        def validate_properties(self):
            radius = self.radius
            gap = self.spacing
            width = self.width
            length = self.length
            total_gap = gap + width

            quad_a = 4 * total_gap
            quad_b = 4 * radius + 2 * np.pi * radius
            min_length = quad_a + quad_b + np.pi * radius
            if length < min_length:
                raise i3.PropertyValidationError(
                    self, "Euler Square Spiral Length should be larger than {}".format(min_length)
                )
            return True

        def _generate_elements(self, elems):
            arc = self.arc
            radius = self.radius
            gap = self.spacing
            width = self.width
            length = self.length
            total_gap = gap + width

            euler_length = arc.ports["out0"].position[1]
            quad_a = 4 * total_gap
            quad_b = 4 * radius + 2 * np.pi * radius
            quad_c = np.pi * radius - length
            N = (-quad_b + np.sqrt(quad_b**2 - 4 * quad_a * quad_c)) / (2.0 * quad_a)

            Nf = int(N)
            Lf = 4 * total_gap * Nf**2 + Nf * (4 * radius + 2 * np.pi * radius) + np.pi * radius

            extraH = (length - Lf) / (2 * Nf + 1)
            baseW = 2 * euler_length
            baseH = total_gap + extraH
            cx = 0
            cy = 0
            straight_1 = StripWaveguide(name=self.name + "_st1").Layout(
                width=width, shape=[(0.0, 0.0), (0, -extraH / 2.0)]
            )
            insts_dict = {"straight1": straight_1}
            specs_array = [i3.Place("straight1", (cx, cy))]
            cy -= extraH / 2.0
            insts_dict.update(
                [
                    ("arc", arc),
                ]
            )
            specs_array.append(i3.Place("arc", (cx, cy), 270))
            cx += euler_length
            cy -= euler_length
            for n in range(1, Nf + 1):
                if n == 1:
                    insts_dict.update(
                        [
                            ("arc_l_1", arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_l_1", (cx, cy)))
                    cx += euler_length
                    cy += euler_length
                    straight = StripWaveguide(name=self.name + "_st_l_1").Layout(
                        width=width, shape=[(0, 0), (0, baseH)]
                    )
                    insts_dict.update(
                        [
                            ("straight_1", straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_1", (cx, cy)))
                    cy += baseH
                    insts_dict.update(
                        [
                            ("arc_r_1", arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_r_1", (cx, cy), 90))
                    cx -= euler_length
                    cy += euler_length
                    straight = StripWaveguide(name=self.name + "_st_r_1").Layout(
                        width=width, shape=[(0, 0), (-(baseW + total_gap), 0)]
                    )
                    insts_dict.update(
                        [
                            ("straight_r_1", straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_r_1", (cx, cy)))
                    cx -= baseW + total_gap
                elif n % 2 == 1:
                    insts_dict.update([("arc_{}".format(n), arc)])
                    specs_array.append(i3.Place("arc_{}".format(n), (cx, cy)))
                    cx += euler_length
                    cy += euler_length
                    straight = StripWaveguide(name=self.name + "_st_r_{}".format(n)).Layout(
                        width=width, shape=[(0, 0), (0, baseH + 2 * (n - 1) * total_gap)]
                    )
                    insts_dict.update(
                        [
                            ("straight_r_{}".format(n), straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_r_{}".format(n), (cx, cy)))
                    cy += baseH + 2 * (n - 1) * total_gap
                    insts_dict.update(
                        [
                            ("arc_l_{}".format(n), arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_l_{}".format(n), (cx, cy), 90))
                    cx -= euler_length
                    cy += euler_length
                    straight = StripWaveguide(name=self.name + "_st_t_{}".format(n)).Layout(
                        width=width, shape=[(0, 0), (-(baseW + (2 * n - 1) * total_gap), 0)]
                    )
                    insts_dict.update(
                        [
                            ("straight_t_{}".format(n), straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_t_{}".format(n), (cx, cy)))
                    cx -= baseW + (2 * n - 1) * total_gap
                else:
                    insts_dict.update(
                        [
                            ("arc_l_{}".format(n), arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_l_{}".format(n), (cx, cy), 180))
                    cx -= euler_length
                    cy -= euler_length
                    straight = StripWaveguide(name=self.name + "_st_l_{}".format(n)).Layout(
                        width=width, shape=[(0, 0), (0, -(baseH + 2 * (n - 1) * total_gap))]
                    )
                    insts_dict.update(
                        [
                            ("straight_l_{}".format(n), straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_l_{}".format(n), (cx, cy)))
                    cy -= baseH + 2 * (n - 1) * total_gap
                    insts_dict.update(
                        [
                            ("arc_r_{}".format(n), arc),
                        ]
                    )
                    specs_array.append(i3.Place("arc_r_{}".format(n), (cx, cy), 270))
                    cx += euler_length
                    cy -= euler_length
                    straight = StripWaveguide(name=self.name + "_st_b_{}".format(n)).Layout(
                        width=width, shape=[(0, 0), ((baseW + (2 * n - 1) * total_gap), 0)]
                    )
                    insts_dict.update(
                        [
                            ("straight_b_{}".format(n), straight),
                        ]
                    )
                    specs_array.append(i3.Place("straight_b_{}".format(n), (cx, cy)))
                    cx += baseW + (2 * n - 1) * total_gap

            if Nf % 2 == 1:
                straight = StripWaveguide(name=self.name + "_st_end").Layout(
                    width=width, shape=[(0, 0), (-(euler_length - total_gap + width / 2.0), 0)]
                )
                insts_dict.update(
                    [
                        ("straight_end", straight),
                    ]
                )
                specs_array.append(i3.Place("straight_end", (cx, cy)))
                outport = (cx - (euler_length - total_gap + width / 2.0), cy)
                cx -= euler_length - total_gap

            else:
                straight = StripWaveguide(name=self.name + "_st_end").Layout(
                    width=width, shape=[(0, 0), ((euler_length - total_gap + width / 2.0), 0)]
                )
                insts_dict.update(
                    [
                        ("straight_end", straight),
                    ]
                )
                specs_array.append(i3.Place("straight_end", (cx, cy)))
                outport = (cx + (euler_length - total_gap + width / 2.0), cy)
                cx += euler_length - total_gap

            layout = i3.place_and_route(
                insts=insts_dict,
                specs=specs_array,
            )
            for elem in layout:
                elems += layout[elem].flat_copy()
            final_elems = elems.flat_copy()
            if outport[0] < 0:
                outport = (-outport[0], -outport[1])
            self.outport = i3.Coord2(outport).translate(translation=(outport[0], outport[1]))
            final_elems.translate(translation=(outport[0], outport[1]))
            elems.rotate(rotation_center=(0, 0), rotation=180)
            elems.translate(translation=(outport[0], outport[1]))
            final_elems += elems
            return final_elems

        def _generate_ports(self, ports):
            self.elements
            outport = self.outport
            tt = WG_TMPL(name=self.name + "_tt")
            tt.Layout(core_width=self.width)
            ports += i3.OpticalPort(
                name="in0",
                trace_template=tt,
                position=(0, 0),
                angle=180.0,
            )
            ports += i3.OpticalPort(
                name="out0",
                trace_template=tt,
                position=outport,
                angle=0.0,
            )
            return ports

    class Netlist(i3.NetlistView):
        def _generate_terms(self, terms):
            ports = self.cell.get_default_view(i3.LayoutView).ports
            terms += [i3.OpticalTerm(name=p.name) for p in ports]
            return terms


class SpiralPcell(i3.PCell):
    """
    Circular Spiral.
    """

    _doc_properties = []

    _name_prefix = "spiral"

    class Layout(i3.LayoutView):
        _doc_properties = ["inner_radius", "length", "spacing", "width"]

        inner_radius = i3.PositiveNumberProperty(doc="Inner Radius of the spiral", default=i3.TECH.TRACE.BEND_RADIUS)
        length = i3.PositiveNumberProperty(doc="Total length of the spiral", default=2900.0)
        spacing = i3.PositiveNumberProperty(doc="Spacing b/w the turns of the spiral", default=1.5)
        width = i3.PositiveNumberProperty(doc="Width of the spiral", default=0.6)

        def validate_properties(self):
            radius = self.inner_radius
            gap = self.spacing
            width = self.width
            length = self.length
            pitch = gap + width

            quad_a = pitch * np.pi
            quad_b = 4 * np.pi * radius
            min_length = quad_a + quad_b + 2 * np.pi * radius
            if length < min_length:
                raise i3.PropertyValidationError(
                    self, "Circular Spiral Length should be larger than {}".format(min_length)
                )
            return True

        def _generate_elements(self, elems):
            radius = self.inner_radius
            gap = self.spacing
            width = self.width
            length = self.length
            pitch = width + gap

            quad_a = pitch * np.pi
            quad_b = 4 * np.pi * radius
            quad_c = 2 * np.pi * radius - length
            turns = np.int((-quad_b + np.sqrt(quad_b**2 - 4 * quad_a * quad_c)) / (2 * quad_a))
            length_mod = np.pi * pitch * turns**2 + turns * 4 * np.pi * radius + 2 * np.pi * radius
            extra_length = length - length_mod
            theta = extra_length / (4 * radius + 2 * turns * pitch)

            straight = StripWaveguide(name=self.name + "_st").Layout(width=width, shape=[(0, 0), (2 * radius, 0)])
            center_arc = ArcPathPcell(name=self.name + "_center_arc").Layout(
                radius=radius,
                arc_angle=180.0,
                width=width,
            )
            arcs = [
                ArcPathPcell(name=self.name + "_arc_{}".format(index))
                .Layout(
                    width=width,
                    radius=(4 * radius + (2 * index + 1) * pitch) / 2.0,
                    arc_angle=180.0,
                )
                .cell
                for index in range(turns)
            ]
            in_arc = ArcPathPcell(name=self.name + "_in_arc").Layout(
                radius=(4 * radius + (2 * turns + 1) * pitch) / 2.0,
                arc_angle=theta * 180 / np.pi,
                width=width,
            )
            arcs_dict = {
                "arc_cl": center_arc,
                "arc_cr": center_arc,
            }
            arcs_dict.update(
                [("arc_{}".format(_), arc) for _, arc in enumerate(arcs[::-1])]
                + [("arc_{}".format(_ + turns), arc) for _, arc in enumerate(arcs[::-1])]
            )
            specs_array = [
                i3.FlipV(["arc_{}".format(_) for _ in range(turns)]),
                i3.Place("arc_cr:out0", (0.0, 0.0)),
                i3.Join("arc_cr:in0", "arc_cl:in0"),
                i3.Join("arc_cl:out0", "arc_{}:out0".format(turns - 1)),
                i3.Join("arc_cr:out0", "arc_{}:in0".format(2 * turns - 1)),
                i3.Join(
                    [
                        ("arc_{}:in0".format(turns - 1 - _), "arc_{}:out0".format(turns - 1 - _ - 1))
                        for _ in range(turns - 1)
                    ]
                ),
                i3.Join(
                    [("arc_{}:in0".format(_ + turns), "arc_{}:out0".format(_ + turns + 1)) for _ in range(turns - 1)]
                ),
            ]
            if extra_length > 0:
                arcs_dict.update(
                    [
                        ("arc_in", in_arc),
                        ("arc_out", in_arc),
                        ("straight_in", straight),
                        ("straight_out", straight),
                    ]
                )
                specs_array.append(i3.Join("arc_in:in0", "arc_0:in0"))
                specs_array.append(i3.Join("arc_in:out0", "straight_in:in0"))
                specs_array.append(i3.Join("arc_out:in0", "arc_{}:out0".format(turns)))
                specs_array.append(i3.Join("arc_out:out0", "straight_out:out0"))
            else:
                arcs_dict.update(
                    [
                        ("straight_in", straight),
                        ("straight_out", straight),
                    ]
                )
                specs_array.append(i3.Join("arc_0:in0", "straight_in:in0"))
                specs_array.append(i3.Join("arc_{}:out0".format(turns), "straight_out:out0"))
            layout = i3.place_and_route(insts=arcs_dict, specs=specs_array)

            straight_in = layout["straight_in"].ports["in0"].position
            straight_out = layout["straight_in"].ports["out0"].position
            rot_angle = np.rad2deg(np.arctan((straight_out[1] - straight_in[1]) / (straight_out[0] - straight_in[0])))

            temp_pt = layout["straight_out"].ports["in0"].position
            if temp_pt[0] < straight_out[0]:
                (temp_pt, straight_out) = (straight_out, temp_pt)
            inport = straight_out.rotate(rotation=-rot_angle, rotation_center=(0, 0))
            temp_outport = temp_pt.rotate(rotation=-rot_angle, rotation_center=(0, 0)).translate(
                translation=(-inport[0], -inport[1])
            )
            outport = temp_outport
            for elem in layout:
                temp_elem = layout[elem].flat_copy().rotate(rotation=-rot_angle, rotation_center=(0, 0))
                elems += temp_elem.translate(translation=(-inport[0], -inport[1]))
                if temp_outport[0] < 0:
                    elems += temp_elem.translate(translation=(-temp_outport[0], -temp_outport[1]))
                    outport = (-temp_outport[0], -temp_outport[1])
            self.outport = outport

            return elems

        def _generate_ports(self, ports):
            self.elements
            outport = self.outport
            tt = WG_TMPL(name=self.name + "_tt")
            tt.Layout(core_width=self.width)
            ports += i3.OpticalPort(
                name="in0",
                trace_template=tt,
                position=(0, 0),
                angle=180.0,
            )
            ports += i3.OpticalPort(
                name="out0",
                trace_template=tt,
                position=outport,
                angle=0.0,
            )
            return ports

    class Netlist(i3.NetlistView):
        def _generate_terms(self, terms):
            ports = self.cell.get_default_view(i3.LayoutView).ports
            terms += [i3.OpticalTerm(name=p.name) for p in ports]
            return terms


class SpiralEulerPcell(i3.PCell):
    """
    Circular Spiral with Euler arcs.
    """

    _doc_properties = []

    _name_prefix = "spiral_euler"

    class Layout(i3.LayoutView):
        _doc_properties = ["inner_radius", "length", "spacing", "width", "p_factor"]

        inner_radius = i3.PositiveNumberProperty(doc="Inner Radius of the spiral", default=i3.TECH.TRACE.BEND_RADIUS)
        length = i3.PositiveNumberProperty(doc="Total length of the spiral", default=2900.0)
        spacing = i3.PositiveNumberProperty(doc="Spacing b/w the turns of the spiral", default=1.5)
        width = i3.PositiveNumberProperty(doc="Width of the spiral", default=0.6)
        p_factor = i3.PositiveNumberProperty(
            default=0.2,
            doc="Fraction of the bend having linearly increasing curvature",
        )

        def validate_properties(self):
            radius = self.inner_radius
            gap = self.spacing
            width = self.width
            length = self.length
            pitch = gap + width

            quad_a = pitch * np.pi
            quad_b = 4 * np.pi * radius
            min_length = quad_a + quad_b + 2 * np.pi * radius
            if length < min_length:
                raise i3.PropertyValidationError(
                    self, "Euler Circular Spiral Length should be larger than {}".format(min_length)
                )
            return True

        def _generate_elements(self, elems):
            radius = self.inner_radius
            gap = self.spacing
            width = self.width
            length = self.length
            p_factor = self.p_factor
            pitch = width + gap

            quad_a = pitch * np.pi
            quad_b = 4 * np.pi * radius
            quad_c = 2 * np.pi * radius - length
            turns = np.int((-quad_b + np.sqrt(quad_b**2 - 4 * quad_a * quad_c)) / (2 * quad_a))
            length_mod = np.pi * pitch * turns**2 + turns * 4 * np.pi * radius + 2 * np.pi * radius
            print("warning: Euler Circular Spiral is being drawn only for the length of {}".format(length_mod))
            # extra_length = length - length_mod
            extra_length = 0
            theta = extra_length / (4 * radius + 2 * turns * pitch)

            straight = StripWaveguide(name=self.name + "_st").Layout(width=width, shape=[(0, 0), (2 * radius, 0)])
            center_arc = EulerBendPcell(name=self.name + "_center_arc").Layout(
                min_radius=radius, arc_angle=180.0, width=width, p_factor=p_factor
            )
            arcs = [
                EulerBendPcell(name=self.name + "_arc_{}".format(index))
                .Layout(
                    width=width,
                    min_radius=(4 * radius + (2 * index + 1) * pitch) / 2.0,
                    arc_angle=180.0,
                    p_factor=p_factor,
                )
                .cell
                for index in range(turns)
            ]
            in_arc = EulerBendPcell(name=self.name + "_in_arc").Layout(
                min_radius=(4 * radius + (2 * turns + 1) * pitch) / 2.0,
                arc_angle=theta * 180 / np.pi,
                width=width,
                p_factor=p_factor,
            )
            arcs_dict = {
                "arc_cl": center_arc,
                "arc_cr": center_arc,
            }
            arcs_dict.update(
                [("arc_{}".format(_), arc) for _, arc in enumerate(arcs[::-1])]
                + [("arc_{}".format(_ + turns), arc) for _, arc in enumerate(arcs[::-1])]
            )
            specs_array = [
                i3.FlipV(["arc_{}".format(_) for _ in range(turns)]),
                i3.Place("arc_cr:out0", (0.0, 0.0)),
                i3.Join("arc_cr:in0", "arc_cl:in0"),
                i3.Join("arc_cl:out0", "arc_{}:out0".format(turns - 1)),
                i3.Join("arc_cr:out0", "arc_{}:in0".format(2 * turns - 1)),
                i3.Join(
                    [
                        ("arc_{}:in0".format(turns - 1 - _), "arc_{}:out0".format(turns - 1 - _ - 1))
                        for _ in range(turns - 1)
                    ]
                ),
                i3.Join(
                    [("arc_{}:in0".format(_ + turns), "arc_{}:out0".format(_ + turns + 1)) for _ in range(turns - 1)]
                ),
            ]
            if extra_length > 0:
                arcs_dict.update(
                    [
                        ("arc_in", in_arc),
                        ("arc_out", in_arc),
                        ("straight_in", straight),
                        ("straight_out", straight),
                    ]
                )
                specs_array.append(i3.Join("arc_in:in0", "arc_0:in0"))
                specs_array.append(i3.Join("arc_in:out0", "straight_in:in0"))
                specs_array.append(i3.Join("arc_out:in0", "arc_{}:out0".format(turns)))
                specs_array.append(i3.Join("arc_out:out0", "straight_out:out0"))
            else:
                arcs_dict.update(
                    [
                        ("straight_in", straight),
                        ("straight_out", straight),
                    ]
                )
                specs_array.append(i3.Join("arc_0:in0", "straight_in:in0"))
                specs_array.append(i3.Join("arc_{}:out0".format(turns), "straight_out:out0"))
            layout = i3.place_and_route(insts=arcs_dict, specs=specs_array)

            straight_in = layout["straight_in"].ports["in0"].position
            straight_out = layout["straight_in"].ports["out0"].position
            rot_angle = np.rad2deg(np.arctan((straight_out[1] - straight_in[1]) / (straight_out[0] - straight_in[0])))

            temp_pt = layout["straight_out"].ports["in0"].position
            if temp_pt[0] < straight_out[0]:
                (temp_pt, straight_out) = (straight_out, temp_pt)
            inport = straight_out.rotate(rotation=-rot_angle, rotation_center=(0, 0))
            temp_outport = temp_pt.rotate(rotation=-rot_angle, rotation_center=(0, 0)).translate(
                translation=(-inport[0], -inport[1])
            )
            outport = temp_outport
            for elem in layout:
                temp_elem = layout[elem].flat_copy().rotate(rotation=-rot_angle, rotation_center=(0, 0))
                elems += temp_elem.translate(translation=(-inport[0], -inport[1]))
                if temp_outport[0] < 0:
                    elems += temp_elem.translate(translation=(-temp_outport[0], -temp_outport[1]))
                    outport = (-temp_outport[0], -temp_outport[1])
            self.outport = outport

            return elems

        def _generate_ports(self, ports):
            self.elements
            outport = self.outport
            tt = WG_TMPL(name=self.name + "_tt")
            tt.Layout(core_width=self.width)
            ports += i3.OpticalPort(
                name="in0",
                trace_template=tt,
                position=(0, 0),
                angle=180.0,
            )
            ports += i3.OpticalPort(
                name="out0",
                trace_template=tt,
                position=outport,
                angle=0.0,
            )
            return ports

    class Netlist(i3.NetlistView):
        def _generate_terms(self, terms):
            ports = self.cell.get_default_view(i3.LayoutView).ports
            terms += [i3.OpticalTerm(name=p.name) for p in ports]
            return terms
