require 'rexml/document'
require 'rexml/streamlistener'

require 'facet/dictionary'
require 'facet/string/blank'

module Nitro

# :section: A collection of standard morphers.

# The base morpher class. Morphers are triggered
# by a special 'key' attribute in the xml stream and
# transform the owner element. 
#
# key = attribute 'key'
# name = element name
# attributes = alement attributes

class Morpher
  def initialize(key, name, attributes, compiler = nil)
    @key = key
    @name = name
    @attributes = attributes
    @value = @attributes[@key]
    @compiler = compiler
  end
  
  def before_start(buffer); end
  def after_start(buffer); end
  def before_end(buffer); end
  def after_end(buffer); end
end

# A useful super class for morphers.

class StandardMorpher < Morpher
  def after_end(buffer)
    # gmosx: leave the leading space.
    buffer << " <?r end ?>"
  end
end

# attribute: times
#
# <li times="3">...</li>
# 
# becomes
#
# <?r 3.times do ?>
#   <li>...</li>
# <?r end ?>

class TimesMorpher < StandardMorpher
  def before_start(buffer)
    # gmosx: leave the trailing space.
    buffer << "<?r #@value.times do ?> "
    @attributes.delete(@key)
  end
end

# attribute: each, for
#
# <li each="item in array">my item is #{item}</li>
#
# becomes
#
# <?r for item in array ?>
#   <li>my item is #{item}</li>
# <?r end ?>

class EachMorpher < Morpher
  def before_start(buffer)
    if @value =~ / in /
      buffer << "<?r for #@value ?> "
      @attributes.delete(@key)
    end
  end
  
  def after_end(buffer)
    if @value =~ / in /
      buffer << " <?r end ?>"
    end
  end
end

# attribute: if, unless
#
# <div prop1="one" if="@mycond" prop2="two">@mycond is true</div>
#
# becomes
#
# <?r if @mycond ?>
#   <div prop1="one" prop2="two">@mycond is true</div>
# <?r end ?>

class IfMorpher < StandardMorpher
  def before_start(buffer)
    buffer << "<?r #@key #@value ?> "
    @attributes.delete(@key)
  end
end

# attribute: selected_if, checked_if, selected_unless, checked_unless
#
# <option value="1" selected_if="@cond">opt1</option>
#
# becomes
#
# <?r if @cond ?>
#   <option value="1" selected="selected">opt1</option>
# <?r else ?>
#   <option value="1">opt1</option>
# <?r end ?>

class SelectedIfMorpher < StandardMorpher
  def before_start(buffer)
    @attr, @cond = @key.split('_')
    @attributes.delete(@key)
    @attributes[@attr] = @attr
    buffer << "<?r #@cond #@value ?> "
  end
  
  def after_start(buffer)
    @start_index = buffer.length
  end
  
  def before_end(buffer)
    @attributes.delete(@attr)
    @end_index = buffer.length
    buffer << Morphing.emit_end(@name)
    buffer << "<?r else ?>"
    buffer << Morphing.emit_start(@name, @attributes)
    buffer << buffer[@start_index...@end_index]
  end
    
end

# :section: The morphing system.

# A compiler module that translates xml stream. Multiple
# 'key' attributes are supported per element.

class Morphing

  #--
  # The listener used to parse the xml stream.
  #
  # TODO: add support for morphing comments, text, etc.
  #++
    
  class Listener # :nodoc: all
    include REXML::StreamListener

    # The compiler that calls this compiling stage. Useful
    # for advanced morphing effects and inter-stage 
    # communication.
    
    attr_accessor :compiler

    attr_accessor :buffer
    
    def initialize
      super
      @buffer = ''
      @stack = []
    end

    def tag_start(name, attributes)    
      morphers = []
      
      #for key, val in attributes
      #  if morpher_class = Morphing.morphers[key]
      #    morphers << morpher_class.new(key, name, attributes)
      #  end
      #end
      
      Morphing.morphers.each do |key, morpher_class|
        if attributes.has_key? key
          morphers << morpher_class.new(key, name, attributes, compiler)
        end
      end
      
      morphers.each { |h| h.before_start(@buffer) }
      @buffer << Morphing.emit_start(name, attributes)
      morphers.each { |h| h.after_start(@buffer) }

      @stack.push(morphers)      
    end

    def tag_end(name)    
      morphers = @stack.pop
      morphers.reverse.each { |h| h.before_end(@buffer) }
      @buffer << Morphing.emit_end(name)
      morphers.reverse.each { |h| h.after_end(@buffer) }
    end

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

    def cdata(content)
      @buffer << "<![CDATA[#{content}]]>"
    end

    def comment(c)
      unless Nitro::Template.strip_xml_comments
        @buffer << "<!--#{c}-->"
      end
    end    

    def doctype(name, pub_sys, long_name, uri)   
      @buffer << "<!DOCTYPE #{name} #{pub_sys} #{long_name} #{uri}>\n"
    end
  end


  def self.emit_start(name, attributes)
    attrs = attributes.map{ |k, v| %|#{k}="#{v}"| }.join(' ')
    attrs.blank? ? "<#{name}>" : "<#{name} #{attrs}>"
  end
  
  def self.emit_end(name)
    "</#{name}>"
  end
  
  def self.transform(source, compiler = nil)
    listener = Listener.new
    listener.compiler = compiler
    REXML::Document.parse_stream(source, listener)
    return listener.buffer
  end

  # The morphers map.
  
  @morphers = Dictionary.new
  
  class << self
  
    def morphers
      @morphers
    end

    def morphers=(hash)
      @morphers = hash
    end
    
    def add_morpher(key, klass)
      @morphers[key.to_s] = klass
    end
    alias_method :add, :add_morpher
    
    def delete_morpher(key)
      @morphers.delete(key)
    end
    alias_method :delete, :delete_morpher
  end

  # Install the default morphers.
  
  add_morpher :times, TimesMorpher
  add_morpher :if, IfMorpher
  add_morpher :unless, IfMorpher
  add_morpher :each, EachMorpher
  add_morpher :for, EachMorpher
  add_morpher :selected_if, SelectedIfMorpher
  add_morpher :selected_unless, SelectedIfMorpher
  add_morpher :checked_if, SelectedIfMorpher
  add_morpher :checked_unless, SelectedIfMorpher
  
end

end