class ChefCLI::Policyfile::Differ

def added_cookbooks

def added_cookbooks
  detect_cookbook_changes if @added_cookbooks.nil?
  @added_cookbooks
end

def color_for_line(line)

def color_for_line(line)
  case line[0].chr
  when "+"
    :green
  when "-"
    :red
  when "@"
    line[1].chr == "@" ? :blue : nil
  else
    nil
  end
end

def detect_cookbook_changes

def detect_cookbook_changes
  all_locked_cookbooks = old_cookbook_locks.keys | new_cookbook_locks.keys
  @added_cookbooks = []
  @removed_cookbooks = []
  @modified_cookbooks = []
  all_locked_cookbooks.each do |cb_name|
    if old_cookbook_locks.key?(cb_name) && new_cookbook_locks.key?(cb_name)
      old_cb_lock = old_cookbook_locks[cb_name]
      new_cb_lock = new_cookbook_locks[cb_name]
      if old_cb_lock != new_cb_lock
        @modified_cookbooks << cb_name
      end
    elsif old_cookbook_locks.key?(cb_name)
      @removed_cookbooks << cb_name
    elsif new_cookbook_locks.key?(cb_name)
      @added_cookbooks << cb_name
    else
      raise "Bug: cookbook lock #{cb_name} cannot be determined as new/removed/modified/unmodified"
    end
  end
end

def diff_lines(old_lines, new_lines)

def diff_lines(old_lines, new_lines)
  file_length_difference = INITIAL_FILE_LENGTH_DIFFERENCE
  previous_hunk = nil
  diffs = Diff::LCS.diff(old_lines, new_lines)
  ui.print("\n")
  diffs.each do |piece|
    hunk = Diff::LCS::Hunk.new(old_lines, new_lines, piece, LINES_OF_CONTEXT, file_length_difference)
    file_length_difference = hunk.file_length_difference
    maybe_contiguous_hunks = (previous_hunk.nil? || hunk.merge(previous_hunk))
    unless maybe_contiguous_hunks
      print_color_diff("#{previous_hunk.diff(FORMAT)}\n")
    end
    previous_hunk = hunk
  end
  print_color_diff("#{previous_hunk.diff(FORMAT)}\n") unless previous_hunk.nil?
  ui.print("\n")
end

def different?

def different?
  !updated_sections.empty?
end

def h1(str)

def h1(str)
  ui.msg(str)
  ui.msg("=" * str.size)
end

def h2(str)

def h2(str)
  ui.msg(str)
  ui.msg("-" * str.size)
end

def initialize(old_name: nil, old_lock: nil, new_name: nil, new_lock: nil, ui: nil)

def initialize(old_name: nil, old_lock: nil, new_name: nil, new_lock: nil, ui: nil)
  @old_lock = old_lock
  @new_lock = new_lock
  @old_name = old_name
  @new_name = new_name
  @ui = ui
  @added_cookbooks    = nil
  @removed_cookbooks  = nil
  @modified_cookbooks = nil
end

def lock_name

def lock_name
  old_lock["name"]
end

def modified_cookbooks

def modified_cookbooks
  detect_cookbook_changes if @modified_cookbooks.nil?
  @modified_cookbooks
end

def new_cookbook_locks

def new_cookbook_locks
  new_lock["cookbook_locks"]
end

def old_cookbook_locks

def old_cookbook_locks
  old_lock["cookbook_locks"]
end

def pastel

def pastel
  @pastel ||= Pastel.new
end

def pretty_json(data)

def pretty_json(data)
  FFI_Yajl::Encoder.encode(data, pretty: true).lines.map(&:chomp)
end

def print_color_diff(hunk)

def print_color_diff(hunk)
  hunk.to_s.each_line do |line|
    line_color = color_for_line(line)
    line = pastel.decorate(line, line_color) unless line_color.nil?
    ui.print(line)
  end
end

def removed_cookbooks

def removed_cookbooks
  detect_cookbook_changes if @removed_cookbooks.nil?
  @removed_cookbooks
end

def report_added_cookbooks

def report_added_cookbooks
  return nil if added_cookbooks.empty?
  h1("ADDED COOKBOOKS")
  added_cookbooks.each do |cb_name|
    ui.print("\n")
    old_lock = []
    new_lock = pretty_json(new_cookbook_locks[cb_name])
    h2(cb_name)
    diff_lines(old_lock, new_lock)
  end
end

def report_default_attribute_changes

def report_default_attribute_changes
  return nil unless updated_sections.include?("default_attributes")
  h1("DEFAULT ATTRIBUTES CHANGED")
  old_default = pretty_json(old_lock["default_attributes"])
  new_default = pretty_json(new_lock["default_attributes"])
  diff_lines(old_default, new_default)
end

def report_modified_cookbooks

def report_modified_cookbooks
  return nil if modified_cookbooks.empty?
  h1("MODIFIED COOKBOOKS")
  modified_cookbooks.each do |cb_name|
    ui.print("\n")
    old_lock = pretty_json(old_cookbook_locks[cb_name])
    new_lock = pretty_json(new_cookbook_locks[cb_name])
    h2(cb_name)
    diff_lines(old_lock, new_lock)
  end
end

def report_override_attribute_changes

def report_override_attribute_changes
  return nil unless updated_sections.include?("override_attributes")
  h1("OVERRIDE ATTRIBUTES CHANGED")
  old_override = pretty_json(old_lock["override_attributes"])
  new_override = pretty_json(new_lock["override_attributes"])
  diff_lines(old_override, new_override)
end

def report_removed_cookbooks

def report_removed_cookbooks
  return nil if removed_cookbooks.empty?
  h1("REMOVED COOKBOOKS")
  removed_cookbooks.each do |cb_name|
    ui.print("\n")
    old_lock = pretty_json(old_cookbook_locks[cb_name])
    new_lock = []
    h2(cb_name)
    diff_lines(old_lock, new_lock)
  end
end

def report_rev_id_changes

def report_rev_id_changes
  h1("REVISION ID CHANGED")
  old_rev = old_lock["revision_id"]
  new_rev = new_lock["revision_id"]
  diff_lines([ old_rev ], [ new_rev ])
end

def report_run_list_changes

def report_run_list_changes
  return nil unless updated_sections.include?("run_list")
  h1("RUN LIST CHANGED")
  old_run_list = old_lock["run_list"]
  new_run_list = new_lock["run_list"]
  diff_lines(old_run_list, new_run_list)
end

def run_report

def run_report
  unless different?
    ui.err("No changes for policy lock '#{lock_name}' between '#{old_name}' and '#{new_name}'")
    return true
  end
  ui.print("Policy lock '#{lock_name}' differs between '#{old_name}' and '#{new_name}':\n\n")
  report_rev_id_changes
  report_run_list_changes
  report_added_cookbooks
  report_removed_cookbooks
  report_modified_cookbooks
  report_default_attribute_changes
  report_override_attribute_changes
end

def updated_sections

def updated_sections
  @updated_sections ||= POLICY_SECTIONS.select do |key|
    old_lock[key] != new_lock[key]
  end
end