#!/usr/bin/env ruby
# Usage: output_directory xml_spec_file [xml_spec_file...]
# 
$: << '..'
require 'cppgen'

class StructGen < CppGen

  def initialize(outdir, amqp)
    super(outdir, amqp)
  end

  EncodingMap={
    "octet"=>"Octet",
    "short"=>"Short",
    "long"=>"Long",
    "longlong"=>"LongLong",
    "longstr"=>"LongString",
    "shortstr"=>"ShortString",
    "timestamp"=>"LongLong",
    "uuid"=>"ShortString",#FIXME
    "table"=>"FieldTable",
    "content"=>"Content",
    "long-struct"=>"LongString"
  }
  SizeMap={
    "octet"=>"1",
    "short"=>"2",
    "long"=>"4",
    "longlong"=>"8",
    "timestamp"=>"8"
  }

  ValueTypes=["octet", "short", "long", "longlong", "timestamp"]

  def printable_form(f)
    if (f.cpptype == "u_int8_t")
      return "(int) " + f.cppname
    else
      return f.cppname
    end
  end

  def generate_encode(f, combined)
    if (f.field_type == "bit")
      genl "uint8_t #{f.cppname}_bits = #{f.cppname};"
      count = 0
      combined.each { |c| genl "#{f.cppname}_bits |= #{c.cppname} << #{count += 1};" }
      genl "buffer.putOctet(#{f.cppname}_bits);"
    else
      encoded = EncodingMap[f.field_type]
      if (encoded)
        genl "buffer.put#{encoded}(#{f.cppname});"
      else 
        genl "#{f.cppname}.encode(buffer);"
      end
    end
  end

  def generate_decode(f, combined)
    if (f.field_type == "bit")
      genl "uint8_t #{f.cppname}_bits = buffer.getOctet();"
      genl "#{f.cppname} = 1 & #{f.cppname}_bits;"
      count = 0
      combined.each { |c| genl "#{c.cppname} = (1 << #{count += 1}) & #{f.cppname}_bits;" }
    else
      encoded = EncodingMap[f.field_type]
      if (encoded)
        if (ValueTypes.include?(f.field_type))
          genl "#{f.cppname} = buffer.get#{encoded}();"
        else
          genl "buffer.get#{encoded}(#{f.cppname});"
        end
      else 
        genl "#{f.cppname}.decode(buffer);"
      end
    end
  end

  def generate_size(f, combined)
    if (f.field_type == "bit")
      names = ([f] + combined).collect {|g| g.cppname}
      genl "+ 1 //#{names.join(", ")}"
    else
      size = SizeMap[f.field_type]
      if (size)
        genl "+ #{size} //#{f.cppname}"
      elsif (f.cpp_member_type == "SequenceNumberSet")
        genl "+ #{f.cppname}.encodedSize()"
      else 
        encoded = EncodingMap[f.field_type]        
        gen "+ 4 " if encoded == "LongString"
        gen "+ 1 " if encoded == "ShortString"
        genl "+ #{f.cppname}.size()"
      end
    end
  end

  def process_fields(s)
    last = nil
    count = 0  
    bits = []
    s.fields.each { 
      |f| if (last and last.bit? and f.bit? and count < 7) 
            count += 1
            bits << f
          else
            if (last and last.bit?)
              yield last, bits
              count = 0
              bits = []
            end
            if (not f.bit?)
              yield f
            end
            last = f
          end
    }
    if (last and last.bit?)
      yield last, bits
    end
  end

  def methodbody_extra_defs(s)
    gen <<EOS
    using  AMQMethodBody::accept;
    void accept(MethodBodyConstVisitor& v) const { v.visit(*this); }

    inline ClassId amqpClassId() const { return CLASS_ID; }
    inline MethodId amqpMethodId() const { return METHOD_ID; }
EOS
    if (s.is_server_method?)
      gen <<EOS
    void invoke(AMQP_ServerOperations& target)
    {
        target.get#{s.amqp_parent.cppname}Handler()->#{s.cppname}
            (            
EOS
      if (s.amqp_parent.name == "message" && (s.name == "transfer" || s.name == "append"))
        indent(4) { genl "*this" }
      else
        indent(4) { genl s.param_names.join(",\n") }
      end
    
      genl <<EOS
            );
    }

    bool invoke(Invocable* target)
    {
        AMQP_ServerOperations::#{s.amqp_parent.cppname}Handler* ptr 
            = dynamic_cast<AMQP_ServerOperations::#{s.amqp_parent.cppname}Handler*>(target);
        if (ptr) {
            ptr->#{s.cppname}(
EOS
      if (s.amqp_parent.name == "message" && (s.name == "transfer" || s.name == "append"))
        indent(5) { genl "*this" }
      else
        indent(5) { genl s.param_names.join(",\n") }
      end

      gen <<EOS
                    );
            return true;
        } else {
            return false;
        }
    }
