""" You can define both DOEs and placer information in a YAML file
All the placer information need to be nested inside a placer section

```yaml
iso_lines_coarse1:
  component: ISO_COARS_OPT1
  settings:
    dx: [50.]

  placer:
    type: pack_row/ pack_col/ grid / fixed_coords
        pack_grid:
    x0: 0
    y0: 0
    align_x: W
    align_y: S
    next_to: iso_lines_coarse1
```

"""
import collections
import os
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union

import klayout.db as pya
import numpy as np
from klayout.dbcore import Cell, CellInstArray, Layout
from numpy import float64
from omegaconf import OmegaConf

from gdsfactory.autoplacer import text
from gdsfactory.autoplacer.helpers import CELLS, import_cell, load_gds
from gdsfactory.config import CONFIG, logger
from gdsfactory.types import NSEW

UM_TO_GRID = 1e3
DEFAULT_BBOX_LAYER_IGNORE = [(8484, 8484)]


def to_grid(x: float64, um_to_grid: int = UM_TO_GRID) -> int:
    return int(x * um_to_grid)


class SizeInfo:
    def __init__(
        self,
        cell: Cell,
        layout: Optional[Layout] = None,
        ignore_layers: List[Tuple[int, int]] = DEFAULT_BBOX_LAYER_IGNORE,
        um_to_grid: int = UM_TO_GRID,
    ) -> None:
        """
        layout is required if cell is a cell reference instead of a cell
        """
        if isinstance(cell, pya.CellInstArray):
            bbox = cell.bbox(layout)
        else:
            parent_layout = cell.layout()
            layer_indexes = parent_layout.layer_indexes()

            for process_id, purpose_id in ignore_layers:
                layer_id = parent_layout.find_layer(process_id, purpose_id)
                if layer_id in layer_indexes:
                    layer_indexes.remove(layer_id)

            bbox = None
            for layer_id in layer_indexes:
                per_layer_bbox = cell.bbox_per_layer(layer_id)
                if bbox is None:
                    bbox = per_layer_bbox
                else:
                    bbox = bbox + per_layer_bbox

        self.box = bbox

        self.west = self.box.left / um_to_grid
        self.east = self.box.right / um_to_grid

        self.south = self.box.bottom / um_to_grid
        self.north = self.box.top / um_to_grid

        self.width = self.east - self.west
        self.height = self.north - self.south

        xc = int(0.5 * (self.east + self.west))
        yc = int(0.5 * (self.north + self.south))

        self.sw = np.array([self.west, self.south])
        self.se = np.array([self.east, self.south])
        self.nw = np.array([self.west, self.north])
        self.ne = np.array([self.east, self.north])

        self.cw = np.array([self.west, yc])
        self.ce = np.array([self.east, yc])
        self.nc = np.array([xc, self.north])
        self.sc = np.array([xc, self.south])
        self.cc = self.center = np.array([xc, yc])

    @property
    def rect(self):
        w, e, s, n = self.west, self.east, self.south, self.north
        return [(w, s), (e, s), (e, n), (w, n)]

    def __str__(self):
        return "w: {}\ne: {}\ns: {}\nn: {}\n".format(
            self.west, self.east, self.south, self.north
        )


def placer_grid_cell_refs(
    cells,
    cols: int = 1,
    rows: int = 1,
    dx: float = 10.0,
    dy: float = 10.0,
    x0: float = 0,
    y0: float = 0,
    um_to_grid: float = UM_TO_GRID,
    **settings,
):
    """cells: list of cells - order matters for placing"""

    indices = [(i, j) for j in range(cols) for i in range(rows)]

    if rows * cols < len(cells):
        raise ValueError(
            "Shape ({}, {}): Not enough emplacements ({}) for all these components"
            " ({}).".format(rows, cols, len(indices), len(cells))
        )
    components = []
    for cell, (i, j) in zip(cells, indices):
        _x = int((x0 + j * dx) * um_to_grid)
        _y = int((y0 + i * dy) * um_to_grid)

        transform = pya.Trans(_x, _y)
        c_ref = pya.CellInstArray(cell.cell_index(), transform)
        components += [c_ref]

    return components


