from twisted.web.resource import Resource
from twisted.web import http, server
from twisted.python import log
from twisted.internet import threads, reactor
from time import time
import json
import twisted_bluetooth as bluetooth
import traceback
from airi.camera.protocol import CameraFactory
from airi.settings import getSettings
from airi import report

settings = getSettings()

SCAN_TIMEOUT=60 # scan is started once each 60 seconds

class ConnectionTest():
  def __init__(self, req):
    self.request = req

  def cleanup(self, address):
    CameraFactory.disconnect(address)
    CameraFactory.removeListener(address, self)
    self.request.finish()

  def lostConnection(self, reason, failed, address):
    print "ConnectionTest.lostConnection", address
    self.request.write("<html><h1>ERROR:</h1>\n<pre>%s</pre></html>" % reason)
    self.request.setResponseCode(500, str(failed))
    self.request.finish()
    self.cleanup(address)

  def gotFrame(self, frame, address):
    print "ConnectionTest.gotFrame", address
    cli = CameraFactory.getCamera(address)
    self.request.write(json.dumps(cli))
    self.cleanup(address)

class DevicesManager(Resource):
  isLeaf = True

  def render_GET(self, request):
    request.setHeader('Content-Type', 'application/json')
    request.setHeader('Cache-Control', 'no-cache')
    request.setHeader('Connection', 'close')
    return json.dumps(list(CameraFactory.getCameras()))

class ConfigurationManager(Resource):
  isLeaf = True

  def render_GET(self, request):
    try:
      request.setHeader('Content-Type', 'application/json')
      request.setHeader('Cache-Control', 'no-cache')
      request.setHeader('Connection', 'close')
      if len(request.postpath) > 0 and len(request.postpath[0])>0:
        address=request.postpath[0].replace("_", ":")
        if len(request.args) == 0:
          return json.dumps(CameraFactory.getCamera(address))
        camera = settings.getCamera(address)
        for key in request.args:
            settings.setCameraSetting(address, key, request.args[key][-1])
        settings.save()
        cli = CameraFactory.getConnected(address)
        if cli:
          cli.updateSettings()
        return json.dumps(CameraFactory.getCamera(address))
      request.setResponseCode(500, "invalid address")
      return "Invalid address"

    except Exception, err:
      print err
      CameraFactory.connect(address, 1, "RFCOMM")
      CameraFactory.registerListener(address, ConnectionTest(request))
      return server.NOT_DONE_YET

  def render_POST(self, request):
    out = {}
    print str(request.args)
    for key, value in request.args.iteritems():
      if len(value) > 1:
        out[key] = True
      else:
        out[key]=value[0]
    settings.setCamera(out)
    print out
    settings.save()
    cli = CameraFactory.getConnected(out['address'])
    if cli:
      cli.updateSettings()
    return "saved"

class doConfiguration(Resource):
  isLeaf = True

  def render_POST(self, request):
    address = request.args["address"][0]
    option = request.args["option"][0]
    value = request.args["value"][0]
    if value.lower() in ["true", "false"]:
	value = value.lower()=="true"
    log.msg("doConfiguration.POST(%s, %s, %s)" % (address, option, value))
    try:
      cli = CameraFactory.getConnected(address)
      if cli:
        cli.client.set(option, value)
      return "updated"
    except Exception, err:
      return "failed: %s" % err

class ConnectionManager(Resource):
  isLeaf = True

  def render_all(self, request):
    out = {}
    for addr, cli in CameraFactory.getConnected().iteritems():
      out[addr] = CameraFactory.getCamera(addr)
    return out

  def render_GET(self, request):
    try:
      request.setHeader('Content-Type', 'application/json')
      request.setHeader('Cache-Control', 'no-cache')
      request.setHeader('Connection', 'close')
      if len(request.postpath) > 0 and len(request.postpath[0])>0:
        address=request.postpath[0].replace("_", ":")
        cli = CameraFactory.getCamera(address, silent=True)
        if cli or not request.args.get("test", None):
          return json.dumps(CameraFactory.getCamera(address))
        CameraFactory.connect(address, 1, "RFCOMM")
        CameraFactory.registerListener(address, ConnectionTest(request))
        return server.NOT_DONE_YET
      else:
        return json.dumps(self.render_all(request))
    except Exception, err:
      request.setHeader('Content-Type', 'text/html')
      request.setHeader('Cache-Control', 'no-cache')
      request.setHeader('Connection', 'close')
      request.setResponseCode(500, str(err))
      return "<html><h1>ERROR:</h1>\n<pre>%s</pre></html>" % (traceback.format_exc())