EOS
      end
  end

  def define_constructor(name, s)
    if (s.fields.size > 0)
      genl "#{name}("
      if (s.kind_of? AmqpMethod)
        indent {gen "ProtocolVersion, "}
      end
      indent { gen s.fields.collect { |f| "#{f.cpptype} _#{f.cppname}" }.join(",\n") }
      gen ")"
      genl ": " if s.fields.size > 0
      indent { gen s.fields.collect { |f| " #{f.cppname}(_#{f.cppname})" }.join(",\n") }
      genl " {}"
    end
    if (s.kind_of? AmqpMethod)
      genl "#{name}(ProtocolVersion=ProtocolVersion()) {}"
    end
  end

  def define_accessors(f)
    genl "void set#{f.name.caps}(#{f.cpptype} _#{f.cppname}) { #{f.cppname} = _#{f.cppname}; }"
    genl "#{f.cpptype} get#{f.name.caps}() const { return #{f.cppname}; }"
  end

  def define_struct(s)
    classname = s.cppname
    inheritance = ""
    if (s.kind_of? AmqpMethod)
      classname = s.body_name
      inheritance = ": public AMQMethodBody"
    end

    h_file("qpid/framing/#{classname}.h") { 
      if (s.kind_of? AmqpMethod)
        gen <<EOS
#include "qpid/framing/AMQMethodBody.h"
#include "qpid/framing/AMQP_ServerOperations.h"
#include "qpid/framing/MethodBodyConstVisitor.h"
EOS
      end

      #need to include any nested struct definitions
      s.fields.select {|f| f.defined_as_struct }.map {|f| include f.name.caps}

      gen <<EOS

#include <ostream>
#include "qpid/framing/amqp_types_full.h"

namespace qpid {
namespace framing {

class #{classname} #{inheritance} {
EOS
  indent { s.fields.each { |f| genl "#{f.cpp_member_type} #{f.cppname};" } }
  genl "public:"
  if (s.kind_of? AmqpMethod)
    indent { genl "static const ClassId CLASS_ID = #{s.amqp_parent.index};" }
    indent { genl "static const MethodId METHOD_ID = #{s.index};" }
  end

  if (s.kind_of? AmqpStruct)
    if (s.type)
      if (s.result?)
        #as result structs have types that are only unique to the
        #class, they have a class dependent qualifier added to them
        #(this is inline with current python code but a formal
        #solution is expected from the WG
        indent { genl "static const uint16_t TYPE = #{s.type} + #{s.amqp_parent.amqp_parent.index} * 256;" } 
      else
        indent { genl "static const uint16_t TYPE = #{s.type};" } 
      end
    end
  end

  indent { 
    define_constructor(classname, s)
    genl ""
    s.fields.each { |f| define_accessors(f) } 
  }
  if (s.kind_of? AmqpMethod)
    methodbody_extra_defs(s)
  end
  if (s.kind_of? AmqpStruct)
    indent {genl "friend std::ostream& operator<<(std::ostream&, const #{classname}&);" }
  end

  gen <<EOS
    void encode(Buffer&) const;
    void decode(Buffer&, uint32_t=0);
    uint32_t size() const;
    void print(std::ostream& out) const;
}; /* class #{classname} */

}}
EOS
    }
    cpp_file("qpid/framing/#{classname}.cpp") { 
      if (s.fields.size > 0)
        buffer = "buffer"
      else
        buffer = "/*buffer*/"
      end
      gen <<EOS
#include "#{classname}.h"

using namespace qpid::framing;

void #{classname}::encode(Buffer& #{buffer}) const
{
EOS
  indent { process_fields(s) { |f, combined| generate_encode(f, combined) } } 
  gen <<EOS
}

void #{classname}::decode(Buffer& #{buffer}, uint32_t /*size*/)
{
EOS
  indent { process_fields(s) { |f, combined| generate_decode(f, combined) } } 
  gen <<EOS
}

uint32_t #{classname}::size() const
{
    return 0
EOS
  indent { process_fields(s) { |f, combined| generate_size(f, combined) } } 
  gen <<EOS
        ;
}

void #{classname}::print(std::ostream& out) const
{
    out << "#{classname}: ";
EOS
  copy = Array.new(s.fields)
  f = copy.shift
  
  indent { 
    genl "out << \"#{f.name}=\" << #{printable_form(f)};" if f
    copy.each { |f| genl "out << \"; #{f.name}=\" << #{printable_form(f)};" } 
  } 
  gen <<EOS
}
EOS

  if (s.kind_of? AmqpStruct)
    gen <<EOS
namespace qpid{
namespace framing{

    std::ostream& operator<<(std::ostream& out, const #{classname}& s) 
    {
      s.print(out);
      return out;
    }

}
}
EOS
  end 

}
  end

  def generate()
    @amqp.amqp_structs.each { |s| define_struct(s) }
    @amqp.amqp_methods.each { |m| define_struct(m) }
    #generate a single include file containing the list of structs for convenience
    h_file("qpid/framing/amqp_structs.h") { @amqp.amqp_structs.each { |s| genl "#include \"#{s.cppname}.h\"" } }
  end
end

StructGen.new(ARGV[0], Amqp).generate()

