# SPDX-License-Identifier: Apache-2.0
# Copyright 2019 The Meson development team
# Copyright © 2023-2025 Intel Corporation

from __future__ import annotations
from pathlib import Path

import abc
import os
import re
import typing as T

from ..mesonlib import SimpleABC

if T.TYPE_CHECKING:
    from ..minit import Arguments


class SampleImpl(metaclass=SimpleABC):

    def __init__(self, args: Arguments):
        self.name = args.name
        self.executable_name = args.executable if args.executable else None
        self.version = args.version
        self.lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
        self.uppercase_token = self.lowercase_token.upper()
        self.capitalized_token = self.lowercase_token.capitalize()
        self.meson_version = '1.0.0'
        self.force = args.force
        self.dependencies = args.deps.split(',') if args.deps else []
        self.sources: list[Path] = []

    @abc.abstractmethod
    def create_executable(self) -> None:
        pass

    @abc.abstractmethod
    def create_library(self) -> None:
        pass

    @property
    @abc.abstractmethod
    def exe_template(self) -> str:
        pass

    @property
    @abc.abstractmethod
    def exe_meson_template(self) -> str:
        pass

    @property
    @abc.abstractmethod
    def lib_template(self) -> str:
        pass

    @property
    @abc.abstractmethod
    def lib_test_template(self) -> T.Optional[str]:
        pass

    @property
    @abc.abstractmethod
    def lib_meson_template(self) -> str:
        pass

    @property
    @abc.abstractmethod
    def source_ext(self) -> str:
        pass

    def _format_dependencies(self) -> str:
        return ''.join(f"\n  dependency('{d}')," for d in self.dependencies)

    def _format_sources(self) -> str:
        return ''.join(f"\n  '{x}'," for x in self.sources)

    def _detect_sources(self, srcfiles: T.List[Path], transform: T.Callable[[str], str]) -> T.Tuple[str, T.List[Path]]:
        # Try a source based on the executable name, fallback to one based
        # on the project name if none is found.
        expected_name = f'{transform(self.executable_name)}.{self.source_ext}'
        if any(str(x) == expected_name for x in srcfiles):
            return expected_name, srcfiles
        expected_name = f'{transform(self.lowercase_token)}.{self.source_ext}'
        if any(str(x) == expected_name for x in srcfiles):
            return expected_name, srcfiles
        return expected_name, [Path(expected_name)]

class ClassImpl(SampleImpl):

    """For Class based languages, like Java and C#"""

    def __init__(self, args: Arguments):
        super().__init__(args)
        self.source_name, self.sources = self._detect_sources(args.srcfiles, str.capitalize)

    def create_executable(self) -> None:
        if not os.path.exists(self.source_name):
            with open(self.source_name, 'w', encoding='utf-8') as f:
                f.write(self.exe_template.format(project_name=self.name,
                                                 class_name=self.capitalized_token))
        if self.force or not os.path.exists('meson.build'):
            with open('meson.build', 'w', encoding='utf-8') as f:
                f.write(self.exe_meson_template.format(project_name=self.name,
                                                       exe_name=self.executable_name,
                                                       source_name=self.source_name,
                                                       version=self.version,
                                                       meson_version=self.meson_version,
                                                       dependencies=self._format_dependencies(),
                                                       source_files=self._format_sources()))

    def create_library(self) -> None:
        test_name = f'{self.capitalized_token}_test.{self.source_ext}'
        kwargs = {
            'utoken': self.uppercase_token,
            'ltoken': self.lowercase_token,
            'class_test': f'{self.capitalized_token}_test',
            'class_name': self.capitalized_token,
            'source_file': self.source_name,
            'test_source_file': test_name,
            'test_exe_name': f'{self.lowercase_token}_test',
            'project_name': self.name,
            'lib_name': self.lowercase_token,
            'test_name': self.lowercase_token,
            'version': self.version,
            'meson_version': self.meson_version,
            'dependencies': self._format_dependencies(),
            'source_files': self._format_sources(),
        }
        if not os.path.exists(self.source_name):
            with open(self.source_name, 'w', encoding='utf-8') as f:
                f.write(self.lib_template.format(**kwargs))
        if self.lib_test_template and not os.path.exists(test_name):
            with open(test_name, 'w', encoding='utf-8') as f:
                f.write(self.lib_test_template.format(**kwargs))
        if self.force or not os.path.exists('meson.build'):
            with open('meson.build', 'w', encoding='utf-8') as f:
                f.write(self.lib_meson_template.format(**kwargs))


class FileImpl(SampleImpl):

    """File based languages without headers"""

    def __init__(self, args: Arguments):
        super().__init__(args)
        self.source_name, self.sources = self._detect_sources(args.srcfiles, str.lower)

    def create_executable(self) -> None:
        if not os.path.exists(self.source_name):
            with open(self.source_name, 'w', encoding='utf-8') as f:
                f.write(self.exe_template.format(project_name=self.name))
        if self.force or not os.path.exists('meson.build'):
            with open('meson.build', 'w', encoding='utf-8') as f:
                f.write(self.exe_meson_template.format(project_name=self.name,
                                                       exe_name=self.executable_name,
                                                       source_name=self.source_name,
                                                       version=self.version,
                                                       meson_version=self.meson_version,
                                                       dependencies=self._format_dependencies(),
                                                       source_files=self._format_sources()))

    def lib_kwargs(self) -> T.Dict[str, str]:
        """Get Language specific keyword arguments

        :return: A dictionary of key: values to fill in the templates
        """
        return {
            'utoken': self.uppercase_token,
            'ltoken': self.lowercase_token,
            'header_dir': self.lowercase_token,
            'class_name': self.capitalized_token,
            'function_name': f'{self.lowercase_token[0:3]}_func',
            'namespace': self.lowercase_token,
            'source_file': f'{self.lowercase_token}.{self.source_ext}',
            'test_source_file': f'{self.lowercase_token}_test.{self.source_ext}',
            'test_exe_name': f'{self.lowercase_token}_test',
            'project_name': self.name,
            'lib_name': self.lowercase_token,
            'test_name': self.lowercase_token,
            'version': self.version,
            'meson_version': self.meson_version,
            'dependencies': self._format_dependencies(),
            'source_files': self._format_sources(),
        }

    def create_library(self) -> None:
        test_name = f'{self.lowercase_token}_test.{self.source_ext}'
        kwargs = self.lib_kwargs()
        if not os.path.exists(self.source_name):
            with open(self.source_name, 'w', encoding='utf-8') as f:
                f.write(self.lib_template.format(**kwargs))
        if self.lib_test_template and not os.path.exists(test_name):
            with open(test_name, 'w', encoding='utf-8') as f:
                f.write(self.lib_test_template.format(**kwargs))
        if self.force or not os.path.exists('meson.build'):
            with open('meson.build', 'w', encoding='utf-8') as f:
                f.write(self.lib_meson_template.format(**kwargs))


class FileHeaderImpl(FileImpl):

    @property
    @abc.abstractmethod
    def header_ext(self) -> str:
        pass

    @property
    @abc.abstractmethod
    def lib_header_template(self) -> str:
        pass

    def lib_kwargs(self) -> T.Dict[str, str]:
        kwargs = super().lib_kwargs()
        kwargs['header_file'] = f'{self.lowercase_token}.{self.header_ext}'
        return kwargs

    def create_library(self) -> None:
        super().create_library()
        kwargs = self.lib_kwargs()
        if not os.path.exists(kwargs['header_file']):
            with open(kwargs['header_file'], 'w', encoding='utf-8') as f:
                f.write(self.lib_header_template.format_map(kwargs))
