#!/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)
major = spec["amqp/@major"]
minor = spec["amqp/@minor"]
isfx = "_v%s_%s" % (major, minor)

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()

  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": "Range<Long>[]",
  "long-struct": "Struct"
  }

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])

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, name, base, type):
    self.name = name
    self.base = base
    self.type = type
    self.fields = []

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

  def interface(self, out):
    out.line("public interface %s extends %s {" % (self.name, self.base))
    out.line()
    if self.type != None:
      out.line("    public static final int TYPE = %d;" % self.type)
    out.line()
    for type, name in self.fields:
      out.line("    %s %s();" % (jtype(type), camel(1, "get", name)))
    out.line()
    out.line("}")

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

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

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

    out.line()
    out.line("    %s%s(Decoder dec) {" % (self.name, isfx))
    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%s(dec);" % (name, STRUCTS[type], isfx))
      else:
        raise Exception("unknown type: %s" % type)
    out.line("    }")

    out.line()
    out.line("    %s%s(%s) {" % (self.name, isfx, 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('            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 delegate(C context, Delegate<C> delegate) {")
    out.line("        delegate.%s(context, this);" % dromedary(self.name))
    out.line("    }")

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

    out.line()
    out.line("    public void write(Encoder enc) {")
    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("        %s.write(enc);" % name)
      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))

  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))

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(name, base, typecode)
  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:
  out = Output(out_dir, out_pkg, s.name)
  s.interface(out)
  out.write()
  iout = Output(out_dir, out_pkg, s.name + isfx)
  s.impl(iout)
  iout.write()

fct = Output(out_dir, out_pkg, "StructFactory")
fct.line("public interface StructFactory {")
fct.line("    Struct create(int type, Decoder dec);")
for s in structs:
  fct.line()
  fct.line("    %s new%s(Decoder dec);" % (s.name, s.name))
  fct.line("    %s new%s(%s);" % (s.name, s.name, s.parameters()))
fct.line("}")
fct.write()

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

for s in structs:
  ifct.line("    public %s new%s(Decoder dec) {" % (s.name, s.name))
  ifct.line("        return new %s%s(dec);" % (s.name, isfx))
  ifct.line("    }")

  ifct.line("    public %s new%s(%s) {" % (s.name, s.name, s.parameters()))
  ifct.line("        return new %s%s(%s);" % (s.name, isfx, s.arguments()))
  ifct.line("    }")

ifct.line("}");
ifct.write()

dlg = Output(out_dir, out_pkg, "Delegate")
dlg.line("public abstract class Delegate<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 void invoke(Method method, Handler<Struct> handler);")
inv.line("    protected abstract StructFactory getFactory();")
inv.line()
for s in structs:
  if s.base != "Method": continue
  dname = dromedary(s.name)
  inv.line("    public void %s(%s) throws QpidException {" % (dname, s.parameters()))
  inv.line("        invoke(getFactory().new%s(%s));" % (s.name, s.arguments()))
  inv.line("    }")
inv.line("}")
inv.write()
