"""
Repustate Python API client.

Requirements:
- Python 2.6+
- python-requests

Want to change it / improve it / share it? Go for it.

Feedback is appreciated at info@repustate.com

More documentation available at https://www.repustate.com/docs
"""
from base64 import b64encode

try:
    # python 2
    from urllib import urlencode
except:
    # python 3
    from urllib.parse import urlencode

import requests

class RepustateAPIError(Exception):
    pass

class Client(object):

    url_template = (
        '%(protocol)s://%(host)s%(port)s/%(version)s/%(api_key)s/%(function)s.json'
        )

    def __init__(self, api_key, version='v3', protocol='https', host='api.repustate.com', port=''):
        self.api_key = api_key
        self.version = version

        if host.startswith('http'):
            raise RepustateAPIError('host should not have a protocol specified')
        self.host = host

        if port and port[0] != ':':
            raise RepustateAPIError("port must start with ':'")
        self.port = port

        if protocol and protocol not in ('http', 'https'):
            raise RepustateAPIError("%s is not a valid protocol; must be one of http or https" % protocol)
        self.protocol = protocol

    def _call_api(self, api_function, method='POST', **params):
        params = dict((x, y) for x, y in params.items() if y is not None)

        data = urlencode(params)

        url_args = self.__dict__
        url_args['function'] = api_function
        url = self.url_template % url_args

        http_function = getattr(requests, method.lower())
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        if method in ("GET", "DELETE"):
            # Both GET & DELETE we put the args in the URL.
            response = http_function(url, params=data, headers=headers)
        else:
            # POST, PUT args go in the body.
            response = http_function(url, data=data, headers=headers)

        if response.status_code == 200:
            return response.json()
        elif response.status_code >= 500:
            raise RepustateAPIError('Internal Server Error: could not process your API call.')
        elif response.status_code >= 400:
            # Missing params, incorrect arguments supplied etc.
            msg = u'%(title)s\n\n%(description)s' % response.json()
            raise RepustateAPIError(msg)
        else:
            # Redirect maybe? Who knows, just return raw response.
            return response.content
    
    def usage(self):
        return self._call_api('usage', method='GET')

    def sentiment(self, text, lang='en'):
        """
        Retrieve the sentiment for a single URl or block of text.
        """
        return self._call_api('score', text=text, lang=lang)

    def bulk_sentiment(self, items, lang='en'):
        """
        Bulk score multiple pieces of text (not urls!). Each text gets an ID
        associated with it and the response will be a mapping of IDs ->
        sentiment scores.
        """
        items_to_score = {}

        for idx, item in enumerate(items):
            items_to_score['text%d' % idx] = item

        return self._call_api('bulk-score', lang=lang, **items_to_score)

    def chunk(self, text, lang='en'):
        """
        Chunk a block of text into smaller, logically grouped phrases. The
        sentiment of each chunk is also returned.
        """
        return self._call_api('chunk', text=text, lang=lang)

    def categorize(self, text, niche, lang='en'):
        """
        Chunk the `text` into categories as defined by `niche` and determine
        the sentiment for each chunk.
        """
        return self._call_api('categorize', text=text, niche=niche, lang=lang)

    def add_sentiment_rule(self, text, sentiment, lang='en'):
        """
        Create a custom sentiment rule. `text` can be a word or phrase that
        you'd like to define the sentiment for. `sentiment` should be one of:
        1. pos
        2. neg
        3. neu
        """
        return self._call_api('sentiment-rules', text=text, sentiment=sentiment, lang=lang)

    def delete_sentiment_rule(self, rule_id, lang):
        return self._call_api('sentiment-rules', method='DELETE', rule_id=rule_id, lang=lang)

    def list_sentiment_rules(self, lang='en', page=0):
        return self._call_api('sentiment-rules', method='GET', lang=lang, page=page)

    def commit_sentiment_rules(self):
        return self._call_api('sentiment-rules', method='PUT')

    def topic_sentiment(self, text, topics, lang='en'):
        """
        Determine the sentiment of a block of text in the context of one or
        more topics. Multiple topics should be comma separated
        e.g. topic1,topic2,topic3
        """
        return self._call_api('topic', text=text, topics=topics, lang=lang)

    def pos_tags(self, text, lang='en'):
        """
        Get the part of speech tags for the given text. The full list of tags
        can be seen at https://www.repustate.com/docs/
        """
        return self._call_api('pos', text=text, lang=lang)

    def entities(self, text, lang='en'):
        """
        Extract any named entities from the text.
        """
        return self._call_api('entities', text=text, lang=lang)

    def clean_html(self, url):
        """
        Clean up a web page. It doesn't work well on home pages - it's designed
        for content pages.
        """
        return self._call_api('clean-html', method='GET', url=url)

    def detect_language(self, text):
        """
        Detect which language this text is in.
        """
        return self._call_api('detect-language', text=text)
    
    def add_filter(self, text, label):
        """
        Add a new label filter.
        """
        return self._call_api('filter-rules', text=text, label=label)
    
    def list_filters(self):
        """
        List existing filters.
        """
        return self._call_api('filter-rules', method='GET')
    
    def delete_filter(self, label):
        """
        Delete an existing filter.
        """
        return self._call_api('filter-rules', method='DELETE', label=label)
    
    def filter(self, text):
        """
        Run a block of text against all previously created filters.
        """
        return self._call_api('filter', text=text)
