class Cucumber::MultilineArgument::DataTable::DiffMatrices

:nodoc:

def call

def call
  prepare_diff
  perform_diff
  fill_in_missing_values
  raise_error if should_raise?
end

def changes

def changes
  require 'diff/lcs'
  diffable_cell_matrix = cell_matrix.dup.extend(::Diff::LCS)
  diffable_cell_matrix.diff(other_table_cell_matrix).flatten(1)
end

def ensure_2d(array)

def ensure_2d(array)
  array[0].is_a?(Array) ? array : [array]
end

def fill_in_missing_values

def fill_in_missing_values
  other_table_cell_matrix.each_with_index do |other_row, i|
    row_index = row_indices.index(i)
    row = cell_matrix[row_index] if row_index
    next unless row
    (original_width..padded_width).each do |col_index|
      surplus_cell = other_row[col_index]
      row[col_index].value = surplus_cell.value if row[col_index]
    end
  end
end

def initialize(cell_matrix, other_table_cell_matrix, options)

def initialize(cell_matrix, other_table_cell_matrix, options)
  @cell_matrix = cell_matrix
  @other_table_cell_matrix = other_table_cell_matrix
  @options = options
end

def inspect_rows(missing_row, inserted_row)

def inspect_rows(missing_row, inserted_row)
  missing_row.each_with_index do |missing_cell, col|
    inserted_cell = inserted_row[col]
    if missing_cell.value != inserted_cell.value && missing_cell.value.to_s == inserted_cell.value.to_s
      missing_cell.inspect!
      inserted_cell.inspect!
    end
  end
end

def mark_as_missing(col)

def mark_as_missing(col)
  col.each do |cell|
    cell.status = :undefined
  end
end

def misplaced_col

def misplaced_col
  cell_matrix[0] != original_header
end

def missing_col

def missing_col
  cell_matrix[0].find { |cell| cell.status == :undefined }
end

def pad_and_match

Pads two cell matrices to same column width and matches columns according to header value.
def pad_and_match
  cols = cell_matrix.transpose
  unmatched_cols = other_table_cell_matrix.transpose
  header_values = cols.map(&:first)
  matched_cols = []
  header_values.each_with_index do |v, i|
    mapped_index = unmatched_cols.index { |unmapped_col| unmapped_col.first == v }
    if mapped_index
      matched_cols << unmatched_cols.delete_at(mapped_index)
    else
      mark_as_missing(cols[i])
      empty_col = ensure_2d(other_table_cell_matrix).collect { SurplusCell.new(nil, self, -1) }
      empty_col.first.value = v
      matched_cols << empty_col
    end
  end
  unmatched_cols.each do
    empty_col = cell_matrix.collect { SurplusCell.new(nil, self, -1) }
    cols << empty_col
  end
  self.cell_matrix = ensure_2d(cols.transpose)
  self.other_table_cell_matrix = ensure_2d((matched_cols + unmatched_cols).transpose)
end

def perform_diff

def perform_diff
  inserted    = 0
  missing     = 0
  last_change = nil
  changes.each do |change|
    if change.action == '-'
      @missing_row_pos = change.position + inserted
      cell_matrix[missing_row_pos].each { |cell| cell.status = :undefined }
      row_indices.insert(missing_row_pos, nil)
      missing += 1
    else # '+'
      @insert_row_pos = change.position + missing
      inserted_row = change.element
      inserted_row.each { |cell| cell.status = :comment }
      cell_matrix.insert(insert_row_pos, inserted_row)
      row_indices[insert_row_pos] = nil
      inspect_rows(cell_matrix[missing_row_pos], inserted_row) if last_change == '-'
      inserted += 1
    end
    last_change = change.action
  end
end

def prepare_diff

def prepare_diff
  @original_width = cell_matrix[0].length
  @original_header = other_table_cell_matrix[0]
  pad_and_match
  @padded_width = cell_matrix[0].length
  @row_indices = Array.new(other_table_cell_matrix.length) { |n| n }
end

def raise_error

def raise_error
  table = DataTable.from([[]])
  table.instance_variable_set :@cell_matrix, cell_matrix
  raise Different, table if should_raise?
end

def should_raise?

def should_raise?
  [
    missing_row_pos && options.fetch(:missing_row, true),
    insert_row_pos  && options.fetch(:surplus_row, true),
    missing_col     && options.fetch(:missing_col, true),
    surplus_col     && options.fetch(:surplus_col, false),
    misplaced_col   && options.fetch(:misplaced_col, false)
  ].any?
end

def surplus_col

def surplus_col
  padded_width > original_width
end