import wx
import sys

from threading import Thread
from string import upper

from traceback import print_exc
from cStringIO import StringIO

from Utility.helpers import getSocket
from Utility.constants import * #IGNORE:W0611


################################################################
#
# Class: WebDialog
#
# Let the user specify settings for the webservice
#
################################################################
class WebDialog(wx.Dialog):
    def __init__(self, parent):
        self.utility = parent.utility
        
        title = self.utility.lang.get('webinterfaceservice')

        pre = wx.PreDialog()
        pre.Create(parent, -1, title)
        self.this = pre.this
        self.parent = parent
        self.window = self.utility.window
        
        self.utility.webserver.webdlg = self
        
        WebRead = self.utility.webconfig.Read
        
        self.warnlowport = [False, WebRead('webport')]

#        # Change old config value
#        oldval = WebRead('webIP')
#        if (oldval == "Automatics") or (oldval == "Automatic"):
#            self.utility.webconfig.Write('webIP', "")
#            self.utility.webconfig.Flush()
#            ip_choice = ""
#        elif (oldval == "Loop Back"):
#            self.utility.webconfig.Write('webIP', "127.0.0.1")
#            self.utility.webconfig.Flush()
#            
#        newval = WebRead('webIP')
        newval = self.utility.webserver.getIP()
        if newval == "":
            default_ip = self.utility.lang.get('automatic')
        elif newval == "127.0.0.1":
            default_ip = self.utility.lang.get('loopback')
        else:
            default_ip = newval
        
        outerbox = wx.BoxSizer(wx.VERTICAL)
        
        outerbox.Add(wx.StaticText(self, -1, self.utility.lang.get('webinterfacetitle')), 0, wx.ALIGN_CENTER|wx.ALL, 10)

        ip_choices = [self.utility.lang.get('automatic'), self.utility.lang.get('loopback')]

        ipandport_box = wx.BoxSizer(wx.HORIZONTAL)
        ipandport_box.Add(wx.StaticText(self, -1, self.utility.lang.get('webip')), 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
        self.iptext = wx.ComboBox(self, -1, default_ip, wx.Point(-1, -1), wx.Size(120, -1), ip_choices, wx.CB_DROPDOWN|wx.CB_READONLY)
        ipandport_box.Add(self.iptext, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
       
        ipandport_box.Add(wx.StaticText(self, -1, self.utility.lang.get('webport')), 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
        self.porttext = self.utility.makeNumCtrl(self, WebRead('webport'), max = 65536)
        ipandport_box.Add(self.porttext, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)

        outerbox.Add(ipandport_box, 0, wx.EXPAND|wx.ALL, 5)

        uniquekey_box = wx.BoxSizer(wx.HORIZONTAL)
        
        uniquekey_box.Add(wx.StaticText(self, -1, self.utility.lang.get('uniquekey')), 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
        self.keytext = wx.TextCtrl(self, -1, WebRead('webID'), wx.Point(-1, -1), wx.Size(165, -1))
        uniquekey_box.Add(self.keytext, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
        
        outerbox.Add(uniquekey_box, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 5)

        self.permissions = [ ['webquery', 'allow_query'], 
                             ['webadd', 'allow_add'], 
                             ['webdelete', 'allow_delete'], 
                             ['webqueue', 'allow_queue'], 
                             ['webstop', 'allow_stop'], 
                             ['webresume', 'allow_resume'], 
                             ['webpause', 'allow_pause'], 
                             ['webclearallcompleted', 'allow_clearcompleted'], 
                             ['priority', 'allow_setprio'], 
                             ['webgetparam', 'allow_getparam'], 
                             ['websetparam', 'allow_setparam' ] ]

        self.perm_checkbox = wx.CheckListBox(self, -1, size = wx.Size(100, 120), style = wx.LB_SINGLE)
        perm_text = [self.utility.lang.get(item[0]) for item in self.permissions]
        self.perm_checkbox.Set(perm_text)
         
        for i in range(0, len(self.permissions)):
            param = self.permissions[i][1]
            checked = WebRead(param, "boolean")
            if checked:
                self.perm_checkbox.Check(i)
        
        outerbox.Add(wx.StaticText(self, -1, self.utility.lang.get('commandpermission')), 0, wx.ALL, 5)
        outerbox.Add(self.perm_checkbox, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 5)
  
        self.webautostart = wx.CheckBox(self, -1, self.utility.lang.get('webautostart'))
        
        outerbox.Add(self.webautostart, 0, wx.EXPAND|wx.ALL, 5)

        #CheckBox Set Value from Config
        #####################################
        self.webautostart.SetValue(WebRead('webautostart', "boolean"))

        self.actionbtn = wx.Button(self, -1, "")

        self.utility.webserver.updateLabels()

        applybtn  = wx.Button(self, wx.NewId(), self.utility.lang.get('apply'))
        self.Bind(wx.EVT_BUTTON, self.onApply, applybtn)

        okbtn  = wx.Button(self, wx.NewId(), self.utility.lang.get('ok'))
        self.Bind(wx.EVT_BUTTON, self.onOK, okbtn)

        cancelbtn = wx.Button(self, wx.ID_CANCEL, self.utility.lang.get('cancel'))
        self.Bind(wx.EVT_BUTTON, self.onClose, cancelbtn)
        
        self.Bind(wx.EVT_CLOSE, self.onClose)

        self.Bind(wx.EVT_BUTTON, self.OnAction, self.actionbtn)

        button_box = wx.BoxSizer(wx.HORIZONTAL)
        button_box.Add(applybtn, 0, wx.ALL, 5)
        button_box.Add(okbtn, 0, wx.ALL, 5)
        button_box.Add(cancelbtn, 0, wx.ALL, 5)

        outerbox.Add(self.actionbtn, 0, wx.ALIGN_CENTER|wx.ALL, 5)

        outerbox.Add(button_box, 0, wx.ALIGN_CENTER)

        self.SetAutoLayout(True)
        self.SetSizer(outerbox)
        self.Fit()

    def onOK(self, event = None):
        self.utility.webserver.webdlg = None
        if self.onApply():
            self.EndModal(wx.ID_OK)
            
    def onClose(self, event = None):
        self.utility.webserver.webdlg = None
        if event is not None:
            event.Skip()
        
    def onApply(self, event = None):
        self.saveParams()
        return True
        
    def saveParams(self):
        PORT = int(self.porttext.GetValue())

        if ((not self.warnlowport[0]
             or self.warnlowport[1] != str(PORT))
            and PORT < 1024):
            dlg = wx.MessageDialog(None, self.utility.lang.get('warningportunder1024') + "\n(" + str(PORT) + ")", self.utility.lang.get('warning'), wx.YES_NO|wx.ICON_INFORMATION)
            if dlg.ShowModal() == wx.ID_NO:
                dlg.Destroy()
                return False
            else:
                self.warnlowport[0] = True
            self.warnlowport[1] = str(PORT)
            dlg.Destroy()

        # Re-assign web parameters
        self.utility.webconfig.Write('webIP', self.iptext.GetStringSelection())
        self.utility.webconfig.Write('webport', str(self.porttext.GetValue()))
        self.utility.webconfig.Write('webID', self.keytext.GetValue())

        for i in range(0, self.perm_checkbox.GetCount()):
            param = self.permissions[i][1]
            checked = self.perm_checkbox.IsChecked(i)
            self.utility.webconfig.Write(param, checked, "boolean")
       
        if self.webautostart.GetValue():
            self.utility.webconfig.Write('webautostart', "1")
        else:
            self.utility.webconfig.Write('webautostart', "0")

        #######################################
        # Record New Config to webservice.conf
        #######################################
        self.utility.webconfig.Flush()
        
        return True
        
    def OnAction(self, event = None):
        if not self.utility.webserver.active:
            if self.saveParams():
                self.startWebService()
        else:
            self.stopWebService()

    def startWebService(self):
        ######################################
        # Start Web Interface Service
        ######################################
        self.utility.webserver.start()

    def stopWebService(self):
        # Stop Web Service
        self.utility.webserver.stop()
        
    def updateLabels(self):
        active = self.utility.webserver.active
        
        if active:
            label = self.utility.lang.get('stopservice')
        else:
            label = self.utility.lang.get('startservice')
        self.actionbtn.SetLabel(label)
        self.porttext.Enable(not active)
        self.keytext.Enable(not active)
        self.iptext.Enable(not active)


################################################################
#
# Class: WebListener
#
# Listens for webservice commands 
#
################################################################
class WebListener:
    def __init__(self, utility, webdlg = None):
        self.s = None
        self.utility = utility
        
        self.utility.webserver = self
        
        self.webdlg = webdlg
        
        self.active = False
        
        self.port = None
        self.ip = None
        
        self.client = WebClient(self.utility)

    def getIP(self):
        # Change old config value
        oldval = self.utility.webconfig.Read('webIP')
        if (oldval == "Automatics") or (oldval == "Automatic"):
            self.utility.webconfig.Write('webIP', "")
            self.utility.webconfig.Flush()
        elif (oldval == "Loop Back"):
            self.utility.webconfig.Write('webIP', "127.0.0.1")
            self.utility.webconfig.Flush()
#        if self.utility.webconfig.Read('webIP') == "Automatic":
#            IP = ""
#        else: #LoopBack
#            IP = "127.0.0.1"
#        return IP
        return self.utility.webconfig.Read('webIP')
    
    def getPort(self):
        try:
            PORT = self.utility.webconfig.Read('webport', "int")
        except:
            PORT = 56667
        return PORT
        
    def start(self):
        # Already running
        if self.active:
            return
        
        self.ip = self.getIP()   # Symbolic name meaning the local host
        self.port = self.getPort() # Arbitrary non-privileged port
        
        self.active = True
        self.updateLabels()
        
        webservice = Thread(target = self.startThread)
        webservice.setDaemon(True)
        webservice.start()
        
    def stop(self):
        # Not running, no need to stop
        if not self.active:
            return
        
        self.client.sendCmd("CLOSE|")
        
        self.port = None
        self.ip = None
        
        self.active = False
        self.updateLabels()
        
    def updateLabels(self):       
        self.utility.actions[ACTION_WEBSERVICE].updateButton()

        if self.webdlg is not None:
            self.webdlg.updateLabels()
        
    def startThread(self):
        self.s = getSocket(self.ip, self.port, "server")

        if self.s is None:
            #Display Dialog Can't open scoket
            dlg = wx.MessageDialog(None, self.utility.lang.get('cantopensocket') , self.utility.lang.get('socketerror'), wx.OK|wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()

            self.active = False
            self.updateLabels()
            return
        WebServiceCmd(self).go()


################################################################
#
# Class: WebClient
#
# Used to sent brief commands to the webservice
# (i.e.: use to send the shutdown command)
#
################################################################
class WebClient:
    def __init__(self, utility):
        self.utility = utility
        self.webserver = self.utility.webserver

    def sendCmd(self, command):
        # Web service isn't even active -- don't bother sending a message
        if not self.webserver.active:
            return False
                
        HOST = self.webserver.ip          # The remote host
        PORT = self.webserver.port        # The same port as used by the server
        s = getSocket(HOST, PORT)
        if s is None:
            dlg = wx.MessageDialog(None, self.utility.lang.get('cantconnectabcwebinterface') , self.utility.lang.get('socketerror'), wx.OK|wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()
            return False
            
        # if request is not close connection request
        # so it's torrent request copy .torrent
        # in backup torrent folder
        ##############################################
        mesg = "ID|" + self.utility.webconfig.Read('webID') + "\n" + command
        
        s.send(mesg)
        s.close()
        return True


################################################################
#
# Class: WebServiceCmd
#
# Processes the actual webservice commands
#
################################################################
class WebServiceCmd:
    def __init__(self, parent):
        self.parent = parent
        self.utility = self.parent.utility
        self.frame  = self.utility.frame
        
    def separate(self, info, splitchar = ","):
        try:
            separated = info.split(splitchar)
        except:
            self.conn.send("Feedback\nError=Bad Arguments")
            self.conn.close()
            separated = None
        return separated
        
    def getMappings(self, separated, splitchar = "="):
        mappings = {}
        
        for item in separated:
            try:
                pair = item.split(splitchar)
                mappings[pair[0]] = pair[1]
            except:
                self.conn.send("Feedback\nError=Bad Arguments")
                self.conn.close()
                mappings = None
                break
                
        return mappings

    def getTorrents(self, info = "", infohash_list = None):
        torrents = None
        
        if infohash_list is None:
            infohash_list = self.separate(info)
            if infohash_list is None:
                return torrents
        
        torrents = []
            
        for infohash in infohash_list:
            torrent = self.utility.queue.getABCTorrent(info_hash = infohash)
            
            if torrent is None:
                self.conn.send("Feedback\nError=No torrents match with this info hash: " + infohash + "\n")
                torrents = None
                break
            else:
                torrents.append(torrent)
                
        return torrents            

    def cmdSetParam(self, info):
        conn = self.conn
        
        if not self.utility.webconfig.Read('allow_setparam', "boolean"):
            conn.send("Feedback\nError=SETPARAM,Permission denied")
            return

        separated = self.separate(info, "|")
        if separated is None:
            return
            
        mappings = self.getMappings(separated)
        if mappings is None:
            return
            
        for param in mappings:
            self.utility.config.Write(param, mappings[param])
        self.utility.config.Flush()
        conn.send("Feedback\nOK")

    def cmdGetParam(self, info):
        conn = self.conn
        
        if not self.utility.webconfig.Read('allow_getparam', "boolean"):
            conn.send("Feedback\nError=GETPARAM,Permission denied")
            return

        separated = self.separate(info)
        if separated is None:
            return

        retmsg = ""
        Read = self.utility.config.Read
        for param in separated:
            value = Read(param)
            retmsg += value + "|"
        retmsg += "\n"
        conn.send("Feedback\n" + str(retmsg))
        
    def cmdClose(self):
        conn = self.conn
        
        self.utility.webserver.active = False
        self.utility.webserver.updateLabels()
        
        conn.close()
        self.parent.s.close()
        
#        sys.stderr.write("\nDone shutting down webservice")
               
    def cmdQuery(self, info = ""):
        conn = self.conn
        
        if not self.utility.webconfig.Read('allow_query', "boolean"):
            conn.send("Feedback\nError=QUERY,Permission denied")
            conn.close()
            return

        if info == "":
            self.QueryFields()
        else:
            fields = self.separate(info, ",")
            if fields is None:
                return

            self.QueryFields(fields)

    def QueryFields(self, fieldlist = None):
        conn = self.conn
        
        maxid = self.utility.list.columns.maxid

        # Default to returning all fields
        if fieldlist is None:
            fieldlist = range(4, maxid)
            
        oldfields    = { "filename"        : COL_TITLE, 
                         "progress"        : COL_PROGRESS, 
                         "btstatus"        : COL_BTSTATUS, 
                         "eta"             : COL_ETA, 
                         "dlspeed"         : COL_DLSPEED, 
                         "ulspeed"         : COL_ULSPEED, 
                         "ratio"           : COL_RATIO, 
                         "peers"           : COL_PEERS, 
                         "seeds"           : COL_SEEDS, 
                         "copies"          : COL_COPIES, 
                         "dlsize"          : COL_DLSIZE, 
                         "ulsize"          : COL_ULSIZE, 
                         "peeravgprogress" : COL_PEERPROGRESS, 
                         "totalspeed"      : COL_TOTALSPEED, 
                         "totalsize"       : COL_SIZE, 
                         "priority"        : COL_PRIO }
        
        fieldids = []
        
        retmsg = ""
        for req_field in fieldlist:
            try:
                # A field number was specified
                fieldid = int(req_field)
                if fieldid >= 4 and fieldid < maxid:
                    fieldids.append(fieldid)
                else:
                    conn.send("Feedback\nError=Invalid field ID (must be between 4 and " + str(maxid) + ") = " + req_field)
                    conn.close()
                    return                    
            except:
                # Old format -- a field name was specified
                if req_field in oldfields:
                    fieldid = oldfields[req_field]
                    fieldids.append(fieldid)
                else:
                    # Can't identify the field
                    conn.send("Feedback\nError=Unknown field name = " + req_field)
                    conn.close()
                    return

            retmsg += self.utility.lang.get("column" + str(fieldid) + "_text") + "|"

        retmsg += "Info Hash\n"

        for ABCTorrentTemp in self.utility.torrents["all"]:
            retmsg += ABCTorrentTemp.getInfo(fieldlist = fieldids)
        
        conn.send(retmsg)
        conn.close()
                
    def cmdAdd(self, info):
        conn = self.conn
        
        if not self.utility.webconfig.Read('allow_add', "boolean"):
            conn.send("Feedback\nError=ADD,Permission denied")
            return

        # What do we do if we don't have a default download location specified
        # and we call this from the webservice?
        ####################################################
        success, msg, ABCTorrentTemp = self.utility.queue.addtorrents.AddTorrentURL(info, "web")
        if success:
            retmsg = "OK"
        else:
            retmsg = "Error="+msg
        conn.send("Feedback\n"+retmsg)
            
    def cmdDelete(self, info):
        conn = self.conn
        
        if upper(info) == "COMPLETED":
            if not self.utility.webconfig.Read('allow_clearcompleted', "boolean"):
                conn.send("Feedback\nError=CLEARCOMPLETED,Permission denied")
                return
                
            self.utility.actions[ACTION_CLEARCOMPLETED].action()
            conn.send("Feedback\nOK")
        else:
            if not self.utility.webconfig.Read('allow_delete', "boolean") != "1":
                conn.send("Feedback\nError=DELETE,Permission denied")
                return
            
            torrents = self.getTorrents(info)
            if torrents is None:
                return

            self.utility.actionhandler.procREMOVE(torrents)
            conn.send("Feedback\nOK")
                
    def cmdResume(self, info = "ALL"):
        conn = self.conn

        if not self.utility.webconfig.Read('allow_resume', "boolean"):
            conn.send("Feedback\nError=RESUME,Permission denied")
            return
        
        if upper(info) == "ALL":
            self.utility.actions[ACTION_UNSTOPALL].action()
            conn.send("Feedback\nOK")   
        else:
            torrents = self.getTorrents(info)
            if torrents is None:
                return

            self.utility.actionhandler.procRESUME(torrents)
            conn.send("Feedback\nOK")
                               
    def cmdStop(self, info = "ALL"):
        conn = self.conn
        
        if not self.utility.webconfig.Read('allow_stop', "boolean"):
            conn.send("Feedback\nError=STOP,Permission denied")
            return
        
        if upper(info) == "ALL":
            self.utility.actions[ACTION_STOPALL].action()
            conn.send("Feedback\nOK")
        else:
            torrents = self.getTorrents(info)
            if torrents is None:
                return

            self.utility.actionhandler.procSTOP(torrents)
            conn.send("Feedback\nOK")

    def cmdPause(self, info, release = False):
        conn = self.conn
        
        if not self.utility.webconfig.Read('allow_pause', "boolean"):
            conn.send("Feedback\nError=PAUSE,Permission denied")
            conn.close()
            return
            
        if upper(info) == "ALL":
            torrents = self.utility.torrents["active"].keys()
        else:
            torrents = self.getTorrents(info)
            if torrents is None:
                return
                       
        self.utility.actionhandler.procPAUSE(torrents, release = release)
        conn.send("Feedback\nOK")
                        
    def cmdQueue(self, info = "ALL"):
        conn = self.conn
        
        if not self.utility.webconfig.Read('allow_queue', "boolean"):
            conn.send("Feedback\nError=QUEUE,Permission denied")
            return

        if upper(info) == "ALL":
            torrents = self.utility.torrents["all"]
        else:
            torrents = self.getTorrents(info)
            if torrents is None:
                return

        self.utility.actionhandler.procQUEUE(torrents)
        conn.send("Feedback\nOK")
        
    def cmdPriority(self, info):
        conn = self.conn
        
        if not self.utility.webconfig.Read('allow_setprio', "boolean"):
            conn.send("Feedback\nError=PRIORITY,Permission denied")
            return
            
        separated = self.separate(info, "|")
        if separated is None:
            return
            
        mappings = self.getMappings(separated, ",")
        if mappings is None:
            return
        
        for infohash in mappings:
            value = mappings[infohash]
            error = False
            try:
                prio = int(value)
                if (prio < 0) or (prio > 4):
                    error = True
            except:
                error = True
            if error:
                conn.send("Feedback\nError=Priority must be a number from 0-4")
                conn.close()
                return

        hashlist = [infohash for infohash in mappings]
        torrents = self.getTorrents(infohash_list = hashlist)
        if torrents is None:
            return
            
        for torrent in torrents:
            torrent.changePriority(prio)
            
        conn.send("Feedback\nOK")
        
    def go(self):
        while 1:
            conn, addr = self.parent.s.accept()
            self.conn = conn
            try:
                try:
                    data = conn.recv(5048)
                except:
                    try:
                        conn.close()
                    except:
                        pass
                    continue
                
                try:
                    idline, cmdline = data.split("\n")
                except:
                    conn.send("Feedback\nError=You need unique ID to command ABC!")
                    conn.close()
                    continue
    
                try:
                    idtag   = idline.split("|")
                    idkey   = idtag[0]
                    idvalue = idtag[1]
                except:
                    conn.send("Feedback\nError=You need unique ID to command ABC!")
                    conn.close()
                    continue

                if idkey != "ID":
                    conn.send("Feedback\nError=You need unique ID to command ABC!")
                    conn.close()
                    continue
                else:
                    if idvalue != self.utility.webconfig.Read('webID'):
                        conn.send("Feedback\nError=Incorrect unique ID")
                        conn.close()
                        continue
                
                try:                
                    cmd, info = cmdline.split("|")
                except:
                    #Bad Command
                    conn.send("Feedback\nError=Command should end with |")
                    conn.close()
                    continue
                
                sys.stdout.write('Web Request Recieved.\n' + data + '\n')
                
                # Convert the command to upper case
                cmd = upper(cmd)
                
                if cmd == "CLOSE" or self.utility.abcquitting:
                    self.cmdClose()
                    return
                
#                elif cmd == "KEEPALIVE":
#                    # Don't actually do anything
#                    pass
                
                elif cmd == "QUERY":
                    self.cmdQuery(info)
                                       
                elif cmd == "ADD":
                    self.cmdAdd(info)
                        
                elif cmd == "DELETE":
                    self.cmdDelete(info)
            
                elif cmd == "RESUME":
                    self.cmdResume(info)
    
                elif cmd == "PAUSE":
                    self.cmdPause(info)

                elif cmd == "UNPAUSE":
                    self.cmdPause(info, release = True)
    
                elif cmd == "STOP":
                    self.cmdStop(info)
    
                elif cmd == "QUEUE":
                    self.cmdQueue(info)
                    
                elif cmd == "PRIORITY":
                    self.cmdPriority(info)

                elif cmd == "SETPARAM":
                    self.cmdSetParam(info)
                    
                elif cmd == "GETPARAM":
                    self.cmdGetParam(info)
                    
                elif cmd == "GETSTRING":
                    conn.send("String\n" + self.utility.lang.get(info))

                elif cmd == "VERSION":
                    conn.send("Version\n" + self.utility.lang.get('version'))

                else: # Bad Command
                    conn.send("Feedback\nError=Command not found: " + cmd)
                    pass
            except:
                pass
            conn.close()
                
