-- Calculates shortest path of Knight from (1, 1) to (x, y).
-- Prints matrix of shortest paths for all cells.
-- Knight can't leave the rectangle from (1, 1) to (x, y).

-- See https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
-- See http://stackoverflow.com/questions/2339101

-- Usage:
-- $ lua knight_dijkstra.lua X Y

local binaryheap = require 'binaryheap'

local ROWS = tonumber(arg[2]) or 8
local COLS = tonumber(arg[1]) or 8

local unvisited = binaryheap.minUnique()

local function Cell(x, y)
  return x .. '_' .. y
end

local function Coordinates(cell)
  local x, y = cell:match('(%d+)_(%d+)')
  return x, y
end

local function neighbours(cell)
  local x, y = Coordinates(cell)
  local gen = coroutine.wrap(function()
    coroutine.yield(x - 1, y - 2)
    coroutine.yield(x - 1, y + 2)
    coroutine.yield(x + 1, y - 2)
    coroutine.yield(x + 1, y + 2)
    coroutine.yield(x - 2, y - 1)
    coroutine.yield(x - 2, y + 1)
    coroutine.yield(x + 2, y - 1)
    coroutine.yield(x + 2, y + 1)
  end)
  return coroutine.wrap(function()
    for xx, yy in gen do
      if 1 <= xx and xx <= COLS and
          1 <= yy and yy <= ROWS then
        coroutine.yield(Cell(xx, yy))
      end
    end
  end)
end

for y = 1, ROWS do
  for x = 1, COLS do
    local cell = Cell(x, y)
    unvisited:insert(math.huge, cell)
  end
end
unvisited:update('1_1', 0)

local final_distance = {}

while unvisited:peek() do
  local current_distance, current = unvisited:peek()
  assert(not final_distance[current])
  final_distance[current] = current_distance
  unvisited:remove(current)
  -- update neighbours
  local new_distance = current_distance + 1
  for neighbour in neighbours(current) do
    if unvisited.reverse[neighbour] then
      local pos = unvisited.reverse[neighbour]
      local distance = unvisited.value[pos]
      if distance > new_distance then
        unvisited:update(neighbour, new_distance)
      end
    end
  end
end

for y = 1, ROWS do
  local row = {}
  for x = 1, COLS do
    local cell = Cell(x, y)
    local distance = final_distance[cell]
    table.insert(row, distance)
  end
  print(table.concat(row, ' '))
end
