
from __future__ import unicode_literals
from datetime import datetime
from tornado.escape import json_encode, json_decode
import tornado.ioloop, tornado.web, tornado.websocket, tornado.template
import random, string, logging, uuid, subprocess, pykka, ast, logging, json, urllib, urllib2, mem, requests

logger = logging.getLogger(__name__)

class WebsocketHandler(tornado.websocket.WebSocketHandler):
    
    # initiate (not the actual object __init__, but run shortly after)
    def initialize(self, core, config):
        self.core = core  
        self.config = config  

    def check_origin(self, origin):
        return True
        
    def select_subprotocol(self, subprotocols):

        # select one of our subprotocol elements and return it. This confirms the connection has been accepted.
        protocols = mem.iris.digest_protocol( subprotocols )

        # if we've auto-generated some ids, the provided subprotocols was a string, so just return it right back
        # this allows a connection to be completed
        if protocols['generated']:
            return subprotocols[0]
            
        # otherwise, just return one of the supplied subprotocols
        else:
            return protocols['client_id']

    def open(self):
    
        # decode our connection protocol value (which is a payload of id/name from javascript)
        protocolElements = mem.iris.digest_protocol(self.request.headers.get('Sec-Websocket-Protocol', []))

        connection_id = protocolElements['connection_id']
        client_id = protocolElements['client_id']
        self.connection_id = connection_id
        username = protocolElements['username']
        created = datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')

        # get our IP
        # if it's local, then check for proxy origin
        ip = self.request.remote_ip
        if (ip == '127.0.0.1' and hasattr(self.request.headers,'X-Forwarded-For')):
            ip = self.request.headers['X-Forwarded-For']

        # construct our client object, and add to our list of connections
        client = {
            'client_id': client_id,
            'connection_id': connection_id,
            'username': username,
            'ip': ip,
            'created': created
        }

        # add to connections
        mem.iris.add_connection(connection_id=connection_id, connection=self, client=client)
 

    def on_message(self, message):
        
        message = json_decode(message)

        if 'data' in message:
            data = message['data']
        else:
            data = {}

        # Handle hard-coded connection_id in messages
        # Otherwise include the origin connection of this message
        if 'connection_id' not in data:
            data['connection_id'] = self.connection_id

        if 'request_id' in message:
            request_id = message['request_id']
        else:
            request_id = False

        # call the method, as specified in payload
        if 'method' in message:

            # make sure the method exists
            if hasattr(mem.iris, message['method']):
                getattr(mem.iris, message['method'])(data=data, callback=lambda response, error=False: self.handle_response(response=response, error=error, request_id=request_id))

            else:
                self.handle_response(error={'message': 'Method "'+message['method']+'" does not exist'}, request_id=request_id)
                return
        else:
            self.handle_response(error={'message': 'Method key missing from request'}, request_id=request_id)
            return


    def on_close(self):
        mem.iris.remove_connection(connection_id=self.connection_id)

    ##
    # Handle a response from our core
    # This is just our callback from an Async request
    ##
    def handle_response(self, *args, **kwargs):
        response = kwargs.get('response', None)
        error = kwargs.get('error', None)
        request_id = kwargs.get('request_id', False)

        # We've been given an error
        if error:
            data = error
            data['status'] = 0
            data['request_id'] = request_id

            # Log error with Sentry
            #mem.iris.raven_client.captureMessage(data.message)

        # We've been handed an AsyncHTTPClient callback. This is the case
        # when our request calls subsequent external requests (eg Spotify, Genius)
        elif isinstance(response, tornado.httpclient.HTTPResponse):
            data = {
                'status': response.code,
                'message': response.reason,
                'response': response.body,
                'request_id': request_id
            }

        # Just a regular json object, so not an external request
        else:
            data = response
            data['status'] = 1
            data['request_id'] = request_id
        
        # Respond to the original request
        mem.iris.send_message(connection_id=self.connection_id, data=data)





        
class HttpHandler(tornado.web.RequestHandler):

    def set_default_headers(self):
        self.set_header("Access-Control-Allow-Origin", "*")
        self.set_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, Client-Security-Token, Accept-Encoding")        

    def initialize(self, core, config):
        self.core = core
        self.config = config

    # Options request
    # This is a preflight request for CORS requests
    def options(self, slug=None):
        self.set_status(204)
        self.finish()
    
    @tornado.web.asynchronous
    def get(self, slug=None):

        # make sure the method exists
        if hasattr(mem.iris, slug):
            getattr(mem.iris, slug)(request=self.request, callback=lambda response, error=False: self.handle_response(response=response, error=error))

        else:
            self.handle_response(error={'message': "Method "+slug+" does not exist"})
            return

    @tornado.web.asynchronous
    def post(self, slug=None):

        # make sure the method exists
        if hasattr(mem.iris, slug):
            try:
                data = json.loads(self.request.body.decode('utf-8'))
                getattr(mem.iris, slug)(data=data, request=self.request, callback=lambda response, error=False: self.handle_response(response=response, error=error))

            except urllib2.HTTPError as e:
                self.handle_response(error={'message': "Invalid JSON payload"})
                return

        else:
            self.handle_response(error={'message': "Method "+slug+" does not exist"})
            return

    ##
    # Handle a response from our core
    # This is just our callback from an Async request
    ##
    def handle_response(self, *args, **kwargs):
        response = kwargs.get('response', None)
        error = kwargs.get('error', None)
        data = {}

        if error:
            data = error
            data['status'] = 0

            # Log error with Sentry
            #mem.iris.raven_client.captureMessage(data.message)


        # We've been handed an AsyncHTTPClient callback. This is the case
        # when our request calls subsequent external requests (eg Spotify, Genius).
        # We don't need to wrap non-HTTPResponse responses as these are dicts
        elif isinstance(response, tornado.httpclient.HTTPResponse):

            # Digest JSON responses into JSON
            content_type = response.headers.get('Content-Type')
            if content_type.startswith('application/json') or content_type.startswith('text/json'):
                body = json.loads(response.body)

            # Non-JSON so just copy as-is
            else:
                body = response.body

            data = {
                'status': response.code,
                'message': response.reason,
                'response': body
            }

        # Regular ol successful response
        else:
            data = response
            data['status'] = 1


        # Write our response
        self.write(data)
        self.finish()


        
