const test = require('tap').test
const BitMatrix = require('core/bit-matrix')
const MaskPattern = require('core/mask-pattern')

test('Mask pattern - Pattern references', function (t) {
  const patternsCount = Object.keys(MaskPattern.Patterns).length
  t.equals(patternsCount, 8, 'Should return 8 patterns')

  t.end()
})

const expectedPattern000 = [
  1, 0, 1, 0, 1, 0,
  0, 1, 0, 1, 0, 1,
  1, 0, 1, 0, 1, 0,
  0, 1, 0, 1, 0, 1,
  1, 0, 1, 0, 1, 0,
  0, 1, 0, 1, 0, 1
]

const expectedPattern001 = [
  1, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 0,
  1, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 0,
  1, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 0
]

const expectedPattern010 = [
  1, 0, 0, 1, 0, 0,
  1, 0, 0, 1, 0, 0,
  1, 0, 0, 1, 0, 0,
  1, 0, 0, 1, 0, 0,
  1, 0, 0, 1, 0, 0,
  1, 0, 0, 1, 0, 0
]

const expectedPattern011 = [
  1, 0, 0, 1, 0, 0,
  0, 0, 1, 0, 0, 1,
  0, 1, 0, 0, 1, 0,
  1, 0, 0, 1, 0, 0,
  0, 0, 1, 0, 0, 1,
  0, 1, 0, 0, 1, 0
]

const expectedPattern100 = [
  1, 1, 1, 0, 0, 0,
  1, 1, 1, 0, 0, 0,
  0, 0, 0, 1, 1, 1,
  0, 0, 0, 1, 1, 1,
  1, 1, 1, 0, 0, 0,
  1, 1, 1, 0, 0, 0
]

const expectedPattern101 = [
  1, 1, 1, 1, 1, 1,
  1, 0, 0, 0, 0, 0,
  1, 0, 0, 1, 0, 0,
  1, 0, 1, 0, 1, 0,
  1, 0, 0, 1, 0, 0,
  1, 0, 0, 0, 0, 0
]

const expectedPattern110 = [
  1, 1, 1, 1, 1, 1,
  1, 1, 1, 0, 0, 0,
  1, 1, 0, 1, 1, 0,
  1, 0, 1, 0, 1, 0,
  1, 0, 1, 1, 0, 1,
  1, 0, 0, 0, 1, 1
]

const expectedPattern111 = [
  1, 0, 1, 0, 1, 0,
  0, 0, 0, 1, 1, 1,
  1, 0, 0, 0, 1, 1,
  0, 1, 0, 1, 0, 1,
  1, 1, 1, 0, 0, 0,
  0, 1, 1, 1, 0, 0
]

test('MaskPattern validity', function (t) {
  t.notOk(MaskPattern.isValid(), 'Should return false if no input')
  t.notOk(MaskPattern.isValid(''), 'Should return false if value is not a number')
  t.notOk(MaskPattern.isValid(-1), 'Should return false if value is not in range')
  t.notOk(MaskPattern.isValid(8), 'Should return false if value is not in range')

  t.end()
})

test('MaskPattern from value', function (t) {
  t.equal(MaskPattern.from(5), 5, 'Should return correct mask pattern from a number')
  t.equal(MaskPattern.from('5'), 5, 'Should return correct mask pattern from a string')
  t.equal(MaskPattern.from(-1), undefined, 'Should return undefined if value is invalid')
  t.equal(MaskPattern.from(null), undefined, 'Should return undefined if value is null')

  t.end()
})

test('Mask pattern - Apply mask', function (t) {
  const patterns = Object.keys(MaskPattern.Patterns).length
  const expectedPatterns = [
    expectedPattern000, expectedPattern001, expectedPattern010, expectedPattern011,
    expectedPattern100, expectedPattern101, expectedPattern110, expectedPattern111
  ]

  for (let p = 0; p < patterns; p++) {
    const matrix = new BitMatrix(6)
    MaskPattern.applyMask(p, matrix)
    t.deepEqual(matrix.data, new Uint8Array(expectedPatterns[p]), 'Should return correct pattern')
  }

  const matrix = new BitMatrix(2)
  matrix.set(0, 0, false, true)
  matrix.set(0, 1, false, true)
  matrix.set(1, 0, false, true)
  matrix.set(1, 1, false, true)
  MaskPattern.applyMask(0, matrix)

  t.deepEqual(matrix.data, new Uint8Array([false, false, false, false]), 'Should leave reserved bit unchanged')

  t.throws(function () { MaskPattern.applyMask(-1, new BitMatrix(1)) }, 'Should throw if pattern is invalid')

  t.end()
})

