# base.py

import sys
import re
import json
import importlib
import subprocess
import contextlib
import warnings
import logging
import ctypes
import os
from pathlib import Path
import threading
from typing import Any, Optional, Dict, Iterable

__all__ = [
    "terminate_thread",
    "suppress",
    "run_silent_command",
    "validate_requirement",
    "documentation",
    "document",
    "to_title",
    "virtualenv_interpreter_location",
    "activate_virtualenv_command",
    "model_repr",
    "load_json",
    "save_json",
    "root",
    "source",
    "assets",
    "icons",
    "dependencies"
]

def root() -> str:
    """
    Returns the root of the source program.

    :return: The path to the source.
    """

    try:
        if os.getcwd() in os.environ['VIRTUAL_ENV']:
            path = Path(__file__).parent

        else:
            raise KeyError
        # end if

    except KeyError:
        if os.getcwd() not in (
                path := str(Path(__file__).parent)
        ):
            path = os.getcwd()
        # end if
    # end try

    return str(path)
# end root

def source() -> str:
    """
    Returns the root of the source program.

    :return: The path to the source.
    """

    return str(Path(root()) / Path("source"))
# end source

def dependencies() -> str:
    """
    Returns the root of the source program.

    :return: The path to the source.
    """

    return str(Path(root()) / Path("dependencies"))
# end dependencies

def assets() -> str:
    """
    Returns the root of the source program.

    :return: The path to the source.
    """

    return str(Path(source()) / Path("assets"))
# end assets

def icons() -> str:
    """
    Returns the root of the source program.

    :return: The path to the source.
    """

    return str(Path(assets()) / Path("icon"))
# end icons

def load_json(path: str) -> Any:
    """
    Loads the chain object from the file.

    :param path: The path to the chain file.

    :return: The chain object.
    """

    with open(path, "r") as file:
        return json.load(file)
    # end open
# end load_json

def save_json(data: Any, path: str) -> None:
    """
    Loads the chain object from the file.

    :param data: The data to save.
    :param path: The path to the chain file.
    """

    with open(path, "w") as file:
        json.dump(data, file, indent=4)
    # end open
# end save_json

def model_repr(model: object, excluded: Iterable[Any] = None) -> str:
    """
    Returns a string representation of the object.

    :param model: The object model to represent.
    :param excluded: The excluded keys.

    :return: The string representation of the object.
    """

    if hasattr(model, '__dict__'):
        data = model.__dict__

    elif isinstance(model, (tuple, list, set, dict)):
        data = model

    else:
        return repr(model)
    # end if

    if isinstance(data, dict):
        items = ", ".join(
            f"{model_repr(key) if not hasattr(model, '__dict__') else key}="
            f"{model_repr(value)}"
            for key, value in data.items() if key not in excluded
        )

    else:
        items = ", ".join(
            model_repr(value) for value in data
        )
    # end if

    return type(model).__name__ + f'({items})'
# end model_repr


def terminate_thread(thread: threading.Thread) -> None:
    """
    Terminates a thread from another thread.

    :param thread: The thread instance.
    """

    logging.disable(logging.FATAL)

    thread_id = thread.ident

    if ctypes.pythonapi.PyThreadState_SetAsyncExc(
            thread_id, ctypes.py_object(SystemExit)
    ) > 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
    # end if

    logging.getLogger().setLevel(logging.FATAL)
# end terminate_thread

def suppress() -> contextlib.redirect_stdout:
    """
    Suppresses the output.

    :return: The output suppressor.
    """

    with warnings.catch_warnings(record=True):
        warnings.simplefilter("ignore")

        return contextlib.redirect_stdout(None)
    # end catch_warnings
# end suppress

def run_silent_command(command: str) -> None:
    """
    Runs a command with no output.

    :param command: The command to run.
    """

    subprocess.Popen(
        command, shell=True, stdout=subprocess.PIPE,
        stdin=subprocess.PIPE, stderr=subprocess.PIPE
    )
# end run_silent_command

def virtualenv_interpreter_location() -> str:
    """
    Returns the location of the interpreter in the venv.

    :return: The location of the interpreter.
    """

    return os.path.split(sys.executable)[0]
# end activate_virtualenv_command

def activate_virtualenv_command() -> str:
    """
    Returns the command to activate the virtual env.

    :return: The command to activate the venv.
    """

    python_startup = (
        virtualenv_interpreter_location() / Path('activate')
    )

    return (
        f"{'' if 'win' in sys.platform else 'source '}"
        f"{python_startup}"
    )
# end activate_virtualenv_command

def validate_requirement(
        name: str,
        path: Optional[str] = None,
        version: Optional[str] = None,
        quiet: Optional[bool] = True,
        silence: Optional[bool] = False
) -> None:
    """
    Installs the required package.

    :param name: The name of the package.
    :param path: The path to the package.
    :param version: The version to install.
    :param quiet: The value to show the installation process.
    :param silence: The value to silence the output.
    """

    if version is None:
        version = ""

    elif isinstance(version, str) and ("=" not in version):
        version = f"~{version}"

    else:
        version = ""
    # end if

    try:
        importlib.import_module(name)

    except ImportError:
        arguments = [
            'install',
            f'{(path if path is not None else name) + version}'
        ]

        if quiet:
            arguments.append("--quiet")
        # end if

        command = (
            f"{activate_virtualenv_command()} && "
            f"{Path(virtualenv_interpreter_location()) / Path('python')} -m pip "
            f"{' '.join(arguments)}"
        )

        if silence:
            with suppress():
                os.system(command)
            # end suppress

        else:
            os.system(command)
        # end if

        try:
            importlib.import_module(name)

        except ImportError:
            raise ImportError(f"{name} module is not found.")
        # end try
    # end try
# end validate_requirements

def documentation(module: str) -> Dict[str, str]:
    """
    Documents a module with its content objects documentation.

    :param module: The name of the module.

    :return: The documentation of the module.
    """

    documents = {
        f"{value.__module__}.{value.__name__}": value
        for name, value in sorted(
            list(sys.modules[module].__dict__.items()),
            key=lambda item: item[0]
        )
        if (
            hasattr(value, '__module__') and
            hasattr(value, '__name__') and
            hasattr(value, '__doc__') and
            hasattr(value, '__init__')
        )
    }

    return {
        key: f'`{key}`\n{value.__doc__}'
        for key, value in documents.items()
    }
# end documentation

def document(obj: Any) -> None:
    """
    Documents a module with its content objects documentation.

    :param obj: The obj to document.
    """

    obj.__doc__ += """\n\n""".join(
        documentation(obj.__module__).values()
    )
# end document

def to_title(text: str) -> str:
    """
    Returns a string representation of the models set.

    :param text: The text to represent in a string.

    :return: The string representation of the models.
    """

    return re.sub(r'(?<!^)(?=[A-Z])', ' ', text)
# end to_title