"""
Exceptions and warnings for the PDR system
"""
from . import PDRSystem, SystemInfoMixin

class SourceSystemMixin(SystemInfoMixin):
    """
    a mixin that stores information about the source system of the error
    """
    def __init__(self, systeminfo):
        self._sysname = systeminfo.system_name or ""
        self._sysabbrev = systeminfo.system_abbrev or ""
        self._subsysname = systeminfo.subsystem_name or systeminfo.system_name
        self._subsysabbrev = \
                       systeminfo.subsystem_abbrev or systeminfo.system_abbrev
        self._sysver = systeminfo.system_version or "x"

    @property 
    def system_version(self): return self._sysver
    @property
    def system_name(self): return self._sysname
    @property
    def system_abbrev(self): return self._sysabbrev
    @property
    def subsystem_name(self): return self._subsysname
    @property
    def subsystem_abbrev(self): return self._subsysabbrev


class PDRWarning(Warning, SourceSystemMixin):
    """
    a base class for warnings generated by the preservation system
    """
    def __init__(self, msg=None, cause=None, sys=None):
        """
        create the base warning.

        :param msg    str:  a specific warning message
        :param cause Exception:  a caught but handled Exception that is the 
                            cause of the warning
        :param sys SystemInfo:  a SystemInfo instance that can provide 
                            information as to the cause of the 
        """
        self.cause = cause
        if not sys or not isinstance(sys, SystemInfoMixin):
            sys = PDRSystem()
        SourceSystemMixin.__init__(self, sys)
        Warning.__init__(self, msg)

class PDRException(Exception, SourceSystemMixin):
    """
    a base class for exceptions occuring in the PDR system
    """
    def __init__(self, msg=None, cause=None, sys=None):
        """
        create the exception.

        :param msg    str:  a specific warning message
        :param cause Exception:  a caught but handled Exception that is the 
                            cause of the warning
        :param sys SystemInfo:  a SystemInfo instance that can provide 
                            information as to the cause of the 
        """
        if not msg:
            if cause:
                msg = str(cause)
            else:
                msg = "Unknown {0} System Error".format(self.subsystem_abbrev)
        Exception.__init__(self, msg)
        self.cause = cause

        if not sys or not isinstance(sys, SystemInfoMixin):
            sys = PDRSystem()
        SourceSystemMixin.__init__(self, sys)


class ConfigurationException(PDRException):
    """
    a class indicating an error in the configuration of the PDR system
    """
    pass

class StateException(PDRException):
    """
    a class indicating that the PDR system or environment is in 
    an uncorrectable state preventing proper processing
    """
    pass

class PODError(StateException):
    """
    a problem found in the provided POD data for the dataset
    """
    def __init__(self, msg, cause=None, src=None, sys=None):
        """
        create the exception.

        :param msg    str:  a specific warning message
        :param cause Exception:  a caught but handled Exception that is the 
                            cause of the warning
        :param src str:  the filename, service, or context that is the source 
                         of the POD data
        :param sys SystemInfo:  a SystemInfo instance that can provide 
                        information as to the cause of the 
        """
        if src:
            msg += " ({0})".format(src)
        super(PODError, self).__init__(msg, cause, sys=sys)
        self.source = src

class NERDError(StateException):
    """
    a problem found in the provided NERDm data for the dataset
    """
    def __init__(self, msg, cause=None, src=None, sys=None):
        """
        create the exception.

        :param msg    str:  a specific warning message
        :param cause Exception:  a caught but handled Exception that is the 
                            cause of the warning
        :param src str:  the filename, service, or context that is the source 
                         of the NERDm data
        :param sys SystemInfo:  a SystemInfo instance that can provide 
                        information as to the cause of the 
        """
        if src:
            msg += " ({0})".format(src)
        super(NERDError, self).__init__(msg, cause, sys=sys)
        self.source = src

class SIPDirectoryError(PDRException):
    """
    a class indicating a problem with the given directory containing 
    the submission data.
    """
    def __init__(self, dir=None, problem=None, cause=None, msg=None, sys=None):
        """
        initial the exception.  By default the exception message will
        be formatted by combining the directory name and the problem statement.
        This can be overridden by providing a verbatim message via the msg
        parameter.

        If no arguments are provided, it is assumed that the problem is that 
        an SIP directory was not provided.

        :param dir  str:   the directory giving the problem
        :param problem str:   a statement of what the problem is; this should not
                           include the name of the directroy.
        :param cause Exception:  a caught exception that represents the 
                           underlying cause of the problem.  
        :param msg  str:   a fully formatted to string to use as the exception
                           message instead of one formed by combining the 
                           directory name and its problem.
        :param sys SystemInfo:  a SystemInfo instance that can provide 
                        information as to the cause of the 
        """
        self.dir = dir
        if not problem:
            if cause:
                problem = str(cause)
            elif not dir:
                problem = "SIP directory not provided"
        if not msg:
            if dir:
                msg = "Problem with SIP directory, {0}: {1}".format(dir, problem)
            else:
                msg = problem
        super(SIPDirectoryError, self).__init__(msg, cause, sys)
        self.problem = problem
                    
