require 'rgen/metamodel_builder'
require 'rgen/instantiator/abstract_instantiator'
require 'nokogiri'

module RGen

module Instantiator

class NodebasedXMLInstantiator < AbstractInstantiator
  
  class << self

    # The prune level is the number of parent/children associations which
    # is kept when the instantiator ascents the XML tree.
    # If the level is 2, information for the node's children and the childrens'
    # children will be available as an XMLNodeDescriptor object.
    # If the level is 0 no pruning will take place, i.e. the whole information
    # is kept until the end of the instantiation process. 0 is default.
    def set_prune_level(level)
      @prune_level = level
    end

    def prune_level # :nodoc:
      @prune_level ||= 0
    end

  end

  class XMLNodeDescriptor
    attr_reader :namespace, :qtag, :prefix, :tag, :parent, :attributes, :chardata
    attr_accessor :object, :children
    
    def initialize(ns, qtag, prefix, tag, parent, children, attributes)
      @namespace, @qtag, @prefix, @tag, @parent, @children, @attributes = 
        ns, qtag, prefix, tag, parent, children, attributes
      @parent.children << self if @parent
      @chardata = []
    end
  end
  
  class Visitor < Nokogiri::XML::SAX::Document
    attr_reader :namespaces
      
    def initialize(inst)
      @instantiator = inst
      @namespaces = {}
    end
    
    def start_element_namespace(tag, attributes, prefix, uri, ns)
      ns.each{|n| @namespaces[n[0]] = n[1]}
      attrs = {}
      attributes.each{|a| attrs[a.prefix ? a.prefix+":"+a.localname : a.localname] = a.value}
      qname = prefix ? prefix+":"+tag : tag
      @instantiator.start_element(uri, qname, prefix, tag, attrs) 
    end
    
    def end_element(name)
      @instantiator.end_element
    end
    
    def characters(str)
      @instantiator.on_chardata(str)
    end
  end
  
  def initialize(env)
    super
    @env = env
    @stack = []
  end
  
  def instantiate_file(file)
    File.open(file) { |f| parse(f.read)}
    resolve
  end
  
  def instantiate(text)
    parse(text)
    resolve
  end
    
  def parse(src)
    @visitor = Visitor.new(self)
    parser = Nokogiri::XML::SAX::Parser.new(@visitor)
    parser.parse(src)
    @visitor = nil
  end  
    
  def start_element(ns, qtag, prefix, tag, attributes)
    node = XMLNodeDescriptor.new(ns, qtag, prefix, tag, @stack[-1], [], attributes)
    @stack.push node
    on_descent(node)
  end
  
  def end_element
    node = @stack.pop
    on_ascent(node)
    prune_children(node, self.class.prune_level - 1) if self.class.prune_level > 0
  end
  
  def on_chardata(str)
    node = @stack.last
    node.chardata << str
  end

  # This method is called when the XML parser goes down the tree.
  # An XMLNodeDescriptor +node+ describes the current node.
  # Implementing classes must overwrite this method.
  def on_descent(node)
    raise "Overwrite this method !"
  end
  
  # This method is called when the XML parser goes up the tree.
  # An XMLNodeDescriptor +node+ describes the current node.
  # Implementing classes must overwrite this method.
  def on_ascent(node)
    raise "Overwrite this method !"
  end

  def namespaces
    @visitor.namespaces if @visitor
  end
    
  private
  
  def prune_children(node, level)
    if level == 0
      node.children = nil
    else
      node.children.each { |c| prune_children(c, level-1) }
    end
  end          
end

end

end