def pack_row(
    cells: List[Cell],
    row_ids: Optional[List[int]] = None,
    nb_cols: Optional[int] = None,
    x0: Union[float, int] = 0,
    y0: Union[float, int] = 0,
    align_x: NSEW = "W",
    align_y: NSEW = "S",
    margin: Union[float, int] = 20,
    margin_x: Optional[Union[float, int]] = None,
    margin_y: Optional[Union[float, int]] = None,
    um_to_grid: int = UM_TO_GRID,
    period_x: Optional[float] = None,
    period_y: Optional[float] = None,
    rotation: int = 0,
) -> List[CellInstArray]:
    """Pack row.

    Args:
        cells: a list of cells (size n)
        row_ids: a list of row ids (size n)
            where each id represents the row where the cell should be placed
            None by default => all cells in the same row
        nb_cols: number of columns
        period_x, period_y: not used by default,
            if set, use this period instead of computing the component spacing
            from the margin and the component dimension

    Returns: list of cell references
    """
    si_list = [SizeInfo(c, um_to_grid=um_to_grid) for c in cells]
    heights = [si.height for si in si_list]
    margin_y = margin_y if margin_y is not None else margin
    margin_x = margin_x if margin_x is not None else margin

    if row_ids is None:
        row_ids = []
        nb_cells = len(cells)
        if nb_cols is None:
            nb_cols = len(cells)
        nb_full_rows = nb_cells // nb_cols
        nb_cols_last_row = nb_cells % nb_cols
        for row_id in range(nb_full_rows):
            row_ids += [row_id] * nb_cols

        last_row_index = row_id + 1
        row_ids += [last_row_index] * nb_cols_last_row

    if len(cells) != len(row_ids):
        raise ValueError(
            "Each cell should be assigned a row id. "
            f"Got {len(cells)} cells for {len(row_ids)} row ids"
        )

    # Find the height of each row to fit the cells
    # Also group the cells by row

    unique_row_ids = list(set(row_ids))
    unique_row_ids.sort()
    _row_to_heights = {r: [] for r in set(row_ids)}
    row_to_cells = {r: [] for r in unique_row_ids}
    for row, h, cell in zip(row_ids, heights, cells):
        _row_to_heights[row] += [h]
        row_to_cells[row] += [cell]

    row_to_height = {k: max(v) for k, v in _row_to_heights.items()}

    components = []

    # Do the packing per row
    y = y0
    for row in unique_row_ids:
        cells = row_to_cells[row]
        x = x0

        for c in cells:
            si = SizeInfo(c, um_to_grid=um_to_grid)
            if align_x == "W" and align_y == "S":
                component_origin = si.sw
            elif align_x == "E" and align_y == "S":
                component_origin = si.se
            elif align_x == "E" and align_y == "N":
                component_origin = si.ne
            elif align_x == "W" and align_y == "N":
                component_origin = si.nw
            try:
                _x = to_grid(x - component_origin[0], um_to_grid)
                _y = to_grid(y - component_origin[1], um_to_grid)

                transform = pya.Trans(rotation / 2, 0, _x, _y)
                c_ref = pya.CellInstArray(c.cell_index(), transform)
                components += [c_ref]

            except BaseException:
                logger.error(x, component_origin[0], um_to_grid)
                logger.error("ISSUE PLACING AT", _x, _y)
                if align_x not in ["W", "E"]:
                    logger.error("align_x should be `W`, `E` or a float")
                if align_y not in ["N", "S"]:
                    logger.error("align_y should be `N`, `S` or a float")
                # raise

            dx = si.width + margin_x if period_x is None else period_x
            if align_x == "W":
                x += dx
            else:
                x -= dx

        dy = row_to_height[row] + margin_y if period_y is None else period_y

        if align_y == "S":
            y += dy
        else:
            y -= dy

    return components


