# 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_Interface
  attr_reader :name
  attr_reader :parent
  attr_reader :constants
  attr_reader :attributes
  attr_reader :methods
  attr_reader :children
  attr_reader :idl_module

  def to_s
    @name
  end

  # parsing the first IDL interface definition contained in remaining_lines
  def initialize (idl_module,      # a reference to the module object
                  remaining_lines) # the IDL text. the parsed interface
    #                              # definition is removed from this array
    @methods    = []
    @attributes = []
    @constants  = []
    @children   = []
    @idl_module = idl_module

    # extract the name of this interface from the idl text
    if (remaining_lines[0] =~ /:/)
      # 1st line contains colon: inheritance!
      # extract this interface's and its parent's name
      regex = /\s*interface\s+([^\s]+)\s*:\s*([^\s]+)\s*\{/
      match = regex.match(remaining_lines[0])

      # first regex group match contains name of this interface
      @name   = match[1]

      # second regex group match contains name of parent interface. Since the
      # IDL file is in inheritance order, the IDL_Interface instance for the
      # parent inteface has already been created.
      @parent = @idl_module.get_interface_with_name(match[2])

      # for debugging:
      puts "interface #{@name} < #{parent}" if ($verbose)

      # let the parent know there is a child
      @parent.add_child(self)
    else
      # this interface is a base class
      @parent = nil

      # extract this interface's name
      regex = /\s*interface\s+([^\s]+)\s*\{/
      match = regex.match(remaining_lines[0])

      # first regex group match contains name of this interface
      @name = match[1]

      # for debugging:
      puts "interface #{@name}" if ($verbose)
    end

    # parse the interface's contents until the closing brace
    until(remaining_lines[0] =~ /}/)
      case (remaining_lines[0])
      when  /^\s*const/              # parse an interface constant
        @constants << Idl_Constant.new(@idl_module,self, remaining_lines.shift)
      when /\sattribute\s/           # parse an attribute
        @attributes << Idl_Attribute.new(@idl_module, self, remaining_lines)
      when /\/\//                    # skip a comment
        remaining_lines.shift
      when /\(/                      # parse a method (line contains "(")
        @methods << Idl_Method.new(@idl_module, self, remaining_lines)
      else                           # skip blank lines
        remaining_lines.shift
      end
    end
    remaining_lines.shift            # skip line containing closing brace
  end

  # learn that there is a derived class
  def add_child(new_child)
    @children << new_child
  end

  def has_children?()
    @children.size() > 0
  end

  # return true if the "interface" is either equal to this or any of its
  # derived interfaces
  def is_or_has_child?(interface)
    if (@name == interface.to_s())
      return true
    else
      inject = false
      @children.each{|c|
        inject = inject || c.is_or_has_child?(interface)
      }
      return inject
    end
  end

  # if this interface is derived from Node and has an assigned node type
  # constant (abstract interfaces don't have this), return a reference to
  # the object representing this constant, else return nil
  def get_matching_node_type_constant()
    constant = nil
    mangled_name = @name.downcase()
    @idl_module.node_constants().each {|c|
      if (c.name().sub(/_NODE$/, "").gsub(/_/, "").downcase() == mangled_name)
        constant = c
      end
    }
    constant
  end

  # return a list of all interfaces from which this interface is derived,
  # directly or indirectly
  def all_parents
    parent ? (parent.all_parents.push(parent)) : []
  end

  # return a list of all interfaces that derive from this interface, directly
  # or indirectly
  def all_children
    children + children.collect{|child| child.all_children}.flatten
  end

  # return the topmost parent of this interface, or the interface itself if it
  # has no parent
  def top_parent
    parent ? parent.top_parent : self
  end

  # return a list of all interfaces that have a parent in common with this
  # interface, but are not derived from this interface nor is this interface
  # derived from any of them.
  def far_relatives
    near_relatives = all_parents + all_children + [self]
    all_relatives  = top_parent.all_children
    return (all_relatives - near_relatives)
  end


  ####################
  def create_ruby_code()
    # the class object VALUE
    @idl_module.add_ruby_code("values",
                              "static VALUE #{rbdom_class_name(@name)};\n")

    # create the function to extract the gdome pointer from the ruby object:
    if (@parent == nil)
      conversion_rb2c_code =
        ("static #{gdome_class_name(self)} * #{rb2c_name(self)} (VALUE rb){\n" +
         "  #{gdome_class_name(self)} * c = NULL;                          \n" +
         "  if (rb != Qnil) {                                              \n" +
         "    Data_Get_Struct(rb, #{gdome_class_name(self)}, c);           \n" +
         "  }                                                              \n" +
         "  return c;                                                      \n" +
         "}                                                                \n")
    else  # == if (@parent != nil)
      # This interface has a parent. This means, it is derived from Node.
      # Descendants from Node are handled differently:
      conversion_rb2c_code =
        ("static #{gdome_class_name(self)} * #{rb2c_name(self)}(VALUE rb) {\n" +
         "  #{gdome_class_name(self)} * c = NULL;                          \n" +
         "  #{gdome_class_name('Node')} *c_node = #{rb2c_name('Node')}(rb);\n" +
         "  if (c_node != NULL) {                                          \n" +
         "    c = (#{gdome_class_name(self)} *) c_node;                    \n" +
         "  }                                                              \n" +
         "  return c;                                                      \n" +
         "}                                                                \n")
    end
    @idl_module.add_ruby_code("conversion_rb2c", conversion_rb2c_code)



    # create the function that wraps a gdome pointer. We also have to provide a
    # function that is called when the ruby object is garbage collected.
    if ((@parent == nil) && (@name != 'Node'))
      unref_method_name = ("unref_" + gdome_class_name(self)).gsub(/\s/, "")
      conversion_c2rb_code = 
        ("static void #{unref_method_name}(#{gdome_class_name(self)} * c) {\n" +
         "  GdomeException e = GDOME_NOEXCEPTION_ERR;                      \n" +
         "  #{gdome_method_name(self, 'unref')} (c, &e);                   \n" +
         "}                                                                \n")
      conversion_c2rb_code <<
        ("static VALUE #{c2rb_name(self)} (#{gdome_class_name(self)} * c) {\n" +
	 "  VALUE rb_wrapper = Qnil;                                       \n" +
         "  if (c != NULL) {                                               \n" +
         "    rb_wrapper = Data_Wrap_Struct(#{rbdom_class_name(self)},     \n" +
         "                                  NULL, &#{unref_method_name},   \n" +
	 "                                  c);                            \n" +
	 "  }                                                              \n" +
	 " return rb_wrapper;                                              \n" +
         "}                                                               \n\n")
    elsif (@parent != nil) # descendant from Node
      unref_method_name = ("unref_" + gdome_class_name('Node')).gsub(/\s/, "")
      conversion_c2rb_code = 
        ("static VALUE #{c2rb_name(self)} (#{gdome_class_name(self)} * c) {\n" +
         "  #{gdome_class_name('Node')} * c_node =                         \n" +
         "              (#{gdome_class_name('Node')} *) c;                 \n" +
         "  return #{c2rb_name('Node')}(c_node);                           \n" +
         "}                                                               \n\n")
    else # @name=='Node'.  In this case we have to return the most derived type
      unref_method_name = ("unref_" + gdome_class_name(self)).gsub(/\s/, "")
      conversion_c2rb_code = 
        ("static void #{unref_method_name}(#{gdome_class_name(self)} * c) {\n" +
         "  GdomeException e = GDOME_NOEXCEPTION_ERR;                      \n" +
         "  #{gdome_method_name(self, 'unref')} (c, &e);                   \n" +
         "}                                                                \n")
      conversion_c2rb_code <<
        ("static VALUE #{c2rb_name(self)} (#{gdome_class_name(self)} * c) {\n" +
         "  GdomeException e = GDOME_NOEXCEPTION_ERR;                      \n" +
         "  VALUE rbdom_class = Qnil;                                      \n" +
	 "  unsigned short nodeType;                                       \n" +
         "  if (c == NULL) return Qnil;                                    \n" +
	 "  nodeType = #{gdome_method_name(self, 'nodeType')}(c, &e);      \n" +
	 "  if (e != GDOME_NOEXCEPTION_ERR) {                              \n" +
	 "    rbdom_raise(e);                                              \n" +
	 "    return Qnil;                                                 \n" +
	 "  }                                                              \n" +
         "  switch (#{gdome_method_name(self, 'nodeType')}(c, &e)) {       \n")
      @idl_module.node_constants.each {|constant|
        interface = constant.get_matching_interface()
        if (interface)
          conversion_c2rb_code <<
            ("  case #{constant.value}:                                    \n" +
             "    rbdom_class = #{rbdom_class_name(interface)};            \n" +
             "    break;                                                   \n")
        end
      }
      conversion_c2rb_code <<
        ("  }                                                              \n" +
         "  return Data_Wrap_Struct(rbdom_class, NULL,                     \n" +
         "                          &#{unref_method_name}, c);             \n" +
         "}                                                               \n\n")
    end
    @idl_module.add_ruby_code("conversion_c2rb", conversion_c2rb_code)

    # initialize the class object
    ruby_parent_class_name = 
      @parent ? rbdom_class_name(@parent) : "rb_cObject"
    init_func_code =
      ("  #{rbdom_class_name(self)} =                                      \n" +
       "    rb_define_class_under(#{@idl_module.get_c_value_name},         \n" +
       "                          \"#{@name}\", #{ruby_parent_class_name});\n")
    @idl_module.add_ruby_code("init_func", init_func_code)

    # create code for the constants, attributes, and methods of this interface

    @constants.each{|c|
      c.create_ruby_code()
    }
    @attributes.each{|a|
      a.create_ruby_code()
    }

    @methods.each{|m|
      m.create_ruby_code()
    }
    
    # debugging support: ref_count and gdome_id
    debug_methods = 
      ("static VALUE #{rbdom_method_name(self, 'ref_count')} (VALUE self)\n" +
       "{                                                                \n" +
       "  int ref_count = 0;                                             \n" +
       "  #{gdome_class_name(self)} * c =  #{rb2c_name(self)}(self);     \n" +
       "  if (c) ref_count = *(int*)(c+2);                               \n" +
       "  return INT2NUM(ref_count);                                     \n" +
       "}                                                                \n" +
       "static VALUE #{rbdom_method_name(self, 'gdome_id')} (VALUE self) \n" +
       "{                                                                \n" +
       "  #{gdome_class_name(self)} * c =  #{rb2c_name(self)}(self);     \n" +
       "  return INT2NUM(((unsigned long)c) >> 1);                       \n" +
       "}                                                                \n")
    init_func_code =
      ("  rb_define_method(#{rbdom_class_name(self)}, \"ref_count\",     \n" +
       "                   &#{rbdom_method_name(self, 'ref_count')}, 0); \n" +
       "  rb_define_method(#{rbdom_class_name(self)}, \"gdome_id\",      \n" +
       "                   &#{rbdom_method_name(self, 'gdome_id')}, 0);  \n")
    @idl_module.add_ruby_code("methods", debug_methods)
    @idl_module.add_ruby_code("init_func", init_func_code)
  end



  ###################
  def create_cpp_code(gdome_cpp_hh, gdome_cpp_cc, extra_declarations)
    # start class declaration
    gdome_cpp_hh << ("\n" + "class #{@name} ")
    if (@parent)
      gdome_cpp_hh << " : public #{@parent}"
    end
    gdome_cpp_hh << ("\n" + "{\n")

    # create code for the contained elements:
    gdome_cpp_hh << 
      ("public:                                                          \n" +
       "  /* ## Begin of official DOM level 2 core API declarations: */  \n")

    # constants:
    if (@constants.size > 0)
      gdome_cpp_hh << 
	("\n /* Constants (as enums, for possible use in case labels): */\n" +
	 "  enum {                                                       \n")
      @constants.each{|c|
	c.create_cpp_code(gdome_cpp_hh)
      }
      gdome_cpp_hh << 
	("  };                                                           \n")
    end

    # attributes:
    if (@attributes.size > 0)
      gdome_cpp_hh << 
	("\n  /* Attribute accessing methods: */                         \n")
      @attributes.each{|a|
	a.create_cpp_code(gdome_cpp_hh, gdome_cpp_cc)
      }
    end

    # methods:
    if (@methods.size > 0)
      gdome_cpp_hh << 
	("\n  /* DOM interface methods: */                               \n")
      @methods.each{|m|
	m.create_cpp_code(gdome_cpp_hh, gdome_cpp_cc)
      }
    end

    gdome_cpp_hh << 
      ("  /* ## End of official DOM level 2 core API declarations. */\n\n\n")

    # gdome specific methods:
    if (extra_declarations[@name])
      gdome_cpp_hh << 
	("  /* ## Begin of Gdome specific extensions of the DOM API: */  \n" +
	 extra_declarations[@name].to_a.join("\n") + "                   \n" +
	 "  /* ## End of Gdome specific extensions of the DOM API. */\n\n\n")
    end

    # Check for NULL
    gdome_cpp_hh << 
      (  " /* ## Begin of libgdome-cpp specific methods required for object" +
       " handling */                                                     \n")
    if (@parent == nil)
      gdome_cpp_hh << 
        ('    // Check if this object is NULL:                ' + "      \n" +
         "    bool                                                       \n" +
	 "    null(void) const;                                          \n" +
         "    // Another way to check for NULL:                          \n" +
         "    bool                                                       \n" +
	 "    operator==(int) const;                                   \n\n")
      gdome_cpp_cc <<
        ("bool #{self}::null(void) const                                 \n" +
         "{                                                              \n" +
         "  return gdome_obj == 0;                                       \n" +
         "}                                                            \n\n" +
         "bool #{self}::operator==(int i) const                          \n" +
         "{                                                              \n" +
         "  return (i == 0) && null();                                   \n" +
         "}                                                            \n\n")
    end

    # copy constructor and assignment operator
    gdome_cpp_hh << 
      ("    // Copy constructor and assignment operator allow you to push\n" +
       "    // DOM objects around:                                       \n" +
       "    #{self}(const #{self} &);                                    \n" +
       "    #{self} &                                                    \n" +
       "    operator=(const #{self} &);                                \n\n")
    if (@parent)
      gdome_cpp_cc <<
        ("#{self}::#{self}(const #{self} & other)                        \n" +
         "  : #{@parent}(other)                                          \n" +
         "{}                                                           \n\n" +
         "#{self} & #{self}::operator=(const #{self} & other)            \n" +
         "{                                                              \n" +
         "  #{top_parent}::operator=(other);                             \n" +
	 "  return *this;                                                \n" +
         "}                                                            \n\n")
    else
      gdome_cpp_cc <<
        ("#{self}::#{self}(const #{self} & other)                        \n" +
         "  : gdome_obj(other.gdome_obj)                                 \n" +
         "{                                                              \n" +
         "  if (gdome_obj) {                                             \n" +
         "    unsigned short e = 0;                                      \n" +
         "    #{gdome_method_name(self, 'ref')}(gdome_obj, &e);          \n" +
         "  }                                                            \n" +
         "}                                                            \n\n" +
         "#{self} & #{self}::operator=(const #{self} & other)            \n" +
         "{                                                              \n" +
         "  if (this == &other) return *this;                            \n" +
         "  unsigned short e = 0;                                        \n" +
         "  if (gdome_obj) {                                             \n" +
         "    #{gdome_method_name(self, 'unref')}(gdome_obj, &e);        \n" +
         "  }                                                            \n" +
         "  gdome_obj = other.gdome_obj;                                 \n" +
         "  if (gdome_obj) {                                             \n" +
         "    #{gdome_method_name(self, 'ref')}(gdome_obj, &e);          \n" +
         "  }                                                            \n" +
         "  return *this;                                                \n" +
         "}                                                            \n\n")
    end

    # cast from topmost parent to this class
    if (@parent)
      gdome_cpp_hh << 
        ("    // Replacement for dynamic_cast. The following constructor \n" +
	 "    // and assignment operator allow you to downcast an object \n" +
	 "    // from a base type to a more derived type. If the object  \n" +
	 "    // in question is not of the expected type, then it will be\n" +
	 "    // null (see method null and operator==(int))              \n" +
         "    #{self}(#{top_parent} &);                                  \n" +
         "    #{self} &                                                  \n" +
         "    operator=(const #{top_parent} &);                        \n\n")
      gdome_cpp_cc <<
        ("#{self}::#{self}(#{top_parent} & p)                            \n" +
         "  : #{@parent}(p)                                              \n" +
         "{}                                                           \n\n" +
         "#{self} & #{self}::operator=(const #{top_parent} & p)          \n" +
         "{                                                              \n" +
         "  #{top_parent}::operator=(p);                                 \n" +
	 "  return *this;                                                \n" +
         "}                                                            \n\n")
    end

    # the constructor to wrap gdome objects
    gdome_cpp_hh <<
      ("  // Use this constructor only to create 0 objects. You never    \n" +
       "  // need to deal with objects of class #{gdome_class_name(self)}\n" +
       "  // directly. This constructor only enables you to declare null \n" +
       "  // objects on the stack and to use a literal 0 for null objects\n" +
       "  // in some API methods, where a #{self} is expected that may   \n" +
       "  // be null.                                                    \n" +
       "  #{self}(#{gdome_class_name(self)} * = 0);                    \n\n")
    if (@parent)
      gdome_cpp_cc <<
        ("#{self}::#{self}(#{gdome_class_name(self)} * gdome_obj_)       \n" +
         "  : #{@parent}(reinterpret_cast<#{gdome_class_name(@parent)} *>\n" +
         "                                                 (gdome_obj_)) \n" +
         "{}                                                           \n\n")
    else
      gdome_cpp_cc <<
        ("#{self}::#{self}(#{gdome_class_name(self)} * gdome_obj_)       \n" +
         "  : gdome_obj(gdome_obj_)                                      \n" +
         "{}                                                           \n\n")
    end

    # operator==
    if (@parent == nil)
      gdome_cpp_hh <<
	("  // Check if two wrapper objects wrap the same XML object:    \n" +
	 "  bool                                                         \n" +
	 "  operator==(const #{self} &) const;                         \n\n")
      gdome_cpp_cc <<
	("bool #{self}::operator==(const #{self} & other) const          \n" +
	 "{                                                              \n" +
	 "  return gdome_id() == other.gdome_id();                       \n" +
	 "}                                                            \n\n")
    end

    # the destructor is *not* virtual, by intention: it saves the space for the
    # vtable pointer and it is really not needed -- only base classes have data
    # members, derived classes just add methods. Derived classes have empty
    # destructors, wo do not need to specify them:
    if (@parent == nil)
      # interface is a base class, implement destructor:
      gdome_cpp_hh << "  ~#{self}();                                     \n"
      gdome_cpp_cc <<
        ("#{self}::~#{self}()                                            \n" +
         "{                                                              \n" +
         "  GdomeException e = 0;                                        \n" +
         "  #{gdome_method_name(self, 'unref')} (gdome_obj, &e);         \n" +
         "}                                                            \n\n")
    end

    gdome_cpp_hh << 
      (    " /* ## End of libgdome-cpp specific methods required for object" +
       " handling */                                                   \n\n")

    # debugging support: ref_count und gdome_id
    if (@parent == nil)
      gdome_cpp_hh <<
        ("\n  /* ## Begin of debugging support methods */               \n\n" +
	 "  // access to the internal reference count of the wrapped gdome\n" +
	 "  // object:                                                    \n" +
	 "  int                                                           \n" +
         "  ref_count (void) const;                                     \n\n" +
	 "  // an integer id that identifies a wrapped gdome object:      \n" +
         "  int                                                           \n" +
         "  gdome_id (void) const;                                      \n\n" +
	 "  /* ## End of debugging support methods */                   \n\n")

      gdome_cpp_cc <<
        ("int #{self}::ref_count (void) const                             \n" +
         "{                                                               \n" +
         "  return * reinterpret_cast<int *>(get_gdome_obj() + 1);        \n" +
         "}                                                             \n\n" +
         "int #{self}::gdome_id (void) const                              \n" +
         "{                                                               \n" +
         "  return reinterpret_cast<unsigned>(get_gdome_obj()) >> 1;      \n" +
         "}                                                             \n\n")
    end

    gdome_cpp_hh <<
      ("  // All methods of #{self} that may be called by the user are   \n" +
       "  // declared above this line. Following declarations in this    \n" +
       "  // class are required by libgdome-cpp itself only.           \n\n" +
       "private:                                                       \n\n")

    # prohibit clearly forbidden casts, e.g. from Element to Entity. We declare
    # these conversion methods private and then don't implement them. This will
    # give a compilation error.
    if (far_relatives.size > 0)
      gdome_cpp_hh << 
	("  // Here we prohibit clearly forbidden casts. We declare these\n" +
	 "  // conversion methods private and then don't implement them. \n" +
	 "// This will result in a compilation error when trying this cast.\n")
      far_relatives.each{|relative|
	gdome_cpp_hh << 
	  ("    #{self}(#{relative} &);                                  \n" +
	   "    #{self} &                                                \n" +
	   "    operator=(#{relative} &);                                \n")
      }
      gdome_cpp_hh << 
	("  // End of forbidden casts                                  \n\n")
    end

    # only base classes have a pointer to a gdome object
    if (@parent == nil)
      gdome_cpp_hh <<
        ("  // A pointer to the wrapped gdome object                     \n" +
	 "  // (only present in base classes)                            \n" +
         "  #{gdome_class_name(self)} * gdome_obj;                     \n\n")
    end

    # C object accessor:
    gdome_cpp_hh <<
      ("  // An accessor to the pointer to the wrapped gdome object      \n" +
       "  protected:                                                     \n" +
       "    #{gdome_class_name(self)} *                                  \n" +
       "    get_gdome_obj(void) const;                                 \n\n")
    if (parent)
      gdome_cpp_cc <<
        ("#{gdome_class_name(self)} * #{self}::get_gdome_obj(void) const \n" +
         "{                                                              \n" +
         "  return reinterpret_cast<#{gdome_class_name(self)}*>          \n" +
         "                                (#{@parent}::get_gdome_obj()); \n" +
         "}                                                              \n")
    else
      gdome_cpp_cc <<
        ("#{gdome_class_name(self)} * #{self}::get_gdome_obj(void) const \n" +
         "{                                                              \n" +
         "  return gdome_obj;                                            \n" +
         "}                                                              \n")
    end

    # also non-related interface classes need access to the pointer in case
    # they have an object of this class as a parameter to one of their methods
    friend_declarations = ""
    @idl_module.interfaces.each{|i|
      if (i != self)
        needs_to_be_friend = false
        i.methods.each{|m|
          m.parameters.each{|p|
            if (p.ptype == @name)
              needs_to_be_friend = true
            end
          }
        }
        if (needs_to_be_friend)
          friend_declarations << "    friend class #{i};                 \n"
        end
      end
    }
    CPP_EXTRA_FRIENDSHIPS[@name].to_a.each{|i|
      friend_declarations << "    friend class #{i};                     \n"
    }
    if (friend_declarations != "")
      gdome_cpp_hh <<
	("  // These unrelated classes need access to the get_gdome_obj()\n" +
	 "  // call:                                                     \n" +
	 "  /* ## Begin of list of friend classes: */                    \n")
      gdome_cpp_hh << friend_declarations
      gdome_cpp_hh << "    /* ## End of list of friend classes */        \n"
    end

    gdome_cpp_hh << "};\n"
  end

  def create_forward_declaration(gdome_cpp_hh)
    gdome_cpp_hh << "class #{self};\n"
  end
end
