module Liquid 
  
  class Assign < Tag
    Syntax = /(\w+)\s*=\s*(#{AllowedVariableCharacters}+)/   
    
    def initialize(markup, tokens)
      if markup =~ Syntax
        @to = $1
        @from = $2
      else
        raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
      end
    end
    
    def render(context)
       context[@to] = context[@from]
       ''       
    end 
    
  end  
  
  class Capture < Block
    Syntax = /(\w+)/

    def initialize(markup, tokens)
      if markup =~ Syntax
        @to = $1
        super 
      else
        raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
      end
    end

    def render(context)
      output = super
      context[@to] = output.to_s
      ''
    end
  end
 
  class Cycle < Tag
    SimpleSyntax = /#{QuotedFragment}/        
    NamedSyntax = /(#{QuotedFragment})\s*\:\s*(.*)/
    
    def initialize(markup, tokens)
      case markup
      when NamedSyntax
      	@variables = variables_from_string($2)
      	@name = $1
      when SimpleSyntax
        @variables = variables_from_string(markup)
      	@name = "'#{@variables.to_s}'"
      else
        raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
      end
      
    end    
    
    def render(context)
      context.registers[:cycle] ||= Hash.new(0)
      
      context.stack do
        key = context[@name]	
        iteration = context.registers[:cycle][key]
        result = context[@variables[iteration]]
        iteration += 1    
        iteration   = 0  if iteration >= @variables.size 
        context.registers[:cycle][key] = iteration
        result 
      end
    end
    
    private
    
    def variables_from_string(markup)
      markup.split(',').collect do |var|
    	  var =~ /\s*(#{QuotedFragment})\s*/
    	  $1 ? $1 : nil
    	end.compact
    end
    
  end
  
  class Comment < Block                                             
    def render(context)
      ''
    end    
  end

  class For < Block                                             
    Syntax = /(\w+)\s+in\s+(#{AllowedVariableCharacters}+)/   
    
    def initialize(markup, tokens)
      super

      if markup =~ Syntax
        @variable_name = $1
        @collection_name = $2
        @name = "#{$1}-#{$2}"
        @attributes = {}
        markup.scan(TagAttributes) do |key, value|
          @attributes[key] = value
        end        
      else
        raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
      end
    end
    
    def render(context)        
      context.registers[:for] ||= Hash.new(0)
      
      collection = context[@collection_name]
      
      return '' if collection.nil? or collection.empty?
      
      range = (0..collection.length)
      
      if @attributes['limit'] or @attributes['offset']
        
        
        offset = 0
        if @attributes['offset'] == 'continue'
          offset = context.registers[:for][@name] 
        else          
          offset = context[@attributes['offset']] || 0
        end
        
        limit  = context[@attributes['limit']]

        range_end = limit ? offset + limit : collection.length
        
        range = (offset..range_end-1)
        
        # Save the range end in the registers so that future calls to 
        # offset:continue have something to pick up
        context.registers[:for][@name] = range_end
      end
              
      result = []
      segment = collection[range]
      return '' if segment.nil?        

      context.stack do 
        length = segment.length
        
        segment.each_with_index do |item, index|
          context[@variable_name] = item
          context['forloop'] = {
            'name'    => @name,
            'length'  => length,
            'index'   => index + 1, 
            'index0'  => index, 
            'rindex'  => length - index,
            'rindex0' => length - index -1,
            'first'   => (index == 0),
            'last'    => (index == length - 1) }
          
          result << render_all(@nodelist, context)
        end
      end
      
      # Store position of last element we rendered. This allows us to do 
      
      result 
    end           
  end
  
   
  class DecisionBlock < Block
    def equal_variables(right, left)
      if left.is_a?(Symbol)
        if right.respond_to?(left.to_s)
          return right.send(left.to_s) 
        else
          raise ArgumentError.new("Error in tag '#{name}' - Cannot call method #{left} on type #{right.class}}")
        end
      end

      if right.is_a?(Symbol)
        if left.respond_to?(right.to_s)
          return left.send(right.to_s) 
        else
          raise ArgumentError.new("Error in tag '#{name}' - Cannot call method #{right} on type #{left.class}}")
        end
      end

      left == right      
    end    

    def interpret_condition(left, right, op, context)   

      # If the operator is empty this means that the decision statement is just 
      # a single variable. We can just poll this variable from the context and 
      # return this as the result.
      return context[left] if op == nil      
      
      left, right = context[left], context[right]
      
      operation = case op
      when '==' then return equal_variables(left, right)
      when '!=' then return !equal_variables(left, right)
      when '>'  then :>
      when '<'  then :<
      when '>=' then :>=
      when '<=' then :<=
      else
        raise ArgumentError.new("Error in tag '#{name}' - Unknown operator #{op}")        
      end
      
      if left.respond_to?(operation) and right.respond_to?(operation)
        left.send(operation, right)      
      else
        nil
      end
    end    
  end
  
  
  class Case < DecisionBlock
    Syntax     = /(#{QuotedFragment})/
    WhenSyntax = /(#{QuotedFragment})/

    def initialize(markup, tokens)
      @nodelists = []
      @else_nodelist = []
      
      super

      if markup =~ Syntax
        @left = $1
      else
        raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
      end
    end
    
    def end_tag
      push_nodelist
    end

    def unknown_tag(tag, params, tokens)
      case tag 
      when 'when'
        if params =~ WhenSyntax          
          push_nodelist
          @right = $1
          @nodelist = [] 
        else
          raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: when [condition] ")
        end
      when 'else'
        push_nodelist
        @right = nil
        @else_nodelist = @nodelist = [] 
      else
        super
      end
    end
    
    def push_nodelist
      if @right
        # only push the nodelist if there was actually a when condition stated before. 
        # we discard all tokens between the case and the first when condition this way...
        @nodelists << [@right, @nodelist] 
      end
    end

    def render(context)
      output = []
      run_else_block = true
      
      @nodelists.each do |right, nodelist|
        if equal_variables(context[@left], context[right])
          run_else_block = false
          context.stack do          
            output += render_all(nodelist, context)
          end
        end
      end            
      
      if run_else_block
        context.stack do          
          output += render_all(@else_nodelist, context)       
        end
      end
      
      output.to_s
    end    
  end  
  
  class If < DecisionBlock
    Syntax = /(#{QuotedFragment})\s*([=!<>]+)?\s*(#{QuotedFragment})?/
    
    def initialize(markup, tokens)
      @nodelist_true = @nodelist = []
      @nodelist_false = []

      super
                       
      if markup =~ Syntax
        @left = $1
        @operator = $2 
        @right = $3
      else
        raise SyntaxError.new("Syntax Error in tag 'if' - Valid syntax: if [condition]")
      end
      
    end
    
    def unknown_tag(tag, params, tokens)
      if tag == 'else'
        @nodelist = @nodelist_false = []
      else
        super
      end
    end
    
    def render(context)
      context.stack do       
        if interpret_condition(@left, @right, @operator, context)
          render_all(@nodelist_true, context)
        else
          render_all(@nodelist_false, context)
        end
      end
    end
  end
  
  class Unless < If
    def render(context)
      context.stack do       
        if interpret_condition(@left, @right, @operator, context)
          render_all(@nodelist_false, context)
        else
          render_all(@nodelist_true, context)
        end
      end
    end
  end
  
  class Include < Tag
    Syntax = /("[^"]+"|'[^']+')(\s+(with|for)\s+(#{QuotedFragment}+))?/
    
    def initialize(markup, tokens)
      if markup =~ Syntax
        @template_name = $1[1...-1]
        if $2
          @collection = ($3 == "for")
          @variable = $4
        end
        @attributes = {}
        markup.scan(TagAttributes) do |key, value|
          @attributes[key] = value
        end
      else
        raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
      end

      super
    end
    
    def parse(tokens)      
      source = Liquid::Template.file_system.read_template_file(@template_name)
      tokens = Liquid::Template.tokenize(source)
      @document = Document.new(tokens)
    end
    
    def render(context)
      result = ''
      variable = context[@variable]
      context.stack do
        @attributes.each do |key, value|
          context[key] = context[value]
        end
        if @collection 
          variable.each do |item|
            context[@template_name] = item
            result << @document.render(context).to_s
          end
        else 
          if @variable          
            context[@template_name] = variable
          end
          result << @document.render(context).to_s
        end
      end
      result
    end
  end
  
  Template.register_tag('assign', Assign)
  Template.register_tag('capture', Capture)
  Template.register_tag('comment', Comment)
  Template.register_tag('for', For)
  Template.register_tag('if', If)
  Template.register_tag('unless', Unless)
  Template.register_tag('case', Case)
  Template.register_tag('cycle', Cycle)
  Template.register_tag('include', Include)  
end 
