#!/usr/bin/python

# Interim code generation script.

import sys, os, mllib
from cStringIO import StringIO

out_dir=sys.argv[1]
out_pkg = sys.argv[2]
spec_file = sys.argv[3]

spec = mllib.xml_parse(spec_file)

class Output:

  def __init__(self, dir, package, name):
    self.dir = dir
    self.package = package
    self.name = name
    self.lines = []

    self.line("package %s;" % self.package)
    self.line()
    self.line("import java.util.Map;")
    self.line("import java.util.UUID;")
    self.line()
    self.line("import org.apache.qpidity.codec.Decoder;")
    self.line("import org.apache.qpidity.codec.Encodable;")
    self.line("import org.apache.qpidity.codec.Encoder;")
    self.line()
    self.line("import org.apache.qpidity.transport.network.Frame;")
    self.line()
    self.line()

  def line(self, l = ""):
    self.lines.append(l)

  def write(self):
    dir = os.path.join(self.dir, *self.package.split("."))
    if not os.path.exists(dir):
      os.makedirs(dir)
    file = os.path.join(dir, "%s.java" % self.name)
    out = open(file, "w")
    for l in self.lines:
      out.write(l)
      out.write(os.linesep)
    out.close()

TYPES = {
  "longstr": "String",
  "shortstr": "String",
  "longlong": "long",
  "long": "long",
  "short": "int",
  "octet": "short",
  "bit": "boolean",
  "table": "Map<String,?>",
  "timestamp": "long",
  "content": "String",
  "uuid": "UUID",
  "rfc1982-long-set": "RangeSet",
  "long-struct": "Struct"
  }

TRACKS = {
  "connection": "Frame.L1",
  "session": "Frame.L2",
  "execution": "Frame.L3",
  None: None
  }

def camel(offset, *args):
  parts = []
  for a in args:
    parts.extend(a.split("-"))
  return "".join(parts[:offset] + [p[0].upper() + p[1:] for p in parts[offset:]])

def dromedary(s):
  return s[0].lower() + s[1:]

def scream(*args):
  return "_".join([a.replace("-", "_").upper() for a in args])

const = Output(out_dir, out_pkg, "Constant")
const.line("public interface Constant")
const.line("{")
for d in spec.query["amqp/constant"]:
	name = d["@name"]
	val = d["@value"]
	datatype = d["@datatype"]
	if datatype == None:
		const.line("public static final int %s = %s;" % (scream(name), val))
const.line("}")
const.write()


DOMAINS = {}
STRUCTS = {}
EXCLUDE = {"access-ticket": True}

for d in spec.query["amqp/domain"]:
  name = d["@name"]
  type = d["@type"]
  if type != None:
    DOMAINS[name] = d["@type"]
  elif d["struct"] != None:
    DOMAINS[name] = name
    STRUCTS[name] = camel(0, name)

def resolve(type):
  if DOMAINS.has_key(type) and DOMAINS[type] != type:
    return resolve(DOMAINS[type])
  else:
    return type

def jtype(type):
  if STRUCTS.has_key(type):
    return STRUCTS[type]
  else:
    return TYPES[type]

OPTIONS = {}

class Struct:

  def __init__(self, node, name, base, type, track, content):
    self.node = node
    self.name = name
    self.base = base
    self.type = type
    self.track = track
    self.content = content
    self.fields = []

  def result(self):
    r = self.node["result"]
    if not r: return
    name = r["@domain"]
    if not name:
      name = self.name + "Result"
    else:
      name = camel(0, name)
    return name

  def field(self, type, name):
    self.fields.append((type, name))

  def impl(self, out):
    out.line("public class %s extends %s {" % (self.name, self.base))

    if self.type != None:
      out.line()
      out.line("    public static final int TYPE = %d;" % self.type)

    out.line()
    out.line("    public int getEncodedType() {")
    if self.type == None:
      out.line("        throw new UnsupportedOperationException();")
    else:
      out.line("        return TYPE;")
    out.line("    }")

    if self.base == "Method":
      out.line()
      out.line("    public boolean hasPayload() {")
      if self.content:
        out.line("        return true;")
      else:
        out.line("        return false;")
      out.line("    }")

      out.line()
      out.line("    public byte getEncodedTrack() {")
      out.line("        return %s;" % self.track)
      out.line("    }")

    out.line()
    for type, name in self.fields:
      out.line("    private %s %s;" % (jtype(type), name))

    if self.fields:
      out.line()
      out.line("    public %s() {}" % self.name)

    out.line()
    out.line("    public %s(%s) {" % (self.name, self.parameters()))
    opts = False
    for type, name in self.fields:
      if not OPTIONS.has_key(name):
        out.line("        this.%s = %s;" % (name, name))
      else:
        opts = True
    if opts:
      for type, name in self.fields:
        if OPTIONS.has_key(name):
          out.line("        boolean _%s = false;" % name)
      out.line("        for (int i=0; i < _options.length; i++) {")
      out.line("            switch (_options[i]) {")
      for type, name in self.fields:
        if OPTIONS.has_key(name):
          out.line("            case %s: _%s=true; break;" % (OPTIONS[name], name))
      out.line("            case NO_OPTION: break;")
      out.line('            default: throw new IllegalArgumentException'
               '("invalid option: " + _options[i]);')
      out.line("            }")
      out.line("        }")
      for type, name in self.fields:
        if OPTIONS.has_key(name):
          out.line("        this.%s = _%s;" % (name, name))
    out.line("    }")

    out.line()
    out.line("    public <C> void dispatch(C context, MethodDelegate<C> delegate) {")
    out.line("        delegate.%s(context, this);" % dromedary(self.name))
    out.line("    }")

    for type, name in self.fields:
      out.line()
      out.line("    public %s %s() {" % (jtype(type), camel(1, "get", name)))
      out.line("        return %s;" % name)
      out.line("    }")
      out.line("    public %s %s(%s value) {" %
               (self.name, camel(1, "set", name), jtype(type)))
      out.line("        this.%s = value;" % name);
      out.line("        return this;")
      out.line("    }")
      out.line("    public %s %s(%s value) {" % (self.name, name, jtype(type)))
      out.line("        this.%s = value;" % name);
      out.line("        return this;")
      out.line("    }")

    out.line()
    out.line("    public void read(Decoder dec, byte major, byte minor) {")
    for type, name in self.fields:
      if TYPES.has_key(type):
        out.line("        %s = dec.read%s();" % (name, camel(0, type)))
      elif STRUCTS.has_key(type):
        out.line("        %s = new %s();" % (name, STRUCTS[type]))
        out.line("        %s.read(dec, major, minor);" % name)
      else:
        raise Exception("unknown type: %s" % type)
    out.line("    }")

    out.line()
    out.line("    public void write(Encoder enc, byte major, byte minor) {")
    for type, name in self.fields:
      if TYPES.has_key(type):
        out.line("        enc.write%s(%s);" % (camel(0, type), name))
      elif STRUCTS.has_key(type):
        out.line("        if (%s == null) {" % name)
        out.line("            new %s().write(enc, major, minor);" % jtype(type))
        out.line("        } else {")
        out.line("            %s.write(enc, major, minor);" % name)
        out.line("        }")
      else:
        raise Exception("unknown type: %s" % type)
    out.line("    }")

    out.line("}")


  def parameters(self):
    params = []
    var = False
    for type, name in self.fields:
      if OPTIONS.has_key(name):
        var = True
      else:
        params.append("%s %s" % (jtype(type), name))
    if var:
      params.append("Option ... _options")
    return ", ".join(params)

  def arguments(self):
    args = []
    var = False
    for type, name in self.fields:
      if OPTIONS.has_key(name):
        var = True
      else:
        args.append(name)
    if var:
      args.append("_options")
    return ", ".join(args)

