# Copyright (C) 2001 Tobias Peters <t-peters@berlios.de>
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA  02111-1307, USA.

class Idl_Module
  attr_reader :name
  attr_reader :constants
  attr_reader :exceptions
  attr_reader :interfaces
  attr_reader :node_constants

  # create a "module" object by parsing the W3C IDL file
  def initialize(istream)
    @name = "dom"
    @constants = []
    @exceptions = []
    @interfaces = []
    @node_constants = []
    
    # load IDL file into memory
    lines = istream.readlines()

    # skip all lines above the module declaration
    nil until (lines.shift =~ /^module dom/)
    lines.shift # skip 1 more line

    # parse exceptions, constants and interfaces
    until (lines.empty?)
      case lines[0]
      when /^\s*exception/       # parse an exception
	@exceptions << Idl_Exception.new(self, lines)
      when /^\s*const/           # parse a constant
	@constants  << Idl_Constant.new(self, nil, lines.shift)
      when /^\s*interface.*\{/   # parse an interface declaration
	@interfaces << Idl_Interface.new(self, lines)
      else
	lines.shift              # skip empty lines and comments
      end
    end
  end

  def get_c_value_name
    C_NAME_PREFIX + "_module"
  end

  def get_rb_name
    "DOM"
  end

  def get_cpp_name
    "DOM"
  end

  # collect all node type constants in a special array
  def add_node_constant(node_constant)
    @node_constants << node_constant
  end

  # The module stores references to all Idl_Interface objects and can thus
  # return such a reference to code which knows the name of this interface
  def get_interface_with_name(name)
    interface = nil
    @interfaces.each {|i|
      if (i.name() == name)
	interface = i
      end
    }
    interface
  end


  ####################
  def create_ruby_code (gdome_ruby_c)
    # We have 5 different areas where code is generated:

    # Here we define the VALUEs that store the class objects and the module
    # object
    @values =         ("                        \n\n\n" +
                       "/*                          \n" +
		       " * VALUE declarations       \n" +
		       " */                         \n")

    # functions for casting ruby DOM objects into their C correspondents
    @conversion_rb2c =("                        \n\n\n" +
                       "/*                          \n" +
		       " * conversion from ruby to c\n" +
		       " */                         \n")

    # casting the other way 'round (wrapping)
    @conversion_c2rb =("                        \n\n\n" +
                       "/*                          \n" +
		       " * conversion from c to ruby\n" +
		       " */                         \n")

    # The C functions that implement the behaviour of the DOM API methods
    @methods =        ("                        \n\n\n" +
                       "/*                          \n" +
		       " * method definitions       \n" +
		       " */                         \n")

    # The function that initializes the class and module VALUEs, defines the
    # constants, and binds the methods to the classes
    @init_func =      ("                        \n\n\n" +
                       "/*                          \n" +
		       " * Init the dom module      \n" +
		       " */                         \n" +
		       "                            \n" +
		       "void Init_gdome()           \n" +
		       "{                           \n")
    
    # the module object
    @values <<
      "static VALUE " + get_c_value_name() + ";\n"

    # the bootstrap method as a method of the module
    @methods <<
      "static VALUE #{C_NAME_PREFIX}_implementation(VALUE self)      \n" +
      "{                                                             \n" +
      "  return #{c2rb_name('DOMImplementation')}(gdome_di_mkref()); \n" +
      "}                                                             \n"

    # an exception raiser
    @conversion_rb2c << "
    static VALUE rbdom_raise(GdomeException e)
    {
      if (e) {
	rb_raise(#{rbdom_class_name('DOMException')}, \"%d\", (int)e);
      }
      return Qnil;
    }\n"

    # initialize the module VALUE, bind the bootstrap method
    @init_func <<
      ("  #{get_c_value_name()} = rb_define_module(\"Dom\");            \n" +
       "                                                                \n" +
       "  rb_define_module_function(#{get_c_value_name()},              \n" +
       "                            \"implementation\",                 \n" +
       "                            &#{C_NAME_PREFIX}_implementation,   \n" +
       "                            0);                                 \n\n")

    # an adapter method
    @methods << <<-EOS
    static char* gdome_di_saveDocToString(GdomeDOMImplementation * di,
					  GdomeDocument * doc,
					  unsigned int mode,
					  GdomeException * e)
    {
      char * return_value = NULL;
      gdome_di_saveDocToMemory(di, doc, &return_value, mode, e);
      return return_value;
    }
    EOS


    # tell objects contained in this module to generate their code

    (@exceptions + @interfaces + @constants).each{|idl_object|
      idl_object.create_ruby_code()
    }

    @init_func << "}\n";

    # write all the code to a file
    [@values, @conversion_rb2c,
      @conversion_c2rb, @methods, @init_func].each {|s|
      gdome_ruby_c.puts (s + "\n\n\n")
    }
  end


  # ruby code is added piece by piece to the member strings
  def add_ruby_code (where, what)
    case (where)
    when "values"
      @values << what
    when "conversion_rb2c"
      @conversion_rb2c << what
    when "conversion_c2rb"
      @conversion_c2rb << what
    when "methods"
      @methods << what
    when "init_func"
      @init_func << what
    else
      raise ("unknown region '#{where}' for code addition")
    end
  end


  ###################
  def create_cpp_code (gdome_cpp_hh, # the header file, open for writing
		       gdome_cpp_cc, # the implementation file, as above
		       extra_declarations) # extra method declarations outside
    #                                        the DOM API

    # tell objects contained in this module to generate their code

    if (@constants.size > 0)
      gdome_cpp_hh << 
	("                                                               \n" +
	 "  /*                                                           \n" +
	 "    Constants: We use enums so that you can use constants as   \n" +
	 "    case labels                                                \n" +
	 "  */                                                           \n" +
	 "  enum {                                                       \n")
      @constants.each{|c|
	c.create_cpp_code(gdome_cpp_hh)
      }
      gdome_cpp_hh << 
	("  };                                                         \n\n")
    end

    gdome_cpp_hh << 
	("                                                               \n" +
	 "  /* Exceptions: */                                            \n")
    @exceptions.each{|e|
      e.create_cpp_code(gdome_cpp_hh, gdome_cpp_cc)
    }

    gdome_cpp_hh << 
	("                                                               \n" +
	 "  /* Forward declarations of all interfaces: */                \n")
    @interfaces.each{|i|
      i.create_forward_declaration(gdome_cpp_hh)
    }

    gdome_cpp_hh << 
	("                                                               \n" +
	 "  /* All interface declarations: */                            \n")
    @interfaces.each{|i|
      i.create_cpp_code(gdome_cpp_hh, gdome_cpp_cc,
			extra_declarations)
    }
  end
end
