"""
     Copyright (C) 2007-2009 Franco Lanza <nextime@nexlab.it>
     Copyright (C) 2007-2009 Sandro Aliano <ita595@hotmail.com>
     Copyright (C) 2007-2009 Ivan Bellia <skylive@skylive.it>

     Web site: http://www.astronomix.org/trac/Skylive-NG

     This file is part of Skylive-NG.

     Skylive-NG is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation, either version 3 of the License, or
     (at your option) any later version.

     Skylive-NG is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with Skylive-NG.  If not, see <http://www.gnu.org/licenses/>.
"""

import sys, string
import time, wx, os
import protocol, config
from utils import webutils
from utils import genutils as utils
from twisted.internet import reactor, ssl, task
from twisted.internet.protocol import ClientFactory
from twisted.protocols.basic import LineReceiver
from twisted.internet.protocol import ServerFactory, Protocol
import base64
import random

isConnected = False

# TODO Remove all globals!!


def is_jpeg(cont):
   try:
      if cont[0:4] == '\xff\xd8\xff\xe0' and cont[6:10] == 'JFIF':
         return True
   except:
      pass
   return False


def pageGet(url, **kwargs):
   proxy=config.get_usrProxy()
   if str(proxy['useProxy']).lower() in ['si', 'yes', 'true', True, 1]:
      p = int(proxy['proxyPort'])
      h = proxy['proxyUrl']
      if str(proxy['proxyAuth']).lower() in ['si', 'yes', 'true', True, 1]:
         headers = {'Proxy-authorization': 'Basic '+proxy['proxyAuthStr'].strip()}
         return webutils.getPage(url, proxy_host=h, proxy_port=p, headers=headers, **kwargs)
      return webutils.getPage(url, proxy_host=h, proxy_port=p, **kwargs)
   return webutils.getPage(url, **kwargs)

