from couchpotato.core.helpers.request import getParams
from couchpotato.core.logger import CPLog
from functools import wraps
from threading import Thread
from tornado.gen import coroutine
from tornado.web import RequestHandler, asynchronous
import json
import threading
import tornado
import traceback
import urllib

log = CPLog(__name__)


api = {}
api_locks = {}
api_nonblock = {}

api_docs = {}
api_docs_missing = []

def run_async(func):
    @wraps(func)
    def async_func(*args, **kwargs):
        func_hl = Thread(target = func, args = args, kwargs = kwargs)
        func_hl.start()
        return func_hl

    return async_func

# NonBlock API handler
class NonBlockHandler(RequestHandler):

    stopper = None

    @asynchronous
    def get(self, route, *args, **kwargs):
        route = route.strip('/')
        start, stop = api_nonblock[route]
        self.stopper = stop

        start(self.onNewMessage, last_id = self.get_argument('last_id', None))

    def onNewMessage(self, response):
        if self.request.connection.stream.closed():
            self.on_connection_close()
            return

        try:
            self.finish(response)
        except:
            log.debug('Failed doing nonblock request, probably already closed: %s', (traceback.format_exc()))
            try: self.finish({'success': False, 'error': 'Failed returning results'})
            except: pass

    def on_connection_close(self):

        if self.stopper:
            self.stopper(self.onNewMessage)

        self.stopper = None

def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
    api_nonblock[route] = func_tuple

    if docs:
        api_docs[route[4:] if route[0:4] == 'api.' else route] = docs
    else:
        api_docs_missing.append(route)

# Blocking API handler
class ApiHandler(RequestHandler):

    @coroutine
    def get(self, route, *args, **kwargs):
        route = route.strip('/')
        if not api.get(route):
            self.write('API call doesn\'t seem to exist')
            return

        api_locks[route].acquire()

        try:

            kwargs = {}
            for x in self.request.arguments:
                kwargs[x] = urllib.unquote(self.get_argument(x))

            # Split array arguments
            kwargs = getParams(kwargs)

            # Remove t random string
            try: del kwargs['t']
            except: pass

            # Add async callback handler
            @run_async
            def run_handler(callback):
                try:
                    result = api[route](**kwargs)
                    callback(result)
                except:
                    log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
                    callback({'success': False, 'error': 'Failed returning results'})
            result = yield tornado.gen.Task(run_handler)

            # Check JSONP callback
            jsonp_callback = self.get_argument('callback_func', default = None)

            if jsonp_callback:
                self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
            elif isinstance(result, tuple) and result[0] == 'redirect':
                self.redirect(result[1])
            else:
                self.write(result)

        except:
            log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
            self.write({'success': False, 'error': 'Failed returning results'})

        api_locks[route].release()

def addApiView(route, func, static = False, docs = None, **kwargs):

    if static: func(route)
    else:
        api[route] = func
        api_locks[route] = threading.Lock()

    if docs:
        api_docs[route[4:] if route[0:4] == 'api.' else route] = docs
    else:
        api_docs_missing.append(route)
