#!/bin/env python

import base64
import httplib
import json
import urlparse
import requests


class NotFoundException(LookupError):
    pass

class PermissionDeniedException(IOError):
    pass

class InvalidRequestException(ValueError):

    def __init__(self, msg, extra):
        Exception.__init__(self, msg)
        self.extra = extra

class RestClient(object):

    def __init__(self, url, **kwargs):
        """
        RESTful client
        """
        self.url = url
        self._url = urlparse.urlparse(self.url)
        self.user = None
        self.password = None
        self.timeout = None
        self.verbose = False

        # overwrite any param from keyword args
        for k in kwargs:
            if hasattr(self, k):
                setattr(self, k, kwargs[k])

    def url_update(self, **kwargs):
        return urlparse.urlunparse(self._url._replace(**kwargs))

    def _request(self, typ, id=0, method='GET', params=None, data=None, url=None):
        """
        send the request, return response obj
        """

        headers = { "Accept": "application/json" }
        auth = None

        if self.user:
            auth = (self.user, self.password)

        if not url:
            if id:
                url = "%s/%s/%s" % (self.url, typ, id)
            else:
                url = "%s/%s" % (self.url, typ)

        return requests.request(method, url, params=params, data=data, auth=auth, headers=headers)


    def _throw(self, res, data):
        self.log('=====> %s' %  data)
        err = data.get('meta', {}).get('error', 'Unknown')
        if res.status_code < 600:
            if res.status_code == 404:
                raise NotFoundException("%d %s" % (res.status_code, err))
            elif res.status_code == 401 or res.status_code == 403:
                raise PermissionDeniedException("%d %s" % (res.status_code, err))
            elif res.status_code == 400:
                raise InvalidRequestException("%d %s" % (res.status_code, err), data)

        # Internal
        raise Exception("%d Internal error: %s" % (res.status_code, err))

    def _load(self, res):
        try:
            data = res.json()
        except ValueError:
            data = {}

        if res.status_code < 300:
            if not data:
                return []
            return data.get('data', [])

        self._throw(res, data)

    def _mangle_data(self, data):
        if not 'id' in data and 'pk' in data:
            data['id'] = data['pk']
        if '_rev' in data:
            del data['_rev']
        if 'pk' in data:
            del data['pk']
        if '_id' in data:
            del data['_id']

    def log(self, msg):
        if self.verbose:
            print msg

    def all(self, typ, **kwargs):
        """
        List all of type
        Valid arguments:
            skip : number of records to skip
            limit : number of records to limit request to
        """
        return self._load(self._request(typ, params=kwargs))

    def get(self, typ, id, **kwargs):
        """
        Load type by id
        """
        return self._load(self._request(typ, id=id, params=kwargs))

    def create(self, typ, data, return_response=False):
        """
        Create new type
        Valid arguments:
            skip : number of records to skip
            limit : number of records to limit request to
        """
        res = self._request(typ, method='POST', data=data)
        if res.status_code != 201:
            try:
              data = res.json()
              self._throw(res, data)
            except ValueError:
              self._throw(res, {})

        loc = res.headers.get("location", None)
        if loc and loc.startswith('/'):
            return self._load(self._request(None, url=self.url_update(path=loc)))
        if return_response:
            return res.json()

        return self._load(self._request(None, url=loc))

    def update(self, typ, id, **kwargs):
        """
        update just fields sent by keyword args
        """
        return self._load(self._request(typ, id=id, method='PUT', data=kwargs))

    def save(self, typ, data):
        """
        Save the dataset pointed to by data (create or update)
        """
        if 'id' in data:
            return self._load(self._request(typ, id=data['id'], method='PUT', data=data))

        return self.create(typ, data)

    def rm(self, typ, id):
        """
        remove typ by id
        """
        return self._load(self._request(typ, id=id, method='DELETE'))

    def type_wrap(self, typ):
        return TypeWrap(self, typ)


class TypeWrap(object):
    def __init__(self, client, typ):
        self.client = client
        self.typ = typ

    def all(self, **kwargs):
        """
        List all
        """
        return self.client.all(self.typ, **kwargs)

    def get(self, id):
        """
        Load by id
        """
        return self.client.get(self.typ, id)

    def save(self, data):
        """
        Save object
        """
        return self.client.save(self.typ, data)

    def rm(self, id):
        """
        remove by id
        """
        return self.client.rm(self.typ, id)

