require 'amrita2/template'

module Amrita2 

  class Core::Template
    def add_macro(m)
      @macros ||= []
      @macros << m
    end
    alias use_macro add_macro 

    compile_old = instance_method(:compile)
    
    define_method(:compile) do |*args|
      macros = @macros
      if macros and macros.size > 0
        filter_setup do |e, name ,filters|
          filters << MacroFilter.new(*macros)
        end
      end
      compile_old.bind(self).call(*args)
    end
  end
  
  module Macro # :nodoc: all
    class Base
      def initialize
        @mt = Template.new(get_macro_template, :amrita_prefix=>"macro:", :inline_ruby=>true)
        @option = {}
        @option = self.class.const_get(:Option) if self.class.const_defined?(:Option)
        raise "Macro Option is not defined propery in #{self.class} #{@option.inspect}" unless @option.kind_of?(Hash)
      end

      def get_macro_template
        self.class.const_get(:TemplateText)
      end
      
      def get_element_name
        #self.class.const_get(:ElementName)
        @option[:tag] || underscore(self.class.name)
      end

      def process(de, element)
        preprocess_element(@mt, element)
      end
      
      def match_element(element)
        element.name == get_element_name.to_s
      end

      def macro_data(element)
        element.as_amrita_dictionary(@option)
      end
      
      def preprocess_element(mt, element)
        if @option[:trace]
          mt.set_trace(@option[:trace])
          @option[:trace] << (macro_data(element)).inspect
        end
        mt.amrita_prefix = "macro:"
        mt.render_with(macro_data(element))
      end
      
      private
      # from activesupport
      def underscore(camel_cased_word)
        camel_cased_word.to_s.gsub(/::/, '/').
          gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
          gsub(/([a-z\d])([A-Z])/,'\1_\2').
          tr("-", "_").
          downcase
      end
    end
  end

  module Filters
    class MacroFilter < Base
      include Amrita2
      include Util
      include OptionSupport
      attr_reader :macros
      
      def initialize(*macros)
        @macros = macros.collect do |m|
          case m
          when Class
            m.new
          else
            m
          end
        end
        @element_names = {}
        @macros.each do |m|
          @element_names[m.get_element_name.to_s] = true
        end
      end

      def check_element(element)
        return true if @element_names[element.name]
        element.each_child do |c|
          next unless c.kind_of?(Hpricot::Elem)
          return true if @element_names[c.name] or check_element(c)
        end
        false
      end

      def filter_element(de, element)
        return element unless check_element(element)
        
        @macros.each do |m|
          if m.match_element(element)
            ret = m.process(de, element)
            ret = Core::PreProcessor.new.process(ret.dup)
            ret.gsub!('<%%', '<%')
            ret.gsub!('%%>', '%>')
            root = Hpricot.make("<_ />").first
            Hpricot.make(ret, :xml=>true).each do |e|
              root.insert_after(e, nil)
            end
            element = replace_target_src(root)
          else
            element.each_child do |c|
              next unless c.kind_of?(Hpricot::Elem)
              cc = filter_element(de, c)
              element.replace_child(c, cc) if c != cc
            end
          end
        end
        element
      end

      def replace_target_src(e)
        %w(src filter v skipif for).each do |k|
          e.set_attribute("am:#{k}", e.attributes["target_#{k}"]) if e.attributes["target_#{k}"]
          e.delete_attribute("target_#{k}")
        end
        e.each_child do |c|
          next unless c.kind_of?(Hpricot::Elem)
          replace_target_src(c)
        end
        e
      end
      
      def element_render_code(de, cg, element, &block)
        if (element.name == 'macroroot')
          yield
        else
          super
        end
      end
    end
  end
end