# Copyright 2023 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from abc import abstractmethod
from typing import List, Optional

from .step import Step
from .tclstep import TclStep
from ..state import DesignFormat, State

from ..config import Variable
from ..common import get_script_dir
from ..utils import DRC as DRCObject


class MagicStep(TclStep):
    inputs = [DesignFormat.GDS]
    outputs = []

    config_vars = [
        Variable(
            "MAGIC_DEF_LABELS",
            bool,
            "A flag to choose whether labels are read with DEF files or not. From magic docs: \"The '-labels' option to the 'def read' command causes each net in the NETS and SPECIALNETS sections of the DEF file to be annotated with a label having the net name as the label text.\"",
            default=True,
        ),
        Variable(
            "MAGIC_GDS_POLYGON_SUBCELLS",
            bool,
            'A flag to enable polygon subcells in magic for gds read potentially speeding up magic. From magic docs: "Put non-Manhattan polygons. This prevents interations with other polygons on the same plane and so reduces tile splitting."',
            default=False,
        ),
        Variable(
            "MAGIC_GDS_ALLOW_ABSTRACT",
            bool,
            "A flag to allow abstract view of macros during magic gds generation..",
            default=False,
        ),
        Variable(
            "MAGIC_DEF_NO_BLOCKAGES",
            bool,
            "If set to true, blockages in DEF files are ignored. Otherwise, they are read as sheets of metal by Magic.",
            default=True,
        ),
        Variable(
            "MAGIC_INCLUDE_GDS_POINTERS",
            bool,
            "A flag to choose whether to include GDS pointers in the generated mag files or not.",
            default=False,
        ),
    ]

    @abstractmethod
    def get_script_path(self):
        pass

    def get_command(self) -> List[str]:
        return [
            "magic",
            "-dnull",
            "-noconsole",
            "-rcfile",
            str(self.config["MAGICRC"]),
        ]

    def run(self, **kwargs) -> State:
        # https://github.com/RTimothyEdwards/magic/issues/218
        kwargs["stdin"] = open(self.get_script_path(), encoding="utf8")
        return super().run(**kwargs)


@Step.factory.register()
class WriteLEF(MagicStep):
    id = "Magic.WriteLEF"
    name = "Write LEF (Magic)"
    flow_control_variable = "RUN_MAGIC_WRITE_LEF"

    inputs = [DesignFormat.GDS]
    outputs = [DesignFormat.LEF]

    config_vars = MagicStep.config_vars + [
        Variable(
            "RUN_MAGIC_WRITE_LEF",
            bool,
            "Generate a LEF view using Magic.",
            default=True,
            deprecated_names=["MAGIC_GENERATE_LEF"],
        ),
        Variable(
            "MAGIC_GENERATE_MAGLEF",
            bool,
            "Generate a MAGLEF view using Magic alongside generated LEF views.",
            default=True,
        ),
        Variable(
            "MAGIC_WRITE_FULL_LEF",
            bool,
            "A flag to specify whether or not the output LEF should include all shapes inside the macro or an abstracted view of the macro lef view via magic.",
            default=False,
        ),
    ]

    def get_script_path(self):
        raise NotImplementedError()