CLASSES = {"file": False, "basic": False, "stream": False, "tunnel": False}
FIELDS = {"ticket": False}

class Visitor(mllib.transforms.Visitor):

  def __init__(self):
    self.structs = []

  def do_method(self, m):
    if CLASSES.get(m.parent["@name"], True):
      name = camel(0, m.parent["@name"], m["@name"])
      type = int(m.parent["@index"])*256 + int(m["@index"])
      self.structs.append((name, "Method", type, m))
      self.descend(m)

  def do_domain(self, d):
    s = d["struct"]
    if s:
      name = camel(0, d["@name"])
      st = s["@type"]
      if st in (None, "none", ""):
        type = None
      else:
        type = int(st)
      self.structs.append((name, "Struct", type, s))
    self.descend(d)

  def do_result(self, r):
    s = r["struct"]
    if s:
      name = camel(0, r.parent.parent["@name"], r.parent["@name"], "Result")
      type = int(r.parent.parent["@index"]) * 256 + int(s["@type"])
      self.structs.append((name, "Result", type, s))
    self.descend(r)

v = Visitor()
spec.dispatch(v)

opts = Output(out_dir, out_pkg, "Option")
opts.line("public enum Option {")
structs = []
for name, base, typecode, m in v.structs:
  struct = Struct(m, name, base, typecode,
                  TRACKS.get(m.parent["@name"], "Frame.L4"),
                  m["@content"] == "1")
  for f in m.query["field", lambda f: FIELDS.get(f["@name"], True)]:
    type = resolve(f["@domain"])
    name = camel(1, f["@name"])
    struct.field(type, name)
    if type == "bit":
      opt_name = scream(f["@name"])
      if not OPTIONS.has_key(name):
        OPTIONS[name] = opt_name
        opts.line("    %s," % opt_name)
  structs.append(struct)
opts.line("    %s," % "NO_OPTION")
opts.line("}")
opts.write()




for s in structs:
  impl = Output(out_dir, out_pkg, s.name)
  s.impl(impl)
  impl.write()

fct = Output(out_dir, out_pkg, "StructFactory")
fct.line("class StructFactory {")
fct.line("    public static Struct create(int type) {")
fct.line("        switch (type) {")
for s in structs:
  if s.type == None: continue
  fct.line("        case %s.TYPE:" % s.name)
  fct.line("            return new %s();" % s.name)
fct.line("        default:")
fct.line('            throw new IllegalArgumentException("type: " + type);')
fct.line("        }")
fct.line("    }")
fct.line("}");
fct.write()

dlg = Output(out_dir, out_pkg, "MethodDelegate")
dlg.line("public abstract class MethodDelegate<C> {")
for s in structs:
  dlg.line("    public void %s(C context, %s struct) {}" %
           (dromedary(s.name), s.name))
dlg.line("}")
dlg.write()

inv = Output(out_dir, out_pkg, "Invoker")
inv.line("public abstract class Invoker {")
inv.line()
inv.line("    protected abstract void invoke(Method method);")
inv.line("    protected abstract <T> Future<T> invoke(Method method, Class<T> resultClass);")
inv.line()
for s in structs:
  if s.base != "Method": continue
  dname = dromedary(s.name)
  result = s.result()
  if result:
    result_type = "Future<%s>" % result
  else:
    result_type = "void"
  inv.line("    public %s %s(%s) {" % (result_type, dname, s.parameters()))
  if result:
    inv.line("        return invoke(new %s(%s), %s.class);" %
             (s.name, s.arguments(), result))
  else:
    inv.line("        invoke(new %s(%s));" % (s.name, s.arguments()))
  inv.line("    }")
inv.line("}")
inv.write()