class HTTPDownloader:

   def __init__(self, parent, timing):
      self._parent = parent
      self.keepRunning = 1
      self.checkUpdate = True
      self.timing = timing
      self.tscope="1"
      self.tsdiff = 0
      self.tsdiffchanged=False
      self.last = time.time()
      self.liveurilist = False
      self.liveurilistsave = False 
      self.kojpeg = 0
      self.okjpeg = 0
      self.defuri = 'http://skylive.astronomix.org'
      self.lastWakeUp = False

   def setLiveUri(self, urilist=False):
      self.liveurilist=urilist
      self.liveurilistsave = False
      self.kojpeg = 0
      self.okjpeg = 0


   def setTiming(self, timing=5):
      self.timing = int(timing)

   def setTSDiff(self, tsdiff):
      self.tsdiff = tsdiff
      self.tsdiffchanged = True

   def oneUri(self, default=False):
      try:
         if self.liveurilist and len(self.liveurilist) > 0 and not default:
            uri = self.liveurilist[random.randint(0, len(self.liveurilist)-1)]
            if uri[-1] != '/':
               uri=uri+'/'
            return str(uri)
         else:
            return self.defuri+'/tele'+str(self.tscope)+'/'
      except:
         return self.defuri+'/tele'+str(self.tscope)+'/'

   def getError(self, error=None):
      #print 'Error in getPage: ', error
      pass

   def setLiveImage(self, result, *args):
      try:
      #if True:
         if args[0]:
            if not args[0] < self.lastWakeUp:
               self._parent.callInGui('on_setLiveImage', result)
      except:
         pass

   def setGuideImage(self, result, *args):
      try:
         if args[0]:
            if not args[0] < self.lastWakeUp:
               self._parent.callInGui('on_setGuideImage', result)
      except:
         pass

   def wakeUp(self):
      ret = {}
      self.lastWakeUp=time.time()
      try:
         if globals()['isConnected'] and self.liveurilist:
            self.getLiveImage(self.oneUri(), 'live640.jpg').addCallbacks(
                  self.setLiveImage, self.getError, [self.lastWakeUp]
               )
            self.getLiveImage(self.oneUri(), 'guida.jpg').addCallbacks(
                  self.setGuideImage, self.getError, [self.lastWakeUp]
               )
         elif globals()['isConnected'] and self.liveurilistsave:
            self.getLiveImage(self.oneUri(True), 'live640.jpg').addCallbacks(
                  self.setLiveImage, self.getError, [self.lastWakeUp]
               )
            self.getLiveImage(self.oneUri(True), 'guida.jpg').addCallbacks(
                  self.setGuideImage, self.getError, [self.lastWakeUp]
               )
      except:
         pass

   def on_GetOk(self, result):
      if is_jpeg(result):
         self.kojpeg = 0
         if self.okjpeg > 20 and not self.liveurilist and self.liveurilistsave:
            self.liveurilist =  self.liveurilistsave
            self.liveurilistsave = False
         self.okjpeg += 1
         return result
      else:
         self.okjpeg = 0
         if self.kojpeg >= 4:
            print 'I can\'t get images with your indicated URL, so, i will fallback to defuri.'
            # Uhmmm, it seem that our url won't work, so
            # switch back to the fallback default uri.
            self.liveurilistsave = self.liveurilist
            self.liveurilist = False
         else:
            self.kojpeg+=1

   def getLiveImage(self, baseuri, filename):
      ret = False
      addtime="?t="
      if self.tsdiffchanged:
         addtime="?tsok="
      addt=str(int(round((int(time.time())-self.tsdiff)/self.timing)*self.timing))
      addtime+=addt
      uri = baseuri+filename+addtime
      return pageGet(uri).addCallback(self.on_GetOk)


   def progressBar(self, data, currentLength, totalLength, uniqueid):
      #print 'ProgressBar', currentLength, totalLength, uniqueid
      percent = "%d/%dK" % (currentLength/1000,
                            totalLength/1000)
      percent += "- %i%%" % (
            (currentLength/totalLength)*100)
      self._parent.callInGui('on_downPopOpen', str(totalLength/1000), uniqueid)
      msg = "Downloading "+str(currentLength/1000)+" kb of "+str(totalLength/1000)+" kb"
      self._parent.callInGui('on_downPopUpdate', msg, (currentLength/totalLength)*100, uniqueid)
      # we need to slowing down a bit the download to let's the download
      # bar have enough time to refresh, only 5/100 of seconds for cicle...
      time.sleep(.05)
      #print percent

   def putFitsFile(self, result):
      fitdata=result[0]
      uniqueid=result[1]
      self._parent.callInGui('on_downPopClose', uniqueid)
      tmpfile = os.tempnam()+".zip"
      f = open(tmpfile,'wb')
      f.write(fitdata)
      f.close()
      self._parent.callInGui('on_FitsDownloaded', tmpfile)

   def getFitsFile(self, telescope):
      uri = self.oneUri(True)+'SkyFrame.Sky?time=%s' % str(time.time())
      return pageGet(uri, progress=self.progressBar, uniqueid=time.time()).addCallback(self.putFitsFile)

   def getLastPhoto(self):
      try:
         self.getLiveImage(self.oneUri(True), 'foto.jpg').addCallbacks(
               self.putLastPhoto, self.getError
            )
      except:
         pass

   def putLastPhoto(self, result):
      try:
         self._parent.callInGui('on_LastPhotoDone', result)
      except:
         pass

   def putFitsFileScript(self, result):
      fitdata=result[0]
      uniqueid=result[1]
      self._parent.callInGui('on_downPopClose', uniqueid)
      tmpfile = os.tempnam()+".zip"
      f = open(tmpfile,'wb')
      f.write(fitdata)
      f.close()
      try:
         self._parent.callInGui('script_Command', 'on_scriptFitsDownloaded', tmpfile)
      except:
         pass


   def getFitsScript(self, telescope):
      uri = self.oneUri(True)+'SkyFrame.Sky?time=%s' % str(time.time())
      return pageGet(uri, progress=self.progressBar, uniqueid=time.time()).addCallback(self.putFitsFileScript)

   def putDssFile(self, result, live=False):
      filedata=result[0]
      uniqueid=result[1]
      self._parent.callInGui('on_downPopClose', uniqueid)
      tmpfile = os.tempnam()+".zip"
      f = open(tmpfile,'wb')
      f.write(filedata)
      f.close()
      try:
         self._parent.callInGui('on_dssGifDownloaded', tmpfile, live)
      except:
         pass


   def getDssFile(self, ra, dec, x, y, live=False):
      uri ='http://archive.stsci.edu/cgi-bin/dss_search?v=poss2ukstu_ir&r='+ra+'&d='+dec+'&e=J2000&h='+x+'&w='
      uri +=y+'&f=gif&c=gz&fov=NONE&v3='
      #return webutils.getPage(str(uri), progress=self.progressBar, uniqueid=time.time()).addCallback(self.putDssFile, live)
      return pageGet(str(uri), progress=self.progressBar, uniqueid=time.time()).addCallback(self.putDssFile, live)

   def putUpgrade(self, result, nmd5):
      #import md5
      try:
         import hashlib 
         md5 = hashlib
         md5.new = hashlib.md5
      except:
         import md5
      try:
         filedata = result[0]
         uniqueid=result[1]
      except:
         filedata = 'NONE'
         uniqueid='unknown'
      self._parent.callInGui('on_downPopClose', uniqueid)
      m = md5.new(filedata)
      cmd5 = str(m.hexdigest())
      print nmd5, cmd5
      if  cmd5 == nmd5.replace("\r", "").replace("\n", ""):
         platform = utils.platform()
         if platform == 'Windows':
            ext = '.exe'
         elif platform  == 'Linux':
            ext = '.run'
         elif platform == 'Darwin':
            ext = '.dmg'
         tmpfile = os.tempnam()+ext
         f = open(tmpfile,'wb')
         f.write(filedata)
         f.close()
         self._parent.upgradeDownloadOk(tmpfile)
      else:
         self._parent.upgradeDownloadFailed()


   def getUpgrade(self, upuri, nmd5):
      return pageGet(str(upuri), progress=self.progressBar, uniqueid=time.time()).addCallback(self.putUpgrade, nmd5)


   def run(self): 
      if self.keepRunning:
         reactor.callLater(self.timing, self.run)
         if globals()['isConnected']:
            self.wakeUp()


   def on_updateFile(self, result):
      mver=int(protocol.VERSION)
      nver = mver
      upuri = ''
      nvermd5 = ''
      try:
         lines = result.split("\n")
         platform = utils.platform()
         for line in lines:
            vars = line.split("|")
            if vars[0] == platform:
               nver = int(vars[1])
               upuri = vars[2]
               nvermd5 = vars[3]
      except:
         pass
      self._parent.callInGui('on_versionCheck', mver, nver, upuri, nvermd5)

   def checkVersion(self, loop=True):
      if self.keepRunning and (self.checkUpdate or not loop):
         pageGet('http://skylive.astronomix.org/version.txt').addCallback(self.on_updateFile)
      if loop:
         reactor.callLater(3600, self.checkVersion)


