#
# 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.
#

import os, cPickle, datatypes
from codec010 import StringCodec
from util import mtime, fill

class Node:

  def __init__(self, children):
    self.children = children
    self.named = {}
    self.docs = []
    self.rules = []

  def register(self):
    for ch in self.children:
      ch.register(self)

  def resolve(self):
    for ch in self.children:
      ch.resolve()

  def __getitem__(self, name):
    path = name.split(".", 1)
    nd = self.named
    for step in path:
      nd = nd[step]
    return nd

  def __iter__(self):
    return iter(self.children)

class Anonymous:

  def __init__(self, children):
    self.children = children

  def register(self, node):
    for ch in self.children:
      ch.register(node)

  def resolve(self):
    for ch in self.children:
      ch.resolve()

class Named:

  def __init__(self, name):
    self.name = name
    self.qname = None

  def register(self, node):
    self.spec = node.spec
    self.klass = node.klass
    node.named[self.name] = self
    if node.qname:
      self.qname = "%s.%s" % (node.qname, self.name)
    else:
      self.qname = self.name

  def __str__(self):
    return self.qname

  def __repr__(self):
    return str(self)

class Lookup:

  def lookup(self, name):
    value = None
    if self.klass:
      try:
        value = self.klass[name]
      except KeyError:
        pass
    if not value:
      value = self.spec[name]
    return value

class Coded:

  def __init__(self, code):
    self.code = code

class Constant(Named, Node):

  def __init__(self, name, value, children):
    Named.__init__(self, name)
    Node.__init__(self, children)
    self.value = value

  def register(self, node):
    Named.register(self, node)
    node.constants.append(self)
    Node.register(self)

class Type(Named, Node):

  def __init__(self, name, children):
    Named.__init__(self, name)
    Node.__init__(self, children)

  def is_present(self, value):
    return value != None

  def register(self, node):
    Named.register(self, node)
    Node.register(self)

class Primitive(Coded, Type):

  def __init__(self, name, code, fixed, variable, children):
    Coded.__init__(self, code)
    Type.__init__(self, name, children)
    self.fixed = fixed
    self.variable = variable

  def register(self, node):
    Type.register(self, node)
    if self.code is not None:
      self.spec.types[self.code] = self

  def is_present(self, value):
    if self.fixed == 0:
      return value
    else:
      return Type.is_present(self, value)

  def encode(self, codec, value):
    getattr(codec, "write_%s" % self.name)(value)

  def decode(self, codec):
    return getattr(codec, "read_%s" % self.name)()

class Domain(Type, Lookup):

  def __init__(self, name, type, children):
    Type.__init__(self, name, children)
    self.type = type
    self.choices = {}

  def resolve(self):
    self.type = self.lookup(self.type)
    Node.resolve(self)

  def encode(self, codec, value):
    self.type.encode(codec, value)

  def decode(self, codec):
    return self.type.decode(codec)

class Enum:

  def __init__(self, name):
    self.name = name
    self._names = ()
    self._values = ()

  def values(self):
    return self._values

  def __repr__(self):
    return "%s(%s)" % (self.name, ", ".join(self._names))

class Choice(Named, Node):

  def __init__(self, name, value, children):
    Named.__init__(self, name)
    Node.__init__(self, children)
    self.value = value

  def register(self, node):
    Named.register(self, node)
    node.choices[self.value] = self
    Node.register(self)
    try:
      enum = node.spec.enums[node.name]
    except KeyError:
      enum = Enum(node.name)
      node.spec.enums[node.name] = enum
    setattr(enum, self.name, self.value)
    enum._names += (self.name,)
    enum._values += (self.value,)

