from notmuch2 import _capi as capi


class NotmuchError(Exception):
    """Base exception for errors originating from the notmuch library.

    Usually this will have two attributes:

    :status: This is a numeric status code corresponding to the error
       code in the notmuch library.  This is normally fairly
       meaningless, it can also often be ``None``.  This exists mostly
       to easily create new errors from notmuch status codes and
       should not normally be used by users.

    :message: A user-facing message for the error.  This can
       occasionally also be ``None``.  Usually you'll want to call
       ``str()`` on the error object instead to get a sensible
       message.
    """

    @classmethod
    def exc_type(cls, status):
        """Return correct exception type for notmuch status."""
        types = {
            capi.lib.NOTMUCH_STATUS_OUT_OF_MEMORY:
                OutOfMemoryError,
            capi.lib.NOTMUCH_STATUS_READ_ONLY_DATABASE:
                ReadOnlyDatabaseError,
            capi.lib.NOTMUCH_STATUS_XAPIAN_EXCEPTION:
                XapianError,
            capi.lib.NOTMUCH_STATUS_OPERATION_INVALIDATED:
                OperationInvalidatedError,
            capi.lib.NOTMUCH_STATUS_FILE_ERROR:
                FileError,
            capi.lib.NOTMUCH_STATUS_FILE_NOT_EMAIL:
                FileNotEmailError,
            capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
                DuplicateMessageIdError,
            capi.lib.NOTMUCH_STATUS_NULL_POINTER:
                NullPointerError,
            capi.lib.NOTMUCH_STATUS_TAG_TOO_LONG:
                TagTooLongError,
            capi.lib.NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
                UnbalancedFreezeThawError,
            capi.lib.NOTMUCH_STATUS_UNBALANCED_ATOMIC:
                UnbalancedAtomicError,
            capi.lib.NOTMUCH_STATUS_UNSUPPORTED_OPERATION:
                UnsupportedOperationError,
            capi.lib.NOTMUCH_STATUS_UPGRADE_REQUIRED:
                UpgradeRequiredError,
            capi.lib.NOTMUCH_STATUS_PATH_ERROR:
                PathError,
            capi.lib.NOTMUCH_STATUS_ILLEGAL_ARGUMENT:
                IllegalArgumentError,
            capi.lib.NOTMUCH_STATUS_NO_CONFIG:
                NoConfigError,
            capi.lib.NOTMUCH_STATUS_NO_DATABASE:
                NoDatabaseError,
            capi.lib.NOTMUCH_STATUS_DATABASE_EXISTS:
                DatabaseExistsError,
            capi.lib.NOTMUCH_STATUS_BAD_QUERY_SYNTAX:
                QuerySyntaxError,
        }
        return types[status]

    def __new__(cls, *args, **kwargs):
        """Return the correct subclass based on status."""
        # This is simplistic, but the actual __init__ will fail if the
        # signature is wrong anyway.
        if args:
            status = args[0]
        else:
            status = kwargs.get('status', None)
        if status and cls == NotmuchError:
            exc = cls.exc_type(status)
            return exc.__new__(exc, *args, **kwargs)
        else:
            return super().__new__(cls)

    def __init__(self, status=None, message=None):
        self.status = status
        self.message = message

    def __str__(self):
        if self.message:
            return self.message
        elif self.status:
            char_str = capi.lib.notmuch_status_to_string(self.status)
            return capi.ffi.string(char_str).decode(errors='replace')
        else:
            return 'Unknown error'


class OutOfMemoryError(NotmuchError): pass
class ReadOnlyDatabaseError(NotmuchError): pass
class XapianError(NotmuchError): pass
class OperationInvalidatedError(XapianError): pass
class FileError(NotmuchError): pass
class FileNotEmailError(NotmuchError): pass
class DuplicateMessageIdError(NotmuchError): pass
class NullPointerError(NotmuchError): pass
class TagTooLongError(NotmuchError): pass
class UnbalancedFreezeThawError(NotmuchError): pass
class UnbalancedAtomicError(NotmuchError): pass
class UnsupportedOperationError(NotmuchError): pass
class UpgradeRequiredError(NotmuchError): pass
class PathError(NotmuchError): pass
class IllegalArgumentError(NotmuchError): pass
class NoConfigError(NotmuchError): pass
class NoDatabaseError(NotmuchError): pass
class DatabaseExistsError(NotmuchError): pass
class QuerySyntaxError(NotmuchError): pass

class ObjectDestroyedError(NotmuchError):
    """The object has already been destroyed and it's memory freed.

    This occurs when :meth:`destroy` has been called on the object but
    you still happen to have access to the object.  This should not
    normally occur since you should never call :meth:`destroy` by
    hand.
    """

    def __str__(self):
        if self.message:
            return self.message
        else:
            return 'Memory already freed'
