import binascii
from abc import ABCMeta
from typing import (
    Any,
    Dict,
    Optional,
    Union,
)


class PosControllerException(Exception):
    __metaclass__ = ABCMeta

    def __init__(
        self,
        error_code,  # type: int
        error_message,  # type: str
    ):
        # type: (...) -> None

        self.error_code = error_code
        self.error_message = error_message

    def __str__(self):
        # type: (...) -> str

        return "PosController error: error code={}, error message={}".format(self.error_code, self.error_message)

    def get_type_name(self):
        # type: (...) -> str

        module = self.__class__.__module__
        if module == "__builtin__":
            return self.__class__.__name__
        else:
            return "{}.{}".format(module, self.__class__.__name__)

    def get_extra_data(self):
        # type: (...) -> Optional[Dict[str, Any]]

        return None


class UnknownError(PosControllerException):
    def __init__(
        self,
        token,      # type: int
        data,       # type: bytes
    ):
        # type: (...) -> None

        self.token = token
        self.data = data

        try:
            text_data = self.data.decode("utf-8")
        except UnicodeDecodeError:
            text_data = binascii.hexlify(self.data).decode("utf-8")

        error_message = "Unknown error: token={}, data={}".format(self.token, text_data)

        super(UnknownError, self).__init__(
            error_code=-99,
            error_message=error_message
        )


class UnmappedApplicationError(PosControllerException):
    def __init__(
        self,
        error_code,         # type: int
        error_message,      # type: str
    ):
        # type: (...) -> None

        super(UnmappedApplicationError, self).__init__(
            error_code=error_code,
            error_message=error_message,
        )


class InvalidNumberOfParameters(PosControllerException):
    def __init__(self):
        super(InvalidNumberOfParameters, self).__init__(
            error_code=-1,
            error_message="Invalid number of parameters.",
        )


class DayAlreadyOpened(PosControllerException):
    def __init__(self):
        super(DayAlreadyOpened, self).__init__(
            error_code=-2,
            error_message="The day is already opened.",
        )


class DayNotOpened(PosControllerException):
    def __init__(self):
        super(DayNotOpened, self).__init__(
            error_code=-3,
            error_message="The day is not opened.",
        )


class InvalidPosId(PosControllerException):
    def __init__(self):
        super(InvalidPosId, self).__init__(
            error_code=-4,
            error_message="Invalid pos id.",
        )


class InvalidInitialFloat(PosControllerException):
    def __init__(self):
        super(InvalidInitialFloat, self).__init__(
            error_code=-5,
            error_message="Initial float cannot be negative.",
        )


class UserAlreadyOpen(PosControllerException):
    def __init__(self):
        super(UserAlreadyOpen, self).__init__(
            error_code=-6,
            error_message="The user is already open.",
        )


class UserNotOpen(PosControllerException):
    def __init__(self):
        super(UserNotOpen, self).__init__(
            error_code=-7,
            error_message="The user is not open.",
        )


class UserNotOpenNorPaused(PosControllerException):
    def __init__(self):
        super(UserNotOpenNorPaused, self).__init__(
            error_code=-8,
            error_message="User is not open nor paused.",
        )


class AnotherUserAlreadyLoggedIn(PosControllerException):
    def __init__(self):
        super(AnotherUserAlreadyLoggedIn, self).__init__(
            error_code=-9,
            error_message="Another user is already logged in.",
        )


class UserNotLoggedIn(PosControllerException):
    def __init__(self):
        super(UserNotLoggedIn, self).__init__(
            error_code=-10,
            error_message="The user is not logged in.",
        )


class DayHasOpenedUser(PosControllerException):
    def __init__(self):
        super(DayHasOpenedUser, self).__init__(
            error_code=-11,
            error_message="The day has an open user.",
        )


class CustomKeyCannotBeEmpty(PosControllerException):
    def __init__(self):
        super(CustomKeyCannotBeEmpty, self).__init__(
            error_code=-12,
            error_message="Custom key cannot be empty.",
        )


class InvalidXml(PosControllerException):
    def __init__(
        self,           # type: InvalidXml
        details,        # type: str
    ):
        self.details = details

        message = "Invalid XML format. " + details

        super(InvalidXml, self).__init__(
            error_code=-13,
            error_message=message,
        )

    def get_extra_data(self):
        return {
            "details": self.details,
        }


class DialogTimeoutCannotBeNegative(PosControllerException):
    def __init__(self):
        super(DialogTimeoutCannotBeNegative, self).__init__(
            error_code=-14,
            error_message="Dialog timeout cannot be negative.",
        )


class DialogNotFound(PosControllerException):
    def __init__(self):
        super(DialogNotFound, self).__init__(
            error_code=-15,
            error_message="Dialog not found.",
        )


class DialogTimeout(PosControllerException):
    def __init__(self):
        super(DialogTimeout, self).__init__(
            error_code=-16,
            error_message="Dialog timed out.",
        )


class PosBlocked(PosControllerException):
    def __init__(self):
        super(PosBlocked, self).__init__(
            error_code=-17,
            error_message="The POS is blocked.",
        )


class ServicePeriodNotFound(PosControllerException):
    def __init__(self):
        super(ServicePeriodNotFound, self).__init__(
            error_code=-18,
            error_message="Service period not found.",
        )


class NoActiveServicePeriod(PosControllerException):
    def __init__(self):
        super(NoActiveServicePeriod, self).__init__(
            error_code=-19,
            error_message="No active service period.",
        )


class InvalidActionResponse(PosControllerException):
    def __init__(
        self,
        token,      # type: int
        data,       # type: Union[bytes, str]
    ):
        super(InvalidActionResponse, self).__init__(
            error_code=-20,
            error_message="Invalid action response",
        )

        self.token = token
        if isinstance(data, str):
            self.data = binascii.unhexlify(data.encode("utf-8"))
        else:
            self.data = data

    def get_extra_data(self):
        return {
            "token": self.token,
            "data": binascii.hexlify(self.data).decode("utf-8"),
        }
