import pya
from . import *
from SiEPIC.utils import get_technology_by_name
from SiEPIC._globals import PIN_LENGTH as pin_length
from pya import DPolygon
import math

# ====================================================================================================
# File:     e_skid_single_side_taper.py
# Author:   Hang (Bobby) Zou
# Purpose:  PCell in EBeam library for a common parallel e-skid design that have adjustable period
#           and fill factor
# Version:  V2.0
# Date:     2023-11-09
# ====================================================================================================


class e_skid_single_side_taper(pya.PCellDeclarationHelper):
    def __init__(self):
        # Important: initialize the super class
        super(e_skid_single_side_taper, self).__init__()
        self.technology_name = "EBeam"

        TECHNOLOGY = get_technology_by_name(self.technology_name)

        # e_skid Parameters:
        # core parameter
        self.param(
            "divider0", self.TypeNone, "==============Core Parameter=============="
        )

        self.param("w", self.TypeDouble, "Core Waveguide Width [um]", default=0.3)

        self.param(
            "divider1", self.TypeNone, "==============Clad Parameter=============="
        )

        # clad parameter
        self.param("p", self.TypeDouble, "e-skid Period [um]", default=0.1)
        self.param("ff", self.TypeDouble, "e-skid Fill Factor [0.2-0.8]", default=0.5)
        self.param("p_num", self.TypeInt, "e-skid Period Number", default=5)
        self.param(
            "length", self.TypeDouble, "Length of e-skid Waveguide [um]", default=10
        )

        self.param(
            "divider2", self.TypeNone, "==============Taper Parameter============="
        )

        # taper
        self.param("taper_True", self.TypeBoolean, "Taper [T/F]", default=True)
        self.param(
            "taperL", self.TypeDouble, "Strip -> e-skid Taper Length [um]", default=5
        )
        self.param("taperW", self.TypeDouble, "Strip Width [um]", default=0.5)

        self.param(
            "divider3", self.TypeNone, "============Extended Clad Parameter=========="
        )

        # extended clad parameter
        self.param(
            "clad_taper_L", self.TypeDouble, "Extended e-skid Length [um]", default=5
        )
        self.param(
            "clad_taper_angle",
            self.TypeDouble,
            "Extended e-skid Angle [degree]",
            default=2,
        )

        self.param("divider4", self.TypeNone, "=====================================")

        # Layer Parameters - Don't touch
        self.param("layer", self.TypeLayer, "Layer", default=TECHNOLOGY["Si"])
        self.param(
            "pinrec", self.TypeLayer, "PinRec Layer", default=TECHNOLOGY["PinRec"]
        )
        self.param(
            "devrec", self.TypeLayer, "DevRec Layer", default=TECHNOLOGY["DevRec"]
        )
        self.param(
            "oxideopen",
            self.TypeLayer,
            "Oxide Open Layer",
            default=TECHNOLOGY["Oxide open (to BOX)"],
        )

    def display_text_impl(self):
        # Provide a descriptive text for the cell
        return "e_skid_with_a_single_side_taper"

    # Creating the layout
    def produce_impl(self):
        # Layout Parameters
        dbu = self.layout.dbu
        ly = self.layout
        shapes = self.cell.shapes

        LayerSi = self.layer
        LayerSiN = ly.layer(LayerSi)
        LayerPinRecN = ly.layer(self.pinrec)
        LayerDevRecN = ly.layer(self.devrec)

        w = self.w  # Core Waveguide Width [um]
        p = self.p  # e-skid Period [um]
        ff = self.ff  # e-skid Duty Cycle [%]
        p_num = self.p_num  # e-skid Period Number
        length = self.length  # e-skid Waveguide Length [um]

        taper_True = self.taper_True  # e-skid Taper [T/F]
        taperW = self.taperW  # e-skid Taper Width [um]
        taperL = self.taperL  # e-skid Taper Length [um]

        clad_taper_L = self.clad_taper_L  # Extended e-skid length [nm]
        clad_taper_angle_radians = (
            self.clad_taper_angle * math.pi / 180
        )  # Extended e-skid angle [degrees]

        ###############################
        ########### e-skid ############
        ###############################

        # 1. e-skid main body

        # x, y coordinates of the core
        x_core = [0, length, length, 0]
        y_core = [-w / 2, -w / 2, w / 2, w / 2]

        dpts = [
            pya.DPoint(x_core[i], y_core[i]) for i in range(len(x_core))
        ]  # Core body
        dpolygon = DPolygon(dpts)
        element = Polygon.from_dpoly(dpolygon * (1 / dbu))
        shapes(LayerSiN).insert(element)

        # draw multiple clad for both top and bottom
        for i in range(1, p_num + 1):
            # x, y coordinates of the top clad
            x_clad_top = [0, length, length, 0]
            y_clad_top = [
                w / 2 + i * p - (p * ff),
                w / 2 + i * p - (p * ff),
                w / 2 + i * p,
                w / 2 + i * p,
            ]

            dpts = [
                pya.DPoint(x_clad_top[i], y_clad_top[i]) for i in range(len(x_clad_top))
            ]  # Core body
            dpolygon = DPolygon(dpts)
            element = Polygon.from_dpoly(dpolygon * (1 / dbu))
            shapes(LayerSiN).insert(element)

            # x, y coordinates of the bottom clad
            x_clad_bottom = [0, length, length, 0]
            y_clad_bottom = [
                -w / 2 - i * p + (p * ff),
                -w / 2 - i * p + (p * ff),
                -w / 2 - i * p,
                -w / 2 - i * p,
            ]

            dpts = [
                pya.DPoint(x_clad_bottom[i], y_clad_bottom[i])
                for i in range(len(x_clad_bottom))
            ]  # Core body
            dpolygon = DPolygon(dpts)
            element = Polygon.from_dpoly(dpolygon * (1 / dbu))
            shapes(LayerSiN).insert(element)

        # 2. taper + Pins

        if taper_True:
            # Top clad
            if clad_taper_L > taperL:
                # Prevent the clad_taper_L to be larger than taperL
                clad_taper_L = taperL

            # Calculate the required offset
            w_offset = taperW * clad_taper_L / taperL

            # fetch the taper angle at the set point
            taper_angle = math.atan(((taperW - w) / 2) / (taperL))

            if clad_taper_angle_radians <= taper_angle:
                clad_taper_angle_radians = taper_angle

            for i in range(1, p_num + 1):
                # draw the left clad, angle method
                # locate the end points of the extended clad
                clad_end_point_position = (
                    w / 2
                    + i * p
                    + i * (clad_taper_L * math.tan(clad_taper_angle_radians))
                )

                # x, y coordinates of the top clad
                x_clad_top = [0, -clad_taper_L, -clad_taper_L, 0]
                y_clad_top = [
                    w / 2 + i * p - (p * ff),
                    clad_end_point_position - (p * ff),
                    clad_end_point_position,
                    w / 2 + i * p,
                ]

                dpts = [
                    pya.DPoint(x_clad_top[i], y_clad_top[i])
                    for i in range(len(x_clad_top))
                ]  # Core body
                dpolygon = DPolygon(dpts)
                element = Polygon.from_dpoly(dpolygon * (1 / dbu))
                shapes(LayerSiN).insert(element)

                # x, y coordinates of the bottom clad
                x_clad_bottom = [0, -clad_taper_L, -clad_taper_L, 0]
                y_clad_bottom = [
                    -w / 2 - i * p + (p * ff),
                    -clad_end_point_position + (p * ff),
                    -clad_end_point_position,
                    -w / 2 - i * p,
                ]

                dpts = [
                    pya.DPoint(x_clad_bottom[i], y_clad_bottom[i])
                    for i in range(len(x_clad_bottom))
                ]  # Core body
                dpolygon = DPolygon(dpts)
                element = Polygon.from_dpoly(dpolygon * (1 / dbu))
                shapes(LayerSiN).insert(element)

            # left taper
            x_taper_left = [0, 0, -taperL, -taperL]
            y_taper_left = [-w / 2, w / 2, taperW / 2, -taperW / 2]

            dpts = [
                pya.DPoint(x_taper_left[i], y_taper_left[i])
                for i in range(len(x_taper_left))
            ]  # Core body
            dpolygon = DPolygon(dpts)
            element = Polygon.from_dpoly(dpolygon * (1 / dbu))
            shapes(LayerSiN).insert(element)

            # Adding Pins for mating
            # Pin 1
            t = pya.DTrans(pya.Trans.R90, -taperL / dbu, 0 / dbu)
            pin = pya.Path(
                [pya.DPoint(0, -pin_length / 2), pya.DPoint(0, pin_length / 2)],
                taperW / dbu,
            )
            pin_t = pin.transformed(t)
            shapes(LayerPinRecN).insert(pin_t)
            text = pya.Text("opt1", pya.DTrans(pya.Trans.R0, -taperL / dbu, 0 / dbu))
            shape = shapes(LayerPinRecN).insert(text)
            shape.text_size = 0.2 / dbu

            # Pin 2
            t = pya.DTrans(pya.Trans.R270, (length) / dbu, 0 / dbu)
            pin = pya.Path(
                [pya.DPoint(0, -pin_length / 2), pya.DPoint(0, pin_length / 2)], w / dbu
            )
            pin_t = pin.transformed(t)
            shapes(LayerPinRecN).insert(pin_t)
            text = pya.Text("opt2", pya.DTrans(pya.Trans.R0, (length) / dbu, 0 / dbu))
            shape = shapes(LayerPinRecN).insert(text)
            shape.text_size = 0.2 / dbu

            # Devbox
            dev = pya.DBox(
                -taperL,
                clad_end_point_position + 0.5,
                length,
                -clad_end_point_position - 0.5,
            )

            shapes(LayerDevRecN).insert(dev)

        else:
            # Adding Pins for mating
            # Pin 1
            t = pya.DTrans(pya.Trans.R90, 0, 0 / dbu)
            pin = pya.Path(
                [pya.DPoint(0, -pin_length / 2), pya.DPoint(0, pin_length / 2)], w / dbu
            )
            pin_t = pin.transformed(t)
            shapes(LayerPinRecN).insert(pin_t)
            text = pya.Text("opt1", pya.DTrans(pya.Trans.R0, 0, 0 / dbu))
            shape = shapes(LayerPinRecN).insert(text)
            shape.text_size = 0.2 / dbu

            # Pin 2
            t = pya.DTrans(pya.Trans.R270, length / dbu, 0 / dbu)
            pin = pya.Path(
                [pya.DPoint(0, -pin_length / 2), pya.DPoint(0, pin_length / 2)], w / dbu
            )
            pin_t = pin.transformed(t)
            shapes(LayerPinRecN).insert(pin_t)
            text = pya.Text("opt2", pya.DTrans(pya.Trans.R0, length / dbu, 0 / dbu))
            shape = shapes(LayerPinRecN).insert(text)
            shape.text_size = 0.2 / dbu

            # Devbox
            dev = pya.DBox(0, w / 2 + p_num * p + 0.5, length, -w / 2 - p_num * p - 0.5)

            shapes(LayerDevRecN).insert(dev)


# ====================================================================================================
# END OF DOCUMENT
# ====================================================================================================