@Step.factory.register()
class StreamOut(MagicStep):
    id = "Magic.StreamOut"
    name = "GDS-II Stream Out (Magic)"
    flow_control_variable = "RUN_MAGIC_STREAMOUT"

    inputs = [DesignFormat.DEF]
    outputs = [DesignFormat.GDS, DesignFormat.MAG_GDS]

    config_vars = MagicStep.config_vars + [
        Variable(
            "RUN_MAGIC_STREAMOUT",
            bool,
            "Enables running GDSII streaming out using Magic.",
            default=True,
            deprecated_names=["RUN_MAGIC"],
        ),
        Variable(
            "MAGIC_PAD",
            bool,
            "A flag to pad the views generated by magic (.mag, .lef, .gds) by one site.",
            default=False,
        ),
        Variable(
            "DIE_AREA",
            Optional[str],
            'Specific die area to be used in floorplanning when `FP_SIZING` is set to `absolute`. Specified as a 4-corner rectangle "x0 y0 x1 y1".',
            units="μm",
        ),
        Variable(
            "MAGIC_ZEROIZE_ORIGIN",
            bool,
            "A flag to move the layout such that it's origin in the lef generated by magic is 0,0.",
            default=False,
        ),
        Variable(
            "MAGIC_GENERATE_GDS",
            bool,
            "A flag to generate gds view via magic.",
            default=True,
        ),
        Variable(
            "MAGIC_DISABLE_HIER_GDS",
            bool,
            "A flag to disable cif hier and array during GDSII writing.* 1=Enabled `<so this hier gds will be disabled>`, 0=Disabled `<so this hier gds will be enabled>`.",
            default=True,
        ),
    ]

    def get_script_path(self):
        return os.path.join(get_script_dir(), "magic", "def", "mag_gds.tcl")

    def run(self, **kwargs) -> State:
        kwargs, env = self.extract_env(kwargs)
        assert isinstance(self.state_in, State)
        if die_area := self.state_in.metrics.get("die__area"):
            env["DIE_AREA"] = die_area

        state_out = super().run(env=env, **kwargs)
        if self.config["PRIMARY_SIGNOFF_TOOL"].value == "magic":
            state_out[DesignFormat.GDS] = state_out[DesignFormat.MAG_GDS]
        return state_out


@Step.factory.register()
class DRC(MagicStep):
    id = "Magic.DRC"
    name = "DRC"
    long_name = "Design Rule Checks"

    flow_control_variable = "RUN_MAGIC_DRC"

    inputs = [DesignFormat.DEF, DesignFormat.GDS]
    outputs = []

    config_vars = [
        Variable(
            "RUN_MAGIC_DRC",
            bool,
            "Enables running magic DRC on GDSII produced by Magic.",
            default=True,
        ),
        Variable(
            "MAGIC_DRC_USE_GDS",
            bool,
            "A flag to choose whether to run the magic DRC checks on GDS or not. If not, then the checks will be done on the DEF/LEF, which is faster.",
            default=False,
        ),
    ]

    def get_script_path(self):
        return os.path.join(get_script_dir(), "magic", "drc.tcl")

    def run(self, **kwargs) -> State:
        state_out = super().run(**kwargs)

        reports_dir = os.path.join(self.step_dir, "reports")
        report_path = os.path.join(reports_dir, "drc.rpt")
        report_str = open(report_path, encoding="utf8").read()

        drc = DRCObject.from_magic(report_str)
        drc_bbox = [
            bbox for violation in drc.violations for bbox in violation.bounding_boxes
        ]
        state_out.metrics["magic__drc_errors"] = len(drc_bbox)

        with open(os.path.join(reports_dir, "drc.klayout.xml"), "w") as f:
            f.write(drc.to_klayout_xml())

        return state_out


@Step.factory.register()
class SpiceExtraction(MagicStep):
    id = "Magic.SpiceExtraction"
    name = "SPICE Extraction"
    long_name = "SPICE Model Extraction"

    inputs = [DesignFormat.GDS, DesignFormat.DEF]
    outputs = [DesignFormat.SPICE]

    config_vars = MagicStep.config_vars + [
        Variable(
            "MAGIC_EXT_USE_GDS",
            bool,
            "A flag to choose whether to use GDS for spice extraction or not. If not, then the extraction will be done using the DEF/LEF, which is faster.",
            default=False,
        ),
        Variable(
            "MAGIC_NO_EXT_UNIQUE",
            bool,
            "Enables connections by label in LVS by skipping `extract unique` in Magic extractions.",
            default=False,
            deprecated_names=["LVS_CONNECT_BY_LABEL"],
        ),
    ]

    def get_script_path(self):
        return os.path.join(get_script_dir(), "magic", "extract_spice.tcl")

    def run(self, **kwargs) -> State:
        state_out = super().run(**kwargs)

        feedback_path = os.path.join(self.step_dir, "feedback.txt")
        feedback_string = open(feedback_path, encoding="utf8").read()
        state_out.metrics["magic__illegal__overlaps"] = feedback_string.count(
            "Illegal overlap"
        )
        return state_out
