#!/usr/bin/env ruby
#
# General purpose C++ code generation.
#
require 'amqpgen'
require 'set'

Copyright=<<EOS
/*
 *
 * 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.
 *
 */

///
/// This file was automatically generated from the AMQP specification.
/// Do not edit.
///


EOS

CppKeywords = Set.new(["and", "and_eq", "asm", "auto", "bitand",
                       "bitor", "bool", "break", "case", "catch", "char",
                       "class", "compl", "const", "const_cast", "continue",
                       "default", "delete", "do", "DomainInfo", "double",
                       "dynamic_cast", "else", "enum", "explicit", "extern",
                       "false", "float", "for", "friend", "goto", "if",
                       "inline", "int", "long", "mutable", "namespace", "new",
                       "not", "not_eq", "operator", "or", "or_eq", "private",
                       "protected", "public", "register", "reinterpret_cast",
                       "return", "short", "signed", "sizeof", "static",
                       "static_cast", "struct", "switch", "template", "this",
                       "throw", "true", "try", "typedef", "typeid",
                       "typename", "union", "unsigned", "using", "virtual",
                       "void", "volatile", "wchar_t", "while", "xor",
                       "xor_eq"])
# Names that need a trailing "_" to avoid clashes.
CppMangle = CppKeywords+Set.new(["std::string"])

class String
  def cppsafe() CppMangle.include?(self) ? self+"_" : self; end

  def amqp2cpp()
    path=split(".")
    name=path.pop
    return name.typename if path.empty?
    path.map! { |n| n.nsname }
    return (path << name.caps.cppsafe).join("::")
  end

  def typename() caps.cppsafe; end
  def nsname() bars.cppsafe; end
  def constname() shout.cppsafe; end
  def funcname() lcaps.cppsafe; end
  def varname() lcaps.cppsafe; end
end

# preview: Hold information about a C++ type.
# 
# new mapping does not use CppType,
# Each amqp type corresponds exactly by dotted name
# to a type, domain or struct, which in turns
# corresponds by name to a C++ type or typedef.
# (see String.amqp2cpp)
# 
class CppType
  def initialize(name) @name=@param=@ret=name; end
  attr_reader :name, :param, :ret, :code
  
  def retref() @ret="#{name}&"; self; end
  def retcref() @ret="const #{name}&"; self; end
  def passcref() @param="const #{name}&"; self; end
  def code(str) @code=str; self; end
  def defval(str) @defval=str; self; end
  def encoded() @code end
  def ret_by_val() @name; end

  def encode(value, buffer)
    @code ? "#{buffer}.put#{@code}(#{value});" : "#{value}.encode(#{buffer});"
  end

  def decode(value,buffer)
    if @code
      if /&$/===param then
        "#{buffer}.get#{@code}(#{value});"
      else
        "#{value} = #{buffer}.get#{@code}();"
      end
    else
      "#{value}.decode(#{buffer});"
    end
  end

  def default_value()
    return @defval ||= "#{name}()"
  end

  def to_s() name; end;
end

class AmqpRoot
  # preview; map 0-10 types to preview code generator types
  @@typemap = {
    "bit"=> CppType.new("bool").code("Octet").defval("false"),
    "boolean"=> CppType.new("bool").code("Octet").defval("false"),
    "uint8"=>CppType.new("uint8_t").code("Octet").defval("0"), 
    "uint16"=>CppType.new("uint16_t").code("Short").defval("0"),
    "uint32"=>CppType.new("uint32_t").code("Long").defval("0"),
    "uint64"=>CppType.new("uint64_t").code("LongLong").defval("0"),
    "datetime"=>CppType.new("uint64_t").code("LongLong").defval("0"),
    "str8"=>CppType.new("std::string").passcref.retcref.code("ShortString"),
    "str16"=>CppType.new("std::string").passcref.retcref.code("MediumString"),
    "str32"=>CppType.new("std::string").passcref.retcref.code("LongString"),
    "vbin8"=>CppType.new("std::string").passcref.retcref.code("ShortString"),
    "vbin16"=>CppType.new("std::string").passcref.retcref.code("MediumString"),
    "vbin32"=>CppType.new("std::string").passcref.retcref.code("LongString"),
    "map"=>CppType.new("FieldTable").passcref.retcref,
    "array"=>CppType.new("Array").passcref.retcref,
    "sequence-no"=>CppType.new("SequenceNumber").passcref,
    "sequence-set"=>CppType.new("SequenceSet").passcref.retcref,
    "struct32"=>CppType.new("std::string").passcref.retcref.code("LongString"),
    "uuid"=>CppType.new("Uuid").passcref.retcref,
    "byte-ranges"=>CppType.new("ByteRanges").passcref.retcref
  }

  # preview: map amqp types to preview cpp types.
  def lookup_cpptype(t) t = @@typemap[t] and return t end
