#!/usr/bin/env ruby
#
# 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.
#
# Usage: output_directory xml_spec_file [xml_spec_file...]
# 
$: << '..'
require 'cppgen'

class OperationsInvokerGen < CppGen
  def initialize(chassis, outdir, amqp)
    super(outdir, amqp)
    @chassis=chassis
    @ops="AMQP_#{@chassis.caps}Operations"
    @classname="#{@ops}::Invoker"
    @filename="qpid/framing/#{@chassis.caps}Invoker"
  end

  def methods_on(parent, chassis)
    chassis == "all"  ? parent.methods_ : parent.methods_on(chassis)
  end

  def handler(c) "#{@ops}::#{c.cppname}Handler"; end
  def getter(c) "get#{c.cppname}Handler"; end
  def invoker(c) "#{handler(c)}::Invoker"; end
  def visit_methods(c) methods_on(c, @chassis).select { |m| !m.content } end
  
  
  def handler_visits_cpp(c)
    visit_methods(c).each { |m|
      scope("void #{invoker(c)}::visit(const #{m.body_name}& body) {") {
        if (m.result)
          genl "this->encode(body.invoke(target), result.result);"
        else
          genl "body.invoke(target);"
        end
        genl "result.handled=true;"
      }
    }
  end

  def ops_visits_cpp()
    @amqp.classes.each { |c|
      visit_methods(c).each { |m|
        scope("void #{@classname}::visit(const #{m.body_name}& body) {") {
        genl "#{handler(c)}::Invoker invoker(*target.#{getter(c)}());"
        genl "body.accept(invoker);"
        genl "result=invoker.getResult();"
        }
      }
    }
  end

  def invoker_h(invoker, target, methods)
    return if methods.empty?
    genl
    cpp_extern_class("QPID_COMMON_CLASS_EXTERN", invoker, "public qpid::framing::Invoker") {
      genl "#{target}& target;"
      public
      genl("Invoker(#{target}& target_) : target(target_) {}")
      genl "using MethodBodyDefaultVisitor::visit;"
      methods.each { |m| genl "QPID_COMMON_EXTERN void visit(const #{m.body_name}& body);" }
    }
  end
  
  def generate()
    h_file(@filename) {
      include "qpid/framing/#{@ops}"
      include "qpid/framing/Invoker.h"
      include "qpid/CommonImportExport.h"
      namespace("qpid::framing") {
        # AMQP_*Operations invoker.
        methods=@amqp.classes.map { |c| visit_methods(c).to_a }.flatten
        invoker_h(@classname, @ops, methods) 

        # AMQP_*Operations::*Handler invokers.
        @amqp.classes.each { |c|
          invoker_h(invoker(c), handler(c), visit_methods(c))
        }
      }
    }

    cpp_file(@filename) {
      include @filename
      @amqp.classes.each { |c|
        visit_methods(c).each { |m|
          include "qpid/framing/#{m.body_name}"
        }}
      namespace("qpid::framing") {
        ops_visits_cpp
        @amqp.classes.each { |c|
          next if visit_methods(c).empty?
          handler_visits_cpp(c)
        }
      }
    }
  end
end

OperationsInvokerGen.new("client",$outdir, $amqp).generate()
OperationsInvokerGen.new("server",$outdir, $amqp).generate()
OperationsInvokerGen.new("all",$outdir, $amqp).generate()