class Composite(Type, Coded):

  def __init__(self, name, label, code, size, pack, children):
    Coded.__init__(self, code)
    Type.__init__(self, name, children)
    self.label = label
    self.fields = []
    self.size = size
    self.pack = pack

  def new(self, args, kwargs):
    return datatypes.Struct(self, *args, **kwargs)

  def decode(self, codec):
    codec.read_size(self.size)
    if self.code is not None:
      code = codec.read_uint16()
      assert self.code == code
    return datatypes.Struct(self, **self.decode_fields(codec))

  def decode_fields(self, codec):
    flags = 0
    for i in range(self.pack):
      flags |= (codec.read_uint8() << 8*i)

    result = {}

    for i in range(len(self.fields)):
      f = self.fields[i]
      if flags & (0x1 << i):
        result[f.name] = f.type.decode(codec)
      else:
        result[f.name] = None
    return result

  def encode(self, codec, value):
    sc = StringCodec(self.spec)
    if self.code is not None:
      sc.write_uint16(self.code)
    self.encode_fields(sc, value)
    codec.write_size(self.size, len(sc.encoded))
    codec.write(sc.encoded)

  def encode_fields(self, codec, values):
    flags = 0
    for i in range(len(self.fields)):
      f = self.fields[i]
      if f.type.is_present(values[f.name]):
        flags |= (0x1 << i)
    for i in range(self.pack):
      codec.write_uint8((flags >> 8*i) & 0xFF)
    for i in range(len(self.fields)):
      f = self.fields[i]
      if flags & (0x1 << i):
        f.type.encode(codec, values[f.name])

  def docstring(self):
    docs = []
    if self.label:
      docs.append(self.label)
    docs += [d.text for d in self.docs]
    s = "\n\n".join([fill(t, 2) for t in docs])
    for f in self.fields:
      fdocs = []
      if f.label:
        fdocs.append(f.label)
      else:
        fdocs.append("")
      fdocs += [d.text for d in f.docs]
      s += "\n\n" + "\n\n".join([fill(fdocs[0], 4, f.name)] +
                                [fill(t, 4) for t in fdocs[1:]])
    return s


class Field(Named, Node, Lookup):

  def __init__(self, name, label, type, children):
    Named.__init__(self, name)
    Node.__init__(self, children)
    self.label = label
    self.type = type
    self.exceptions = []

  def default(self):
    return None

  def register(self, node):
    Named.register(self, node)
    node.fields.append(self)
    Node.register(self)

  def resolve(self):
    self.type = self.lookup(self.type)
    Node.resolve(self)

  def __str__(self):
    return "%s: %s" % (self.qname, self.type.qname)

class Struct(Composite):

  def register(self, node):
    Composite.register(self, node)
    if self.code is not None:
      self.spec.structs[self.code] = self
    self.spec.structs_by_name[self.name] = self
    self.pyname = self.name
    self.pydoc = self.docstring()

  def __str__(self):
    fields = ",\n    ".join(["%s: %s" % (f.name, f.type.qname)
                             for f in self.fields])
    return "%s {\n    %s\n}" % (self.qname, fields)

class Segment:

  def __init__(self):
    self.segment_type = None

  def register(self, node):
    self.spec = node.spec
    self.klass = node.klass
    node.segments.append(self)
    Node.register(self)

class Instruction(Composite, Segment):

  def __init__(self, name, label, code, children):
    Composite.__init__(self, name, label, code, 0, 2, children)
    Segment.__init__(self)
    self.track = None
    self.handlers = []

  def __str__(self):
    return "%s(%s)" % (self.qname, ", ".join(["%s: %s" % (f.name, f.type.qname)
                                              for f in self.fields]))

  def register(self, node):
    Composite.register(self, node)
    self.pyname = self.qname.replace(".", "_")
    self.pydoc = self.docstring()
    self.spec.instructions[self.pyname] = self

class Control(Instruction):

  def __init__(self, name, code, label, children):
    Instruction.__init__(self, name, code, label, children)
    self.response = None

  def register(self, node):
    Instruction.register(self, node)
    node.controls.append(self)
    self.spec.controls[self.code] = self
    self.segment_type = self.spec["segment_type.control"].value
    self.track = self.spec["track.control"].value

