# Copyright (C) 2005-2020 Anders Logg, Michal Habera, Jack S. Hale
#
# This file is part of FFCx. (https://www.fenicsproject.org)
#
# SPDX-License-Identifier:    LGPL-3.0-or-later
"""Options."""

from __future__ import annotations

import functools
import json
import logging
import os
import os.path
import pprint
from pathlib import Path

import numpy.typing as npt

logger = logging.getLogger("ffcx")

FFCX_DEFAULT_OPTIONS = {
    "language": (str, "C", "Language to output kernel in", None),
    "epsilon": (float, 1e-14, "Machine precision, used for dropping zero terms in tables.", None),
    "scalar_type": (
        str,
        "float64",
        "Scalar type to use in generated code.",
        ("float32", "float64", "complex64", "complex128"),
    ),
    "sum_factorization": (bool, False, "Use sum factorization.", None),
    "table_rtol": (
        float,
        1e-6,
        "Relative precision to use when comparing finite element table values for reuse.",
        None,
    ),
    "table_atol": (
        float,
        1e-9,
        "Absolute precision to use when comparing finite element table values reuse.",
        None,
    ),
    "verbosity": (
        int,
        30,
        "Logger verbosity, follows standard library levels, i.e. INFO=20, DEBUG=10, etc.",
        None,
    ),
    "part": (str, "full", "Part of bilinear tensor to assemble", ("full", "diagonal")),
}


@functools.cache
def _load_options() -> tuple[dict, dict]:
    """Load options from JSON files."""
    user_config_file = os.getenv("XDG_CONFIG_HOME", default=Path.home().joinpath(".config")) / Path(
        "ffcx", "ffcx_options.json"
    )
    try:
        with open(user_config_file) as f:
            user_options = json.load(f)
    except FileNotFoundError:
        user_options = {}

    pwd_config_file = Path.cwd().joinpath("ffcx_options.json")
    try:
        with open(pwd_config_file) as f:
            pwd_options = json.load(f)
    except FileNotFoundError:
        pwd_options = {}

    return (user_options, pwd_options)


def get_options(
    priority_options: dict[str, npt.DTypeLike | int | float] | None = None,
) -> dict[str, int | float | npt.DTypeLike]:
    """Return (a copy of) the merged option values for FFCX.

    Args:
        priority_options: take priority over all other option values (see notes)

    Returns:
        merged option values

    Note:
        This function sets the log level from the merged option values prior to
        returning.

        The `ffcx_options.json` files are cached on the first call. Subsequent
        calls to this function use this cache.

        Priority ordering of options from highest to lowest is:

        -  **priority_options** (API and command line options)
        -  **$PWD/ffcx_options.json** (local options)
        -  **$XDG_CONFIG_HOME/ffcx/ffcx_options.json** (user options)
        -  **FFCX_DEFAULT_OPTIONS** in `ffcx.options`

        `XDG_CONFIG_HOME` is `~/.config/` if the environment variable is not set.

        Example `ffcx_options.json` file:

          { "epsilon": 1e-7 }

    """
    options: dict[str, npt.DTypeLike | int | float] = {}

    for opt, (_, value, _, _) in FFCX_DEFAULT_OPTIONS.items():
        options[opt] = value  # type: ignore

    # NOTE: _load_options uses functools.lru_cache
    user_options, pwd_options = _load_options()

    options.update(user_options)
    options.update(pwd_options)
    if priority_options is not None:
        options.update(priority_options)

    logger.setLevel(int(options["verbosity"]))  # type: ignore
    logger.info("Final option values")
    logger.info(pprint.pformat(options))

    return options


def get_language(options: dict[str, int | float | npt.DTypeLike]) -> str:
    """Retrieve the language option from the options database.

    Applies for internal languages the alias conversion.
    """
    lang = str(options.get("language", FFCX_DEFAULT_OPTIONS["language"][1]))
    return {"C": "ffcx.codegeneration.C", "numba": "ffcx.codegeneration.numba"}.get(lang, lang)