class ScanManager(Resource):
  isLeaf = True
  lastscan = None
  scancache = None
  scanning = None
  waiting = []

  def do_work(self):
    error = None
    try:
      log.msg("Doing scan")
      cache = bluetooth.discover_devices(lookup_names=True)
      log.msg(cache)
      self.scancache = [{
          'address':x[0], 
          'name':x[1], 
          'state': CameraFactory.isConnected(x[0])}
          for x in cache]
      log.msg(self.scancache)
      remove = []
      print self.waiting
      for request in self.waiting:
        request.setHeader('Content-Type', 'application/json')
        request.setHeader('Cache-Control', 'no-cache')
        request.setHeader('Connection', 'close')
        request.write(json.dumps(self.scancache))
        request.finish()
      if len(self.waiting)==0:
        return server.NOT_DONE_YET
    except Exception, err:
      log.err(err)
      error = err

    try:
      error = "<html><h1>ERROR:</h1>\n<pre>%s</pre></html>" % traceback.format_exc()
      for request in self.waiting:
        if request.finished:
          continue
        request.setHeader('Content-Type', 'text/html')
        request.setHeader('Cache-Control', 'no-cache')
        request.setHeader('Connection', 'close')
        request.setResponseCode(500, error)
        request.write("<html><h1>ERROR:</h1>\n")
        request.write("<pre>%s</pre></html>" % traceback.format_exc())
        request.finish()
    except Exception, err:
      log.err(err)
    self.waiting = []
    self.lastscan = None
    log.msg("Scan thread done")
    return server.NOT_DONE_YET


  def lost_connection(self, err, request):
    log.msg("lost_connection(%s)" % str(request))
    self.waiting.remove(request)
  
  def render_GET(self, request):
    if self.lastscan and time()-self.lastscan < SCAN_TIMEOUT:
      if not self.scancache:
        self.waiting.append(request)
        request.notifyFinish().addErrback(self.lost_connection, request)
        log.msg("%s" % self.waiting)
        log.msg("Tried to start a parallel scan, we wait")
        return server.NOT_DONE_YET

      request.setHeader('Content-Type', 'application/json')
      request.setHeader('Cache-Control', 'no-cache')
      request.setHeader('Connection', 'close')
      request.write(json.dumps(self.scancache))
      request.finish()
      log.msg("Scan return from cache")
      return server.NOT_DONE_YET

    self.lastscan = time()
    self.waiting.append(request)
    request.notifyFinish().addErrback(self.lost_connection, request)
    log.msg("%s" % self.waiting)
    log.msg("Starting scan thread")
    threads.deferToThread(self.do_work)
    return server.NOT_DONE_YET

class DisconnectManager(Resource):
  isLeaf = True

  def render_GET(self, request):
    try:
      request.setHeader('Content-Type', 'application/json')
      request.setHeader('Cache-Control', 'no-cache')
      request.setHeader('Connection', 'close')
      if len(request.postpath) > 0 and len(request.postpath[0])>0:
        address=request.postpath[0].replace("_", ":")
        if not CameraFactory.isConnected(address):
          return json.dumps({"result": "Not Connected"})
        CameraFactory.disconnect(address)
        return json.dumps({"result": "Disconnected"})
      return json.dumps({"result": "No Address"})
    except Exception, err:
      request.setHeader('Content-Type', 'text/html')
      request.setHeader('Cache-Control', 'no-cache')
      request.setHeader('Connection', 'close')
      request.setResponseCode(500, str(err))
      return "<html><h1>ERROR:</h1>\n<pre>%s</pre></html>" % (traceback.format_exc())

