# Web::Template
# 
# Author::    MoonWolf (mailto:moonwolf@moonwolf.com)
# Copyright:: Copyright (c) 2003-2004 MoonWolf
# License::   Distributes under the same terms as Ruby
require "web/escape"

module Web
  class Template
    CACHE = Hash.new
    
    def initialize(opt={})
      @param       = {}
      @tree        = nil
      @filename    = nil
      #
      if filename=opt['filename']
        path = opt['path'] || ['.']
        if opt['cache']
          file = Parser.find_file(filename, path)
          raise Errno::ENOENT,filename unless file
          if tmp = CACHE[file]
            mtime, tree, proc = tmp
            if File.mtime(file) == mtime
              @tree = tree
              @proc = proc
              return
            else
              #p "cache expired"
            end
          end
        end
        
        parser = Parser.new
        @filename, tmpl = parser.load_file(filename,path)
        @tree = parser.parse(tmpl)
        
        writer = RubyWriter.new
        src = "Proc.new {|param,io|\n" # }
        writer.out = src
        writer.run(@tree)
        src << "io\n"
        src << "}\n"
        @src = src
        @proc = eval(src)
        if opt['cache']
          self.class.cache[@filename] = [File.mtime(@filename), @tree, @proc]
        end
      end
    end
    
    # syntax tree
    attr_accessor :tree 
    
    # execute template and return result string
    def output(io='')
      raise "not parsed" unless @tree
      begin
        @proc.call(@param,io)
      rescue
        puts @src
        raise
      end
      io
    end

    # clear params
    def clear_params
      @param = {}
    end

    # 
    def param(hash=nil)
      if hash
        @param.update(hash)
        self
      else
        @param
      end
    end

    # set params
    def param=(hash)
      @param = hash
    end
    
    
    
    
    
    class Node
      def initialize(value)
        @value    = value
        @children = []
      end
      # node value
      attr_reader :value
      # chilren nodes(Array)
      attr_reader :children

      # append child node
      def <<(node)
        @children << node
        self
      end

      def accept(visitor)
        name = self.class.name
        name.sub!(/.+::/,'')
        method = "visit_#{name}"
        visitor.send(method, self)
      end
      class RootNode < Node
      end
      
      class TextNode < Node
      end
      
      class VarNode < Node
      end
      
      class IfNode < Node
      end
      
      class UnlessNode < Node
      end
      
      class BlockNode < Node
      end
      
      class LoopNode < Node
      end
    end
    
    
    
    
    
    class Parser
      
      def self::find_file(filename, path=['.'])
        path.each {|dir|
          file = File.expand_path(filename,dir)
          return file if FileTest.exist? file
        }
        nil
      end
      
      def initialize
        @tokens = nil
        @tree   = nil
      end
      
      def load_file(filename,path=['.'])
        file = self.class::find_file(filename,path)
        raise Errno::ENOENT,filename unless file
        
        tmpl = nil
        open(file,'rb') {|f|
          tmpl = f.read || ''
        }
        tmpl = filter(tmpl,path)
        [file, tmpl]
      end
      
      def filter(tmpl,path)
        tmpl.gsub(/\{\{INCLUDE\s*(.+?)\}\}/i) {|match|
          attr = $1
          attrs = {}
          attr.scan(/(\w+)=(?:(?:"(.*?)")|(?:'(.*?)')|(\w+))/) {
            name  = $1.downcase
            value = $2 || $3 || $4
            attrs[name] = value
          }
          filename = attrs['name']
          file, tmp = load_file(filename, path)
          tmp
        }
      end
      
      def parse(src)
        @tokens = tokenize(src)
        
        #@tokens.each {|token|
        #  type,value = token
        #  puts "#{type}\t#{value.inspect}"
        #}
        
        @tree = root
      end

      def next_token
        token = @tokens.shift
      end
      
      def tokenize(src)
        tokens = []
        text = ''
        until src.empty?
          if src =~ %r!\A\{\{([a-zA-Z]+)(\s.+?)?\}\}!
            src = $'
            attrs = {}
            case $1.downcase
            when 'var'
              type = :TMPL_VAR
              attrs['escape']='HTML'
            when 'if'
              type = :TMPL_IF
            when 'unless'
              type = :TMPL_UNLESS
            when 'else'
              type = :TMPL_ELSE
            when 'loop'
              type = :TMPL_LOOP
            end
            attr = $2
            if attr
              attr.scan(/(\w+)=(?:(?:"(.*?)")|(?:'(.*?)')|([0-9a-zA-Z_$\-]+))/) {
                name  = $1.downcase
                value = $2 || $3 || $4
                attrs[name] = value
              }
            end
            unless text.empty?
              tokens << [:TEXT, text]
              text = ''
            end
            tokens << [type, attrs]
          elsif src =~ %r!\A\{\{/([a-zA-Z]+)\}\}!
            src = $'
            type = $1.downcase.intern
            unless text.empty?
              tokens << [:TEXT, text]
              text = ''
            end
            tokens << [:CLOSE, type]
          elsif src =~ %r!\A[^{]+!
            src = $'
            text << $&
          elsif src =~ %r!\A.!
            src = $'
            text << $&
          end
        end
        unless text.empty?
          tokens << [:TEXT, text]
        end
        tokens
      end

      def root
        node = Node::RootNode.new(nil)
        while token = next_token
          type, value = token
          case type
          when :TEXT
            node << Node::TextNode.new(value)
          when :TMPL_VAR
            node << Node::VarNode.new([value['name'],value['escape'] ? value['escape'].upcase : nil])
          when :TMPL_IF
            node << if_node(token)
          when :TMPL_UNLESS
            node << unless_node(token)
          when :TMPL_LOOP
            node << loop_node(token)
          else
            p node
            raise type.to_s
          end
        end
        node
      end

      def if_node(token)
        type, value = token
        name = value['name']
        node = Node::IfNode.new(name)
        node << then_block = Node::BlockNode.new(nil)
        node << else_block = Node::BlockNode.new(nil)
        catch(:END_IF) {
          while token = next_token
            type, value = token
            case type
            when :TEXT
              then_block << Node::TextNode.new(value)
            when :TMPL_VAR
              then_block << Node::VarNode.new([value['name'],value['escape'] ? value['escape'].upcase : nil])
            when :TMPL_IF
              then_block << if_node(token)
            when :TMPL_UNLESS
              then_block << unless_node(token)
            when :TMPL_ELSE
              break
            when :TMPL_LOOP
              then_block << loop_node(token)
            when :CLOSE
              throw :END_IF
            else
              p type,value
              raise
            end
          end
          while token = next_token
            type, value = token
            case type
            when :TEXT
              else_block << Node::TextNode.new(value)
            when :TMPL_VAR
              else_block << Node::VarNode.new([value['name'],value['escape'] ? value['escape'].upcase : nil])
            when :TMPL_IF
              else_block << if_node(token)
            when :TMPL_UNLESS
              else_block << unless_node(token)
            when :TMPL_LOOP
              else_block << loop_node(token)
            when :CLOSE
              throw :END_IF
            else
              p type,value
              raise
            end
          end
        }
        node
      end
      
      def unless_node(token)
        type, value = token
        name = value['name']
        node = Node::UnlessNode.new(name)
        node << then_block = Node::BlockNode.new(nil)
        node << else_block = Node::BlockNode.new(nil)
        catch(:END_IF) {
          while token = next_token
            type, value = token
            case type
            when :TEXT
              then_block << Node::TextNode.new(value)
            when :TMPL_VAR
              then_block << Node::VarNode.new([value['name'],value['escape'] ? value['escape'].upcase : nil])
            when :TMPL_IF
              then_block << if_node(token)
            when :TMPL_UNLESS
              then_block << unless_node(token)
            when :TMPL_ELSE
              break
            when :TMPL_LOOP
              then_block << loop_node(token)
            when :CLOSE
              throw :END_IF
            else
              p type,value
              raise
            end
          end
          while token = next_token
            type, value = token
            case type
            when :TEXT
              else_block << Node::TextNode.new(value)
            when :TMPL_VAR
              else_block << Node::VarNode.new([value['name'],value['escape'] ? value['escape'].upcase : nil])
            when :TMPL_IF
              else_block << if_node(token)
            when :TMPL_UNLESS
              else_block << unless_node(token)
            when :TMPL_LOOP
              else_block << loop_node(token)
            when :CLOSE
              throw :END_IF
            else
              p type,value
              raise
            end
          end
        }
        node
      end

      def loop_node(token)
        type, value = token
        name = value['name']
        node = Node::LoopNode.new(name)
        while token = next_token
          type, value = token
          case type
          when :TEXT
            node << Node::TextNode.new(value)
          when :TMPL_VAR
            node << Node::VarNode.new([value['name'],value['escape'] ? value['escape'].upcase : nil])
          when :TMPL_IF
            node << if_node(token)
          when :TMPL_UNLESS
            node << unless_node(token)
          when :TMPL_LOOP
            node << loop_node(token)
          when :CLOSE
            break
          else
            p node
            raise type.to_s
          end
        end
        node
      end
    end # Parser
    
    
    
    
    
    class RubyWriter
      def initialize
        @out           = ''
      end
      # output stream(String or IO)
      attr_accessor :out

      # execute tree
      def run(tree)
        tree.accept(self)
        @out
      end

      def visit_children(node)
        node.children.each {|n|
          n.accept(self)
        }
      end

      def visit_RootNode(node)
        @out << "root  = param\n"
        @out << "stack = []\n"
        @out << "list  = []\n"
        @out << "index = 0\n"
        visit_children(node)
        @out << "io\n"
      end
      
      def visit_TextNode(node)
        @out << "io << #{node.value.dump}\n"
      end

      def visit_VarNode(node)
        key, escape = node.value
        if key=~/\A\$(\w+)/
          key = $1
          case escape
          when "HTML"
            @out << "io << Web::escapeHTML(root[#{key.dump}])\n"
          when "URL"
            @out << "io << Web::escape(root[#{key.dump}])\n"
          else
            @out << "io << root[#{key.dump}]\n"
          end
        else
          case escape
          when "HTML"
            @out << "io << Web::escapeHTML(param[#{key.dump}])\n"
          when "URL"
            @out << "io << Web::escape(param[#{key.dump}])\n"
          else
            @out << "io << param[#{key.dump}]\n"
          end
        end
      end
      
      def visit_IfNode(node)
        key  = node.value
        case key
        when "__FIRST__"
          @out << "if index==0\n"
        when "__LAST__"
          @out << "if index==list.size-1\n"
        when "__INNER__"
          @out << "if !((index==0) || (index==list.size-1))\n"
        when "__ODD__"
          @out << "if index % 2 == 0\n"
        else
          if key=~/\A\$(\w+)/
            key = $1
            @out << "if root[#{key.dump}]\n"
          else
            @out << "if param[#{key.dump}]\n"
          end
        end
        n = node.children[0]
        n.accept(self)
        
        if (n = node.children[1]) && (n.children.size > 0)
          @out << "else\n"
          n.accept(self)
        end
        
        @out << "end\n"
      end
      
      def visit_UnlessNode(node)
        key  = node.value
        case key
        when "__FIRST__"
          @out << "unless index==0\n"
        when "__LAST__"
          @out << "unless index==list.size-1\n"
        when "__INNER__"
          @out << "unless !((index==0) || (index==list.size-1))\n"
        when "__ODD__"
          @out << "unless index % 2 == 0\n"
        else
          if key=~/\A\$(\w+)/
            key = $1
            @out << "unless root[#{key.dump}]\n"
          else
            @out << "unless param[#{key.dump}]\n"
          end
        end
        
        n = node.children[0]
        n.accept(self)
        
        if (n = node.children[1]) && (n.children.size > 0)
          @out << "else\n"
          n.accept(self)
        end
        
        @out << "end\n"
      end
      
      def visit_BlockNode(node)
        visit_children(node)
      end
      
      def visit_LoopNode(node)
        key  = node.value
        @out << "stack.push [param, list, index]\n"
          if key=~/\A\$(\w+)/
            key = $1
            @out << "list = root[#{key.dump}]\n"
          else
            @out << "list = param[#{key.dump}]\n"
          end
        @out << "index = 0\n"
        @out << "list.each {|param|\n"
        visit_children(node)
        @out << "index += 1\n"
        @out << "}\n"
        @out << "param,list,index = stack.pop\n"
      end
    end # RubyWriter

  end # Template
end # Web