end


class AmqpElement
  # convert my amqp type_ attribute to a C++ type.
  def amqp2cpp()
    return "ArrayDomain<#{array_type(name).amqp2cpp}> " if type_=="array"
    return type_.amqp2cpp
  end

  # Does this object have a type-like child named name?
  def typechild(name)
    child = domain(name) if respond_to? :domain
    child = struct(name) if not child and respond_to? :struct
    child = type_(name) if not child and respond_to? :type_
    child
  end

  # dotted name to a type object
  def dotted_typechild(name)
    names=name.split('.')
    context = self
    while context and names.size > 1
      context = context.child_named(names.shift) 
    end
    return context.typechild(names[0]) if context
  end
  
  # preview mapping - type_ attribute to C++ type
  def lookup_cpptype(name)
    if t = root.lookup_cpptype(name) then return t 
    elsif c = containing_class.typechild(name) then return c.cpptype
    elsif c= root.dotted_typechild(name) then return c.cpptype
    else raise "Cannot resolve type-name #{name} from #{self}" 
    end
  end
  
  def containing_class()
    return self if is_a? AmqpClass
    return parent && parent.containing_class
  end
end


class AmqpField
  def struct?() 
    c=containing_class
    c.struct(type_)
  end
  def cpptype() lookup_cpptype(type_)  or raise "no cpptype #{type_} for field #{self}" end
  def cppname() name.lcaps.cppsafe; end
  def bit?() type_ == "bit"; end
  def signature() cpptype.param+" "+cppname; end

  def fqtypename()
    unless type_.index(".") 
      c=containing_class
      return c.domain(type_).fqtypename if c.domain(type_)
      return c.struct(type_).fqclassname if c.struct(type_)
    end
    return amqp2cpp
  end
  def paramtype()
    /^(int|uint|char|boolean|bit)/ === type_ ? fqtypename : "const #{fqtypename}&"
  end
  def param_default() "=#{fqtypename}()"  end

  # Default value is normally the C++ default but over-ridden in specific cases
  def default_value()
    defval = cpptype.default_value;
    if name == "accept-mode" and parent.name == "transfer" then defval = "1";  end
    return defval
  end
end

class AmqpMethod
  def cppname() name.lcaps.cppsafe; end
  def param_names() fields.map { |f| f.cppname }; end
  def signature() fields.map { |f| f.signature }; end
  def classname() parent.name; end
  def body_name() 
    classname().caps+name.caps+"Body"      
  end
  def cpp_pack_type() root.lookup_cpptype("uint16") end
end

module AmqpHasFields
  def parameters(with_default=nil)
    fields.map { |f|
      "#{f.paramtype} #{f.cppname}_#{f.param_default if with_default}"
    }
  end
  def unused_parameters() fields.map { |f| "#{f.paramtype} /*#{f.cppname}_*/"} end
  def arguments() fields.map { |f| "#{f.cppname}_"} end
  def values() fields.map { |f| "#{f.cppname}"} end
  def initializers() fields.map { |f| "#{f.cppname}(#{f.cppname}_)"}  end
end

class AmqpAction
  def classname() name.typename; end
  def funcname() parent.name.funcname + name.caps; end
  def fqclassname() parent.name+"::"+classname; end
  def full_code() (containing_class.code.hex << 8)+code.hex; end
  include AmqpHasFields
end

class AmqpType
  def cpptype() root.lookup_cpptype(name) end # preview
  def typename() name.typename; end      # new mapping
  def fixed?() fixed_width; end
end

class AmqpCommand < AmqpAction
  def base() "Command";  end
end

class AmqpControl < AmqpAction
  def base() "Control";  end
end

class AmqpClass
  def cppname() name.caps; end  # preview
  def nsname() name.nsname; end
end

class AmqpDomain
  # preview
  def cpptype() lookup_cpptype(type_) end
  def cppname() name.caps; end

  # new mapping
  def fqtypename()
    return containing_class.nsname+"::"+name.typename if containing_class
    name.typename
  end
end

class AmqpResult
  # preview
  def cpptype()
    if type_ then lookup_cpptype(type_)
    else CppType.new(parent.parent.name.caps+parent.name.caps+"Result").passcref
    end
  end
end

