# AUTOGENERATED! DO NOT EDIT! File to edit: source/01_factories.ipynb (unless otherwise specified).

__all__ = ['layer', 'point', 'vector', 'box', 'polygon', 'polygon', 'polygon', 'Point', 'Points', 'path', 'cell',
           'Shape', 'transform', 'layout', 'library']

# Internal Cell
from typing import Dict, List, Optional, Tuple, Union, overload

import pya
from .cell import (
    LibReference,
    PCellLibReference,
    PCellReference,
    Reference,
    _add_cell_to_layout,
    _add_lib_cell_to_layout,
    _add_lib_pcell_to_layout,
    add_cells_to_layout,
    add_pcells_to_layout,
    reference,
)

# Cell


# Cell
def layer(lr: int, dt: int, name: str = "") -> pya.LayerInfo:
    layer = pya.LayerInfo(int(lr), int(dt))
    if name:
        layer.name = name
    return layer

# Cell
def point(x: Union[float, int], y: Union[float, int]) -> pya.DPoint:
    """create a KLayout DPoint

    Args:
        x: the x-value of the point
        y: the y-value of the point

    Returns:
        the point
    """
    return pya.DPoint(float(x), float(y))

# Cell
def vector(x: Union[float, int], y: Union[float, int]) -> pya.DVector:
    """create a KLayout DVector

    Args:
        x: the x-value of the vector
        y: the y-value of the vector

    Returns:
        the vector
    """
    return pya.DVector(float(x), float(y))

# Cell

def _validate_point(p):
    if isinstance(p, pya.Point):
        p = p.to_dtype()
    if not isinstance(p, pya.DPoint):
        p = pya.DPoint(*(float(x) for x in p))
    return p

def box(p1: Union[pya.Point, pya.DPoint], p2: Union[pya.Point, pya.DPoint]) -> pya.DBox:
    p1 = _validate_point(p1)
    p2 = _validate_point(p2)
    return pya.DBox(p1, p2)

# Cell

Point = Union[pya.DPoint, Tuple[int, int], Tuple[float, float], 'np.ndarray']
Points = Union[List[Point], 'np.ndarray']

@overload
def polygon(
    hull: Points,
    *,
    holes: None = None,
    raw: bool = False,
) -> pya.DSimplePolygon:
    ...


@overload
def polygon(
    hull: Points,
    *,
    holes: List[Points] = None,
    raw: bool = False,
) -> pya.DPolygon:
    ...


def polygon(
    hull: Points,
    *,
    holes: Optional[List[Points]] = None,
    raw: bool = False,
) -> Union[pya.DSimplePolygon, pya.DPolygon]:
    """create a KLayout polygon

    Args:
        hull: the points that make the polygon hull
        holes: the collection of points that make the polygon holes
        raw: if true, the points won't be compressed.

    Returns:
        the polygon
    """
    hull = [(p if isinstance(p, (pya.Point, pya.DPoint)) else point(*p)) for p in hull]
    if holes is None:
        poly = pya.DSimplePolygon(hull, raw)
    else:
        poly = pya.DPolygon(hull, raw)
        for hole in holes:
            hole = [(p if isinstance(p, pya.DPoint) else point(*p)) for p in hole]
            poly.insert_hole(hole)
    return poly

# Cell
def path(
    pts: Points,
    width: float,
    *,
    bgn_ext: float = 0.0,
    end_ext: float = 0.0,
    round: bool = False,
) -> pya.DPath:
    """create a KLayout path

    Args:
        pts: the points that make the path
        width: the width of the path
        bgn_ext: the begin extension of the path
        end_ext: the end extension of the path
        round: round the ends of the path

    Returns:
        the path
    """
    pts = [(p if isinstance(p, (pya.Point, pya.DPoint)) else point(*p)) for p in pts]
    print(pts)
    return pya.DPath(pts, width, bgn_ext, end_ext, round)