class Command(Instruction):

  def __init__(self, name, label, code, children):
    Instruction.__init__(self, name, label, code, children)
    self.result = None
    self.exceptions = []
    self.segments = []

  def register(self, node):
    Instruction.register(self, node)
    node.commands.append(self)
    self.spec.commands[self.code] = self
    self.segment_type = self.spec["segment_type.command"].value
    self.track = self.spec["track.command"].value

class Header(Segment, Node):

  def __init__(self, children):
    Segment.__init__(self)
    Node.__init__(self, children)
    self.entries = []

  def register(self, node):
    Segment.register(self, node)
    self.segment_type = self.spec["segment_type.header"].value
    Node.register(self)

class Entry(Lookup):

  def __init__(self, type):
    self.type = type

  def register(self, node):
    self.spec = node.spec
    self.klass = node.klass
    node.entries.append(self)

  def resolve(self):
    self.type = self.lookup(self.type)

class Body(Segment, Node):

  def __init__(self, children):
    Segment.__init__(self)
    Node.__init__(self, children)

  def register(self, node):
    Segment.register(self, node)
    self.segment_type = self.spec["segment_type.body"].value
    Node.register(self)

  def resolve(self): pass

class Class(Named, Coded, Node):

  def __init__(self, name, code, children):
    Named.__init__(self, name)
    Coded.__init__(self, code)
    Node.__init__(self, children)
    self.controls = []
    self.commands = []

  def register(self, node):
    Named.register(self, node)
    self.klass = self
    node.classes.append(self)
    Node.register(self)

class Doc:

  def __init__(self, type, title, text):
    self.type = type
    self.title = title
    self.text = text

  def register(self, node):
    node.docs.append(self)

  def resolve(self): pass

class Role(Named, Node):

  def __init__(self, name, children):
    Named.__init__(self, name)
    Node.__init__(self, children)

  def register(self, node):
    Named.register(self, node)
    Node.register(self)

class Rule(Named, Node):

  def __init__(self, name, children):
    Named.__init__(self, name)
    Node.__init__(self, children)

  def register(self, node):
    Named.register(self, node)
    node.rules.append(self)
    Node.register(self)

class Exception(Named, Node):

  def __init__(self, name, error_code, children):
    Named.__init__(self, name)
    Node.__init__(self, children)
    self.error_code = error_code

  def register(self, node):
    Named.register(self, node)
    node.exceptions.append(self)
    Node.register(self)

class Spec(Node):

  ENCODINGS = {
    basestring: "vbin16",
    int: "int64",
    long: "int64",
    float: "float",
    None.__class__: "void",
    list: "list",
    tuple: "list",
    dict: "map"
    }

  def __init__(self, major, minor, port, children):
    Node.__init__(self, children)
    self.major = major
    self.minor = minor
    self.port = port
    self.constants = []
    self.classes = []
    self.types = {}
    self.qname = None
    self.spec = self
    self.klass = None
    self.instructions = {}
    self.controls = {}
    self.commands = {}
    self.structs = {}
    self.structs_by_name = {}
    self.enums = {}

  def encoding(self, klass):
    if Spec.ENCODINGS.has_key(klass):
      return self.named[Spec.ENCODINGS[klass]]
    for base in klass.__bases__:
      result = self.encoding(base)
      if result != None:
        return result

class Implement:

  def __init__(self, handle):
    self.handle = handle

  def register(self, node):
    node.handlers.append(self.handle)

  def resolve(self): pass

class Response(Node):

  def __init__(self, name, children):
    Node.__init__(self, children)
    self.name = name

  def register(self, node):
    Node.register(self)

class Result(Node, Lookup):

  def __init__(self, type, children):
    self.type = type
    Node.__init__(self, children)

  def register(self, node):
    node.result = self
    self.qname = node.qname
    self.klass = node.klass
    self.spec = node.spec
    Node.register(self)

  def resolve(self):
    self.type = self.lookup(self.type)
    Node.resolve(self)

