# (c) Copyright 2011, Synapse Wireless, Inc.
"""Main file for Robot Arm Remote E10"""

import logging
import time
import os
import sys
import os.path
import uuid

import tornado.escape
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket

from snapconnect import snap
from apy import ioloop_scheduler

# Monkey Patch so APY is happy with stock Tornado
tornado.ioloop.IOLoop.timefunc = time.time

# Monkey Patch so Tornado does NOT autoreload the python modules in debug mode,
# because it messes up snapconnect, and we really just want JS/Template reloads.
import tornado.autoreload
tornado.autoreload.start = lambda x=None, y=None: (x,y)

from tornado.options import define, options

define("port", default=8888, help="run on the given port", type=int)

# Tornado options note: Set --logging=none to see all Tornado logging messages

log = logging.getLogger('robotServer')

# Local system settings for E10 environment
serial_conn = snap.SERIAL_TYPE_RS232
serial_port = '/dev/ttyS1'
snap_addr = None   # Intrinsic address on E10

# Local system overrides for development/test
try:
    from local_overrides import *
except:
    pass

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", MainHandler),
            (r"/wshub", WebSocketHandler),
        ]
        settings = dict(
            cookie_secret="43oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
            template_path=os.path.join(os.path.dirname(__file__), "templates"),
            static_path=os.path.join(os.path.dirname(__file__), "static"),
            xsrf_cookies=True,
            autoescape=None,
            debug=True,
        )
        tornado.web.Application.__init__(self, handlers, **settings)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html")

class WebSocketHandler(tornado.websocket.WebSocketHandler):
    ''' Send and receive websocket messages between server and browser(s).
        Messages TO the browser are encoded "rpc-like" as
            {'kind' : 'funcName', 'args' : params}
        Messages FROM the browser are translated to RPC calls and invoked on
        our SNAP Connect instance.
    '''
    waiters = set()    # Browser connections

    def allow_draft76(self):
        '''Allow older browser websocket versions'''
        return True
    
    def open(self):
        WebSocketHandler.waiters.add(self)

    def on_close(self):
        WebSocketHandler.waiters.remove(self)

    @classmethod
    def send_updates(cls, message):
        log.info("sending message to %d waiters", len(cls.waiters))
        for waiter in cls.waiters:
            try:
                waiter.write_message(message)
            except:
                log.error("Error sending message", exc_info=True)

    def on_message(self, message):
        '''Translate browser message into RPC function call (into local SnapCom object)'''
        log.info("got message %r", message)
        parsed = tornado.escape.json_decode(message)
        print "received: ", parsed
        try:
            func = getattr(snapCom, parsed['funcname'])
        except AttributeError:
            log.exception('Browser called unknown function: %s' % str(parsed))
        else:
            try:
                func(*parsed['args'])
            except:
                log.exception('Error calling function: %s' % str(parsed))
            

class SnapCom(object):

    SNAPCONNECT_POLL_INTERVAL = 5 # ms

    def __init__(self):
        self.snapRpcFuncs = {'buttonPress' : self.cycle_timer,
                             'lightReport' : self.light_report,
                            }

        cur_dir = os.path.dirname(__file__)

        # Create SNAP Connect instance. Note: we are using TornadoWeb's scheduler.
        self.snapconnect = snap.Snap(license_file = os.path.join(cur_dir, 'license.dat'),
                                     addr = snap_addr,
                                     scheduler=ioloop_scheduler.IOLoopScheduler.instance(),
                                     funcs = self.snapRpcFuncs
                                    )

        # Connect to local SNAP wireless network
        self.snapconnect.open_serial(serial_conn, serial_port)
        
        # Tell the Tornado scheduler to call SNAP Connect's internal poll function. Tornado already polls asyncore.
        tornado.ioloop.PeriodicCallback(self.snapconnect.poll_internals, self.SNAPCONNECT_POLL_INTERVAL).start()

        # Robot program state
        self.lampState = False
        self.timer_states = ('reset', 'start', 'stop')
        self.i_timer_state = 0
        self.DARK_THRESH = 3500       
                
    def toggle_lamp(self):
        '''Javascript-RPC received from Browser'''
        self.lampState = not self.lampState
        self.snapconnect.mcastRpc(1, 2, 'lampOn' if self.lampState else 'lampOff')
        
    def robot_control(self, grip, wrist, elbow, shoulder, base):
        '''Javascript-RPC received from Browser: move the robot arm!'''
        log.debug("issuing robot_control")
        self.snapconnect.mcastRpc(1, 2, 'robotPulse', grip, wrist, elbow, shoulder, base)
        
    def cycle_timer(self):
        '''RPC from remote SNAP device, typically button-press to start/stop/reset timer'''
        self.i_timer_state = (self.i_timer_state + 1) % len(self.timer_states)
        message = {'kind' : 'timer', 'args' : self.timer_states[self.i_timer_state]}
        WebSocketHandler.send_updates(message)
        
    def light_report(self, val):
        '''RPC received from remote SNAP Light Sensor'''
        # Forward to browser observers
        message = {'kind' : 'light', 'args' : val}
        WebSocketHandler.send_updates(message)

        # Darkness will stop a running timer
        if val > self.DARK_THRESH and self.i_timer_state == 1:
            self.cycle_timer()
            
        
def main():
    import logging
    logging.basicConfig(level=logging.DEBUG, format='%(asctime)-15s %(levelname)-8s %(name)-8s %(message)s')
    log.info("***** Begin Console Log *****")

    global webApp, snapCom
    
    tornado.options.parse_command_line()
    tornado.options.logging = logging.DEBUG
    webApp = Application()
    webApp.listen(options.port)
    
    snapCom = SnapCom()
    
    tornado.ioloop.IOLoop.instance().start()
    

if __name__ == '__main__':
    main()





    
    