# @Opulent
module Opulent
  # @Compiler
  class Compiler
    # Generate the code for a if-elsif-else control structure
    #
    # @param node [Array] Node code generation data
    # @param indent [Fixnum] Size of the indentation to be added
    # @param context [Context] Processing environment data
    #
    def if_node(node, indent, context)
      # Check if we have any condition met, or an else branch
      index = node[@value].index do |value|
        value.empty? || context.evaluate(value)
      end

      # If we have a branch that meets the condition, generate code for the
      # children related to that specific branch
      if index
        node[@children][index].each do |child|
          root child, indent, context
        end
      end
    end

    # Generate the code for a case-when-else control structure
    #
    # @param node [Array] Node code generation data
    # @param indent [Fixnum] Size of the indentation to be added
    # @param context [Context] Processing environment data
    #
    def case_node(node, indent, context)
      # Evaluate the switching condition
      switch_case = context.evaluate node[@options][:condition]

      # Check if we have any condition met, or an else branch
      index = node[@value].index do |value|
        value.empty? || switch_case == context.evaluate(value)
      end

      # If we have a branch that meets the condition, generate code for the
      # children related to that specific branch
      if index
        node[@children][index].each do |child|
          root child, indent, context
        end
      end
    end

    # Generate the code for a while control structure
    #
    # @param node [Array] Node code generation data
    # @param indent [Fixnum] Size of the indentation to be added
    # @param context [Context] Processing environment data
    #
    def while_node(node, indent, context)
      # While we have a branch that meets the condition, generate code for the
      # children related to that specific branch
      while context.evaluate node[@value]
        node[@children].each do |child|
          root child, indent, context
        end
      end
    end

    # Generate the code for a while control structure
    #
    # @param node [Array] Node code generation data
    # @param indent [Fixnum] Size of the indentation to be added
    # @param context [Context] Processing environment data
    #
    def until_node(node, indent, context)
      # Until we have a branch that doesn't meet the condition, generate code for the
      # children related to that specific branch
      until context.evaluate node[@value]
        node[@children].each do |child|
          root child, indent, context
        end
      end
    end

    # Generate the code for a while control structure
    #
    # @param node [Array] Node code generation data
    # @param indent [Fixnum] Size of the indentation to be added
    # @param context [Context] Processing environment data
    #
    def each_node(node, indent, context)
      result = []

      # Process named variables for each structure
      variables = node[@value][0].clone

      # The each structure accept missing arguments as well, therefore we need to
      # substitute them with our defaults
      #
      # each in iterable
      # each value in iterable
      # each key, value in iterable

      # Value argument name provided only
      if variables.length == 1
        variables.unshift Settings::DefaultEachKey

      # Missing key and value arguments
      elsif variables.empty?
        variables[0] = Settings::DefaultEachKey
        variables[1] = Settings::DefaultEachValue
      end

      # Evaluate in current context and add to results
      evaluate_children = Proc.new do |key, value, context|
        # Update the local variables in the each context with the values from the
        # current loop iteration
        locals = {
          variables[0] => key,
          variables[1] => value
        }
        context.extend_locals locals

        # Add the mapped child elements
        node[@children].each do |child|
          root child, indent, context
        end
      end

      # Create a new context based on the parent context and progressively update
      # variables in the new context
      each_context = Context.new Hash.new, context.binding.clone
      each_context.parent = context

      # Evaluate the iterable object
      enumerable = each_context.evaluate(node[@value][1])

      # Check if input can be iterated
      error :enumerable, node[@value][1] unless enumerable.respond_to? :each

      # Selectively iterate through the input and add the result using the previously
      # defined proc object
      case enumerable
      when Hash
        enumerable.each do |key, value|
          evaluate_children[key, value, context]
        end
      else
        enumerable.each_with_index do |value, key|
          evaluate_children[key, value, context]
        end
      end
    end
  end
end