class skyClientFactory(ClientFactory):

   protocol = protocol.clientOldProtocol
   autoreconnect = True

   def __init__(self, parent, transport, *args, **kwargs):
      self._parent = parent
      self._transport = transport
      self.checkTimer = task.LoopingCall(self.checkConnection)

   def clientConnectionFailed(self, connector, reason):
      try:
         self.checkTimer.stop()
      except:
         pass
      self._parent.connected = False
      globals()['isConnected'] = False
      #reactor.callLater(2, self._transport.run)
      reactor.callLater(0, self._parent.notifyGuiReconnect)

   def clientConnectionLost(self, connector, reason):
      try:
         self.checkTimer.stop()
      except:
         pass
      self._parent.connected = False
      globals()['isConnected'] = False
      if self.autoreconnect:
         reactor.callLater(0, self._parent.notifyGuiReconnect)
      else:
         reactor.callLater(0, self._parent.notifyGuiDisconnect)

   def put(self, msg):
      try:
         return self.protocolinstance.put(msg)
      except:
         pass

   def connectionMade(self):
      self._parent.connected = True
      self.checkTimer.start(2)
      globals()['isConnected'] = True


   def checkConnection(self):
      try:
         if int(time.time())-int(self.lastrec) > 120:
               # Client is disconnected?
               self.lastrec = time.time()
               self.clientConnectionLost(False, False)
      except:
         pass

   def buildProtocol(self, addr):
      p = self.protocol(self)
      self.protocolinstance = p
      return p

