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

import logging as log
import sys
import os

from CfgJson import load_hjson

import FormalCfg
import CdcCfg
import LintCfg
import SimCfg
import 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'] = os.path.dirname(path)

    # 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:
        raise RuntimeError('{!r}: No value for the "flow" key. Are you sure '
                           'this is a dvsim configuration file?'
                           .format(path))

    classes = [
        CdcCfg.CdcCfg,
        LintCfg.LintCfg,
        SynCfg.SynCfg,
        FormalCfg.FormalCfg,
        SimCfg.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:
        raise RuntimeError('{}: Configuration file sets "flow" to {!r}, but '
                           'this is not a known flow (known: {}).'
                           .format(path, flow, ', '.join(known_types)))

    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.error(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:
        raise RuntimeError('{}: Configuration file has use_cfgs, but is '
                           'itself included from another configuration.'
                           .format(path))

    # 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):
    '''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': os.path.dirname(path),
    }
    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.error(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)
