from typing import Callable
from pp.components.grating_coupler.elliptical_trenches import grating_coupler_te
from pp.components.grating_coupler.elliptical_trenches import grating_coupler_tm

import pp
from pp.add_tapers import add_tapers
from pp.components.taper import taper
from pp.container import container

from pp.routing.route_fiber_array import route_fiber_array
from pp.routing.get_input_labels import get_input_labels
from pp.component import Component


def add_fiber_array_te(*args, **kwargs):
    return add_fiber_array(*args, **kwargs)


def add_fiber_array_tm(*args, grating_coupler=grating_coupler_tm, **kwargs):
    return add_fiber_array(*args, grating_coupler=grating_coupler, **kwargs)


@container
def add_fiber_array(
    component: Component,
    grating_coupler: Component = grating_coupler_te,
    gc_port_name: str = "W0",
    component_name: None = None,
    taper_factory: Callable = taper,
    taper_length: float = 10.0,
    get_route_factory: Callable = route_fiber_array,
    get_input_labels_function: Callable = get_input_labels,
    **kwargs,
) -> Component:
    """returns component with optical IO (tapers, south routes and grating_couplers)

    Args:
        component: to connect
        grating_coupler: grating coupler instance, function or list of functions
        bend_factory: bend_circular
        straight_factory: waveguide
        fanout_length: None  # if None, automatic calculation of fanout length
        max_y0_optical: None
        with_align_ports: True, adds loopback structures
        waveguide_separation: 4.0
        bend_radius: BEND_RADIUS
        list_port_labels: None, adds TM labels to port indices in this list
        connected_port_list_ids: None # only for type 0 optical routing
        nb_optical_ports_lines: 1
        force_manhattan: False
        excluded_ports:
        grating_indices: None
        routing_waveguide: None
        routing_method: connect_strip
        gc_port_name: W0
        optical_routing_type: None: autoselection, 0: no extension, 1: standard, 2: check
        gc_rotation: -90
        layer_label: LAYER.LABEL
        input_port_indexes: [0]
        component_name: for the label
        taper_factory: taper function
        get_route_factory: route_fiber_array

    .. plot::
      :include-source:

       import pp
       from pp.routing import add_fiber_array

       c = pp.c.crossing()
       cc = add_fiber_array(c)
       pp.plotgds(cc)

    """
    c = component
    if not c.ports:
        return c

    if isinstance(grating_coupler, list):
        gc = grating_coupler[0]
    else:
        gc = grating_coupler
    gc = pp.call_if_func(gc)

    gc_polarization = gc.polarization

    component_name = component_name or c.name
    name = f"{component_name}_{gc_polarization}"
    cc = pp.Component(name=name)

    port_width_gc = gc.ports[gc_port_name].width

    optical_ports = c.get_ports_list(port_type="optical")
    port_width_component = optical_ports[0].width

    if port_width_component != port_width_gc:
        c = add_tapers(
            c,
            taper_factory(