class SSLTransport:

   def __init__(self, parent, addr, port=10543):
      self._parent = parent
      self.addr = addr
      self.port = port
      self.sslContext = ssl.ClientContextFactory()
      self.clientFactory = skyClientFactory(self._parent, self)

   def run(self):
      self.proxyData = config.get_usrProxy()
      if str(self.proxyData['useProxy']).lower() in ['si', 'yes', 'true', True, 1]:

         #self.interface = reactor.connectSSL("127.0.0.1", 43335, self.clientFactory, self.sslContext)
         self.interface = reactor.connectTCP("127.0.0.1", 43335, self.clientFactory)
         #self.interface = reactor.connectSSL(self.addr, self.port, self.clientFactory, self.sslContext)
      else:
         self.interface = reactor.connectSSL(self.addr, self.port, self.clientFactory, self.sslContext)
      #self.interface = reactor.connectTCP(self.addr, 10444, self.clientFactory)

   def put(self, msg):
      return self.clientFactory.put(msg)

   def disconnect(self):
      self.clientFactory.autoreconnect = False
      self.interface.disconnect()
      self.interface = False
      #self.interface.transport.loseConnection()

   def restart(self):
      self.clientFactory.autoreconnect = True
      #self.interface.transport.loseConnection()
      self.interface.disconnect()
      self.interface = False


class skyClient:

   connected=False

   def __init__(self, parent, ip, transport='SSL'):
      self.ip = ip
      self.transport = transport
      self.parent = parent
      #self.gui = gui

   def changeTransport(self, transport):
      # XXX vediamo se la riconnessione
      # e' automatica
      # qui dovremmo prima aspettare la disconnessione in 
      # qualche modo...
      if self.connected:
         self.disconnect()
      self.transport = transport
      self.run()

   def stop(self):
      return self.disconnect()

   def disconnect(self):
      f=getattr(self, self.transport+'_disconnect')
      if f and callable(f):
         return f()

   def reconnect(self):
      f=getattr(self, self.transport+'_reconnect')
      if f and callable(f):
         return f()

   def run(self):
      if not self.connected:
         f=getattr(self, self.transport+'_run')
         if f and callable(f):
            return f()

   def transportPut(self, msg):
      try:
         return self.skytransport.put(msg)
      except:
         pass

   def SSL_run(self):
      self.skytransport = SSLTransport(self.parent, self.ip)
      self.skytransport.run()

   def SSL_disconnect(self):
      try:
         self.skytransport.disconnect()
      except:
         pass

   def SSL_reconnect(self):
      try:
         self.skytransport.restart()
      except:
         pass



