from getopt import getopt
from typing import List, Tuple

from common import exit_message


class Param(object):
    def __init__(
        self,
        verbose: str = None,
        short: str = None,
        has_value: bool = True,
        is_required: bool = False,
        only_verbose: bool = False,
        default_value: any = None,
    ) -> None:
        try:
            if not verbose and not short:
                raise NameError(
                    "É obrigado a informar uma definição do parâmetro, seja ela `verbose` ou `short`"
                )

            self.verbose = verbose

            if short is not None:
                self.short = short
            elif only_verbose is False:
                self.short = verbose[0]
            else:
                self.short = None

            if self.short and len(self.short) > 1:
                raise ValueError(
                    f"O parâmetro curto (-{self.short}) deve conter apenas 1 letra."
                )

            if self.verbose:
                self.verbose_command = f"--{self.verbose}"

            if self.short:
                self.short_command = f"-{self.short}"

            self.has_value = has_value

            self._is_required = is_required
            self._value = default_value

        except Exception as err:
            exit_message(err)

    def required(self) -> bool:
        return self._is_required

    def required_has_no_value(self) -> bool:
        return self.required() and self._value is None

    def set_param_value(self, value) -> None:
        self._value = value if value else True

    def get_param_value(self) -> any:
        return self._value

    def get_param_tuple(self) -> Tuple[str, str]:
        list_params = []

        if self.verbose:
            list_params.append(self.verbose_command)

        if self.short:
            list_params.append(self.short_command)

        return tuple(list_params)


class GetOptBuilder(object):
    def __init__(self, argv) -> None:
        self._argv: List[str] = argv

        self._params: List[Param] = []
        self._short_params: List[Param] = []
        self._verbose_params: List[Param] = []

    def add_params(self, params: List[Param]) -> None:
        for param in params:
            self.add_param(param)

    def add_param(self, param: Param) -> None:
        try:
            short_params_raw = list(map(lambda p: p.short, self._params))
            if param.short in short_params_raw:
                raise ValueError(
                    f"O parâmetro '-{param.short}' já existe, talvez você tenha que especifica-lo melhor."
                )

            self._params.append(param)

            if param.verbose:
                verbose_params = (
                    f"{param.verbose}=" if param.has_value else param.verbose
                )

                self._verbose_params.append(verbose_params)

            if param.short:
                short_params = f"{param.short}:" if param.has_value else param.short

                self._short_params.append(short_params)

        except Exception as err:
            exit_message(err)

    def build(self) -> None:
        try:
            opts, _ = getopt(
                self._argv,
                self._get_short_params(),
                self._get_verbose_params(),
            )

            if len(opts) == 0:
                required_params = self._get_required_params()

                self._check_required_params_are_valid(required_params)

            for opt, arg in opts:
                arg = arg.strip()

                params = self._get_params()

                for param in params:
                    if opt in param.get_param_tuple():
                        param.set_param_value(arg)

            required_params_without_value = self._get_required_params_without_value()

            self._check_required_params_are_valid(required_params_without_value)

        except Exception as err:
            exit_message(err)

    @staticmethod
    def _check_required_params_are_valid(required_params: List[Param]) -> None:
        if len(required_params) > 0:
            message = ""

            for index, required_param in enumerate(required_params):
                verbose_param_message = f"{required_param.verbose_command if required_param.verbose else ''}"

                short_param_message = ""

                if required_param.verbose and required_param.short:
                    short_param_message = f" ({required_param.short_command})"
                elif required_param.short:
                    short_param_message = required_param.short_command

                message += (
                    f"{verbose_param_message}{short_param_message}"
                    f"{',' if index != len(required_params)-1 else ''} "
                )

            raise AssertionError(f"Os parâmetros {message}são obrigatórios.")

    def _get_verbose_params(self) -> List[str]:
        return self._verbose_params

    def _get_short_params(self) -> str:
        return "".join(self._short_params)

    def _get_params(self) -> List[Param]:
        return self._params

    def _get_required_params(self) -> List[Param]:
        return list(filter(lambda param: param.required(), self._params))

    def _get_required_params_without_value(self) -> List[Param]:
        return list(filter(lambda param: param.required_has_no_value(), self._params))
