#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
# 
#   http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#

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

def num(x, default=None):
  if x is not None and x != "":
    return int(x, 0)
  else:
    return default

def klass(nd):
  parent = nd.parent
  while parent is not None:
    if hasattr(parent, "name") and parent.name == "class":
      return parent
    parent = parent.parent

untyped = -1

def code(nd):
  global untyped
  cd = num(nd["@code"])
  if cd is None:
    cd = untyped
    untyped -= 1
    return cd

  cls = klass(nd)
  if cls:
    cd |= (num(cls["@code"]) << 8)
  return cd

def root(nd):
  if nd.parent is None:
    return nd
  else:
    return root(nd.parent)

def qname(nd):
  name = nd["@name"]
  cls = klass(nd)
  if cls != None:
    return "%s.%s" % (cls["@name"], name)
  else:
    return name

RESOLVED = {}

def resolve(node, name):
  key = (node, name)
  if RESOLVED.has_key(key):
    return RESOLVED[key]
  else:
    spec = root(node)
    cls = klass(node)
    if cls:
      for nd in cls.query["#tag"]:
        if nd["@name"] == name:
          RESOLVED[key] = nd
          return nd
    for nd in spec.query["amqp/#tag"] + spec.query["amqp/class/#tag"]:
      if name == qname(nd):
        RESOLVED[key] = nd
        return nd
    raise Exception("unresolved name: %s" % name)

def resolve_type(nd):
  if hasattr(nd, "_resolved_type"):
    return nd._resolved_type
  else:
    name = nd["@type"]
    type = resolve(nd, name)
    if type.name == "domain" and not type["enum"]:
      type = resolve_type(type)
    nd._resolved_type = type
    return type

TYPES = {
  "bit": "boolean",
  "uint8": "short",
  "uint16": "int",
  "uint32": "long",
  "uint64": "long",
  "datetime": "long",
  "uuid": "UUID",
  "sequence-no": "int",
  "sequence-set": "RangeSet", # XXX
  "byte-ranges": "RangeSet", # XXX
  "str8": "String",
  "str16": "String",
  "vbin8": "byte[]",
  "vbin16": "byte[]",
  "vbin32": "byte[]",
  "struct32": "Struct",
  "map": "Map<String,Object>",
  "array": "List<Object>"
  }

def cname(nd, field="@name"):
  cls = klass(nd)
  if cls:
    if (nd.name in ("struct", "result") and
        cls["@name"] != "session" and
        nd[field] != "header"):
      return camel(0, nd[field])
    else:
      return camel(0, cls["@name"], nd[field])
  else:
    return camel(0, nd[field])

def jtype(nd):
  if nd.name == "struct" or nd["enum"]:
    return cname(nd)
  else:
    return TYPES[nd["@name"]]

REFS = {
  "boolean": "Boolean",
  "byte": "Byte",
  "short": "Short",
  "int": "Integer",
  "long": "Long",
  "float": "Float",
  "double": "Double",
  "char": "Character"
}

def jref(jt):
  return REFS.get(jt, jt)

def jclass(jt):
  idx = jt.find('<')
  if idx > 0:
    return jt[:idx]
  else:
    return jt

DEFAULTS = {
  "long": 0,
  "int": 0,
  "short": 0,
  "byte": 0,
  "char": 0,
  "boolean": "false"
  }

class Field:

  def __init__(self, index, nd):
    self.index = index
    self.name = camel(1, nd["@name"])
    self.type_node = resolve_type(nd)
    if self.type_node.name == "domain":
      self.prim_type = resolve_type(self.type_node)
    else:
      self.prim_type = self.type_node
    self.variable_width = num(self.prim_type["@variable-width"], 0)
    self.fixed_width = num(self.prim_type["@fixed-width"], 0)
    self.empty = self.variable_width == 0 and self.fixed_width == 0 and self.prim_type.name != "struct"
    tname = cname(self.type_node)
    if self.type_node.name == "struct":
      self.read = "(%s) dec.readStruct(%s.TYPE)" % (tname, tname)
      self.write = "enc.writeStruct(%s.TYPE, check(struct).%s)" % (tname, self.name)
      self.coder = "Struct"
    elif self.type_node.name == "domain":
      self.coder = camel(0, self.prim_type["@name"])
      self.read = "%s.get(dec.read%s())" % (tname, self.coder)
      self.write = "enc.write%s(check(struct).%s.getValue())" % (self.coder, self.name)
    else:
      self.coder = camel(0, self.type_node["@name"])
      self.read = "dec.read%s()" % self.coder
      self.write = "enc.write%s(check(struct).%s)" % (self.coder, self.name)
    self.type = jtype(self.type_node)
    self.default = DEFAULTS.get(self.type, "null")
    self.has = camel(1, "has", self.name)
    self.get = camel(1, "get", self.name)
    self.set = camel(1, "set", self.name)
    self.clear = camel(1, "clear", self.name)
    if self.type == "boolean":
      self.option = scream(nd["@name"])
    else:
      self.option = None

  def flag_mask(self, pack):
    flag = pack * 8 - 8 - (self.index/8)*8 + (self.index % 8)
    return 1 << flag


def get_fields(nd):
  fields = []
  index = 0
  for f in nd.query["field"]:
    fields.append(Field(index, f))
    index += 1
  return fields

def get_parameters(type, fields):
  params = []
  options = False
  for f in fields:
    if f.option:
      options = True
    else:
      params.append("%s %s" % (f.type, f.name))
  if type["segments"]:
    params.append("Header header")
    params.append("ByteBuffer body")
  if options or type.name in ("control", "command"):
    params.append("Option ... _options")
  return params

def get_arguments(type, fields):
  args = []
  options = False
  for f in fields:
    if f.option:
      options = True
    else:
      args.append(f.name)
  if type["segments"]:
    args.append("header")
    args.append("body")
  if options or type.name in ("control", "command"):
    args.append("_options")
  return args

def get_options(fields):
  return [f for f in fields if f.option]
