#Twisted: still my favorite module

MODE_NETSTAT = 0
MODE_UPLOAD = 1

import os, math, time

from twisted.internet.protocol import ClientFactory
from twisted.internet import reactor
from twisted.spread.jelly import jelly, unjelly
from twisted.spread.banana import Banana

def decodeTuplizedSimBlock(block):
    decodedBlock = {}
    for key, val in block.iteritems():
        decodedBlock[key] = [decodeTuplizedSim(i) for i in val]
    return decodedBlock
    
        
def decodeTuplizedSim(tuplized):
    #I am *pretty sure* this is safe enough
    return (tuplized[0].decode("utf-8"),tuplized[1].decode("utf-8"), tuplized[2], tuplized[3])

class ClientListen(Banana):
    def __init__(self, **kwargs):
        Banana.__init__(self, isClient = True)

        self.factory = kwargs["myFactory"]

        if "netstat" in kwargs:
            self.mode = MODE_NETSTAT
        else:
            self.mode = MODE_UPLOAD
            #Yes, this implies you don't make the listenfactory until you know *exactly* what you want.
            self.username = kwargs["username"]
            self.batchname = kwargs["batchname"]
            self.filenames = kwargs["filenames"]

        self.workingFile = None
        self.workingFileHandle = None
        self.workingChunk = None
        self.maxChunks = None

    def expressionReceived(self, sexp):
        data = unjelly(sexp)

        #Now we figure out where we are, in two ways: our own chunk count, if any, and what we hear from the server

        if len(data) == 2 and data[0] == "NETSTAT":
            netStats = decodeTuplizedSimBlock(data[1])
            self.factory.manager.reportNetstats(data[1])
            self.factory.manager.killConnection()
            self.transport.loseConnection()

        if data == "OHAI":
            #We have just connected. Let's talk.
            if self.mode == MODE_UPLOAD:
                self.send(("UID", self.username.encode("utf-8")))
            elif self.mode == MODE_NETSTAT:
                self.send("NETSTAT")

        elif data == "OK UID":
            #UID acknowledged. Announce the batch.
            self.send(("BATCH", self.batchname.encode("utf-8")))

        elif data == "OK BATCH" or data == "OK FILE":

            #First, consider cleaning up from a previous run.
            if self.workingFileHandle:
                self.workingFileHandle.close()
                self.workingFileHandle = None

            #Either we need to start on files, or we just need to start on a *new* file. All the same to me.
            if self.filenames:
                import oommflink_core as core #You really didn't want it earlier.
                self.workingFile = self.filenames.pop(0)
                self.workingChunk = 0
                self.maxChunks = core.countFileChunks(self.workingFile)
                self.workingFileHandle = open(self.workingFile, "rb")

                filenameonly = self.workingFile.rsplit(os.path.sep, 1)[1]
                self.send(("FNAME", filenameonly.encode("utf-8")))

            else:
                self.send("OK ALL START")
                #Cool, we're done! We'll reconnect if we need to do something new.
                self.factory.manager.successfulUpload()
                self.factory.manager.killConnection()
                self.transport.loseConnection()

        elif data == "OK FNAME":
            #Announce how many chunks
            self.send(("CHUNKNUM", self.maxChunks))

        elif data == "OK CHUNKNUM" or data == "OK CHUNK":
            from oommflink_core import CHUNK_SIZE
            if self.workingChunk == self.maxChunks:
                print "WARNING: I thought we were done sending chunks, but server disagrees."
                return
            tosend = self.workingFileHandle.read(CHUNK_SIZE)
            self.send(("CHUNK",self.workingChunk,tosend))
            self.workingChunk += 1
            self.factory.manager.reportUploadedChunk()

        elif data == "WARN_DATA EXISTS_BATCHPATH":
            self.factory.manager.reportPathInUse(self.batchname)


    def send(self, msg):
        Banana.sendEncoded(self, jelly(msg))

class ClientListenFactory(ClientFactory):
    protocol = ClientListen

    def __init__(self, **kwargs):
        self.manager = kwargs.pop("manager")
        kwargs["myFactory"] = self
        self.kwargs = kwargs


    def buildProtocol(self, addr):
        return ClientListen(**self.kwargs)

    def clientConnectionFailed(self, connector, reason):
        self.manager.reportFailedConnection(reason)


class _UploadManager(object):
    #This is the singleton object that manages starting and stopping the server
    def __init__(self):
        self.connection = None
        self.core = None
        self.gui = None

        reactor.startRunning()

    def linkToData(self, core):
        self.core = core

    def linkToGUI(self, gui):
        self.gui = gui

    def upload(self, **kwargs):
        kwargs["manager"] = self #Hand self down to get progress reports later!
        self.connection = reactor.connectTCP(kwargs["servername"], kwargs["port"], ClientListenFactory(**kwargs))

    def getNetstats(self, **kwargs):
        kwargs["manager"] = self #Hand self down to get progress reports later!
        self.connection = reactor.connectTCP(kwargs["servername"], kwargs["port"], ClientListenFactory(**kwargs))

    def killConnection(self):
        if self.connection:
            self.connection.disconnect()

    def reportFailedConnection(self, reason):
        self.gui.announceFailedConnection()

    def reportNetstats(self, netstats):
        if self.core and self.gui:
            #...but if not, I really have no idea how you managed to call this.
            self.core.netStats = netstats
            self.gui.updateNetstats()

    def reportPathInUse(self, pathname):
        if self.gui:
            self.gui.reportPathInUse(pathname)

    def successfulUpload(self):
        #Yay! Reset everything for future transfers.
        self.core.reset()
        self.gui.fileList.refreshData()

    def reportUploadedChunk(self):
        if self.gui:
            self.gui.increaseUploadProgress()

    def netDoWork(self):
        reactor.runUntilCurrent()
        reactor.doIteration(0)
        
    def reactorDown(self):
        #I bet you want to quit.
        reactor.stop()

UploadManager = _UploadManager()

# Debug!

if __name__ == "__main__":
    UploadManager.upload(servername="127.0.0.1", port=16101, username="DMark", batchname="test1",
                         filenames = [r"G:\Work\OOMMFqTests\rhomboid.mif", r"G:\Work\OOMMFqTests\rhomboid.bmp"])