# Cell
Shape = Union[pya.DBox, pya.DSimplePolygon, pya.DPolygon, pya.DPath, pya.Box, pya.SimplePolygon, pya.Polygon, pya.Path]
def cell(
    name: str,
    *,
    shapes: Optional[Dict[pya.LayerInfo, Shape]] = None,
    child_cells: Optional[List[Union[pya.Cell, Tuple[pya.Cell, pya.CplxTrans]]]] = None,
) -> pya.Cell:
    """create a KLayout cell

    Args:
        name: the name of the cell
        shapes: the shapes to add to the cell
        child_cells: the child cells to add to the cell

    Returns:
        the cell
    """
    layout = pya.Layout()
    cell = layout.create_cell(name)
    cell._layout = layout  # make sure layout does not get destroyed
    if shapes:
        for lr, lrshapes in shapes.items():
            if not isinstance(lr, pya.LayerInfo):
                lr = layer(*lr)
            lr = layout.layer(lr)
            for i, shape in enumerate(lrshapes):

                # if isinstance(shape, np.ndarray):
                if type(shape).__name__ == 'ndarray': # yeah... I don't want to import numpy...
                    lrshapes[i] = polygon(shape)

            for i, shape in enumerate(lrshapes):  # TODO: insert all at once?
                cell.shapes(lr).insert(shape)
    if child_cells:
        cell_idxs = {}
        for ref in child_cells:
            if isinstance(ref, (str, pya.Cell, pya.PCellDeclaration)):
                ref = reference(ref)
            else:
                ref = reference(*ref)
            if isinstance(ref, Reference):
                if ref.cell not in cell_idxs:
                    cell_idxs[ref.cell] = _add_cell_to_layout(layout, ref.cell)
                cell.insert(pya.CellInstArray(cell_idxs[ref.cell], ref.trans))
            elif isinstance(ref, LibReference):
                # don't store index in cell_idxs... Cell belongs to library and will not be copied.
                idx = _add_lib_cell_to_layout(layout, ref.lib, ref.cell)
                cell.insert(pya.CellInstArray(idx, ref.trans))
            elif isinstance(ref, PCellReference):
                raise ValueError(
                    f"One can only add pcells belonging to a library to to a new cell. "
                    f"Add a pcell reference using the following string format to "
                    f"represent the cell: '<libname>.<pcellname>'."
                )
            elif isinstance(ref, PCellLibReference):
                # don't store index in cell_idxs... PCell belongs to library and will not be copied.
                idx = _add_lib_pcell_to_layout(layout, ref.lib, ref.cell, ref.params)
                cell.insert(pya.CellInstArray(idx, ref.trans))

    return cell

# Cell
def transform(
    mag: float = 1.0,
    rot: float = 0.0,
    mirrx: bool = False,
    x: float = 0.0,
    y: float = 0.0,
) -> pya.CplxTrans:
    """create a KLayout Transformation

    Args:
        mag: the magnitude of the transformation
        rot: the rotation of the transformation
        mirrx: mirror over the x-axis (x=0 line).
        x: translation distance in the x-direction
        y: translation distance in the y-direction

    Returns:
        the cell reference
    """
    return pya.CplxTrans(mag, rot, mirrx, int(1000 * x), int(1000 * y))

# Cell
def layout(*, cells: Optional[List[pya.Cell]] = None) -> pya.Layout:
    """create a KLayout Layout

    Args:
        cells: the cells to add to the layout

    Returns:
        the KLayout layout
    """
    layout = pya.Layout()
    layout.dbu = 0.001  # hard coded for now
    if cells:
        add_cells_to_layout(layout, cells)
    return layout

# Cell
def library(
    name: str,
    *,
    cells: Optional[List[pya.Cell]] = None,
    pcells: Optional[List[pya.Cell]] = None,
    description: str = "",
) -> pya.Library:
    """create a KLayout Library

    Args:
        name: the name of the library
        cells: the cells to add to the library
        pcells: the pcells to add to the library

    Returns:
        the KLayout library
    """
    lib = pya.Library()
    if description:
        lib.description = description
    layout = lib.layout()
    layout.dbu = 0.001  # hard coded for now
    if cells:
        add_cells_to_layout(layout, cells)
    if pcells:
        add_pcells_to_layout(layout, pcells)
    lib.register(name)
    return lib