class StateManager(Resource):
  isLeaf = True

  def render_POST(self, request):
    try:
      request.setHeader('Content-Type', 'application/json')
      request.setHeader('Cache-Control', 'no-cache')
      request.setHeader('Connection', 'close')
      address = request.args["address"][0]
      if CameraFactory.isConnected(address):
        CameraFactory.disconnect(address)
        return json.dumps({"result": "disconnected"})
      else:
        CameraFactory.connect(address)
        return json.dumps({"result": "connecting"})
    except Exception, err:
      request.setHeader('Content-Type', 'text/html')
      request.setHeader('Cache-Control', 'no-cache')
      request.setHeader('Connection', 'close')
      request.setResponseCode(500, str(err))
      return "<html><h1>ERROR:</h1>\n<pre>%s</pre></html>" % (traceback.format_exc())

class UpdateManager(Resource):
  isLeaf = True
  clients = {}
  reference = None

  @report(category="UpdateManager")
  def __init__(self):
    UpdateManager.reference=self

  @report("UpdateManager")
  def timeout(self, connection, address):
    log.msg("UpdateManager.timeout (%s, %s)" % ( connection, address ))
    connection.write(json.dumps({"result": "timeout"}))
    connection.finish()
    if address in UpdateManager.reference.clients:
      if connection in UpdateManager.reference.clients[address]:
        UpdateManager.reference.clients[address].remove(connection)

  @classmethod
  @report(category="UpdateManager")
  def propagate(klass, address, args):
    if not klass.reference:
      return

    if not address in klass.reference.clients:
      return

    if "address" not in args:
      args["address"] = address

    for r in klass.reference.clients[address]:
      try:
        r.write(json.dumps(args))
        r.finish()
        r.delayed.cancel()
      except Exception, err:
        log.err(err)
    klass.reference.clients.pop(address)

  @report(category="UpdateManager")
  def lost_connection(self, err, address, request):
    log.msg("updateManager.lost_connection(%s, %s)" % (address, request))
    request.delayed.cancel()
    if address in self.clients:
      if request in self.clients[address]:
        self.clients[address].remove(request)

  @report(category="UpdateManager")
  def render_POST(self, request):
    address = request.args["address"][0]
    if address not in self.clients:
      self.clients[address] = []
    self.clients[address].append(request)
    request.delayed = reactor.callLater(60, self.timeout, connection=request, address=address)
#    request.delayed.addErrback(log.err)
    request.notifyFinish().addErrback(self.lost_connection, address=address, request=request)
    request.setHeader('Content-Type', 'application/json')
    request.setHeader('Cache-Control', 'no-cache')
    request.setHeader('Connection', 'close')
    return server.NOT_DONE_YET

class API_Root(Resource):
  isLeaf = True

  def render_GET(self, request):
    return "Invalid"

class API(Resource):
  isLeaf = False

  def __init__(self, *a, **kw):
    log.msg("API()")
    Resource.__init__(self, *a, **kw)
    self.putChild("", API_Root())
    self.putChild("scan", ScanManager())
    self.putChild("devices", DevicesManager())
    self.putChild("connected", ConnectionManager())
    self.putChild("configure", ConfigurationManager())
    self.putChild("doconfigure", doConfiguration())
    self.putChild("disconnect", DisconnectManager())
    self.putChild("updates", UpdateManager())
    self.putChild("switchstate", StateManager())

if __name__=='__main__':
  from twisted.application.service import Application
  from twisted.application.internet import TCPServer
  from twisted.web.server import Site
  from twisted.internet import reactor
  import sys
  log.startLogging(sys.stdout)

  root = Resource()
  root.putChild("api", API())
  reactor.listenTCP(8800, Site(root), interface="0.0.0.0")
  reactor.run()#!/usr/bin/env python
