"""Via cutback."""

import gdsfactory as gf
from gdsfactory.add_pins import LayerSpec
from gdsfactory.component import Component
from gdsfactory.components.compass import compass
from gdsfactory.components.via_stack import via_stack_heater_m3
from gdsfactory.types import ComponentSpec, Float2


@gf.cell
def _via_iterable(
    via_spacing: float,
    wire_width: float,
    layer1: LayerSpec,
    layer2: LayerSpec,
    via_layer: LayerSpec,
    via_width: float,
) -> Component:
    """Via chain.

    Args:
        via_spacing: via_spacing (um).
        wire_width: width of wire.
        layer1: top wiring.
        layer2: bottom wiring.
        via_layer: via.
        via_width: width of via.
    """
    c = gf.Component()
    wire1 = c.add_ref(compass(size=(via_spacing, wire_width), layer=layer1))
    wire2 = c.add_ref(compass(size=(via_spacing, wire_width), layer=layer2))
    viac = c.add_ref(compass(size=(via_width, via_width), layer=via_layer))
    via1 = c.add_ref(compass(size=(via_width, via_width), layer=via_layer))
    wire1.connect(port="e3", destination=wire2.ports["e1"], overlap=wire_width)
    viac.connect(
        port="e1", destination=wire1.ports["e3"], overlap=(wire_width + via_width) / 2
    )
    via1.connect(
        port="e1", destination=wire2.ports["e3"], overlap=(wire_width + via_width) / 2
    )
    c.add_port(name="e1", port=wire1.ports["e1"], port_type="electrical", layer=layer1)
    c.add_port(name="e3", port=wire2.ports["e3"], port_type="electrical", layer=layer2)
    c.add_port(
        name="e4",
        center=((1 * wire_width) + wire_width / 2, -wire_width / 2),
        width=wire_width,
        orientation=-90,
        port_type="electrical",
        layer=layer2,
    )
    c.add_port(
        name="e2",
        center=((1 * wire_width) + wire_width / 2, wire_width / 2),
        width=wire_width,
        orientation=90,
        port_type="electrical",
        layer=layer2,
    )

    return c


@gf.cell
def via_cutback(
    num_vias: float = 100.0,
    wire_width: float = 10.0,
    via_width: float = 5.0,
    via_spacing: float = 40.0,
    min_pad_spacing: float = 0.0,
    pad: ComponentSpec = via_stack_heater_m3,
    pad_size: Float2 = (150, 150),
    layer1: LayerSpec = "HEATER",
    layer2: LayerSpec = "M1",
    via_layer: LayerSpec = "VIAC",
    wire_pad_inclusion: float = 12.0,
) -> Component:
    """Via cutback to extract via resistance.

    adapted from phidl.geometry

    Args:
        num_vias: total requested vias needs to be even.
        wire_width: width of wire.
        via_width: width of via.
        via_spacing: via_spacing.
        pad_size: (width, height).
        min_pad_spacing.
        pad_layer.
        layer1: top wiring.
        layer2: bottom wiring.
        via_layer: via.
        wire_pad_inclusion: in um.
    """
    c = gf.Component()

    pad_component = pad(size=pad_size)
    pad1 = c.add_ref(pad_component)
    pad2 = c.add_ref(pad_component)

    nub = c.add_ref(pad(size=(3 * wire_width, wire_width)))
    head = c.add_ref(pad(size=(wire_width, wire_width)))
    nub.ymax = pad1.ymax - 5
    nub.xmin = pad1.xmax - wire_pad_inclusion
    head.connect(port="e1", destination=nub.ports["e3"])

    old_port = head.ports["e4"]
    count = 0
    width_via_iter = 2 * via_spacing - 2 * wire_width

    pad2.xmin = pad1.xmax + min_pad_spacing
    up = False
    down = True
    edge = True
    current_width = 3 * wire_width + wire_width  # width of nub and 1 overlap
    obj_old = head
    obj = head
    via_iterable = _via_iterable(
        via_spacing=via_spacing,
        wire_width=wire_width,
        layer1=layer1,
        layer2=layer2,
        via_layer=via_layer,
        via_width=via_width,
    )
    while (count + 2) <= num_vias:
        obj = c.add_ref(via_iterable)
        obj.connect(port="e1", destination=old_port, overlap=wire_width)
        old_port = obj.ports["e3"]
        edge = False
        if obj.ymax > pad1.ymax:
            obj.connect(port="e1", destination=obj_old.ports["e4"], overlap=wire_width)
            old_port = obj.ports["e4"]
            current_width += width_via_iter
            down = True
            up = False
            edge = True

        elif obj.ymin < pad1.ymin:
            obj.connect(port="e1", destination=obj_old.ports["e2"], overlap=wire_width)
            old_port = obj.ports["e2"]
            current_width += width_via_iter
            up = True
            down = False
            edge = True
        count += 2
        obj_old = obj

    if (
        current_width < min_pad_spacing
        and (min_pad_spacing - current_width) > 3 * wire_width
    ):
        tail = c.add_ref(
            pad(
                size=(min_pad_spacing - current_width + wire_width, wire_width),
            )
        )
    else:
        tail = c.add_ref(pad(size=(3 * wire_width, wire_width)))

    if up and not edge:
        tail.connect(port="e1", destination=obj.ports["e4"], overlap=wire_width)
    elif down and not edge:
        tail.connect(port="e1", destination=obj.ports["e2"], overlap=wire_width)
    else:
        tail.connect(port="e1", destination=obj.ports["e3"], overlap=wire_width)

    pad2.xmin = tail.xmax - wire_pad_inclusion
    return c


if __name__ == "__main__":
    c = via_cutback()
    c.show(show_ports=True)