test('Mask pattern - Penalty N1', function (t) {
  let matrix = new BitMatrix(11)
  matrix.data = [
    1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1,
    1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
    0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
    1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
    1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
    1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
    1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
    1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
    0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1
  ]

  t.equals(MaskPattern.getPenaltyN1(matrix), 59,
    'Should return correct penalty points')

  matrix = new BitMatrix(6)
  matrix.data = expectedPattern000

  t.equals(MaskPattern.getPenaltyN1(matrix), 0,
    'Should return correct penalty points')

  matrix.data = expectedPattern001

  t.equals(MaskPattern.getPenaltyN1(matrix), 24,
    'Should return correct penalty points')

  matrix.data = expectedPattern010

  t.equals(MaskPattern.getPenaltyN1(matrix), 24,
    'Should return correct penalty points')

  matrix.data = expectedPattern101

  t.equals(MaskPattern.getPenaltyN1(matrix), 20,
    'Should return correct penalty points')

  t.end()
})

test('Mask pattern - Penalty N2', function (t) {
  let matrix = new BitMatrix(8)
  matrix.data = [
    1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 0, 0, 0, 1, 1,
    0, 1, 1, 1, 0, 0, 1, 1,
    1, 0, 0, 0, 1, 1, 0, 1,
    0, 0, 0, 0, 0, 0, 0, 0,
    1, 0, 1, 1, 0, 0, 0, 0,
    1, 1, 1, 1, 1, 0, 0, 0,
    1, 1, 0, 0, 1, 0, 1, 1
  ]

  t.equals(MaskPattern.getPenaltyN2(matrix), 45,
    'Should return correct penalty points')

  matrix = new BitMatrix(6)
  matrix.data = expectedPattern000

  t.equals(MaskPattern.getPenaltyN2(matrix), 0,
    'Should return correct penalty points')

  matrix.data = expectedPattern010

  t.equals(MaskPattern.getPenaltyN2(matrix), 30,
    'Should return correct penalty points')

  matrix.data = expectedPattern100

  t.equals(MaskPattern.getPenaltyN2(matrix), 36,
    'Should return correct penalty points')

  t.end()
})

test('Mask pattern - Penalty N3', function (t) {
  const matrix = new BitMatrix(11)
  matrix.data = [
    0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
    0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1,
    0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1,
    0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1,
    0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1,
    1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1,
    1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0,
    1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0
  ]

  t.equals(MaskPattern.getPenaltyN3(matrix), 160,
    'Should return correct penalty points')

  matrix.data = [
    1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0,
    1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0,
    1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0,
    1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0,
    1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1,
    1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0,
    0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1,
    1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1,
    0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1,
    1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0,
    1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1
  ]

  t.equals(MaskPattern.getPenaltyN3(matrix), 280,
    'Should return correct penalty points')

  t.end()
})

test('Mask pattern - Penalty N4', function (t) {
  const matrix = new BitMatrix(10)
  matrix.data = new Array(50).fill(1).concat(new Array(50).fill(0))

  t.equals(MaskPattern.getPenaltyN4(matrix), 0,
    'Should return correct penalty points')

  const matrix2 = new BitMatrix(21)
  matrix2.data = new Array(190).fill(1).concat(new Array(251).fill(0))

  t.equals(MaskPattern.getPenaltyN4(matrix2), 10,
    'Should return correct penalty points')

  const matrix3 = new BitMatrix(10)
  matrix3.data = new Array(22).fill(1).concat(new Array(78).fill(0))

  t.equals(MaskPattern.getPenaltyN4(matrix3), 50,
    'Should return correct penalty points')

  t.end()
})

test('Mask pattern - Best mask', function (t) {
  const matrix = new BitMatrix(11)
  matrix.data = [
    0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
    0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1,
    0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1,
    0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1,
    0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1,
    1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1,
    1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0,
    1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0
  ]

  const mask = MaskPattern.getBestMask(matrix, function () {})
  t.ok(!isNaN(mask), 'Should return a number')

  t.ok(mask >= 0 && mask < 8,
    'Should return a number in range 0,7')

  t.end()
})