# 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_Method
  attr_reader :interface
  attr_reader :name
  attr_reader :return_type
  attr_reader :parameters
  attr_reader :idl_module

  def raises_exception?
    @raises_exception
  end

  def to_s
    @name
  end

  def initialize(idl_module, interface, remaining_lines,
		 attribut_method = false)
    @idl_module = idl_module
    @interface = interface
    @attribut_method = attribut_method
    @raises_exception = false
    @parameters = []

    # extract return type and method name from declaration
    match = /\s*([^\s].*[^\s])\s+([^\s]+)\s*\((.*)/.match(remaining_lines[0])
    @name = match[2]
    @return_type = match[1]
    # cut first of remaining lines to the part behind the opening "(":
    remaining_lines[0] = match[3]
    
    if (not attribut_method)
      puts("Method #{@return_type} #{interface.name()}::#{@name}") if ($verbose)
    end

    # read parameters
    current_line = nil
    begin
      current_line = remaining_lines[0]

      # extract parameter type and name if there is another parameter
      match = /in\s+([^\s].*[^\s])\s+([^\s]+)[,\)]/.match(remaining_lines[0])
      if (match)
	@parameters << Idl_Parameter.new(@idl_module,
					 match[1], match[2], attribut_method)
      end
      remaining_lines.delete_at(0)
    end while ((current_line =~ /\)/) == nil) # stop when ")" closes params list

    # read exception
    if (remaining_lines[0] =~ /\s+raises\s*\(/)
      @raises_exception = true
      remaining_lines.delete_at(0)
      puts("... raises exception") if ((attribut_method == false) && $verbose)
    end
  end


  ####################
  def create_ruby_code()    
    # The start of the function definition that implements this method's
    # functionality. More parameters are added to this C function's
    # parameters list as we iterate over the @parameters of this method
    methods_code =
      ("static VALUE #{rbdom_method_name(@interface, self)} (VALUE self ")

    # local variable definitions. These will be placed directly after the
    # opening brace of the C function block. More local variables (C equivalents
    # of the ruby parameters) will be added to this list as we iterate over the
    # @parameters of this method
    c_definitions = 
      ("  VALUE rb_return_value = Qnil;                                  \n" +
       "  GdomeException e = GDOME_NOEXCEPTION_ERR;                      \n" +
       "  #{gdome_class_name(@interface)} * c_self =                     \n" +
       "            #{rb2c_name(@interface)}(self);                      \n")
    
    # the call to the gdome function that implements this method. We will also
    # add more parameters here.
    c_call = gdome_method_name(@interface, self) + "(c_self, "

    # when we translate ruby strings to GdomeDOMStrings for a gdome function
    # call, then we will need to unref these strings again after the gdome
    # function call took place. We will place the unref statements in this
    # string as we iterate over the @parameters and find DOMString parameters
    c_unref_domstrings = ""

    # Now iterate over the parameters and do all these additions
    @parameters.each {|p|
      methods_code <<  (",\n   VALUE rb_#{p.name}")

      # conversion to C depending on the type of the parameter
      case p.ptype
      when "boolean"
	c_definitions <<
	  ("  GdomeBoolean c_#{p.name} =                                 \n" +
	   "                    !(rb_#{p.name} == Qfalse                 \n" +
           "                      || rb_#{p.name} == Qnil);              \n")
      when "unsigned long"
	c_definitions <<
	  ("  unsigned long c_#{p.name} = NUM2ULONG(rb_#{p.name});       \n")
      when "unsigned short", "unsigned int"
	c_definitions <<
	  ("  #{p.ptype} c_#{p.name} = NUM2UINT(rb_#{p.name});           \n")
      when "DOMString"
	c_definitions <<
	  ("  #{gdome_class_name(p.ptype)} * c_#{p.name} =               \n" +
	   "            (rb_#{p.name} == Qnil) ? NULL :                  \n" +
	   "            gdome_str_mkref_dup(STR2CSTR(rb_#{p.name}));     \n")
	# A DOMString needs to be unref'd after the call
	c_unref_domstrings <<
	  ("  if (c_#{p.name}) gdome_str_unref(c_#{p.name});             \n")
      when "string"
      c_definitions <<
        ("  char * c_#{p.name} =                                       \n" +
         "            (rb_#{p.name} == Qnil) ? NULL :                  \n" +
         "            STR2CSTR(rb_#{p.name});                          \n")
      else
	c_definitions <<
	  ("  #{gdome_class_name(p.ptype)} * c_#{p.name} =               \n" +
	   "                    #{rb2c_name(p.ptype)}(rb_#{p.name});     \n")
      end
      c_call <<
	("  c_#{p.name},")
    }
    # close parameters lists
    methods_code << ")\n"
    c_call  << " &e)"
    
    # place the locale variable definitions at the start of the function
    methods_code <<
      ("{                                                                \n" +
       c_definitions                                                        )

    # place the call to the gdome function and convert the return value to ruby
    case @return_type
    when "void"
      methods_code <<
	("  #{c_call};                                                   \n")
    when "boolean"
      methods_code <<
	("  rb_return_value = #{c_call} ? Qtrue : Qfalse;                \n")
    when "unsigned short", "unsigned long"
      methods_code <<
	("  rb_return_value = INT2NUM(#{c_call});                        \n")
    when "DOMString"
      methods_code <<
	("  {                                                            \n" +
         "    GdomeDOMString * ds = #{c_call};                           \n" +
	 "    rb_return_value = ds ? rb_str_new2(ds->str) : Qnil;        \n" +
         "    if (ds) gdome_str_unref(ds);                               \n" +
         "  }                                                            \n")
    when "string"
      methods_code <<
      ("  {                                                              \n" +
       "    char * cs = #{c_call};                                       \n" +
       "    rb_return_value = cs ? rb_str_new2(cs) : Qnil;               \n" +
       "    if (cs) free(cs);                                            \n" +
       "  }                                                              \n")
    else
      methods_code <<
	("  rb_return_value = #{c2rb_name(@return_type)}(#{c_call});     \n")
    end

    # unref all GdomeDOMString parameters
    methods_code <<
      c_unref_domstrings

    # Check if an exception was raised
    if (raises_exception? || "ALWAYS CHECK FOR EXCEPTIONS")
      methods_code <<
	("  if (e != GDOME_NOEXCEPTION_ERR) {                            \n" +
	 "    rb_raise(#{rbdom_class_name('DOMException')},              \n" +
	 "             \"%d\", (int)e);                                  \n" +
	 "  }                                                            \n")
    end

    methods_code << 
      ("  return rb_return_value;                                        \n" +
       "}                                                                \n")

    # some ruby-ization of method names
    ruby_method_name = @name
    if (@attribut_method && (@parameters.size() == 1) &&
	(ruby_method_name =~ /^set_/))
      ruby_method_name = ruby_method_name.sub(/^set_/, "") + "="
    elsif (@return_type == "boolean")
      ruby_method_name += "?"
    end

    # bind this method to its class
    init_func_code = 
      ("  rb_define_method(#{rbdom_class_name(@interface)},              \n" +
       "                   \"#{ruby_method_name}\",                      \n" +
       "                   &#{rbdom_method_name(@interface, self)},      \n" +
       "                   #{@parameters.size()});                       \n")

    @idl_module.add_ruby_code("methods", methods_code)
    @idl_module.add_ruby_code("init_func", init_func_code)
  end





  ###################
  def create_cpp_code(gdome_cpp_hh, gdome_cpp_cc)
    # This does basically the same as the create_ruby_code method, only for
    # C++. see implementation comments there for explanations
    gdome_cpp_hh << "   #{cpp_type(@return_type)}\n"
    declaration =  "    #{self}("
    definition  =  "#{cpp_type(@return_type)} #{@interface}::#{self}("
    locals = 
      ("  GdomeException e = 0;                                          \n" +
       "  #{gdome_class_name(@interface)} * c_self = get_gdome_obj();    \n")

    c_call = gdome_method_name(@interface, self) + "(c_self, "
    c_unref_domstrings = ""
    separator = ""
    @parameters.each {|p|
      # determine wether we pass the parameter by value or by reference
      if (Gdome_ABBREVIATIONS[p.ptype] == nil)
	p_ref = ""
      else
	p_ref = "&"
      end
      p_ref = ("const " + p_ref) if (p.ptype == 'DOMString')
	
      # commando back. Using references would save us some copying, but it
      # would mostly imply const references, otherwise it would be very
      # difficult to pass NULL objects here and there (Compiler would say
      # initialization of non-const reference type `class Gdome::DocumentType &'
      # from rvalue of type `Gdome::DocumentType' and the like).
      p_ref = "" # all parameters are passed by value.

      declaration <<  ("#{separator}#{cpp_type(p.ptype)} #{p_ref} #{p.name}")
      definition  <<  ("#{separator}#{cpp_type(p.ptype)} #{p_ref} #{p.name}")
      separator = ", "
      c_call      <<  ("c_#{p.name}#{separator}")
      if (p.ptype == "boolean")
	locals <<
	  ("  GdomeBoolean c_#{p.name} = #{p.name};                      \n")
      elsif ((p.ptype == "unsigned long") || (p.ptype == "unsigned short"))
	locals <<
	  ("  #{cpp_type(p.ptype)} c_#{p.name} = #{p.name};              \n")
      elsif (p.ptype == "DOMString")
	locals <<
	  ("  GdomeDOMString * c_#{p.name} =                             \n" +
	   "      gdome_str_mkref_dup(#{p.name}.gc_str());               \n")
	c_unref_domstrings <<
	  ("  if (c_#{p.name}) gdome_str_unref(c_#{p.name});             \n")
      else
	locals <<
	  ("  #{gdome_class_name(p.ptype)} * c_#{p.name} =               \n" +
	   "                                 #{p.name}.get_gdome_obj();  \n")
      end
    }
    if (raises_exception?) 
      declaration << ")"
      definition  << ")\n  throw (DOMException)\n"
    else
      declaration << ");"
      definition  << ")\n"
    end
    c_call      << "&e)"
    
    definition  <<
      ("{                                                                \n" +
       locals                                                               )

    if (@return_type == "void")
      definition <<
	("  #{c_call};                                                   \n")
    elsif ((@return_type == "boolean") ||
	   (@return_type == "unsigned short") ||
	   (@return_type == "unsigned long"))
      definition <<
	("  #{cpp_type(@return_type)} return_value = #{c_call};          \n")
    elsif (@return_type == "DOMString")
      definition <<
	("  GdomeDOMString * ds = #{c_call};                             \n" +
	 "  DOMString return_value = ds ? ds->str :                      \n" +
	 "                           static_cast<const char *>(0);       \n" +
         "  if (ds) gdome_str_unref(ds);                                 \n")
    else
      definition <<
	("  #{cpp_type(@return_type)} return_value(                      \n" +
	 "    #{c_call}                                                  \n" +
	 "    );                                                         \n")
    end
    definition <<
      c_unref_domstrings

    if (raises_exception?) 
      definition  <<
	("  if (e != 0) throw DOMException(e,\"#{@interface}::#{self}\");\n")
    end
    
    definition  << "  return return_value; \n"    if (@return_type != "void")
    definition  << "}                                                    \n"

    while (declaration.length > 80)
      breakpoint = declaration.rindex(",", 79) + 1
      gdome_cpp_hh << (declaration[0,breakpoint] + "\n")
      declaration = "            " + declaration[breakpoint, 1000]
    end
    if (raises_exception?) 
      declaration << "\n      throw (DOMException);"
    end

    gdome_cpp_hh << declaration + "\n\n"
    gdome_cpp_cc << definition
  end
end