class ProxyProto(LineReceiver):

   next=False
   storedata=""
   delimiter='\r\n'

   def __init__(self, factory):
      self.factory = factory

   def connectionMade(self):
      self.proxyData = config.get_usrProxy()
      proxyauth=False
      if str(self.proxyData['proxyAuth']).lower() in ['yes', 'si', 'true', True, '1', 1]:
         proxyauth = 'Proxy-authorization: Basic '+self.proxyData['proxyAuthStr'].strip()
      connect = "CONNECT skylive.astronomix.org:443 HTTP/1.0"
      self.sendLine(connect)
      if proxyauth:
         self.sendLine(proxyauth)
      useragent = "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)\r\n"
      self.sendLine(useragent)


   def lineReceived(self, line):
      if self.next:
         self.setRawMode()
         if len(str(self.storedata)) > 0:
            self.transport.write(self.storedata)
            self.storedata=""

      lines = line.split()
      if len(lines) > 1:
         if lines[1] == '200':
            self.next=True

   def rawDataReceived(self, data):
      self.factory.receivedData(data)

   def write(self, data):
      if self.next:
         if len(str(self.storedata)) > 0:
            self.transport.write(self.storedata)
            self.storedata=""
         self.transport.write(data)
      else:
         self.storedata = self.storedata+data

class ProxyFactory(ClientFactory):

   protocol = ProxyProto
   autoreconnect = True

   def __init__(self, parent, *args, **kwargs):
      self._parent = parent

   def clientConnectionFailed(self, connector, reason):
      self._parent.connected = False
      globals()['proxyisConnected'] = False
      reactor.callLater(0, self._parent.start)

   def clientConnectionLost(self, connector, reason):
      self._parent.connected = False
      globals()['proxyisConnected'] = False
      if self.autoreconnect:
         reactor.callLater(0, self._parent.start)

   def put(self, msg):
      try:
         return self.protocolinstance.write(msg)
      except:
         pass

   def receivedData(self, data):
      self._parent.sendServer(data)

   def connectionMade(self):
      self._parent.connected = True
      globals()['proxyisConnected'] = True

   def buildProtocol(self, addr):
      p = self.protocol(self)
      self.protocolinstance = p
      return p



class ProxyLocalServer(Protocol):

   def __init__(self, factory):
      self.factory = factory

   def connectionMade(self):
      if not self.factory.connected:
         self.factory.startProxy()
         self.factory.protocolinstance = self
      else:
         self.transport.loseConnection()

   def connectionLost(self, reason):
      self.factory.stopProxy()

   def dataReceived(self, data):
      self.factory.receivedData(data)

   def write(self, data):
      self.transport.write(data)


class ProxyLocalFactory(ServerFactory):

   protocol=ProxyLocalServer
   connected = False

   def __init__(self, parent, *args, **kwargs):
      self._parent = parent

   def startProxy(self):
      self.connected = True
      self._parent.start()

   def stopProxy(self):
      self.connected = False
      self._parent.stop()

   def receivedData(self, data):
      self._parent.sendProxy(data)

   def buildProtocol(self, addr):
      p = self.protocol(self)
      self.protocolinstance = p
      return p

   def put(self, msg):
      try:
         return self.protocolinstance.write(msg)
      except:
         pass


class ConnectProxy:

   proxy = False
   server = False

   def __init__(self):
      self.proxyfactory = ProxyFactory(self)
      self.serverfactory = ProxyLocalFactory(self)

   def start(self):
      self.proxyData = config.get_usrProxy()
      if self.proxyData['proxyUrl'][:8] == 'https://':
         self.sslContext = ssl.ClientContextFactory()
         self.proxy = reactor.connectSSL(self.proxyData['proxyUrl'][8:], int(self.proxyData['proxyPort']), 
                  self.proxyfactory, self.sslContext)
      else:
         self.proxy = reactor.connectTCP(self.proxyData['proxyUrl'], int(self.proxyData['proxyPort']), self.proxyfactory)
   
   def stop(self):
      if self.proxy:
         self.proxyfactory.autoreconnect=False
         self.proxy.disconnect()
         self.proxy = False

   def startServer(self):
      self.server  = reactor.listenTCP(43335, self.serverfactory)

   def stopServer(self):
      if self.server:
         self.stop()
         self.server.stopListening()

   def sendProxy(self, data):
      self.proxyfactory.put(data)

   def sendServer(self, data):
      self.serverfactory.put(data)

