import { KetchupSynced } from '.'

interface TTTState {
  board: ('X' | 'O' | undefined)[][]
}
const tttInitial = {
  board: [
    [undefined, undefined, undefined],
    [undefined, undefined, undefined],
    [undefined, undefined, undefined],
  ],
}
interface TTTAction {
  row: number
  col: number
  mark: 'X' | 'O'
}
function tttReducer(state: TTTState, { row, col, mark }: TTTAction): TTTState {
  if (state.board[row][col] !== undefined) {
    return state
  }
  return {
    board: state.board.map((rArr, rIdx) =>
      rArr.map((cell, cIdx) => rIdx === row && cIdx === col ? mark : cell)
    ),
  }
}

test('Basic linear', () => {
  const s = new KetchupSynced(tttInitial, 1000, tttReducer)
  expect(s.tryProcess({
    action: { row: 0, col: 0, mark: 'X' },
    timestamp: 1010,
    clientId: 0,
  })).toBe(true)
  expect(s.tryProcess({
    action: { row: 1, col: 2, mark: 'O' },
    timestamp: 1020,
    clientId: 0,
  })).toBe(true)
  expect(s.tryProcess({
    action: { row: 2, col: 1, mark: 'X' },
    timestamp: 1030,
    clientId: 0,
  })).toBe(true)
  expect(s.projState).toEqual({
    board: [
      ['X', undefined, undefined],
      [undefined, undefined, 'O'],
      [undefined, 'X', undefined],
    ],
  })
})

test('Out-of-order', () => {
  const s = new KetchupSynced(tttInitial, 1000, tttReducer)
  expect(s.tryProcess({
    action: { row: 0, col: 0, mark: 'X' },
    timestamp: 1010,
    clientId: 0,
  })).toBe(true)
  expect(s.tryProcess({
    action: { row: 1, col: 2, mark: 'X' },
    timestamp: 1040,
    clientId: 0,
  })).toBe(true)
  expect(s.projState).toEqual({
    board: [
      ['X', undefined, undefined],
      [undefined, undefined, 'X'],
      [undefined, undefined, undefined],
    ],
  })
  expect(s.tryProcess({
    action: { row: 1, col: 2, mark: 'O' },
    timestamp: 1030,
    clientId: 0,
  })).toBe(true)
  expect(s.tryProcess({
    action: { row: 2, col: 1, mark: 'X' },
    timestamp: 1020,
    clientId: 0,
  })).toBe(true)
  expect(s.projState).toEqual({
    board: [
      ['X', undefined, undefined],
      [undefined, undefined, 'O'],
      [undefined, 'X', undefined],
    ],
  })
})

test('Out-of-order with confirms', () => {
  const s = new KetchupSynced(tttInitial, 1000, tttReducer)
  expect(s.tryProcess({
    action: { row: 0, col: 0, mark: 'X' },
    timestamp: 1010,
    clientId: 0,
  })).toBe(true)

  expect(s.confirmBefore(1010)).toBe(0)
  expect(s.confState).toEqual(tttInitial)
  expect(s.projs).toEqual([{
    action: { row: 0, col: 0, mark: 'X' },
    timestamp: 1010,
    clientId: 0,
    state: {
      board: [
        ['X', undefined, undefined],
        [undefined, undefined, undefined],
        [undefined, undefined, undefined],
      ],
    },
  }])

  expect(s.confirmBefore(1011)).toBe(1)
  expect(s.confState).toEqual({
    board: [
      ['X', undefined, undefined],
      [undefined, undefined, undefined],
      [undefined, undefined, undefined],
    ],
  })
  expect(s.projs).toEqual([])

  expect(s.tryProcess({
    action: { row: 0, col: 1, mark: 'X' },
    timestamp: 1040,
    clientId: 0,
  })).toBe(true)
  expect(s.tryProcess({
    action: { row: 0, col: 2, mark: 'O' },
    timestamp: 1030,
    clientId: 0,
  })).toBe(true)
  expect(s.tryProcess({
    action: { row: 1, col: 0, mark: 'X' },
    timestamp: 1020,
    clientId: 0,
  })).toBe(true)

  expect(s.confirmBefore(1021)).toBe(1)
  expect(s.confState).toEqual({
    board: [
      ['X', undefined, undefined],
      ['X', undefined, undefined],
      [undefined, undefined, undefined],
    ],
  })
  expect(s.projs).toEqual([{
    action: { row: 0, col: 2, mark: 'O' },
    timestamp: 1030,
    clientId: 0,
    state: {
      board: [
        ['X', undefined, 'O'],
        ['X', undefined, undefined],
        [undefined, undefined, undefined],
      ],
    },
  }, {
    action: { row: 0, col: 1, mark: 'X' },
    timestamp: 1040,
    clientId: 0,
    state: {
      board: [
        ['X', 'X', 'O'],
        ['X', undefined, undefined],
        [undefined, undefined, undefined],
      ],
    },
  }])
  expect(s.projState).toEqual({
    board: [
      ['X', 'X', 'O'],
      ['X', undefined, undefined],
      [undefined, undefined, undefined],
    ],
  })
})
