# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Alexander Shorin
# All rights reserved.
#
# This software is licensed as described in the file LICENSE, which
# you should have received as part of this distribution.
#

"""
Exception hierarchy
-------------------

.. code::

  BaseException
  +-- Exception
      +-- aiohttp.errors.HttpException
          +-- aiohttp.errors.HttpErrorException
              +-- aiocouchdb.errors.HttpErrorException
                  +-- aiocouchdb.errors.BadRequest
                  +-- aiocouchdb.errors.Unauthorized
                  +-- aiocouchdb.errors.Forbidden
                  +-- aiocouchdb.errors.ResourceNotFound
                  +-- aiocouchdb.errors.MethodNotAllowed
                  +-- aiocouchdb.errors.ResourceConflict
                  +-- aiocouchdb.errors.PreconditionFailed
                  +-- aiocouchdb.errors.RequestedRangeNotSatisfiable
                  +-- aiocouchdb.errors.ServerError
"""


import asyncio
import aiohttp.errors


class HttpErrorException(aiohttp.errors.HttpErrorException):
    """Extension of :exc:`aiohttp.errors.HttpErrorException` for CouchDB related
    errors."""

    error = ''
    reason = ''

    def __init__(self, error, reason, headers=None):
        self.error = error
        self.reason = reason
        self.headers = headers

    def __str__(self):
        return self.reason or self.message


class BadRequest(HttpErrorException):
    """The request could not be understood by the server due to malformed
    syntax."""

    code = 400
    message = 'Bad Request'

    def __str__(self):
        if self.error:
            return '({}) {}'.format(self.error, self.reason)
        return super().__str__()


class Unauthorized(HttpErrorException):
    """The request requires user authentication."""

    code = 401
    message = 'Unauthorized'


class Forbidden(HttpErrorException):
    """The server understood the request, but is refusing to fulfill it."""

    code = 403
    message = 'Forbidden'


class ResourceNotFound(HttpErrorException):
    """The server has not found anything matching the Request-URI."""

    code = 404
    message = 'Resource Not Found'


class MethodNotAllowed(HttpErrorException):
    """The method specified in the Request-Line is not allowed for
    the resource identified by the Request-URI."""

    code = 405
    message = 'Method Not Allowed'


class ResourceConflict(HttpErrorException):
    """The request could not be completed due to a conflict with the current
    state of the resource."""

    code = 409
    message = 'Resource Conflict'


class PreconditionFailed(HttpErrorException):
    """The precondition given in one or more of the Request-Header fields
    evaluated to false when it was tested on the server."""

    code = 412
    message = 'Precondition Failed'


class RequestedRangeNotSatisfiable(HttpErrorException):
    """The client has asked for a portion of the file, but the server
    cannot supply that portion."""
    code = 416
    message = 'Requested Range Not Satisfiable'


class ServerError(HttpErrorException):
    """The server encountered an unexpected condition which prevented it from
    fulfilling the request."""

    code = 500
    message = 'Server Error'

    def __str__(self):
        if self.error:
            return '({}) {}'.format(self.error, self.reason)
        return super().__str__()


HTTP_ERROR_BY_CODE = {err.code: err
                      for err in HttpErrorException.__subclasses__()}


@asyncio.coroutine
def maybe_raise_error(resp):
    """Raises :exc:`aiohttp.errors.HttpErrorException` exception in case of
    ``>=400`` response status code."""
    if resp.status < 400:
        return
    exc_cls = HTTP_ERROR_BY_CODE[resp.status]
    data = yield from resp.json()
    if isinstance(data, dict):
        error, reason = data.get('error', ''), data.get('reason', '')
        exc = exc_cls(error, reason, resp.headers)
    else:
        exc = exc_cls('', data, resp.headers)
    raise exc
