require 'digest'

module Kameleon

  class Command

    attr_accessor :string_cmd
    attr_accessor :raw_cmd_id
    attr_accessor :microstep_name
    attr_accessor :identifier

    def initialize(yaml_cmd, microstep_name)
      @string_cmd = YAML.dump(yaml_cmd).gsub("---", "").strip
      @raw_cmd_id = Digest::SHA1.hexdigest(YAML.dump(yaml_cmd).gsub("---", "").strip)
      @microstep_name = microstep_name
      @identifier = nil
    end

    def resolve!
      key
      value
    end

    def key
      if @key.nil?
        object = YAML.unsafe_load(@string_cmd)
        if object.kind_of? String
          @key = object
        else
          @key = object.keys.first
        end
      end
      @key
    rescue
      lines = @string_cmd.split( /\r?\n/ ).map {|l| "> #{l}" }
      fail RecipeError, "Syntax error for microstep #{@microstep_name}: \n"\
                        "#{ lines.join "\n"}"
    end

    def value
      if @value.nil?
        Kameleon.ui.debug("Parsed string = #{@string_cmd}")
        object = YAML.unsafe_load(@string_cmd)
        if object.kind_of? Command
          @value = object
        elsif object.kind_of? String
          @value = nil
        else
          raise RecipeError unless object.kind_of? Hash
          raise RecipeError unless object.keys.count == 1
          _, val = object.first
          unless val.kind_of?(Array)
            val = val.to_s
          end
          # Nested commands
          if val.kind_of? Array
            val = val.map { |item| Command.new(item, @microstep_name) }
          end
          @value = val
        end
      end
      @value
    rescue
      fail RecipeError, "Syntax error after variable resolution for microstep #{@microstep_name}, parsed string =\n"\
                        "#{@string_cmd}\n"\
                        "Maybe you should remove trailing newline from variable using '>-' or '|-'"
    end

    def to_array
      if value.kind_of? Array
        map = value.map { |val| val.to_array }
        return { key => map }
      else
        return { key => value }
      end
    end

    def string_cmd=(str)
      Kameleon.ui.debug("Set string_cmd to '#{str}' and clear cached value")
      @string_cmd = str
      @value = nil
    end

    def remaster_string_cmd_from_value!
      self.string_cmd = YAML.dump(to_array).gsub("---", "").strip
      return self
    end

    def gsub!(arg1, arg2)
      if value.kind_of? Array
        value.each { |cmd| cmd.gsub!(arg1, arg2) }
      else
        @value.gsub!(arg1, arg2)
      end
      remaster_string_cmd_from_value!
      return self
    end

  end

  class Microstep
    attr_accessor :commands
    attr_accessor :name
    attr_accessor :identifier
    attr_accessor :slug
    attr_accessor :has_checkpoint_ahead
    attr_accessor :in_checkpoint_window
    attr_accessor :on_checkpoint
    attr_accessor :order

    def initialize(string_or_hash)
      @identifier = nil
      @has_checkpoint_ahead = false
      @in_checkpoint_window = true
      @on_checkpoint = "use_cache"
      @commands = []
      @name, cmd_list = string_or_hash.first
      cmd_list.each do |cmd_hash|
        if cmd_hash.kind_of? Command
          @commands.push cmd_hash
        else
          if cmd_hash.kind_of?(Hash) && cmd_hash.keys.first == "on_checkpoint"
            @on_checkpoint = cmd_hash["on_checkpoint"]
          else
            @commands.push Command.new(cmd_hash, @name)
          end
        end
      end
    rescue
      fail RecipeError, "Syntax error for microstep #{name}"
    end

    def resolve!
      @commands.each {|cmd| cmd.resolve! }
    end

    def gsub!(arg1, arg2)
      @commands.each {|cmd| cmd.gsub!(arg1, arg2) }
    end

    def unshift(cmd_list)
      cmd_list.reverse.each {|cmd| @commands.unshift cmd}
    end

    def push(cmd)
      @commands.push cmd
    end

    def calculate_identifier(salt)
      commands_str = @commands.map { |cmd| cmd.string_cmd.to_s }
      content_id = commands_str.join(' ') + salt
      @identifier = "#{ Digest::SHA1.hexdigest content_id }"[0..11]
      @commands.each do |cmd|
        map_id = cmd.string_cmd.to_s + @identifier
        cmd.identifier = "#{ Digest::SHA1.hexdigest map_id }"[0..11]
      end
      @identifier
    end

    def to_array
      microstep_array = @commands.map do |cmd|
        cmd.to_array
      end
      return microstep_array
    end

  end

  class Macrostep
    attr_accessor :name
    attr_accessor :clean_microsteps
    attr_accessor :init_microsteps
    attr_accessor :microsteps
    attr_accessor :path
    attr_accessor :variables

    def initialize(name, microsteps, variables, path)
      @name = name
      @variables = variables
      @path = path
      @microsteps = microsteps
      @clean_microsteps = []
      @init_microsteps = []
    end

    def resolve_variables!(global, recipe)
      # Resolve dynamically-defined variables !!
      tmp_resolved_vars = {}
      @variables.clone.each do |key, value|
        yaml_vars = { key => value }.to_yaml.chomp
        yaml_resolved = Utils.resolve_vars(yaml_vars,
                                           @path,
                                           tmp_resolved_vars.merge(global),
                                           recipe)
        tmp_resolved_vars.merge! YAML.unsafe_load(yaml_resolved.chomp)
      end
      @variables.merge! tmp_resolved_vars
      @microsteps.each do |m|
        m.commands.each do |cmd|
          cmd.string_cmd = Utils.resolve_vars(cmd.string_cmd,
                                              @path,
                                              global.merge(@variables),
                                              recipe)
        end
      end
    end

    def sequence
      @init_microsteps.each { |m| yield m }
      @microsteps.each { |m| yield m }
      @clean_microsteps.each  { |m| yield m }
    end

    def to_array
      macrostep_array = []
      @variables.each do |k, v|
        macrostep_array.push({ k => v })
      end
      sequence do |microstep|
        macrostep_array.push({ microstep.name => microstep.to_array })
      end
      return macrostep_array
    end

  end

  class Section
    attr_accessor :name
    attr_accessor :clean_macrostep
    attr_accessor :init_macrostep
    attr_accessor :macrosteps

    def initialize(name)
      @name = name
      @clean_macrostep = Macrostep.new("_clean_#{name}", [], {}, nil)
      @init_macrostep = Macrostep.new("_init_#{name}", [], {}, nil)
      @macrosteps = []
    end

    def sequence
      yield @init_macrostep
      @macrosteps.each { |m| yield m }
      yield @clean_macrostep
    end

    def to_array
      section_array = []
      sequence do |macrostep|
        macrostep.sequence do |microstep|
          hash = {
            "identifier" => microstep.identifier.to_s,
            "cmds" => microstep.to_array
          }

          section_array.push({ microstep.slug => hash })
        end
      end
      return section_array
    end
  end

end
