#!/usr/bin/python
import sys, os.path, os, tempfile, socket, select
import squash_frontend, traceback, errno, signal
from os import getenv, popen, system
from fcntl import fcntl, F_SETFL, F_GETFL
BUF_SIZE = 4096

class FdProcessor:
  def run(self, fd):
    raise "run() must be implemented by FdProcessor subclass"

  def pollinfo(self):
    raise "pollinfo() must be implemented by FdProcessor subclass"
    
  def finished(self, fd):
    pass

# looks like i should make this use the new subprocess module, but that wont work on current rhel yet
class SquashRunner(FdProcessor):
  def __init__(self, params, main):
    self.main = main
    self.params = params
    cmd =  "%s %s" % (squash_frontend.RUN_SQUASH, params)
    print cmd
    child_stdin, child_stdout, child_stderr = os.popen3(cmd, "b", BUF_SIZE)
    child_stdin.close()
    self.input = child_stdout
    self.errinput = child_stderr
    self.errbuf = ""
    self.buf = ""
    self.running = 2
    
  def pollinfo(self):
    return [(self.input, select.POLLIN), (self.errinput, select.POLLIN)]
    
  def run(self, infd):
    buf = os.read(infd, BUF_SIZE)
    if infd == self.input.fileno():
      self.buf = self.buf + buf
    else:
      self.errbuf = self.errbuf + buf

  def readStatus(self):
    b = self.errbuf
    self.errbuf = ""
    return b

  def finished(self, fd):
    self.running = self.running - 1
    if self.running == 0:
      self.main.nextSquash()

  def getPatch(self):
    if self.running == 0:
      return "patch " + self.buf
    else:
      return "error patch is still being generated"
      
class ServerSocket(FdProcessor):
  def __init__(self, port, main):
    self.main = main
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("", port))
    s.listen(50)
    print "Listening on %d" % port
    self.s = s
  
  def pollinfo(self):
    return [(self.s, select.POLLIN)]
  
  def run(self, _):
    self.main.register(Client(self.s.accept()[0], self.main))

class Client(FdProcessor):
  def __init__(self, socket, main):
    self.s = socket
    self.main = main
    self.buf = ""

  def run(self, _):
    try:
      s = self.s
      newbuf = s.recv(BUF_SIZE)
      self.buf = self.buf + newbuf
      try:
        newbuf.rindex("\n")
      except ValueError:
        return
      args = self.buf.strip().split(" ")
      self.buf = ""
      cmd = args.pop(0)
      if cmd == "prepare" and len(args) > 2:
        renameclass = args.pop(0)
        member = args.pop(0)
        renamed_member = args.pop(0)
        rest = " ".join(args)
        s.sendall(self.main.prepare(renameclass, member, renamed_member, rest))
      elif cmd == "run" and len(args) > 2:
        s.sendall(self.main.run(" ".join(args)))
      elif cmd == "status" and len(args) > 2:
        params = " ".join(args)
        status = self.main.status(params)
        s.sendall(status)
      elif cmd == "getPatch" and len(args) > 2:
        params = " ".join(args)
        s.sendall(self.main.getPatch(params))
    except socket.error:
      pass
    s.close()
    
  def pollinfo(self):
    return [(self.s, select.POLLIN)]

class Squashing:
  def __init__(self, main, renameclass, member, renamed_member, rest):
    self.main = main
    self.cmdline = squash_frontend.getCmdLine(renameclass, member, renamed_member, rest)
    self.id = " ".join([renameclass, member, renamed_member, rest]).strip()
    self.squash = None

  def prepare(self):
    # this is pretty fugly, should the file list functionality into squash_frontend
    return self.id + "\n" + open(squash_frontend.getInputFile(self.cmdline)).read()

  def run(self):
    if self.squash == None:
      self.squash = SquashRunner(self.cmdline, self.main)
      self.main.register(self.squash)

  def status(self):
    s = self.squash
    if s == None:
      try:
        return "queued " + str(self.main.squash_queue.index(self))
      except:
        return "error must called 'prepare'"
    else:
      status = s.readStatus()
      if s.running or status != "" :
        return "status " + status
      else: #this should be split up into completed/failed
        return "done"

  def isRunning(self):
    return self.squash.running != 0

  def getPatch(self):
    return self.squash.getPatch()
  
class Main:
  def __init__(self):
    self.renames = {}
    self.pollmapping = {}
    self.squash = None
    self.squash_queue = []
    self.poller = select.poll()
    s = ServerSocket(9000, self)
    self.register(s)
    try:
      while True:
        ls = self.poller.poll()
        for (s,state) in ls:
          if state == select.POLLHUP or state == select.POLLNVAL:
            self.poller.unregister(s)
            self.pollmapping[s].finished(s)
            del self.pollmapping[s]
          else:
            self.pollmapping[s].run(s)
    except Exception, e:
      traceback.print_exc()
      self.close()

  def close(self):
    print "Closing"
    for fd in self.pollmapping.keys():
      os.close(fd)
    os.kill(0, signal.SIGTERM)
    
  def register(self, processor):
    for (handle, mode) in processor.pollinfo():
      fd = handle.fileno()
      fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | os.O_NONBLOCK)
      self.pollmapping[fd] = processor
      self.poller.register(handle, mode)

  def run(self, params):
    try:
      print "run '%s'" % params
      #make sure that params are valid
      new_squash = self.renames[params]
      n = self.nextSquash(new_squash)
      if n == None:
        return "queued " + str(self.squash_queue.index(new_squash))
      else:
        return "ok"
    except KeyError:
      return "Must 'prepare' before run"
      
  def nextSquash(self, new_squash = None):
    q = self.squash_queue
    if new_squash != None:
      q.append(new_squash)
    print "nextSquash"
    if len(q) and (self.squash == None or not self.squash.isRunning()):
      self.squash = q.pop(0)
      self.squash.run()
      return self.squash
    return None

  def prepare(self, renameclass, member, renamed_member, rest):
    #try:
      #cmdline = " ".join([renameclass, member, renamed_member, rest]).strip()
      #return self.renames[cmdline].prepare()
    #except KeyError:
    #just do the exception part because caching is being annoying
    s = Squashing(self, renameclass, member, renamed_member, rest)
    print "adding '%s'" %s.id
    self.renames[s.id] = s
    return s.prepare()

  def status(self, params):
    try:
      p = self.renames[params]
      return p.status()
    except KeyError:
      return "error no status for '%s'. %s " % (params, str(self.renames))

  def getPatch(self, params):
    try:
      p = self.renames[params]
      return p.getPatch()
    except KeyError:
      return "error no patch"

if __name__ == "__main__":
  Main()