import mllib

def num(s):
  if s: return int(s, 0)

REPLACE = {" ": "_", "-": "_"}
KEYWORDS = {"global": "global_",
            "return": "return_"}

def id(name):
  name = str(name)
  for key, val in REPLACE.items():
    name = name.replace(key, val)
  try:
    name = KEYWORDS[name]
  except KeyError:
    pass
  return name

class Loader:

  def __init__(self):
    self.class_code = 0

  def code(self, nd):
    c = num(nd["@code"])
    if c is None:
      return None
    else:
      return c | (self.class_code << 8)

  def list(self, q):
    result = []
    for nd in q:
      result.append(nd.dispatch(self))
    return result

  def children(self, n):
    return self.list(n.query["#tag"])

  def data(self, d):
    return d.data

  def do_amqp(self, a):
    return Spec(num(a["@major"]), num(a["@minor"]), num(a["@port"]),
                self.children(a))

  def do_type(self, t):
    return Primitive(id(t["@name"]), self.code(t), num(t["@fixed-width"]),
                     num(t["@variable-width"]), self.children(t))

  def do_constant(self, c):
    return Constant(id(c["@name"]), num(c["@value"]), self.children(c))

  def do_domain(self, d):
    return Domain(id(d["@name"]), id(d["@type"]), self.children(d))

  def do_enum(self, e):
    return Anonymous(self.children(e))

  def do_choice(self, c):
    return Choice(id(c["@name"]), num(c["@value"]), self.children(c))

  def do_class(self, c):
    code = num(c["@code"])
    self.class_code = code
    children = self.children(c)
    children += self.list(c.query["command/result/struct"])
    self.class_code = 0
    return Class(id(c["@name"]), code, children)

  def do_doc(self, doc):
    text = reduce(lambda x, y: x + y, self.list(doc.children))
    return Doc(doc["@type"], doc["@title"], text)

  def do_xref(self, x):
    return x["@ref"]

  def do_role(self, r):
    return Role(id(r["@name"]), self.children(r))

  def do_control(self, c):
    return Control(id(c["@name"]), c["@label"], self.code(c), self.children(c))

  def do_rule(self, r):
    return Rule(id(r["@name"]), self.children(r))

  def do_implement(self, i):
    return Implement(id(i["@handle"]))

  def do_response(self, r):
    return Response(id(r["@name"]), self.children(r))

  def do_field(self, f):
    return Field(id(f["@name"]), f["@label"], id(f["@type"]), self.children(f))

  def do_struct(self, s):
    return Struct(id(s["@name"]), s["@label"], self.code(s), num(s["@size"]),
                  num(s["@pack"]), self.children(s))

  def do_command(self, c):
    return Command(id(c["@name"]), c["@label"], self.code(c), self.children(c))

  def do_segments(self, s):
    return Anonymous(self.children(s))

  def do_header(self, h):
    return Header(self.children(h))

  def do_entry(self, e):
    return Entry(id(e["@type"]))

  def do_body(self, b):
    return Body(self.children(b))

  def do_result(self, r):
    type = r["@type"]
    if not type:
      type = r["struct/@name"]
    return Result(id(type), self.list(r.query["#tag", lambda x: x.name != "struct"]))

  def do_exception(self, e):
    return Exception(id(e["@name"]), id(e["@error-code"]), self.children(e))

def load(xml):
  fname = xml + ".pcl"

  if os.path.exists(fname) and mtime(fname) > mtime(__file__):
    file = open(fname, "r")
    s = cPickle.load(file)
    file.close()
  else:
    doc = mllib.xml_parse(xml)
    s = doc["amqp"].dispatch(Loader())
    s.register()
    s.resolve()

    try:
      file = open(fname, "w")
    except IOError:
      file = None

    if file:
      cPickle.dump(s, file)
      file.close()

  return s
