// The Witness play journal day 4
// tsc app.ts && node app.js

let level = [".X...",
             ".....",
             ".X.XX",
             "X....",
             ".....",
             ".X.X.",]

let pieces = [
  ["111",
   "..1"],

   ["22",
    "22"],

   ["3.",
    "33",
    ".3"],

   ["4",
    "4",
    "4",
    "4"],

   ["555",
    "5.."],

   [".6.",
    "666"],

   ["77.",
    ".77"]
]

function shallowcopy<T>(x:T[]) { return x.slice(0) } // Copy js array

function pickrandom<T>(x:T[]) : T { // Remove item from js array and return
  return x.splice( Math.floor(Math.random() * x.length), 1 )[0]
}

// All strings in a slate are assumed 1-letter. indexes are [y][x]
type Slate = string[][]
function blankslate(target:string[]=level) {
  return target.map(x => x.split(''))
}
function printslate(x:Slate) {
  x.forEach(function (x:string[]) {
    console.log(x.join(''))
  })
  console.log('')
}
function testslate(x:Slate) { // Puzzle criteria: No Xs visible
  return x.reduce(function(previous:boolean, current:string[], _,_2) {
    return previous && current.indexOf("X") < 0
  },true)
}

class Point { constructor(public x:number, public y:number) {} }

function testpoints(target:string[]=level) : Point[] { // all 30 coordinates
  let result = []
  for(let y = 0; y < level.length; y++)
    for(let x = 0; x < target[0].length; x++)
      result.push(new Point(x,y))
  return result
}
function mayinsertslate(slate:Slate, piece:string[], at:Point) : boolean {
  for(let y = 0; y < piece.length; y++) {
    for(let x = 0; x < piece[0].length; x++) {
      let sx = x + at.x, sy = y + at.y
      if (sy >= slate.length || sx >= slate[0].length) return false
      if (piece[y][x] != '.') {
        let into = slate[sy][sx]
        if (into != '.' && into != 'X') return false
      }
    }
  }
  return true
}
function insertslate(slate:Slate, piece:string[], at:Point) {
  for(let y = 0; y < piece.length; y++)
    for(let x = 0; x < piece[0].length; x++)
      if (piece[y][x] != '.')
        slate[y+at.y][x+at.x] = piece[y][x]
}

function attempt() {
  let slate = blankslate()
  let bag = shallowcopy(pieces)
  while (bag.length > 0) {
    let piece = pickrandom(bag)
    let candidates = testpoints()
    while (true) {
      if (candidates.length == 0) return null;
      let candidate = pickrandom(candidates)
      if (mayinsertslate(slate, piece, candidate)) {
        insertslate(slate, piece, candidate)
        break
      }
    }
  }
  if (!testslate(slate)) return null
  return slate
}

for(let trials = 0;;trials++) {
  let slate = attempt()
  if (slate) printslate(slate)
  if (trials > 0 && trials%10000==0) console.log(trials + " configurations attempted\n")
}

/*
    (TOPOGRAPHICALLY) UNIQUE SOLUTIONS FOUND BY PROGRAM

    4555.
    45111
    43771
    43377
    .6322
    66622

    .223.
    42233
    45553
    45111
    46771
    66677

    4555.
    45111
    43771
    43377
    2236.
    22666

    4555.
    45111
    42231
    42233
    .6773
    66677

    .1114
    22314
    22334
    55534
    5677.
    66677

    .111.
    22314
    22334
    55534
    56774
    66677
*/