#! /usr/bin/python

import socket
import subprocess
import errno
import sys
import os
import json
import traceback
import shlex
import struct
import threading
import Queue

class ArgParser:
  def __init__(self):
    self.i = 0
    self.args = []

  def parse_type(self, make_type):
    opt = self.args[self.i]
    self.i += 1
    if self.i >= len(self.args):
      return ("Not enough params to " + opt, None)
    val = None
    try:
      val = self.args[self.i] if make_type is None else make_type(self.args[self.i])
    except Exception, e:
      pass
    if val is None:
      return ("Bad argument to " + opt + ": " + self.args[self.i], None)
    return (None, val)

  def possible(self, opts, make_type):
    return lambda v: make_type(v) if v in opts else None

  def condition(self, make_type, condition):
    return lambda v: make_type(v) if condition(make_type(v)) else None

  def parse_args(self, args):
    self.args = args
    self.i = 0
    ret = {"input":[]}
    allow_opts = True

    while self.i < len(self.args):
      arg = self.args[self.i].strip()
      if arg == "--":
        allow_opts = False
        self.i += 1
        continue

      if arg == "--port" or arg == "-p":
        (msg, p) = self.parse_type(int)
        if msg is not None:
          return (msg, None)
        ret["port"] = p
      elif arg == "--type" or arg == "-t":
        (msg, t) = self.parse_type(self.possible(["1", "3"], int))
        if msg is not None:
          return (msg, None)
        ret["type"] = t
      elif arg == "--scale" or arg == "-s":
        (msg, s) = self.parse_type(self.condition(int, lambda s: s > 0.00001))
        if msg is not None:
          return (msg, None)
        ret["scale"] = s
      else:
        ret["input"].append(arg)
      self.i += 1
    return (None, ret)
      
def usage():
  print "usage 1: ./exporter.py [--port/-p wrapper_communication_port] [--type/-t (1,3)] [--scale/-s skeletal_scale] input_files..."
  print "  Exports the given file(s) with the given settings"
  print
  print "usage 2: ./exporter.py [--port/-p wrapper_communication_port] -"
  print "  Reads commands (everything after --port in usage 1) as lines from stdin"
  print
  print "usage 3: ./export.py  --help|-h"
  print "  Shows this help message"

if "-h" in sys.argv or "--help" in sys.argv:
  usage()
  sys.exit(0)

(err, args) = ArgParser().parse_args(sys.argv[1:])
if err is not None:
  print err
  usage()
  sys.exit(2)

from_stdin = False
if "-" in args["input"]:
  from_stdin = True
  if len(args["input"]) > 1 or "type" in args or "scale" in args:
    print "If reading from stdin ('-') is specified, no other input may be given"
    usage()
    sys.exit(2)

if not len(args["input"]):
  usage()
  sys.exit(0)

port = args["port"] if "port" in args else 7890

s = socket.socket()
s.bind(("localhost", port))

exporter_args = [os.getcwd() + "/GraphicExport.exe"]
#exporter_args = [os.getcwd() + "/GraphicExport.app/GraphicExport.exe", str(port)]
g = subprocess.Popen(exporter_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

s.settimeout(0.5)
s.listen(0)

stdin_q = Queue.Queue()

def get_stdin():
  while True:
    line = sys.stdin.readline()
    stdin_q.put(line)
    if line == "":
      return

def make_base_cmd(args):
  return {"type": args["type"] if "type" in args else 1, \
          "scale": args["scale"] if "scale" in args else 1}

num_jobs_sent = 0
def send_cmd(conn, base_cmd, in_file):
  global num_jobs_sent
  num_jobs_sent += 1
  cmd = dict(base_cmd)
  cmd["command"] = "job"
  cmd["input"] = in_file
  cmd["job_num"] = num_jobs_sent
  send_msg(conn, cmd)
  
def send_msg(conn, msg):
  data = json.dumps(msg)
  datalen = struct.pack(">H", len(data))
  conn.send(datalen)
  conn.send(data)


conn = None
while conn is None and g.returncode is None:
  try:
    (conn, _) = s.accept()
  except socket.timeout, e:
    pass
  except e:
    print e
    s.close()
    g.wait()
    sys.exit(1)
  g.poll()

if conn:
  conn.setblocking(False)

  if from_stdin:
    reader = threading.Thread(target=get_stdin)
    reader.daemon = True
    reader.start()
  else:
    base_cmd = make_base_cmd(args)
    for i in args["input"]:
      send_cmd(conn, base_cmd, i)

def handle_command(msg_str):
  global quit
  msg = json.loads(msg_str)
  if "command" in msg:
    cmd = msg["command"]
    if cmd == "exit":
      quit = True
    if cmd == "print" and "string" in msg:
      sys.stdout.write(msg["string"])
      sys.stdout.flush()
    if cmd == "done" and "job_num" in msg:
      job_num = int(cmd["job_num"])
      if job_num == num_jobs_sent:
        quit = True

buf = ""
quit = False
while g.returncode is None:
  try:
    d = conn.recv(4096)
    if len(d):
      buf += d
      if len(buf) >= 2:
        buf_len = struct.unpack_from(">H", buf, 0)[0]
        if len(buf) >= 2 + buf_len:
          msg = buf[2:2+buf_len]
          buf = buf[2+buf_len:]
          handle_command(msg)
  except socket.error, e:
    if e.args[0] == errno.EWOULDBLOCK or e.args[0] == errno.EAGAIN:
      pass
    else:
      print e
      quit = True
  if not quit and from_stdin and stdin_q.qsize():
    line = stdin_q.get()
    if line == "":
      quit = True
    else:
      parts = shlex.split(line)
      (err, line_args) = ArgParser.parse_args(parts)
      if err is not None:
        print err
      else:
        base_cmd = make_base_cmd(line_args)
        for i in line_args["input"]:
          send_cmd(conn, base_cmd, i)
  if quit:
    send_msg(conn, {"command":"quit"})
    conn.close()
    s.close()
    g.kill()
    sys.exit(1)
  g.poll()

if (conn):
  conn.close()
s.close()
