require 'rgen/instantiator/nodebased_xml_instantiator'

module RGen

module Instantiator

# A default XML instantiator.
# Derive your own instantiator from this class or use it as is.
# 
class DefaultXMLInstantiator < NodebasedXMLInstantiator
  include Util::NameHelper

  NamespaceDescriptor = Struct.new(:prefix, :target)
  
  class << self
  
    def map_tag_ns(from, to, prefix="")
      tag_ns_map[from] = NamespaceDescriptor.new(prefix, to)
    end
    
    def tag_ns_map # :nodoc:
      @tag_ns_map ||={}
      @tag_ns_map
    end
    
  end  
  
  def initialize(env, default_module, create_mm=false)
    super(env)
    @default_module = default_module
    @create_mm = create_mm
  end
    
  def on_descent(node)
    obj = new_object(node)
    @env << obj unless obj.nil?
    node.object = obj
    node.attributes.each_pair { |k,v| set_attribute(node, k, v) }
  end

  def on_ascent(node)
    node.children.each { |c| assoc_p2c(node, c) }
    node.object.class.has_attr 'chardata', Object unless node.object.respond_to?(:chardata)
    set_attribute(node, "chardata", node.chardata)
  end
  
  def class_name(str)
    saneClassName(str)
  end
  
  def new_object(node)
    ns_desc = self.class.tag_ns_map[node.namespace]
    class_name = class_name(ns_desc.nil? ? node.qtag : ns_desc.prefix+node.tag)
    mod = (ns_desc && ns_desc.target) || @default_module
    build_on_error(NameError, :build_class, class_name, mod) do
      begin
        mod.const_get(class_name, false).new
      rescue ArgumentError
        # Ruby 1.8
        mod.const_get(class_name).new
      end
    end
  end
  
  def build_class(name, mod)
    mod.const_set(name, Class.new(RGen::MetamodelBuilder::MMBase))
  end
  
  def method_name(str)
    saneMethodName(str)
  end

  def assoc_p2c(parent, child)
    return unless parent.object && child.object
    method_name = method_name(className(child.object))
    build_on_error(NoMethodError, :build_p2c_assoc, parent, child, method_name) do
      parent.object.addGeneric(method_name, child.object)
      child.object.setGeneric("parent", parent.object)
    end
  end
  
  def build_p2c_assoc(parent, child, method_name)
    parent.object.class.has_many(method_name, child.object.class)
    child.object.class.has_one("parent", RGen::MetamodelBuilder::MMBase)
  end
  
  def set_attribute(node, attr, value)
    return unless node.object
      build_on_error(NoMethodError, :build_attribute, node, attr, value) do
      node.object.setGeneric(method_name(attr), value)
    end
  end
  
  def build_attribute(node, attr, value)
    node.object.class.has_attr(method_name(attr))
  end

  protected
  
  # Helper method for implementing classes.
  # This method yields the given block.
  # If the metamodel should be create automatically (see constructor)
  # rescues +error+ and calls +builder_method+ with +args+, then 
  # yields the block again.
  def build_on_error(error, builder_method, *args)
    begin
      yield
    rescue error
      if @create_mm
        send(builder_method, *args)
        yield
      else
        raise
      end
    end
  end

end

end

end
