# Copyright lowRISC contributors (OpenTitan project).
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

import pathlib
import sys

from dvsim.flow.base import FlowCfg
from dvsim.flow.cdc import CdcCfg
from dvsim.flow.formal import FormalCfg
from dvsim.flow.hjson import load_hjson
from dvsim.flow.lint import LintCfg
from dvsim.flow.rdc import RdcCfg
from dvsim.flow.sim import SimCfg
from dvsim.flow.syn import SynCfg
from dvsim.logging import log

FLOW_HANDLERS = {
    "cdc": CdcCfg,
    "formal": FormalCfg,
    "lint": LintCfg,
    "rdc": RdcCfg,
    "sim": SimCfg,
    "syn": SynCfg,
}


def _load_cfg(path, initial_values):
    """Worker function for make_cfg.

    initial_values is passed to load_hjson (see documentation there).

    Returns a pair (cls, hjson_data) on success or raises a RuntimeError on
    failure.

    """
    # Set the `self_dir` template variable to the path of the currently
    # processed Hjson file.
    assert "self_dir" in initial_values
    initial_values["self_dir"] = pathlib.Path(path).parent

    # Start by loading up the hjson file and any included files
    hjson_data = load_hjson(path, initial_values)

    # Look up the value of flow in the loaded data. This is a required field,
    # and tells us what sort of FlowCfg to make.
    flow = hjson_data.get("flow")
    if flow is None:
        msg = (
            f'{path!r}: No value for the "flow" key. Are you sure '
            "this is a dvsim configuration file?"
        )
        raise RuntimeError(
            msg,
        )

    classes = [
        RdcCfg,
        CdcCfg,
        LintCfg,
        SynCfg,
        FormalCfg,
        SimCfg,
    ]
    found_cls = None
    known_types = []
    for cls in classes:
        assert cls.flow is not None
        known_types.append(cls.flow)
        if cls.flow == flow:
            found_cls = cls
            break
    if found_cls is None:
        msg = (
            '{}: Configuration file sets "flow" to {!r}, but '
            "this is not a known flow (known: {}).".format(path, flow, ", ".join(known_types))
        )
        raise RuntimeError(
            msg,
        )

    return (found_cls, hjson_data)


def _make_child_cfg(path, args, initial_values):
    try:
        cls, hjson_data = _load_cfg(path, initial_values)
    except RuntimeError as err:
        log.exception(str(err))
        sys.exit(1)

    # Since this is a child configuration (from some primary configuration),
    # make sure that we aren't ourselves a primary configuration. We don't need
    # multi-level hierarchies and this avoids circular dependencies.
    if "use_cfgs" in hjson_data:
        msg = (
            f"{path}: Configuration file has use_cfgs, but is "
            "itself included from another configuration."
        )
        raise RuntimeError(
            msg,
        )

    # Call cls as a constructor. Note that we pass None as the mk_config
    # argument: this is not supposed to load anything else.
    return cls(path, hjson_data, args, None)


def make_cfg(path, args, proj_root) -> FlowCfg:
    """Make a flow config by loading the config file at path.

    args is the arguments passed to the dvsim.py tool and proj_root is the top
    of the project.

    """
    initial_values = {
        "proj_root": proj_root,
        "self_dir": pathlib.Path(path).parent,
    }
    if args.tool is not None:
        initial_values["tool"] = args.tool

    try:
        cls, hjson_data = _load_cfg(path, initial_values)
    except RuntimeError as err:
        log.exception(str(err))
        sys.exit(1)

    def factory(child_path):
        child_ivs = initial_values.copy()
        child_ivs["flow"] = hjson_data["flow"]
        return _make_child_cfg(child_path, args, child_ivs)

    return cls(path, hjson_data, args, factory)