class SIPDirectoryNotFound(SIPDirectoryError):
    """
    An exception indicating the SIPDirectory does not exist
    """
    def __init__(self, dir=None, cause=None, msg=None, sys=None):
        """
        :param dir  str:   the directory giving the problem
        :param cause Exception:  a caught exception that represents the 
                           underlying cause of the problem.  
        :param msg  str:   A message to override the default.
        """
        prob = "directory not found"
        super(SIPDirectoryNotFound, self).__init__(dir, prob, cause, msg, sys)


class PDRServiceException(PDRException):
    """
    an exception indicating a problem using a PDR service.
    """

    def __init__(self, service_name, resource=None, http_code=None,
                 http_status=None, message=None, cause=None, sys=None):
        if not message:
            if resource:
                message = "Trouble accessing {0} from the {1} service". \
                          format(resource, service_name)
            else:
                message = "Problem accessing the {0} service". \
                          format(service_name)
            if http_code or http_status:
                message += ":"
                if http_code:
                    message += " "+str(http_code)
                if http_status:
                    message += " "+str(http_status)
            elif cause:
                message += ": "+str(cause)
                
        super(PDRServiceException, self).__init__(message, cause, sys)
        self.service = service_name
        self.resource = resource
        self.code = http_code
        self.status = http_status
                                        
class PDRServerError(PDRServiceException):
    """
    an exception indicating a problem using a PDR service.
    """

    def __init__(self, service_name, resource=None, http_code=None,
                 http_status=None, message=None, cause=None, sys=None):
        if not message:
            if resource:
                message = "Server-side error occurred while accessing " + \
                          resource + " from the " + service_name + " service"
            else:
                message = "Server-side error occurred while accessing the " + \
                          service_name + " service"
            if http_code or http_status:
                message += ":"
                if http_code:
                    message += " "+str(http_code)
                if http_status:
                    message += " "+str(http_status)
            elif cause:
                message += ": "+str(cause)
        super(PDRServerError, self).__init__(service_name, resource, http_code,
                                             http_status, message, cause, sys)

class PDRServiceClientError(PDRServiceException):
    """
    an exception indicating a problem using a PDR service due to a user/client 
    error.  
    """

    def __init__(self, service_name, resource=None, http_code=None,
                 http_status=None, message=None, cause=None, sys=None):
        if not message:
            if resource:
                message = "Client-side error occurred while accessing " + \
                          resource + " from the " + service_name + " service"
            else:
                message = "Client-side error occurred while accessing the " + \
                          service_name + " service"
            if http_code or http_status:
                message += ":"
                if http_code:
                    message += " "+str(http_code)
                if http_status:
                    message += " "+str(http_status)
            elif cause:
                message += ": "+str(cause)
        super(PDRServiceClientError, self).__init__(service_name, resource, http_code,
                                                    http_status, message, cause, sys)

class PDRServiceAuthFailure(PDRServiceException):
    """
    an exception indicating a failure using a service due to incorrect or lack of 
    authorization credentials.
    """

    def __init__(self, service_name, resource=None, http_status=None, 
                 message=None, cause=None, http_code=401, sys=None):
        if not message:
            if resource:
                message = "Client not properly authorized to access " + \
                          resource + " from the " + service_name + " service"
            else:
                message = "Client not properly authorized to access the " + \
                          service_name + " service"
            if http_code or http_status:
                message += ":"
                if http_code:
                    message += " "+str(http_code)
                if http_status:
                    message += " "+str(http_status)
            elif cause:
                message += ": "+str(cause)
        super(PDRServiceAuthFailure, self).__init__(service_name, resource, http_code,
                                                    http_status, message, cause, sys)

class IDNotFound(PDRException):
    """
    An error indicating a request for an identifier that is not recognized 
    as existing in the PDR system.
    """
    def __init__(self, id, message=None, cause=None):
        if not message:
            if id:
                message = "{0}: Identifier not recognized".format(id)
            else:
                message = "Requested unrecognized identifier"
            if cause:
                message += " ("+str(cause)+")"
        super(IDNotFound, self).__init__(message, cause)