def pack_col(
    cells: List[Cell],
    col_ids: None = None,
    nb_rows: Optional[int] = None,
    x0: float = 0,
    y0: float = 0,
    align_x: NSEW = "W",
    align_y: NSEW = "S",
    margin: int = 20,
    margin_x: Optional[int] = None,
    margin_y: Optional[int] = None,
    um_to_grid: int = UM_TO_GRID,
    period_x: Optional[float] = None,
    period_y: Optional[float] = None,
    rotation: int = 0,
) -> List[CellInstArray]:
    """

    Args:
        cells: a list of cells  (size n)
        col_ids: a list of column ids (size n)
            where each id represents the row where the cell should be placed
            None by default => all cells are packed in the same column

    Returns:
        list of cell references
    """
    widths = [SizeInfo(c, um_to_grid=um_to_grid).width for c in cells]
    margin_y = margin_y if margin_y is not None else margin
    margin_x = margin_x if margin_x is not None else margin

    if col_ids is None:
        col_ids = []
        nb_cells = len(cells)
        if nb_rows is None:
            nb_rows = len(cells)
        nb_full_cols = nb_cells // nb_rows
        nb_rows_last_col = nb_cells % nb_rows
        for col_id in range(nb_full_cols):
            col_ids += [col_id] * nb_rows

        last_col_index = col_id + 1
        col_ids += [last_col_index] * nb_rows_last_col

    if len(cells) != len(col_ids):
        raise ValueError(
            "Each cell should be assigned a row id. "
            f"Got {len(cells)} cells for {len(col_ids)} col ids"
        )

    # Find the width of each column to fit the cells
    # Also group the cells by column

    unique_col_ids = list(set(col_ids))
    unique_col_ids.sort()
    _col_to_widths = {r: [] for r in set(col_ids)}
    col_to_cells = {r: [] for r in unique_col_ids}
    for col, w, cell in zip(col_ids, widths, cells):
        _col_to_widths[col] += [w]
        col_to_cells[col] += [cell]

    col_to_width = {k: max(v) for k, v in _col_to_widths.items()}

    components = []

    # Do the packing per column
    x = x0
    for col in unique_col_ids:
        cells = col_to_cells[col]
        y = y0

        for c in cells:
            si = SizeInfo(c, um_to_grid=um_to_grid)
            if align_x == "W" and align_y == "S":
                component_origin = si.sw
            elif align_x == "E" and align_y == "S":
                component_origin = si.se
            elif align_x == "E" and align_y == "N":
                component_origin = si.ne
            elif align_x == "W" and align_y == "N":
                component_origin = si.nw

            _x = to_grid(x - component_origin[0], um_to_grid=um_to_grid)
            _y = to_grid(y - component_origin[1], um_to_grid=um_to_grid)

            try:
                transform = pya.Trans(rotation / 2, 0, _x, _y)
                # transform = pya.Trans(_x, _y)
                c_ref = pya.CellInstArray(c.cell_index(), transform)
                components += [c_ref]
            except BaseException:
                print(x, component_origin[0], um_to_grid)
                print(y, component_origin[1], um_to_grid)
                print("ISSUE PLACING AT", _x, _y)
                print("ISSUE PLACING at", _x, _y)

            dy = si.height + margin_y if period_y is None else period_y
            if align_y == "S":
                y += dy
            else:
                y -= dy

        dx = col_to_width[col] + margin_x if period_x is None else period_x
        if align_x == "W":
            x += dx
        else:
            x -= dx

    return components


def placer_fixed_coords(
    cells,
    x,
    y,
    x0: float = 0,
    y0: float = 0,
    do_permutation: bool = False,
    um_to_grid=UM_TO_GRID,
    **kwargs,
):
    """place cells using a list of coordinates"""

    # List all coordinates
    if do_permutation:
        coords = [(_x, _y) for _x in x for _y in y]
    else:
        coords = [(_x, _y) for _x, _y in zip(x, y)]

    # Update origin
    coords = [(c[0] + x0, c[1] + y0) for c in coords]

    # Generate cell list
    if len(cells) == 1:
        cells = cells * len(coords)

    # update coordinates from um to grid
    coords = [(int(c[0] * um_to_grid), int(c[1] * um_to_grid)) for c in coords]

    # Generate transforms
    transforms = [pya.Trans(*c) for c in coords]

    return [pya.CellInstArray(c.cell_index(), t) for c, t in zip(cells, transforms)]


def load_yaml(filepath: Path) -> Any:
    """load placer settings

    Args:
        filepath: a yaml file containing the does and placer information

    Returns:
        a dictionnary of DOEs with:
        {
            doe_name1: {...}
            doe_name2: {...}
            ...
        }

    """

    does = {}
    data = OmegaConf.load(filepath)
    data = OmegaConf.to_container(data)
    mask = data.pop("mask")

    if "layer_doe_label" not in mask:
        mask["layer_doe_label"] = (102, 6)

    for doe_name, doe in data.items():
        _doe = {}
        _doe.update(doe)
        does[doe_name] = _doe
    return does, mask


