# Copyright edalize contributors
# Licensed under the 2-Clause BSD License, see LICENSE for details.
# SPDX-License-Identifier: BSD-2-Clause

import collections
import logging
import os.path
from edalize.edatool import Edatool

logger = logging.getLogger(__name__)


class Ghdl(Edatool):

    argtypes = ["vlogparam", "generic"]

    @classmethod
    def get_doc(cls, api_ver):
        if api_ver == 0:
            return {
                "description": "GHDL is an open source VHDL simulator, which fully supports IEEE 1076-1987, IEEE 1076-1993, IEE 1076-2002 and partially the 1076-2008 version of VHDL",
                "lists": [
                    {
                        "name": "analyze_options",
                        "type": "String",
                        "desc": "Options to use for the import (ghdl -i) and make (ghdl -m) phases",
                    },
                    {
                        "name": "run_options",
                        "type": "String",
                        "desc": "Options to use for the run (ghdl -r) phase",
                    },
                ],
            }

    def configure_main(self):
        logger.warning(
            "This backend is deprecated and will eventually be removed. Please migrate to the flow API instead.  See https://edalize.readthedocs.io/en/latest/ref/migrations.html#migrating-from-the-tool-api-to-the-flow-api for more details."
        )
        (src_files, incdirs) = self._get_fileset_files()
        analyze_options = self.tool_options.get("analyze_options", "")

        # Check of std=xx analyze option, this overyides the dynamic determination of vhdl standard
        import re

        rx = re.compile("^--std=([0-9]+)")
        m = None
        for o in analyze_options:
            m = rx.match(o)
            if m:
                stdarg = [m.group()]
                analyze_options.remove(o)
                break

        if m:
            logger.warning(
                "Analyze option "
                + m.group()
                + " given, will override any vhdlSource-xxxx specification\n"
            )
            standard = m.group(1)
        else:
            # ghdl does not support mixing incompatible versions
            # specifying 93c as std should allow 87 syntax
            # 2008 can't be combined so try to parse everthing with 08 std

            has87 = has93 = has08 = False
            for f in src_files:
                if f.file_type == "vhdlSource-87":
                    has87 = True
                elif f.file_type == "vhdlSource-93":
                    has93 = True
                elif f.file_type == "vhdlSource-2008":
                    has08 = True
            stdarg = []
            if has08:
                if has87 or has93:
                    logger.warning(
                        "ghdl can't mix vhdlSource-2008 with other standard version\n"
                        + "Trying with treating all as vhdlSource-2008"
                    )
                stdarg = ["--std=08"]
            elif has87 and has93:
                stdarg = ["--std=93c"]
            elif has87:
                stdarg = ["--std=87"]
            elif has93:
                stdarg = ["--std=93"]
            else:
                stdarg = ["--std=93c"]

            standard = rx.match(stdarg[0]).group(1)

        run_options = self.tool_options.get("run_options", [])

        analyze_options = " ".join(analyze_options)

        _vhdltypes = ("vhdlSource", "vhdlSource-87", "vhdlSource-93", "vhdlSource-2008")

        libraries = collections.OrderedDict()
        library_options = "--work={lib} --workdir=./{lib}"
        ghdlimport = ""
        vhdl_sources = ""

        # GHDL versions older than 849a25e0 don't support the dot notation (e.g.
        # my_lib.top_design) for the top level.
        # Nonetheless, we unconditionally split the library and the primary unit,
        # if the user specified the top level using the dot notation.
        top = self.toplevel.split(".")

        if len(top) > 2:
            logger.error("Invalid dot notation in toplevel: {}".format(self.toplevel))

        top_libraries = ""

        if len(top) > 1:
            libraries[top[0]] = []
            top_libraries = library_options.format(lib=top[0])

        top_unit = top[-1]

        for f in src_files:
            if f.file_type in _vhdltypes:
                # Files without a specified library will by added to
                # libraries[None] which is perhaps poor form but avoids
                # conflicts with user generated names
                libraries[f.logical_name] = libraries.get(f.logical_name, []) + [f.name]
                vhdl_sources += " {file}".format(file=f.name)
            elif f.file_type in ["user"]:
                pass
            else:
                _s = "{} has unknown file type '{}'"
                logger.warning(_s.format(f.name, f.file_type))

        ghdlimport = ""
        make_libraries_directories = ""

        for lib, files in libraries.items():
            lib_opts = ""
            if lib:
                analyze_options += " -P./{}".format(lib)
                make_libraries_directories += "\tmkdir -p {}\n".format(lib)
                lib_opts = library_options.format(lib=lib)
            ghdlimport += "\t$(EDALIZE_LAUNCHER) ghdl -i $(STD) $(ANALYZE_OPTIONS) {} {}\n".format(
                lib_opts, " ".join(files)
            )

        self.render_template(
            "Makefile.j2",
            "Makefile",
            {
                "std": " ".join(stdarg),
                "toplevel": top_unit,
                "vhdl_sources": vhdl_sources,
                "standard": standard,
                "analyze_options": analyze_options,
                "run_options": " ".join(run_options),
                "make_libraries_directories": make_libraries_directories,
                "ghdlimport": ghdlimport,
                "top_libraries": top_libraries,
            },
        )

    def run_main(self):
        cmd = "make"
        args = ["run"]

        # GHDL doesn't support Verilog, but the backend used vlogparam since
        # edalize didn't support generic at the time. Now that generic support
        # has been added support for vlogparam is deprecated and will be
        # removed in the future. For now support either option.

        if self.vlogparam:
            logger.warning(
                "GHDL backend support for vlogparam is deprecated and will be removed.\n"
                + "Use generic instead."
            )

        if self.vlogparam or self.generic:
            extra_options = "EXTRA_OPTIONS="
            for d in [self.vlogparam, self.generic]:
                for k, v in d.items():
                    extra_options += " -g{}={}".format(
                        k, self._param_value_str(v, '"', bool_is_str=True)
                    )
            args.append(extra_options)
        self._run_tool(cmd, args)
