#!/usr/bin/env ruby
# stdin: sgt dominosa save
# stdout: sgt dominosa save with one pass worth of solution
# can optionally take an argument in the form of a number to highlight

require 'pp'

def valid(i1, vm) # valid cell index?
  if i1 >= 0 and i1 < (vm+1) * (vm+2)
    return i1
  end
end

def l(i1, vm) # left from index
  if i1%(vm+2) > 0 and valid(i1, vm)
    return(i1 - 1)
  end
end

def r(i1, vm) # right from index
  if i1%(vm+2) <= vm and valid(i1, vm)
    return(i1 + 1)
  end
end

def u(i1, vm) # up from index
  return(valid(i1-vm-2, vm))
end

def d(i1, vm) # down from index
  return(valid(i1+vm+2, vm))
end

def export_format(name, value) # export savefile line
  return(sprintf("%-8s:%d:%s", name, value.length, value))
end

config = Hash.new # Save file contents
moves = Hash.new{|h,k| h[k]=Hash.new(&h.default_proc) } # Move list. May as well be sparse; we don't fill it and we don't walk it.
results = Array.new # Domino combination possibility table
moves_to_merge = Array.new
highlight = ARGV[0] # emphasize a particular number
argtuples = Array.new # record of all tuples with ARGV[0] == v1

$stdin.each_line do |line|
    if line =~ /^MOVE/
      moves_to_merge += line.scan(/.+:([DE])([0-9]+),([0-9]+)/)
    else
      config[line.chomp.split(':')[0].strip] = line.chomp.split(':')[2].strip
    end
end

vm = config['PARAMS'].to_i # domino value max

# Build results table before walking moves so we can record existing tuples

for v1 in 0..vm
  results[v1] = Array.new
  for v2 in 0..vm
  results[v1][v2] = Hash.new
  results[v1][v2]["count"]=0 # total tuples
  results[v1][v2]["D"]=0 # multiple 'D's are a bad thing we should highlight
  results[v1][v2]["origin"]=nil # If there's only one tuple possibility, we care about its origin.
  end
end

# Build board before walking moves so we can record existing tuples

board = config['DESC'].scan(/\[[0-9]+\]|[0-9]/).map{|n| n.gsub(/[\[\]]/,'').to_i}

# Flatten moves. We'll also be generating and saving a lot of edge 'E' states to rely on.
# BUG: A 'D' placed on the edge of another 'D' is handled pathologically.

if config['STATEPOS'].to_i > 1 # position in the move history

  moves_to_merge[0..config['STATEPOS'].to_i-2].each do | move |

    i1 = move[1].to_i
    i2 = move[2].to_i

    v1 = board[i1]
    v2 = board[i2]

    case move[0]
    when 'D'
      case moves[i1][i2]
      when 'D' # D + D -> nil + edge nil
        ( edge = l(i1, vm) ) and moves[edge][i1] = nil
        ( edge = r(i1, vm) ) and moves[i1][edge] = nil
        ( edge = u(i1, vm) ) and moves[edge][i1] = nil
        ( edge = d(i1, vm) ) and moves[i1][edge] = nil

        ( edge = l(i2, vm) ) and moves[edge][i2] = nil
        ( edge = r(i2, vm) ) and moves[i2][edge] = nil
        ( edge = u(i2, vm) ) and moves[edge][i2] = nil
        ( edge = d(i2, vm) ) and moves[i2][edge] = nil

        moves[i1][i2] = 'nil'
        results[v1][v2]['D'] -= 1
        results[v2][v1]['D'] -= 1
      else # E|nil + D -> D + edge E
        ( edge = l(i1, vm) ) and moves[edge][i1] = '34' # color code for $stderr.printing
        ( edge = r(i1, vm) ) and moves[i1][edge] = '34' # 3[0-7] will be converted back to E on export
        ( edge = u(i1, vm) ) and moves[edge][i1] = '34'
        ( edge = d(i1, vm) ) and moves[i1][edge] = '34'

        ( edge = l(i2, vm) ) and moves[edge][i2] = '34'
        ( edge = r(i2, vm) ) and moves[i2][edge] = '34'
        ( edge = u(i2, vm) ) and moves[edge][i2] = '34'
        ( edge = d(i2, vm) ) and moves[i2][edge] = '34'

        moves[i1][i2] = 'D'
        results[v1][v2]['D'] += 1
        results[v2][v1]['D'] += 1
      end
    when 'E'
      case moves[i1][i2]
      when '36' # E + E -> nil
        moves[i1][i2] = 'nil'
      else # nil + E -> E
        moves[i1][i2] = '36'
      end
    end

  end
 
end

