# -*- ruby -*-
#
# XTemplate
#

require 'xtemplate/util'
require 'xtemplate/xpath'
require 'xtemplate/node'
require 'xtemplate/xml'
begin
  require 'xtemplate_ext'
rescue LoadError
end

module XTemplate
  def warn(*msg)
    $stderr.puts(msg.join)
  end

  class XMLTemplate
    attr_reader :node
    protected :node

    def initialize(doc, templates = nil)
      case doc
      when XNode
	@node = doc
	@templates = templates || {}
      else
	parser = XMLParser.new
	@node = parser.parse(doc)
	@templates = templates || {}
	@node.prepare(nil, nil, @templates)
      end
    end

    def dup()
      self.class.new(@node.deep_dup, @templates)
    end
    alias clone dup

    def [](name)
      node = @templates[name]
      if( node )
	self.class.new(node)
      else
	nil
      end
    end

    def []=(name, template)
      node = @templates[name]
      node.children = []
      if( template )
	[template.node.children].flatten.each{|child|
	  node.add_child(child)
	}
      end
      node
    end

    def <<(template)
      @node.add_child(template.node)
      self
    end

    def to_s
      @node.to_s
    end

    def dump()
      [Marshal.dump(self)].pack("m")
    end

    def XMLTemplate.load(str)
      if( str.respond_to?(:read) )
	str = str.read
      end
      Marshal.load(str.unpack("m")[0])
    end

    DEFAULT_OPTS = {
      :keep_template => true,
      :keep_data     => false,
      :plugin        => XPath::Action.new,
    }

    def expand(data, opts = nil)
      s = SanitizedString['']
      expand2(s, data, opts)
      s
    end

    def expand2(io, data, opts = nil)
      if( opts )
	DEFAULT_OPTS.each{|key,val|
	  opts[key] ||= val
	}
      else
	opts = DEFAULT_OPTS
      end
      if( opts[:keep_template] )
	node = @node.deep_dup()
      else
	node = @node
      end
      if( opts[:keep_data] )
	data = XTemplate::dup_data(data)
      end
      if( defined?(data.to_hash) )
	data = data.to_hash
      end
      node.expand(data, nil, data, opts[:plugin]) # (1)current data (2)parent data (3)root data
      node.dump(io)
    end
  end

  module_function

  def use_simple_expand()
    XNode::use_simple_expand()
  end

  def use_default_expand()
    XNode::use_default_expand()
  end

  def use_simple_xpath()
    XPath::use_simple_xpath()
  end

  def use_default_xpath()
    XPath::use_default_xpath()
  end

  def dup_data(data, oids=[])
    if( oids.include?(data.id) )
      data
    else
      oids.push(data.id)
      case data
      when Hash
	data = data.dup
	data.each{|key,val| data[key] = dup_data(val,oids)}
	data
      when Array
	data = data.dup
	data.collect!{|val| dup_data(val,oids) }
	data
#      when Numeric, TrueClass, FalseClass  # immutable objects
#        data
      else
        begin
          data.dup
        rescue TypeError
          data
        end
      end
    end
  end
  
  EscapedString = SanitizedString
  Template = XMLTemplate
  TextNode = XPath::TextNode
end