class AmqpStruct
  include AmqpHasFields

  @@pack_types={ "1"=>"uint8", "2"=>"uint16", "4"=>"uint32"}
  def cpp_pack_type()           # preview
    root.lookup_cpptype(@@pack_types[pack])
  end 
  def cpptype() CppType.new(cppname).passcref.retcref end
  #def cppname() containing_class.cppname+name.caps;  end
  def cppname()
    if parent.kind_of? AmqpResult
      parent.parent.parent.name.caps+parent.parent.name.caps+"Result"
    else
      name.caps  
    end
  end
  def fqclassname() containing_class.nsname+"::"+name.typename;  end
  def classname() name.typename; end
  def full_code() (containing_class.code.hex << 8)+code.hex; end
end

class CppGen < Generator
  def initialize(outdir, *specs)
    super(outdir,*specs)
    # need to sort classes for dependencies
    @actions=[]                 # Stack of end-scope actions
  end

  # Write a header file. 
  def h_file(path, &block)
    path = (/\.h$/ === path ? path : path+".h")
    guard=path.upcase.tr('./-','_')
    file(path) { 
      gen "#ifndef #{guard}\n"
      gen "#define #{guard}\n"
      gen Copyright
      yield
      gen "#endif  /*!#{guard}*/\n" 
    }
  end

  # Write a .cpp file.
  def cpp_file(path, &block)
    path = (/\.cpp$/ === path ? path : path+".cpp")
    file(path) do
      gen Copyright
      yield
    end
  end

  def include(header)
    header+=".h" unless /(\.h|[">])$/===header
    header="\"#{header}\"" unless /(^<.*>$)|(^".*"$)/===header
    genl "#include #{header}"
  end

  def scope(open="{",close="}", &block)
    genl open
    indent &block
    genl close
  end
  
  def namespace(name, &block) 
    genl
    names = name.split("::")
    names.each { |n| genl "namespace #{n} {" }
    genl "namespace {" if (names.empty?)
    genl
    yield
    genl
    genl('}'*([names.size, 1].max)+" // namespace "+name)
    genl
  end

  def struct_class(type, name, bases, &block)
    gen "#{type} #{name}"
    if (!bases.empty?)
      genl ":"
      indent { gen "#{bases.join(",\n")}" }
    end
    genl
    scope("{","};", &block)
  end

  def struct(name, *bases, &block)
    struct_class("struct", name, bases, &block);
  end
  def cpp_class(name, *bases, &block)
    struct_class("class", name, bases, &block);
  end
  def cpp_extern_class(scope, name, *bases, &block)
    struct_class("class "+scope, name, bases, &block);
  end

  def typedef(type, name) genl "typedef #{type} #{name};\n"; end

  def variant(types) "boost::variant<#{types.join(", ")}>"; end
  def variantl(types) "boost::variant<#{types.join(", \n")}>"; end
  def blank_variant(types) variant(["boost::blank"]+types); end
  def tuple(types) "boost::tuple<#{types.join(', ')}>"; end

  def public() outdent { genl "public:" } end
  def private() outdent { genl "private:" } end
  def protected() outdent { genl "protected:" } end

  # Returns [namespace, classname, filename]
  def parse_classname(full_cname)
    names=full_cname.split("::")
    return names[0..-2].join('::'), names[-1], names.join("/") 
  end

  def doxygen_comment(&block)
    genl "/**"
    prefix(" * ",&block)
    genl " */"
  end

  # Generate code in namespace for each class
  def each_class_ns()
    @amqp.classes.each { |c| namespace(c.nsname) { yield c } }
  end

  def signature(ret_name, params, trailer="")
    if params.size <= 1
      genl ret_name+"(#{params})"+trailer
    else
      scope(ret_name+"(",")"+trailer) { genl params.join(",\n") }
    end
  end
  
  def function_decl(ret_name, params=[], trailer="")
    signature(ret_name, params, trailer+";")
  end

  def function_defn(ret_name, params=[], trailer="")
    genl
    signature(ret_name, params, trailer)
    scope() { yield }
  end

  def ctor_decl(name, params=[]) function_decl(name, params); end
  
  def ctor_defn(name, params=[], inits=[])
    signature(name, params, inits.empty? ? "" : " :")
    indent { gen inits.join(",\n") } if not inits.empty?
    scope() { yield }
  end

  def function_call(name, params=[], trailer="")
    gen name
    list "(",params, ")"
    gen trailer
  end
end

# Fully-qualified class name
class FqClass < Struct.new(:fqname,:namespace,:name,:file)
  def initialize(fqclass)
    names=fqclass.split "::"
    super(fqclass, names[0..-2].join('::'), names[-1], names.join("/"))
  end
end