DOE_CELLS = {}


def load_doe(doe_name: str, doe_root: Path) -> List[Layout]:
    """
    Load all components for this DOE from the cache
    """
    doe_dir = os.path.join(doe_root, doe_name)
    content_file = os.path.join(doe_dir, "content.txt")

    if os.path.isfile(content_file):
        with open(content_file) as f:
            lines = f.read().split("\n")
            line = lines[0]

            if line.startswith("TEMPLATE:"):
                # If using a template, load the GDS from DOE folder used as a template
                template_name = line.split(":")[1].strip()
                return load_doe(template_name, doe_root)

            else:
                # Otherwise load the GDS from the current folder
                component_names = line.split(" , ")
                gdspaths = [
                    os.path.join(doe_dir, name + ".gds") for name in component_names
                ]
                cells = [load_gds(gdspath) for gdspath in gdspaths]

        # print("LOAD DOE")
        # for _c in cells:
        # print(_c.top_cell().name)
        # print()

        return cells


PLACER_NAME2FUNC = {
    "grid": placer_grid_cell_refs,
    "pack_row": pack_row,
    "pack_col": pack_col,
    "fixed_coords": placer_fixed_coords,
}


def separate_does_from_templates(dicts: Dict[str, Any]) -> Any:
    templates = {}
    does = {}
    for name, d in dicts.items():
        if "type" in d.keys():  # and d["type"] == "template":
            templates[name] = d
        else:
            does[name] = d

    # We do not want to propagate "type": template to the does => removing it here
    for d in templates.values():
        d.pop("type")

    return does, templates


def update_dicts_recurse(
    target_dict: Dict[str, Any], default_dict: Dict[str, Any]
) -> Dict[str, Any]:
    target_dict = target_dict.copy()
    default_dict = default_dict.copy()
    for k, v in default_dict.items():
        if k not in target_dict:
            vtype = type(v)
            if vtype == dict or vtype == collections.OrderedDict:
                target_dict[k] = v.copy()  # To avoid issues when popping
            else:
                target_dict[k] = v
        else:
            vtype = type(target_dict[k])
            if vtype == dict or vtype == collections.OrderedDict:
                target_dict[k] = update_dicts_recurse(target_dict[k], default_dict[k])
    return target_dict


