require 'rexml/document'
require 'rexml/streamlistener'
require 'facet/class/by_name'

require 'nitro/element'
require "glue/html"


module Nitro

# A compiler that handles the processing of Elements

class Elements # :nodoc: all
  
  class Listener # :nodoc: all
    include REXML::StreamListener
    
    attr_accessor :buffer
    attr_accessor :stack
    
    def initialize(compiler)
      super()
      @compiler = compiler
      @buffer = ''
      @stack = []
    end
    
    PREFIX_RE = /^#{Element.prefix}:/
    CAPITALIZED_RE = /^[A-Z]/
    
    def tag_start(name, attributes)    
      # check if the name starts with the element prefix, or
      # is capitalized.
      
      if klass = is_element?(name)

        obj = klass.new 
        attributes.each do | k, v | 
          obj.instance_variable_set("@#{k}", v)
        end        
          
        @stack.push [obj, @buffer, @parent]
          
        @buffer = obj._text
        @parent.add_child(obj) if @parent
          
        @parent = obj

      else # This is a static element.
        attrs = []
        
        attributes.each do | k, v | 
          attrs << %|#{k}="#{v}"|
        end
        
        attrs = attrs.empty? ? nil : " #{attrs.join(' ')}"
        
        @buffer << "<#{name}#{attrs}>"
      end
    end

    def tag_end(name)    
      # check if the name starts with the element prefix, or
      # is capitalized.
      if is_element? name
        obj, @buffer, @parent = @stack.pop
        @buffer << obj.render
      else
        @buffer << "</#{name}>"
      end
    end


    # Check if a tag is a Nitro::Element. If found, it also tries to
    # auto-extend the klass.
    #
    # returns the Element class if found
    def is_element?(name)
      # Doesn't support modulized classes
#      name = name.demodulize
      return false unless name =~ PREFIX_RE or name =~ CAPITALIZED_RE

      name = name.gsub(PREFIX_RE,'').camelize if name =~ PREFIX_RE

      # First try to use Nitro::Element::xxx then ::xxx
      
      begin
        klass = Class.by_name("Nitro::Element::#{name}")
      rescue
      end

      # Look into the module the controller's module if any

      begin
        namespace = @compiler.controller.name
        if /::/ =~ namespace
          namespace = namespace.gsub( /::[a-zA-Z]+$/, "::#{name}" )
          klass = Class.by_name(namespace)
        end
      rescue
      end unless klass

      # Look in the root module

      begin
        klass = Class.by_name(name)
      rescue
      end unless klass

      return false unless klass.kind_of?( Class )

      # Try to auto-extend

      unless klass.ancestors.include? Nitro::Element
        if Element.auto_extend
          klass.send(:include, Nitro::ElementMixin)
        else
          return false
        end
      end

      return klass
    end
    
    def text(str)
      @buffer << str
    end
    
    def instruction(name, attributes)
      @buffer << "<?#{name}#{attributes}?>"
    end

    def comment(c)
      unless Glue::Template.strip_xml_comments
        @buffer << "<!--#{c}-->"
      end
    end
  end
  
  class << self
    def parse(source)
      self.new.parse(source)
    end
    
    def transform(source, compiler)
      self.new.transform(source, compiler)
    end
  end

  # Expand the elemens found in source.
  #--
  # gmosx, FIXME: optimize this, how?
  # gmosx, FIXME: this is a hack fix, improve.
  # TODO:farms why is cleanup called this many times?!?!? ... waste of gsubs
  #++
    
  def transform(source, compiler)
    listener = Listener.new(compiler)

    REXML::Document.parse_stream(source, listener)
    
    return listener.buffer
  end
end

# An (old) alias.

unless const_defined? :ElementProcessor
   ElementProcessor = Elements
   ElementCompiler = Elements
end

end

# * George Moschovitis <gm@navel.gr>
# * Chris Farmiloe <chris.farmiloe@farmiloe.com>