from __future__ import annotations

from typing import List, Sequence
from enum import Enum, auto
from dataclasses import dataclass, replace, asdict, fields
from argparse import ArgumentParser

from jinja2.filters import do_indent

from .exceptions import SfgException

HEADER_FILE_EXTENSIONS = {'h', 'hpp'}
SOURCE_FILE_EXTENSIONS = {'c', 'cpp'}


@dataclass
class SfgCodeStyle:
    indent_width: int = 2

    def indent(self, s: str):
        return do_indent(s, self.indent_width, first=True)


@dataclass
class SfgConfiguration:
    header_extension: str = None
    """File extension for generated header files."""

    source_extension: str = None
    """File extension for generated source files."""

    header_only: bool = None
    """If set to `True`, generate only a header file without accompaning source file."""

    base_namespace: str = None
    """The outermost namespace in the generated file. May be a valid C++ nested namespace qualifier
    (like `a::b::c`) or `None` if no outer namespace should be generated."""

    codestyle: SfgCodeStyle = None
    """Code style that should be used by the code generator."""

    output_directory: str = None
    """Directory to which the generated files should be written."""

    def __post_init__(self):
        if self.header_only:
            raise SfgException(
                "Header-only code generation is not implemented yet.")

        if self.header_extension and self.header_extension[0] == '.':
            self.header_extension = self.header_extension[1:]

        if self.source_extension and self.source_extension[0] == '.':
            self.source_extension = self.source_extension[1:]

    def override(self, other: SfgConfiguration):
        other_dict = asdict(other)
        other_dict = {k: v for k, v in other_dict.items() if v is not None}
        return replace(self, **other_dict)


DEFAULT_CONFIG = SfgConfiguration(
    header_extension='h',
    source_extension='cpp',
    header_only=False,
    base_namespace=None,
    codestyle=SfgCodeStyle(),
    output_directory=""
)


def run_configurator(configurator_script: str):
    raise NotImplementedError()


def config_from_commandline(argv: List[str]):
    parser = ArgumentParser("pystencilssfg",
                            description="pystencils Source File Generator",
                            allow_abbrev=False)

    parser.add_argument("-sfg-d", "--sfg-output-dir",
                        type=str, default='.', dest='output_directory')
    parser.add_argument("-sfg-e", "--sfg-file-extensions",
                        type=str, default=None, nargs='*', dest='file_extensions')
    parser.add_argument("--sfg-header-only",
                        type=str, default=None, nargs='*', dest='header_only')
    parser.add_argument("--sfg-configurator", type=str,
                        default=None, nargs='*', dest='configurator_script')

    args, script_args = parser.parse_known_args(argv)

    if args.configurator_script is not None:
        project_config = run_configurator(args.configurator_script)
    else:
        project_config = None

    if args.file_extensions is not None:
        h_ext, src_ext = _get_file_extensions(args.file_extensions)
    else:
        h_ext, src_ext = None, None

    cmdline_config = SfgConfiguration(
        header_extension=h_ext,
        source_extension=src_ext,
        header_only=args.header_only,
        output_directory=args.output_directory
    )

    return project_config, cmdline_config, script_args


def merge_configurations(project_config: SfgConfiguration,
                         cmdline_config: SfgConfiguration,
                         script_config: SfgConfiguration):
    #   Project config completely overrides default config
    config = DEFAULT_CONFIG

    if project_config is not None:
        config = config.override(project_config)

    if cmdline_config is not None:
        cmdline_dict = asdict(cmdline_config)
        #   Commandline config completely overrides project and default config
        config = config.override(cmdline_config)
    else:
        cmdline_dict = {}

    if script_config is not None:
        #   User config may only set values not specified on the command line
        script_dict = asdict(script_config)
        for key, cmdline_value in cmdline_dict.items():
            if cmdline_value is not None and script_dict[key] is not None:
                raise SfgException(
                    f"Conflicting configuration: Parameter {key} was specified both in the script and on the command line.")

        config = config.override(script_config)

    return config


def _get_file_extensions(extensions: Sequence[str]):
    h_ext = None
    src_ext = None

    extensions = ((ext[1:] if ext[0] == '.' else ext) for ext in extensions)

    for ext in extensions:
        if ext in HEADER_FILE_EXTENSIONS:
            if h_ext is not None:
                raise ValueError("Multiple header file extensions found.")
            h_ext = ext
        elif ext in SOURCE_FILE_EXTENSIONS:
            if src_ext is not None:
                raise ValueError("Multiple source file extensions found.")
            src_ext = ext
        else:
            raise ValueError(f"Don't know how to interpret extension '.{ext}'")

    return h_ext, src_ext