board.each_index do | i1 |
  v1 = board[i1]
  results_to_merge = Array.new
  valid_moves = Array.new
  ( i2 = u(i1, vm) ) && ( v2 = board[i2] ) && ( ( ( results[v2][v1]['D'] > 0 and moves[i2][i1] !~ /D|3[0-7]/ ) && moves[i2][i1] = '35' ) or ( moves[i2][i1] !~ /3[0-7]/ && results_to_merge << v2 && valid_moves << [i2, i1] ) )
  ( i2 = d(i1, vm) ) && ( v2 = board[i2] ) && ( ( ( results[v1][v2]['D'] > 0 and moves[i1][i2] !~ /D|3[0-7]/ ) && moves[i1][i2] = '35' ) or ( moves[i1][i2] !~ /3[0-7]/ && results_to_merge << v2 && valid_moves << [i1, i2] ) )
  ( i2 = l(i1, vm) ) && ( v2 = board[i2] ) && ( ( ( results[v2][v1]['D'] > 0 and moves[i2][i1] !~ /D|3[0-7]/ ) && moves[i2][i1] = '35' ) or ( moves[i2][i1] !~ /3[0-7]/ && results_to_merge << v2 && valid_moves << [i2, i1] ) )
  ( i2 = r(i1, vm) ) && ( v2 = board[i2] ) && ( ( ( results[v1][v2]['D'] > 0 and moves[i1][i2] !~ /D|3[0-7]/ ) && moves[i1][i2] = '35' ) or ( moves[i1][i2] !~ /3[0-7]/ && results_to_merge << v2 && valid_moves << [i1, i2] ) )
  if valid_moves.length == 1 # If only one move total is possible...
    moves[valid_moves[0][0]][valid_moves[0][1]] = "D" # ...Put a domino there.
  end
  results_to_merge.uniq.each do | v2 | # uniq eliminates duplicate tuples...
    results[v1][v2]["count"] += 1 # ...which get flattened down to 1...
    results[v1][v2]["origin"] = i1 # ...so we can tell later if this index is interesting
  end
  highlight == v1.to_s && argtuples << results_to_merge.sort.uniq
end

vw = vm.to_s.length # value max string width

$stderr.print("Tuple possibility table:\n")
$stderr.print(' ' * ( vw + 1 ) )
for value in 0..vm
  $stderr.printf " %#{vw}d", value
end
$stderr.print("\n\n")
for v1 in 0..vm
  v1complete = 0 # total of completed tuples for v1
  $stderr.printf "%#{vw}d ", v1
  for v2 in 0..vm
    if results[v1][v2]["count"] == 1 or ( v1 == v2 and results[v1][v2]["count"] == 2 )
      v1complete += 1
      $stderr.printf " \e[1;36m%#{vw}d\e[0m", results[v1][v2]["count"]
    elsif results[v1][v2]["count"] == 0
      $stderr.printf " \e[1;31m%#{vw}d\e[0m", results[v1][v2]["count"]
    else
      $stderr.printf " %#{vw}d", results[v1][v2]["count"]
    end
    if results[v1][v2]["count"] == 1 or ( v1 == v2 && results[v1][v2]["count"] == 2 )
      i1 = results[v1][v2]["origin"]
      ( i2 = u(i1, vm) ) and board[i2] != v2 and moves[i2][i1] !~ /3[0-7]/ and moves[i2][i1] = '33'
      ( i2 = d(i1, vm) ) and board[i2] != v2 and moves[i1][i2] !~ /3[0-7]/ and moves[i1][i2] = '33'
      ( i2 = l(i1, vm) ) and board[i2] != v2 and moves[i2][i1] !~ /3[0-7]/ and moves[i2][i1] = '33'
      ( i2 = r(i1, vm) ) and board[i2] != v2 and moves[i1][i2] !~ /3[0-7]/ and moves[i1][i2] = '33'
    end
  end
  $stderr.printf("  %#{vw}d \e[1;36m%#{vw}d\e[0m\n", v1, v1complete)
end
$stderr.print("\n")
$stderr.print(' '*(vw+1))
for v1 in 0..vm
  $stderr.printf " %#{vw}d", v1
end
$stderr.print("\n\nBoard:")

barrierrow = String.new

board.each_index do | i1 |
  if i1%(vm+2) == 0
    $stderr.print("\n" + barrierrow + "\n")
    barrierrow = ""
  end
  v = board[i1]
  highlight == v.to_s && $stderr.print("\e[1;31m")
  $stderr.printf "%#{vw}d\e[0m", v
  if ( i2 = d(i1, vm) ) and moves[i1][i2] =~ /3[0-7]/
    barrierrow = barrierrow + "\e[1;#{moves[i1][i2]}m" + "-" * vw + "\e[0m   "
  else
    barrierrow = barrierrow + " " * vw + "   "
  end
  if ( i2 = r(i1, vm) ) and moves[i1][i2] =~ /3[0-7]/
    $stderr.print(" \e[1;#{moves[i1][i2]}m|\e[0m ")
  else
    $stderr.print("   ")
  end
end

$stderr.print("\n\n")

if highlight
  $stderr.print("Possible tuples beginning with " + highlight + ":\n")
  argtuples.sort.each do | tuple |
    tuple.length > 1 && PP.pp(tuple, $stderr)
  end
end

moveEexport = Array.new
moveDexport = Array.new
moves.each_pair do | i1, subhash |
  subhash.each_pair do | i2, state |
    state =~ /3[0-7]/ && moveEexport << "E" + i1.to_s + "," + i2.to_s
    state =~ /D/      && moveDexport << "D" + i1.to_s + "," + i2.to_s
  end
end

moveexport = moveEexport + moveDexport
#moveexport = moveDexport

config.each_pair do | k, v |
  config['NSTATES']  = (moveexport.length + 1).to_s
  config['STATEPOS'] = (moveexport.length + 1).to_s
  $stdout.print(export_format(k, v) + "\n")
end

moveexport.each do | move |
  $stdout.print(export_format("MOVE", move) + "\n")
end