def place_from_yaml(
    filepath_yaml: Path,
    root_does: Path = CONFIG["cache_doe_directory"],
    precision: float = 1e-9,
    fontpath: Path = text.FONT_PATH,
    default_align_x: NSEW = "W",
    default_align_y: NSEW = "S",
    default_margin: int = 10,
    default_x0: NSEW = "E",
    default_y0: NSEW = "S",
) -> Cell:
    """Returns a gds cell composed of DOEs/components given in a yaml file
    allows for each DOE to have its own x and y spacing (more flexible than method1)

    Args:
        filepath_yaml:
        root_does: used for cache, requires content.txt
    """
    transform_identity = pya.Trans(0, 0)
    dicts, mask_settings = load_yaml(filepath_yaml)

    does, templates = separate_does_from_templates(dicts)

    placed_doe = None
    placed_does = {}
    top_level_name = mask_settings.get("name", "TOP_LEVEL")
    layer_doe_label = mask_settings["layer_doe_label"]
    top_level_layout = pya.Layout()

    # Set database units according to precision
    top_level_layout.dbu = precision / 1e-6
    dbu = top_level_layout.dbu
    um_to_grid = int(1 / dbu)

    top_level = top_level_layout.create_cell(top_level_name)
    global CELLS
    CELLS[top_level_name] = top_level_layout

    default_doe_settings = {
        "add_doe_label": False,
        "add_doe_visual_label": False,
        "dx_visual_label": 0,
        "dy_visual_label": 0,
    }

    for doe_name, doe in does.items():

        # If a template is specified, apply it
        if "template" in doe:
            doe_templates = doe["template"]
            if type(doe_templates) != list:
                doe_templates = [doe_templates]
            for doe_template in doe_templates:
                try:
                    doe = update_dicts_recurse(doe, templates[doe_template])

                except BaseException:
                    print(doe_template, "does not exist")
                    raise
        doe = update_dicts_recurse(doe, default_doe_settings)

        # Get all the components
        components = load_doe(doe_name, root_does)

        # Check that the high level components are all unique
        # For now this is mostly to circumvent a bug
        # But the design manual also specifies that DOE components should have
        # unique names. So one instance per cell

        if components:
            if len(components) != len(set([_c.top_cell().name for _c in components])):
                __dict_component_debug = {}
                for _c in components:
                    _name = _c.top_cell().name
                    if _name not in __dict_component_debug:
                        __dict_component_debug[_name] = 0
                    __dict_component_debug[_name] += 1
                duplicates_components = [
                    _name
                    for _name, _count in __dict_component_debug.items()
                    if _count > 1
                ]
                print("Please remove duplicate components at DOE entry level: ")
                print(duplicates_components)

            components = [
                import_cell(top_level_layout, _c.top_cell()) for _c in components
            ]

        default_placer_settings = {
            "align_x": default_align_x,
            "align_y": default_align_y,
            "margin": default_margin,
            "x0": default_x0,
            "y0": default_y0,
        }
        settings = default_placer_settings.copy()
        placer = doe.get("placer")

        if placer:
            placer_type = placer.pop("type", "pack_col")
            settings.update(doe["placer"])
        else:
            placer_type = "pack_col"

        if placer_type not in PLACER_NAME2FUNC:
            raise ValueError(
                f"{placer_type} is not an available placer, Choose:"
                f" {list(PLACER_NAME2FUNC.keys())}"
            )
        _placer = PLACER_NAME2FUNC[placer_type]

        # All other attributes are assumed to be settings for the placer

        # Check if the cell should be attached to a specific parent cell
        if "parent" in settings:
            parent_name = settings.pop("parent")
            if parent_name not in CELLS:
                # Create parent cell in layout and insert it under top level
                parent_cell = top_level_layout.create_cell(parent_name)
                CELLS[parent_name] = parent_cell
                parent_cell_instance = pya.CellInstArray(
                    parent_cell.cell_index(), transform_identity
                )
                top_level.insert(parent_cell_instance)
            doe_parent_cell = CELLS[parent_name]
        else:
            # If no parent specified, insert the DOE at top level
            doe_parent_cell = top_level

        # Check if we should create a DOE cell which regroups the DOEs
        if "with_doe_cell" in settings:
            with_doe_cell = settings.pop("with_doe_cell")
        else:
            with_doe_cell = True

        # x0, y0 can either be float or string
        x0 = settings.pop("x0")
        y0 = settings.pop("y0")

        # Check whether we are doing relative or absolute placement
        # if (x0 in ["E", "W"] or y0 in ["N", "S"]) and not placed_doe:
        #     raise ValueError(
        #         "At least one DOE must be placed to use relative placement"
        #     )

        # For relative placement (to previous DOE)
        if "margin_x" not in settings:
            settings["margin_x"] = settings["margin"]
        if "margin_y" not in settings:
            settings["margin_y"] = settings["margin"]

        if "inter_margin_x" not in settings:
            inter_margin_x = settings["margin_x"]
        else:
            inter_margin_x = settings.pop("inter_margin_x")

        if "inter_margin_y" not in settings:
            inter_margin_y = settings["margin_y"]
        else:
            inter_margin_y = settings.pop("inter_margin_y")

        align_x = settings["align_x"]
        align_y = settings["align_y"]

        # Make sure that the alignment is sensible depending on how we stack

        # If we specify a DOE to place next to, use it
        if "next_to" in settings:
            placed_doe = placed_does[settings.pop("next_to")]

        # print(placed_doe)
        # print(placed_does)

        # Otherwise, use previously placed DOE as starting point
        doe_si = (
            SizeInfo(placed_doe, top_level_layout, um_to_grid=um_to_grid)
            if placed_doe is not None
            else None
        )
        if x0 == "E":
            x0 = doe_si.east
            if align_x == "W":
                x0 += inter_margin_x

        if x0 == "W":
            x0 = doe_si.west
            if align_x == "E":
                x0 -= inter_margin_x

        if y0 == "N":
            y0 = doe_si.north
            if align_y == "S":
                y0 += inter_margin_y

        if y0 == "S":
            y0 = doe_si.south
            if align_y == "N":
                y0 -= inter_margin_y

        # Add x0, y0 in settings as float
        settings["x0"] = x0
        settings["y0"] = y0

        settings["um_to_grid"] = um_to_grid

        placed_components = _placer(components, **settings)

        # Place components within a cell having the DOE name

        if with_doe_cell or len(placed_components) > 1:
            doe_cell = top_level_layout.create_cell(doe_name)
            CELLS[doe_name] = doe_cell
            for instance in placed_components:
                doe_cell.insert(instance)
            placed_does[doe_name] = doe_cell
            placed_doe = doe_cell
            doe_instance = pya.CellInstArray(doe_cell.cell_index(), transform_identity)
        else:
            # If only single cell and we want to skip the sweep cell
            doe_instance = placed_components[0]
            placed_does[doe_name] = doe_instance
            placed_doe = doe_instance

        add_doe_label = doe["add_doe_label"]
        add_doe_visual_label = doe["add_doe_visual_label"]

        if add_doe_label:
            layer_label_index, layer_label_datatype = layer_doe_label
            layer_index = top_level.layout().insert_layer(
                pya.LayerInfo(layer_label_index, layer_label_datatype)
            )
            # Add the name of the DOE at the center of the cell
            _p = doe_instance.bbox(top_level_layout).center()
            _text = pya.Text(doe_name, _p.x, _p.y)
            top_level.shapes(layer_index).insert(_text)

        if add_doe_visual_label:
            _bbox = doe_instance.bbox(top_level_layout)

            idbu = 1 / top_level.layout().dbu
            x_text = _bbox.center().x + doe["dx_visual_label"] * idbu
            y_text = _bbox.bottom + (15.0 + doe["dy_visual_label"]) * idbu
            _text = text.add_text(
                top_level, doe_name, position=(x_text, y_text), fontpath=fontpath
            )
            # _transform = pya.DTrans(x_text, y_text)
            # top_level.insert(pya.CellInstArray(_text.cell_index(), _transform))

        doe_parent_cell.insert(doe_instance)

    return top_level


