module Less  
  class Engine < String
    REGEX = {
      :path     => /([#.][->#.\w ]+)?( ?> ?)?@([-\w]+)/,     # #header > .title > @var
      :selector => /[-\w #.,>*:\(\)]/,                       # .cow .milk > a
      :variable => /@([-\w]+)/,                              # @milk-white
      :property => /@[-\w]+|[-a-z]+/,                        # font-size
      :color    => /#([a-zA-Z0-9]{3,6})\b/,                  # #f0f0f0
      :number   => /\d+(?>\.\d+)?/,                          # 24.8
      :unit     => /px|em|pt|cm|mm|%/                        # em
    }
    REGEX[:numeric] = /#{REGEX[:number]}(#{REGEX[:unit]})?/
    REGEX[:operand] = /#{REGEX[:color]}|#{REGEX[:numeric]}/
    
    def initialize s
      super
      @tree = Tree.new self.hashify
    end

    def compile     
      #
      # Parse the variables and mixins
      #
      # We use symbolic keys, such as :mixins, to store LESS-only data,
      # each branch has its own :mixins => [], and :variables => {}
      # Once a declaration has been recognised as LESS-specific, it is copied 
      # in the appropriate data structure in that branch. The declaration itself
      # can then be deleted.
      #
      @tree = @tree.traverse :leaf do |key, value, path, node|
        matched = if match = key.match( REGEX[:variable] )          
          node[:variables] ||= Tree.new
          node[:variables][ match.captures.first ] = value
        elsif value == :mixin
          node[:mixins] ||= []          
          node[:mixins] << key
        end
        node.delete key if matched # Delete the property if it's LESS-specific
      end
      
      #
      # Evaluate mixins
      #
      @tree = @tree.traverse :branch do |path, node|
        if node.include? :mixins
          node[:mixins].each do |m|
            @tree.find( :mixin, m.delete(' ').split('>') ).each {|k, v| node[ k ] = v }
          end
        end
      end
      
      # Call `evaluate` on variables, such as '@dark: @light / 2'
      @tree = @tree.traverse :branch do |path, node|
        node.vars.each do |key, value|
          evaluate key, value, path, node.vars
        end if node.vars?
      end
      
      # Call `evaluate` on css properties, such as 'font-size: @big'
      @tree = @tree.traverse :leaf do |key, value, path, node|
        evaluate key, value, path, node
      end
      
      #
      # Evaluate operations (2+2)
      #
      # Units are: 1px, 1em, 1%, #111
      @tree = @tree.traverse :leaf do |key, value, path, node| 
        node[ key ] = value.gsub /(#{REGEX[:operand]}(\s?)[-+\/*](\4))+(#{REGEX[:operand]})/ do |operation|
          if (unit = operation.scan(/#{REGEX[:numeric]}|(#)/i).flatten.compact.uniq).size <= 1
            unit = unit.join            
            operation = if unit == '#'
              evaluate = lambda do |v| 
                result = eval v
                unit + ( result.zero?? '000' : result.to_s(16) )
              end
              operation.gsub REGEX[:color] do
                hex = $1 * ( $1.size < 6 ? 6 / $1.size : 1 )
                hex.to_i(16)
              end.delete unit
            else
              evaluate = lambda {|v| eval( v ).to_s + unit }
              operation.gsub REGEX[:unit], ''
            end.to_s                
            next if operation.match /[a-z]/i
            evaluate.call operation
          else
            raise MixedUnitsError, value
          end
        end
      end
    end
    alias render compile
    
    #
    # Evaluate variables
    #
    def evaluate key, value, path, node               
      if value.is_a? String and value.include? '@'      # There's a var to evaluate    
        value.scan REGEX[:path] do |var|
          var = var.join.delete ' '
          var = if var.include? '>'
            @tree.find :var, var.split('>')             # Try finding it in a specific namespace
          else            
           node.var( var ) or @tree.nearest var, path   # Try local first, then nearest scope            
          end

          if var
            node[ key ] = value.gsub REGEX[:path], var  # Substitute variable with value
          else
            node.delete key                             # Discard the declaration if the variable wasn't found
          end
        end    
      end
    end
    
    def to_css chain
      self.compile.to_css chain
    end
    
    def hashify
    #
    # Parse the LESS structure into a hash
    #
    ###
      #   less:     color: black;
      #   hashify: "color" => "black"
      #
      hash = self.gsub(/\t+/, ' ').                                                       # Tabs
                  gsub(/\r\n/, "\n").                                                     # m$
                  gsub(/\/\/.*/, '').                                                     # Comments //
                  gsub(/\/\*.*?\*\//m, '').                                               # Comments /*
                  gsub(/"/, "'").                                                         # " => '
                  gsub(/("|')(.+?)(\1)/) { $1 + CGI.escape( $2 ) + $1 }.                  # Escape string values
                  gsub(/(#{REGEX[:property]}):\s*(.+?)\s*(;|(?=\}))/,'"\1"=>"\2",').      # Rules
                  gsub(/\}/, "},").                                                       # Closing }
                  gsub(/( *)(#{REGEX[:selector]}+?)[ \n]*(?=\{)/m, '\1"\2"=>').           # Selectors
                  gsub(/([.#][->\w .#]+);/, '"\\1" => :mixin,')                           # Mixins
      eval "{" + hash + "}"                                                               # Return {hash}
    end
  end
end