from math import floor

import gdsfactory as gf
from gdsfactory.typings import Float2, LayerSpec


@gf.cell
def nmos_5v(
    diffusion_layer: LayerSpec = (65, 20),
    poly_layer: LayerSpec = (66, 20),
    gate_width: float = 0.75,
    gate_length: float = 0.5,
    sd_width: float = 0.3,
    end_cap: float = 0.13,
    contact_size: Float2 = (0.17, 0.17),
    contact_spacing: Float2 = (0.17, 0.17),
    contact_layer: LayerSpec = (66, 44),
    contact_enclosure: Float2 = (0.06, 0.06),
    diff_spacing: float = 0.37,
    diff_enclosure: Float2 = (0.33, 0.33),
    diffp_layer: LayerSpec = (65, 44),
    pwell_layer: LayerSpec = (64, 13),
    dnwell_enclosure: Float2 = (0.4, 0.4),
    dnwell_layer: LayerSpec = (64, 18),
    nf: int = 1,
    sdm_enclosure: Float2 = (0.125, 0.125),
    nsdm_layer: LayerSpec = (93, 44),
    psdm_layer: LayerSpec = (94, 20),
    sdm_spacing: float = 0.13,
    hvi_layer: LayerSpec = (75, 20),
    hvntm_layer: LayerSpec = (125, 20),
    hvntm_enclosure: Float2 = (0.185, 0.185),
    li_width: float = 0.17,
    li_spacing: float = 0.17,
    li_layer: LayerSpec = (67, 20),
    li_enclosure: float = 0.08,
    mcon_layer: LayerSpec = (67, 44),
    mcon_enclosure: Float2 = (0.03, 0.06),
    m1_layer: LayerSpec = (68, 20),
    npc_layer: LayerSpec = (95, 20),
    npc_spacing: float = 0.09,
) -> gf.Component:
    """Return NMOS_5v.

    Args:
        diffusion_layer: spec.
        poly_layer: spec.
        gate_width: for poly.
        gate_length: for poly.
        sd_width: source drain length.
        end_cap: end cap length.
        contact_size: contact dimension (length and width).
        contact_layer: for contacts.
        contact_enclosure: for contacts within diffusion or poly.
        diff_spacing: for two adjacent diffusions.
        diff_enclosure: for diffusion within well.
        diffp_layer: for bulk tie.
        dnwell layer: for deep nwell.
        nf: for finger option.

    .. plot::
      :include-source:

      import sky130

      c = sky130.pcells.nmos_5v(gate_length= 2, gate_width=10)
      c.plot()

    """

    c = gf.Component()

    # generating poly and n+ diffusion
    w_p = end_cap + gate_width + end_cap  # poly total width
    rect_p = gf.components.rectangle(size=(gate_length, w_p), layer=poly_layer)

    # adding fingers
    # poly = c.add_ref(rect_p)
    poly = c.add_array(rect_p, rows=1, columns=nf, spacing=[sd_width + gate_length, 0])

    l_d = (nf + 1) * (sd_width + gate_length) - gate_length  # n diffution total length
    rect_d = gf.components.rectangle(size=(l_d, gate_width), layer=diffusion_layer)
    diff_n = c.add_ref(rect_d)

    poly.movex(sd_width)
    poly.movey(-end_cap)

    # generating n+ implant
    rect_nm = gf.components.rectangle(
        size=(l_d + 2 * sdm_enclosure[0], gate_width + 2 * sdm_enclosure[1]),
        layer=nsdm_layer,
    )
    nsdm = c.add_ref(rect_nm)
    nsdm.movex(-sdm_enclosure[0])
    nsdm.movey(-sdm_enclosure[1])

    # generating contacts and local interconnects and mcon and m1 of  n+ diffusion
    rect_c = gf.components.rectangle(size=contact_size, layer=contact_layer)
    rect_mc = gf.components.rectangle(size=contact_size, layer=mcon_layer)

    nr = floor(gate_width / (2 * contact_size[1]))
    nc = floor(sd_width / (2 * contact_size[0]))

    con_sp = list(contact_spacing)
    con_sp[0] = con_sp[1] = contact_spacing[0] + contact_size[0]

    min_gate_width = 0.42

    cont_arr1 = c.add_array(rect_c, rows=nr, columns=nc, spacing=con_sp)
    cont_arr2 = c.add_array(rect_c, rows=nr, columns=nc, spacing=con_sp)

    cont_arr1.movey((min_gate_width - contact_size[1]) / 2)
    cont_arr2.movey((min_gate_width - contact_size[1]) / 2)

    mcont_arr1 = c.add_array(rect_mc, rows=nr, columns=nc, spacing=con_sp)
    mcont_arr2 = c.add_array(rect_mc, rows=nr, columns=nc, spacing=con_sp)

    mcont_arr1.movey((min_gate_width - contact_size[1]) / 2)
    mcont_arr2.movey((min_gate_width - contact_size[1]) / 2)

    rect_lid = gf.components.rectangle(
        size=(li_width, gate_width + li_enclosure), layer=li_layer
    )
    li1 = c.add_array(rect_lid, rows=1, columns=nc, spacing=con_sp)
    li2 = c.add_array(rect_lid, rows=1, columns=nc, spacing=con_sp)

    rect_m1d = gf.components.rectangle(
        size=(contact_size[0] + 2 * mcon_enclosure[0], gate_width), layer=m1_layer
    )
    m1d1 = c.add_array(rect_m1d, rows=1, columns=nc, spacing=con_sp)
    m1d2 = c.add_array(rect_m1d, rows=1, columns=nc, spacing=con_sp)

    if nc > 1:
        cont_arr1.movex((sd_width - (cont_arr1.xmax - cont_arr1.xmin)) / 2)
        cont_arr2.movex(
            (nf * (sd_width + gate_length))
            + ((sd_width - (cont_arr2.xmax - cont_arr2.xmin)) / 2)
        )
        mcont_arr1.movex((sd_width - (cont_arr1.xmax - cont_arr1.xmin)) / 2)
        mcont_arr2.movex(
            (nf * (sd_width + gate_length))
            + ((sd_width - (cont_arr2.xmax - cont_arr2.xmin)) / 2)
        )
        li1.movex((sd_width - (cont_arr1.xmax - cont_arr1.xmin)) / 2)
        li2.movex(
            (nf * (sd_width + gate_length))
            + ((sd_width - (cont_arr2.xmax - cont_arr2.xmin)) / 2)
        )
        m1d1.movex(
            (sd_width - (cont_arr1.xmax - cont_arr1.xmin)) / 2 - mcon_enclosure[0]
        )
        m1d2.movex(
            (nf * (sd_width + gate_length))
            + ((sd_width - (cont_arr2.xmax - cont_arr2.xmin)) / 2)
            - mcon_enclosure[0]
        )
    else:
        cont_arr1.movex((sd_width - contact_size[0]) / 2)
        cont_arr2.movex(
            (nf * (sd_width + gate_length)) + ((sd_width - contact_size[0]) / 2)
        )
        mcont_arr1.movex((sd_width - contact_size[0]) / 2)
        mcont_arr2.movex(
            (nf * (sd_width + gate_length)) + ((sd_width - contact_size[0]) / 2)
        )
        li1.movex((sd_width - contact_size[0]) / 2)
        li2.movex((nf * (sd_width + gate_length)) + ((sd_width - contact_size[0]) / 2))
        m1d1.movex((sd_width - contact_size[0]) / 2 - mcon_enclosure[0])
        m1d2.movex(
            (nf * (sd_width + gate_length))
            + ((sd_width - contact_size[0]) / 2)
            - mcon_enclosure[0]
        )

    li1.movey(-li_enclosure / 2)
    li2.movey(-li_enclosure / 2)

    # generating contacts and local interconnects and mcon and m1 of poly

    nc_p = floor(gate_length / (2 * contact_size[0]))
    for i in range(nf):
        cont_arr3 = c.add_array(rect_c, rows=1, columns=nc_p, spacing=con_sp)
        cont_arr3.movex(
            sd_width
            + ((gate_length - (cont_arr3.xmax - cont_arr3.xmin)) / 2)
            + (i * (gate_length + sd_width))
        )
        cont_arr3.movey(gate_width + end_cap + contact_enclosure[1])
        cont_arr5 = c.add_array(rect_c, rows=1, columns=nc_p, spacing=con_sp)
        cont_arr5.movex(
            sd_width
            + ((gate_length - (cont_arr5.xmax - cont_arr5.xmin)) / 2)
            + (i * (gate_length + sd_width))
        )
        cont_arr5.movey(-contact_size[1] - end_cap - contact_enclosure[1])

        mcont_arr3 = c.add_array(rect_mc, rows=1, columns=nc_p, spacing=con_sp)
        mcont_arr3.movex(
            sd_width
            + ((gate_length - (cont_arr3.xmax - cont_arr3.xmin)) / 2)
            + (i * (gate_length + sd_width))
        )
        mcont_arr3.movey(gate_width + end_cap + contact_enclosure[1])
        mcont_arr5 = c.add_array(rect_mc, rows=1, columns=nc_p, spacing=con_sp)
        mcont_arr5.movex(
            sd_width
            + ((gate_length - (cont_arr5.xmax - cont_arr5.xmin)) / 2)
            + (i * (gate_length + sd_width))
        )
        mcont_arr5.movey(-contact_size[1] - end_cap - contact_enclosure[1])

    pc_size = (
        gate_length,
        contact_enclosure[1] + contact_size[1] + contact_enclosure[1],
    )  # poly size to contain contact
    rect_pc = gf.components.rectangle(size=pc_size, layer=poly_layer)
    rect_m1p = gf.components.rectangle(
        size=(
            gate_length + 2 * mcon_enclosure[0] - 2 * contact_enclosure[0],
            contact_size[1] + 2 * mcon_enclosure[1],
        ),
        layer=m1_layer,
    )

    pc_u = c.add_array(rect_pc, rows=1, columns=nf, spacing=[sd_width + gate_length, 0])
    pc_u.movex(sd_width)
    pc_u.movey(gate_width + end_cap)

    pc_d = c.add_array(rect_pc, rows=1, columns=nf, spacing=[sd_width + gate_length, 0])
    pc_d.movex(sd_width)
    pc_d.movey(-pc_size[1] - end_cap)

    m1p_u = c.add_array(
        rect_m1p, rows=1, columns=nf, spacing=[sd_width + gate_length, 0]
    )
    m1p_u.movex(sd_width + contact_enclosure[0] - mcon_enclosure[0])
    m1p_u.movey(gate_width + end_cap + contact_enclosure[1] - mcon_enclosure[1])

    m1p_d = c.add_array(
        rect_m1p, rows=1, columns=nf, spacing=[sd_width + gate_length, 0]
    )
    m1p_d.movex(sd_width + contact_enclosure[0] - mcon_enclosure[0])
    m1p_d.movey(-pc_size[1] - end_cap + contact_enclosure[1] - contact_enclosure[1])

    rect_lip = gf.components.rectangle(
        size=(pc_size[0] + li_enclosure, li_width), layer=li_layer
    )
    lip_u = c.add_array(
        rect_lip, rows=1, columns=nf, spacing=[sd_width + gate_length, 0]
    )
    lip_u.movex(sd_width - li_enclosure / 2)
    lip_u.movey(gate_width + end_cap + contact_enclosure[1])

    lip_d = c.add_array(
        rect_lip, rows=1, columns=nf, spacing=[sd_width + gate_length, 0]
    )
    lip_d.movex(sd_width - li_enclosure / 2)
    lip_d.movey(-contact_size[1] - end_cap - contact_enclosure[1])

    # generating npc for poly contacts

    npc_en = end_cap - npc_spacing
    rect_npc = gf.components.rectangle(
        size=(pc_size[0] + npc_en, pc_size[1] + npc_en), layer=npc_layer
    )

    npc_u = c.add_array(
        rect_npc, rows=1, columns=nf, spacing=[sd_width + gate_length, 0]
    )
    npc_u.movex(sd_width - npc_en / 2)
    npc_u.movey(gate_width + npc_spacing + npc_en / 2)

    npc_d = c.add_array(
        rect_npc, rows=1, columns=nf, spacing=[sd_width + gate_length, 0]
    )
    npc_d.movex(sd_width - npc_en / 2)
    npc_d.movey(-pc_size[1] - npc_en - npc_spacing - npc_en / 2)

    # generaing p+ bulk tie and its contact and mcon and m1
    rect_dp = gf.components.rectangle(size=(sd_width, gate_width), layer=diffp_layer)
    diff_p = c.add_ref(rect_dp)
    diff_p.connect("e1", destination=diff_n.ports["e3"])
    diff_p.movex(diff_spacing + sdm_spacing)

    cont_arr4 = c.add_array(rect_c, rows=nr, columns=nc, spacing=con_sp)
    cont_arr4.movey((min_gate_width - contact_size[1]) / 2)

    mcont_arr4 = c.add_array(rect_mc, rows=nr, columns=nc, spacing=con_sp)
    mcont_arr4.movey((min_gate_width - contact_size[1]) / 2)

    rect_m1dp = gf.components.rectangle(
        size=(contact_size[0] + 2 * mcon_enclosure[0], gate_width), layer=m1_layer
    )
    m1dp = c.add_array(rect_m1dp, rows=1, columns=nc, spacing=con_sp)

    # generate its local interconnects
    li4 = c.add_array(rect_lid, rows=1, columns=nc, spacing=con_sp)

    if nc > 1:
        cont_arr4.movex(
            l_d
            + diff_spacing
            + sdm_spacing
            + ((sd_width - (cont_arr4.xmax - cont_arr4.xmin)) / 2)
        )
        mcont_arr4.movex(
            l_d
            + diff_spacing
            + sdm_spacing
            + ((sd_width - (cont_arr4.xmax - cont_arr4.xmin)) / 2)
        )
        li4.movex(
            l_d
            + diff_spacing
            + sdm_spacing
            + ((sd_width - (cont_arr4.xmax - cont_arr4.xmin)) / 2)
        )
        m1dp.movex(
            l_d
            + diff_spacing
            + sdm_spacing
            + ((sd_width - (cont_arr4.xmax - cont_arr4.xmin)) / 2)
            - mcon_enclosure[0]
        )
    else:
        cont_arr4.movex(
            l_d + diff_spacing + sdm_spacing + ((sd_width - contact_size[0]) / 2)
        )
        mcont_arr4.movex(
            l_d + diff_spacing + sdm_spacing + ((sd_width - contact_size[0]) / 2)
        )
        li4.movex(l_d + diff_spacing + sdm_spacing + ((sd_width - contact_size[0]) / 2))
        m1dp.movex(
            l_d
            + diff_spacing
            + sdm_spacing
            + ((sd_width - contact_size[0]) / 2)
            - mcon_enclosure[0]
        )

    li4.movey(-li_enclosure / 2)

    # generating p+ implant for bulk tie
    rect_pm = gf.components.rectangle(
        size=(sd_width + 2 * sdm_enclosure[0], gate_width + 2 * sdm_enclosure[1]),
        layer=psdm_layer,
    )
    psdm = c.add_ref(rect_pm)
    psdm.connect("e1", destination=diff_n.ports["e3"])
    psdm.movex(diff_spacing + sdm_spacing - sdm_enclosure[0])

    # generating pwell
    rect_pw = gf.components.rectangle(
        size=(
            2 * diff_enclosure[0] + l_d + diff_spacing + sdm_spacing + sd_width,
            2 * diff_enclosure[1] + gate_width,
        ),
        layer=pwell_layer,
    )
    pwell = c.add_ref(rect_pw)
    pwell.movex(-diff_enclosure[0])
    pwell.movey(-diff_enclosure[1])

    # generating deep nwell
    rect_dnw = gf.components.rectangle(
        size=(
            rect_pw.xmax - rect_pw.xmin + 2 * dnwell_enclosure[0],
            rect_pw.ymax - rect_pw.ymin + 2 * dnwell_enclosure[1],
        ),
        layer=dnwell_layer,
    )
    dnwell = c.add_ref(rect_dnw)
    dnwell.movex(-diff_enclosure[0] - dnwell_enclosure[0])
    dnwell.movey(-diff_enclosure[1] - dnwell_enclosure[1])

    # generating hvi
    rect_hv = gf.components.rectangle(
        size=(
            rect_pw.xmax - rect_pw.xmin + 2 * dnwell_enclosure[0],
            rect_pw.ymax - rect_pw.ymin + 2 * dnwell_enclosure[1],
        ),
        layer=hvi_layer,
    )
    dnwell = c.add_ref(rect_hv)
    dnwell.movex(-diff_enclosure[0] - dnwell_enclosure[0])
    dnwell.movey(-diff_enclosure[1] - dnwell_enclosure[1])

    # generating hvntm for n+ implants
    rect_hvn = gf.components.rectangle(
        size=(
            l_d + 2 * sdm_enclosure[0] + 2 * hvntm_enclosure[0],
            gate_width + 2 * sdm_enclosure[1] + 2 * hvntm_enclosure[1],
        ),
        layer=hvntm_layer,
    )
    hvntm = c.add_ref(rect_hvn)
    hvntm.movex(-sdm_enclosure[0] - hvntm_enclosure[0])
    hvntm.movey(-sdm_enclosure[1] - hvntm_enclosure[1])

    return c


if __name__ == "__main__":
    # c = nmos_5v(gate_length= 2, gate_width=5)
    c = nmos_5v()
    c.show(show_ports=True)
