"""Classes and utils for working with KLayout technology files (.lyp, .lyt).

This module enables conversion between gdsfactory settings and KLayout technology.
"""

import pathlib
import xml.etree.ElementTree as ET
from typing import List, Optional, Tuple

from pydantic import BaseModel, Field

from gdsfactory.config import PATH
from gdsfactory.technology import LayerViews

Layer = Tuple[int, int]
ConductorViaConductorName = Tuple[str, str, str]


class KLayoutTechnology(BaseModel):
    """A container for working with KLayout technologies (requires KLayout Python package).

    Useful for importing/exporting Layer Properties (.lyp) and Technology (.lyt) files.

    Properties:
        layer_views: Defines all the layer display properties needed for a .lyp file from LayerView objects.
        technology: KLayout Technology object from the KLayout API. Set name, dbu, etc.
        connectivity: List of layer names connectivity for netlist tracing.
    """

    # TODO: Add import method
    import klayout.db as db

    name: str
    layer_views: Optional[LayerViews] = None
    technology: db.Technology = Field(default_factory=db.Technology)
    connectivity: Optional[List[ConductorViaConductorName]] = None

    def export_technology_files(
        self,
        tech_dir: str,
        lyp_filename: str = "layers.lyp",
        lyt_filename: str = "tech.lyt",
        d25_filename: str = "generic.lyd25",
        layer_stack: Optional = None,
        mebes_config: Optional[dict] = None,
    ) -> None:
        """Write technology files into 'tech_dir'.

        Args:
            tech_dir: Where to write the technology files to.
            lyp_filename: Name of the layer properties file.
            lyt_filename: Name of the layer technology file.
            d25_filename: Name of the 2.5D stack file (only works on KLayout >= 0.28)
            layer_stack: If specified, write a 2.5D section in the technology file based on the LayerStack.
            mebes_config: A dictionary specifying the KLayout mebes reader config.
        """
        from gdsfactory.utils.xml_utils import make_pretty_xml

        # Format file names if necessary
        tech_path = pathlib.Path(tech_dir)
        lyp_path = tech_path / lyp_filename
        lyt_path = tech_path / lyt_filename
        d25_path = tech_path / "d25" / d25_filename

        if not self.technology.name:
            self.technology.name = self.name

        self.technology.layer_properties_file = lyp_path.name
        # TODO: Also interop with xs scripts?

        # Write lyp to file
        self.layer_views.to_lyp(lyp_path)

        root = ET.XML(self.technology.to_xml().encode("utf-8"))

        # KLayout tech doesn't include mebes config, so add it after lefdef config:
        if not mebes_config:
            mebes_config = {
                "invert": False,
                "subresolution": True,
                "produce-boundary": True,
                "num-stripes-per-cell": 64,
                "num-shapes-per-cell": 0,
                "data-layer": 1,
                "data-datatype": 0,
                "data-name": "DATA",
                "boundary-layer": 0,
                "boundary-datatype": 0,
                "boundary-name": "BORDER",
                "layer-map": "layer_map()",
                "create-other-layers": True,
            }
        mebes = ET.Element("mebes")
        for k, v in mebes_config.items():
            v = str(v).lower() if isinstance(v, bool) else str(v)
            ET.SubElement(mebes, k).text = v

        reader_opts = root.find("reader-options")
        lefdef_idx = list(reader_opts).index(reader_opts.find("lefdef"))
        reader_opts.insert(lefdef_idx + 1, mebes)

        if layer_stack is not None:
            # KLayout 0.27.x won't have a way to read/write the 2.5D info for technologies, so add manually to xml
            d25_element = root.find("d25")

            # Would be nice to have a klayout.__version__ to check
            klayout28 = d25_element is None

            dbu = len(str(self.technology.dbu).split(".")[-1])

            d25_script = layer_stack.get_klayout_3d_script(
                klayout28=klayout28,
                layer_views=self.layer_views,
                dbu=dbu,
            )

            if klayout28:
                d25_script = f"# {self.name!r} 2.5D script generated by GDSFactory\n\n{d25_script}"
                d25_path.write_bytes(d25_script.encode("utf-8"))
            else:
                src_element = d25_element.find("src")
                src_element.text = d25_script

        # root['connectivity']['connection']= '41/0,44/0,45/0'
        if self.connectivity is not None:
            src_element = [e for e in list(root) if e.tag == "connectivity"]
            if len(src_element) != 1:
                raise KeyError("Could not get a single index for the src element.")
            src_element = src_element[0]
            for layer_name_c1, layer_name_via, layer_name_c2 in self.connectivity:
                layer_c1 = self.layer_views.layer_views[layer_name_c1].layer
                layer_via = self.layer_views.layer_views[layer_name_via].layer
                layer_c2 = self.layer_views.layer_views[layer_name_c2].layer
                connection = ",".join(
                    [
                        f"{layer[0]}/{layer[1]}"
                        for layer in [layer_c1, layer_via, layer_c2]
                    ]
                )

                ET.SubElement(src_element, "connection").text = connection

        # Write lyt to file
        lyt_path.write_bytes(make_pretty_xml(root))

    class Config:
        """Allow db.Technology type."""

        arbitrary_types_allowed = True


layer_views = LayerViews.from_lyp(str(PATH.klayout_lyp))


def yaml_test():
    tech_dir = PATH.repo / "extra" / "test_tech"

    # Load from existing layer properties file
    lyp = LayerViews.from_lyp(str(PATH.klayout_lyp))
    print("Loaded from .lyp", lyp)

    # Export layer properties to yaml files
    layer_yaml = str(tech_dir / "layers.yml")
    lyp.to_yaml(layer_yaml)

    # Load layer properties from yaml files and check that they're the same
    lyp_loaded = LayerViews.from_yaml(layer_yaml)
    print("Loaded from .yaml", lyp_loaded)

    assert lyp_loaded == lyp


if __name__ == "__main__":
    from gdsfactory.generic_tech import LAYER_STACK

    lyp = LayerViews.from_lyp(str(PATH.klayout_lyp))

    # str_xml = open(PATH.klayout_tech / "tech.lyt").read()
    # new_tech = db.Technology.technology_from_xml(str_xml)
    # generic_tech = KLayoutTechnology(layer_views=lyp)
    connectivity = [("M1", "VIA1", "M2"), ("M2", "VIA2", "M3")]

    c = generic_tech = KLayoutTechnology(
        name="test_tech", layer_views=lyp, connectivity=connectivity
    )
    tech_dir = PATH.repo / "extra" / "test_tech"
    # tech_dir = pathlib.Path("/home/jmatres/.klayout/salt/gdsfactory/tech/")
    tech_dir.mkdir(exist_ok=True, parents=True)
    generic_tech.export_technology_files(tech_dir=tech_dir, layer_stack=LAYER_STACK)

    # yaml_test()
