$partials                   = {}
$deplomat_tasks             = []
$deplomat_tasks_callbacks   = {}
$log_tasks_status           = true

def execute_in(env:)
  yield if $env == env
end

def execute_partial(name, args={})
  print_to_terminal $partials[name][:before_message]
  $partials[name][:block].call(args)
  print_to_terminal $partials[name][:after_message]
end

def partial(name, message: [], &block)
  message         = [message] if message.kind_of?(String)
  before_message  = message[0]
  after_message   = message[1]
  $partials[name] = { block: block, before_message: before_message, after_message: after_message }
end

def print_to_terminal(message, color: nil, newline: true)
  return unless message
  message += "\n" if newline
  message = message.send(color) if color
  $stdout.print message
end

def print_task_status(name, status)
  print_to_terminal("\n--- #{name} task #{status.upcase}", color: :light_blue, newline: true)
end

# This wrapper allows us to insert external before/after
# tasks into every method executed by deplomat.
# Very useful for the one-time-tasks implementation.
def add_task(*tasks)
  tasks.each do |t|
    init_task_callbacks(t)
    $deplomat_tasks_callbacks[t][:before] << lambda { print_task_status(t, "started")  }
    $deplomat_tasks_callbacks[t][:after]  << lambda { print_task_status(t, "finished") }
  end if $log_tasks_status
  $deplomat_tasks += tasks
end
alias :add_tasks :add_task

def before_task(task_name, requisite_number, &block)
  init_task_callbacks(task_name)
  $deplomat_tasks_callbacks[task_name][:before] << lambda { block.call; update_requisite_number!(requisite_number) }
end

def after_task(task_name, requisite_number, &block)
  init_task_callbacks(task_name)
  $deplomat_tasks_callbacks[task_name][:after] << lambda { block.call; update_requisite_number!(requisite_number) }
end

def init_task_callbacks(task_name)
  $deplomat_tasks_callbacks[task_name] ||= {}
  $deplomat_tasks_callbacks[task_name][:before] ||= []
  $deplomat_tasks_callbacks[task_name][:after]  ||= []
end

def execute_tasks!
  $deplomat_tasks.each do |t|
    $deplomat_tasks_callbacks[t][:before].each { |block| block.call } if $deplomat_tasks_callbacks[t] && $deplomat_tasks_callbacks[t][:before]
    self.send(t)
    $deplomat_tasks_callbacks[t][:after].each { |block| block.call } if $deplomat_tasks_callbacks[t] && $deplomat_tasks_callbacks[t][:after]
  end
end

# This should be redefined in the deployment script.
def update_requisite_number!(n)
  # call Node#update_requisite_number! here
  # or do something else, more complicated.
end

def load_requisites!(counter, requisites_path: "#{Dir.pwd}/deployment_requisites")

  log = []
  log_requisites_count = "Checking #{requisites_path} dir for requisites..."
  files = Dir["#{requisites_path}/*.rb"].map do |fn|
    number = fn.split("/").last.match(/\A\d+_/).to_a.first&.chomp("_")
    number ? [number.to_i, fn] : nil
  end

  if files
    files = files.compact               # ignore files that don't start with a number
    .sort { |x,y| x.first <=> y.first } # Sort files according to the number in front of their names
    .map { |fn| fn.last }               # lose the folded Array we used to store the consecutive requisite number
    .slice(counter..-1)               # lose files whose consecutive requisite number is below or equals the counter
  end

  if files && files.size > 0
    log_requisites_count += "found #{files.size} new requisites."
    files.each do |fn|
      log << "Loading requisite: #{fn.split("/").last }"
      require fn
    end
  else
    log_requisites_count += "no files found"
  end
  log.unshift log_requisites_count

  return { counter: (files ? (counter + files.size) : 0), log: log }

end