# Copyright 2019 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""Exception module."""

from importlib import import_module
from mindinsight.utils.constant import MindInsightModules, GeneralErrors


class MindInsightException(Exception):
    """
    Base class for MindInsight exception.

    Examples:
        >>> raise MindInsightException(GeneralErrors.PATH_NOT_EXISTS_ERROR, 'path not exists')
        >>> raise MindInsightException(DataVisualErrors.CUSTOMIZED_ERROR, 'datavisual error message')
    """

    RUNTIME = 1
    TYPE = 1
    LEVEL = 0
    SYSID = 42

    def __init__(self, error, message, http_code=400):
        """
        Initialization of MindInsightException.

        Args:
            error (Enum): Error value for specified case.
            message (str): Description for exception.
            http_code (int): Http code for exception. Default is 400.
        """
        if isinstance(message, str):
            message = ' '.join(message.split())
        super(MindInsightException, self).__init__(message)
        self.error = error
        self.message = message
        self.http_code = http_code

    def parse_module(self):
        """
        Parse module according to error enum class.

        Note:
            Each enum value, excluding GENERAL, has an Errors class name starting with the enum value
            in Camel-Case referring to specific module.

        Returns:
            Enum, module for specified error.
        """

        module = None
        constant = import_module('mindinsight.utils.constant')
        errors_names = [item for item in dir(constant) if item.endswith('Errors')]
        for name in errors_names:
            errors_cls = getattr(constant, name)
            if isinstance(self.error, errors_cls):
                key = name[:-len('Errors')].upper()
                module = getattr(MindInsightModules, key, None)
                break

        return module

    @property
    def error_code(self):
        """
        Transform exception no to MindInsight error code.

        code compose(4bytes):
        runtime 2bits, type 2bits, level 3bits, sysid 8bits, modid 5bits, value 12bits.

        num = ((0xFF & runtime) << 30) \
                | ((0xFF & type) << 28) \
                | ((0xFF & level) << 25) \
                | ((0xFF & sysid) << 17) \
                | ((0xFF & modid) << 12) \
                | (0x0FFF & value)

        Returns:
            str, Hex string representing the composed MindInsight error code.
        """

        module = self.parse_module()
        if not module:
            raise UnknownError('Unknown module for {}.'.format(self.error))

        num = (((0xFF & self.RUNTIME) << 30)
               | ((0xFF & self.TYPE) << 28)
               | ((0xFF & self.LEVEL) << 25)
               | ((0xFF & self.SYSID) << 17)
               | ((0xFF & module.value) << 12)
               | (0x0FFF & self.error.value))

        return hex(num)[2:].zfill(8).upper()

    def __str__(self):
        return '[{}] code: {}, msg: {}'.format(self.__class__.__name__, self.error_code, self.message)


class ParamValueError(MindInsightException):
    """Request param value error."""
    def __init__(self, error_detail=None):
        if error_detail is None:
            error_msg = "Invalid parameter value."
        else:
            error_msg = 'Invalid parameter value. {}'.format(error_detail)
        super(ParamValueError, self).__init__(
            GeneralErrors.PARAM_VALUE_ERROR,
            error_msg,
            http_code=400)


class ParamTypeError(MindInsightException):
    """Request param type error."""
    def __init__(self, param_name, expected_type):
        error_msg = "Invalid parameter type. '{}' expect {} type.".format(param_name, expected_type)
        super(ParamTypeError, self).__init__(
            GeneralErrors.PARAM_TYPE_ERROR,
            error_msg,
            http_code=400)


class ParamMissError(MindInsightException):
    """Missing param error."""
    def __init__(self, param_name):
        error_msg = "Param missing. '{}' is required.".format(param_name)
        super(ParamMissError, self).__init__(
            GeneralErrors.PARAM_MISSING_ERROR,
            error_msg,
            http_code=400)


class PathNotExistError(MindInsightException):
    """Raised when specified path do not exist."""
    def __init__(self, error_detail):
        """Initialize PathNotExistError."""
        error_msg = 'Specified path does not exist. Detail: {}'.format(error_detail)
        super(PathNotExistError, self).__init__(
            GeneralErrors.PATH_NOT_EXISTS_ERROR,
            error_msg,
            http_code=400)


class FileSystemPermissionError(MindInsightException):
    """Can not access file or dir."""
    def __init__(self, error_detail):
        error_msg = 'File or dir access failed. Detail: {}'.format(error_detail)
        super(FileSystemPermissionError, self).__init__(
            GeneralErrors.FILE_SYSTEM_PERMISSION_ERROR,
            error_msg,
            http_code=400)


class PortReuseException(MindInsightException):
    """Port not available error.."""
    def __init__(self, error_detail):
        error_msg = '{}'.format(error_detail)
        super(PortReuseException, self).__init__(
            GeneralErrors.PORT_NOT_AVAILABLE_ERROR,
            error_msg,
            http_code=400)


class PortNotAvailableError(MindInsightException):
    """Port not available error.."""
    def __init__(self, error_detail):
        error_msg = 'Port not available error. Detail: {}'.format(error_detail)
        super(PortNotAvailableError, self).__init__(
            GeneralErrors.PORT_NOT_AVAILABLE_ERROR,
            error_msg,
            http_code=400)


class UnknownError(MindInsightException):
    """Unknown error."""
    def __init__(self, error_msg):
        super(UnknownError, self).__init__(
            GeneralErrors.UNKNOWN_ERROR,
            error_msg,
            http_code=500)


class UrlDecodeError(MindInsightException):
    """Url decoding failed"""
    def __init__(self, error_detail):
        error_msg = f"Url decode failed. Detail: {error_detail}"
        super(UrlDecodeError, self).__init__(GeneralErrors.URL_DECODE_ERROR,
                                             error_msg,
                                             http_code=400)


class MindInsightMemoryError(MindInsightException):
    """Unknown error."""
    def __init__(self, error_msg):
        super(MindInsightMemoryError, self).__init__(
            GeneralErrors.MEMORY_ERROR,
            error_msg,
            http_code=400)

class SettingValueError(MindInsightException):
    """Setting value error."""
    def __init__(self, error_msg):
        super(SettingValueError, self).__init__(
            GeneralErrors.SETTING_VALUE_ERROR,
            error_msg,
            http_code=400)


class VersionNotMatchError(MindInsightException):
    """Version not match error."""
    def __init__(self, error_msg):
        super(VersionNotMatchError, self).__init__(
            GeneralErrors.PARAM_TYPE_ERROR,
            error_msg,
            http_code=400)