def place_and_write(
    filepath_yaml, root_does=CONFIG["cache_doe_directory"], filepath_gds="top_level.gds"
):
    c = place_from_yaml(filepath_yaml, root_does)
    logger.info("writing...")
    c.write(filepath_gds)


def assemble_subdies_from_yaml(filepath, subdies_directory, mask_directory=None):
    data = OmegaConf.load(filepath)
    data = OmegaConf.to_container(data)

    mask = data.pop("mask")
    mask_name = mask["name"]

    # Remaining entries are subdies
    dict_subdies = {
        k: (v["x"], v["y"], v["R"] if "R" in v else 0) for k, v in data.items()
    }

    return assemble_subdies(mask_name, dict_subdies, subdies_directory, mask_directory)


def assemble_subdies(
    mask_name,
    dict_subdies,
    subdies_directory,
    mask_directory=None,
    um_to_grid=UM_TO_GRID,
):
    """
    Args:
        dict_subdies: {subdie_name: (x, y, rotation) in (um, um, deg)}
        subdies_directory: directory where the subdies should be looked for
    """
    top_level_layout = pya.Layout()
    top_level = top_level_layout.create_cell(mask_name)
    if mask_directory is None:
        mask_directory = subdies_directory

    for subdie_name, (x_um, y_um, R) in dict_subdies.items():
        gdspath = os.path.join(subdies_directory, subdie_name + ".gds")
        subdie = load_gds(gdspath).top_cell()

        _subdie = import_cell(top_level_layout, subdie)

        t = pya.Trans(R / 2, 0, int(x_um * um_to_grid), int(y_um * um_to_grid))
        # t = pya.Trans(0, 0)
        subdie_instance = pya.CellInstArray(_subdie.cell_index(), t)
        top_level.insert(subdie_instance)

    top_level.write(os.path.join(mask_directory, mask_name + ".gds"))
    return top_level


def _demo():
    import gdsfactory as gf

    c = gf.components.straight()
    gdspath = c.write_gds_with_metadata()

    layout1 = load_gds(gdspath)
    cell1 = layout1.top_cell()
    cell1_instance1 = pya.CellInstArray(cell1.cell_index(), pya.Trans(10, 0))

    layout2 = pya.Layout()
    layout2.create_cell("TOP_LEVEL")

    layout2.cell("TOP_LEVEL").insert(cell1_instance1)
    layout2.write("test.gds")


if __name__ == "__main__":
    _demo()
    print(CELLS)

numpy.array