import fs from "fs-extra"
import _ from "lodash"

import * as action from "../../src/action/common"
import Session, { dispatch, getState } from "../../src/common/session"
import {
  Label2DList,
  makeDrawableLabel2D
} from "../../src/drawable/2d/label2d_list"
import { commit2DLabels } from "../../src/drawable/states"
import { makeImageViewerConfig } from "../../src/functional/states"
import { Size2D } from "../../src/math/size2d"
import { Vector2D } from "../../src/math/vector2d"
import { IdType, RectType, SimpleRect } from "../../src/types/state"
import { setupTestStore } from "../components/util"
import { checkBox2D } from "../util/shape"
import {
  drawBox2D,
  initializeTestingObjects,
  moveBox2D,
  resizeBox2D
} from "./util"

const data = JSON.parse(
  fs.readFileSync("./app/test/test_states/sample_state.json", "utf8")
)

beforeEach(() => {
  setupTestStore(data)
})

beforeAll(() => {
  setupTestStore(data)

  Session.images.length = 0
  Session.images.push({ [-1]: new Image(1000, 1000) })
  for (let i = 0; i < getState().task.items.length; i++) {
    dispatch(action.loadItem(i, -1))
  }
  dispatch(action.addViewerConfig(0, makeImageViewerConfig(0)))
  dispatch(action.goToItem(0))
})

test("Add new valid drawable", () => {
  const state = Session.getState()
  expect(_.size(state.task.items[0].labels)).toEqual(3)
  const label2dlist = new Label2DList()
  const label = makeDrawableLabel2D(label2dlist, "box2d", {})
  expect(label).not.toBeNull()
  if (label !== null) {
    label.initTemp(state, new Vector2D(10, 10))
    // New labels can ignore selected or highlighted property
    // _handleIndex is actually not used, put a random
    label.onMouseDown(new Vector2D(10, 10), 1)
    // Mouse move is essential
    // labelIndex and handleIndex is actually not used here, put a random
    label.onMouseMove(new Vector2D(20, 20), new Size2D(1000, 1000), 1, 2)
    label.onMouseUp(new Vector2D(20, 20))

    commit2DLabels([label])

    const currentState = Session.getState()
    expect(_.size(currentState.task.items[0].labels)).toEqual(4)
    expect(currentState.task.items[0].labels[label.labelId]).not.toBeUndefined()
    // Shape id is generated by every usage, until it is saved in the states
    // So cannot use original label's shape id to test
    const savedLabel = currentState.task.items[0].labels[label.labelId]
    expect(
      currentState.task.items[0].shapes[savedLabel.shapes[0]]
    ).not.toBeUndefined()
  }
})

test("Add new invalid drawable", () => {
  const state = Session.getState()
  expect(_.size(state.task.items[0].labels)).toEqual(3)
  const label2dlist = new Label2DList()
  const label = makeDrawableLabel2D(label2dlist, "box2d", {})
  expect(label).not.toBeNull()
  if (label !== null) {
    label.initTemp(state, new Vector2D(10, 10))
    label.onMouseDown(new Vector2D(10, 10), 1)
    // Mouse move is essential
    label.onMouseMove(new Vector2D(12, 12), new Size2D(1000, 1000), 1, 2)
    label.onMouseUp(new Vector2D(12, 12))

    commit2DLabels([label])

    const currentState = Session.getState()
    expect(_.size(currentState.task.items[0].labels)).toEqual(3)
    expect(currentState.task.items[0].labels[label.labelId]).toBeUndefined()
    expect(_.size(currentState.task.items[0].shapes)).toEqual(
      _.size(state.task.items[0].shapes)
    )
  }
})

test("Update existing drawable", () => {
  const state = Session.getState()
  expect(_.size(state.task.items[0].labels)).toEqual(3)
  // Label coord is [459, 276][752, 400]
  const label2dList = new Label2DList()
  label2dList.updateState(state)
  const label = label2dList.get(1)
  // Existing label must call selected and highlighted property
  label.setSelected(true)
  label.setHighlighted(true, 5) // Handles.BOTTOM_RIGHT
  label.onMouseDown(new Vector2D(752, 400), 1)
  // Mouse move is essential
  label.onMouseMove(new Vector2D(700, 300), new Size2D(1000, 1000), 1, 2)
  label.onMouseUp(new Vector2D(700, 300))

  commit2DLabels([label])

  const currentState = Session.getState()
  const newLabel = currentState.task.items[0].labels["1"]
  const rect = currentState.task.items[0].shapes[newLabel.shapes[0]] as RectType
  expect(rect.x2).toEqual(700)
  expect(rect.y2).toEqual(300)
})

test("Update existing drawable to invalid", () => {
  const state = Session.getState()
  expect(_.size(state.task.items[0].labels)).toEqual(3)
  // Label coord is [459, 276][752, 400]
  const label2dList = new Label2DList()
  label2dList.updateState(state)
  const label = label2dList.get(1)
  expect(label.labelId).toEqual("1")
  // Existing label must call selected and highlighted property
  // This will be handled by Label2DHandler and Label2DCanvas in program
  // In test we do it manually
  label.setSelected(true)
  label.setHighlighted(true, 5) // Handles.BOTTOM_RIGHT
  label.onMouseDown(new Vector2D(752, 400), 1)
  // Mouse move is essential
  label.onMouseMove(new Vector2D(460, 280), new Size2D(1000, 1000), 1, 2)
  label.onMouseUp(new Vector2D(460, 280))

  commit2DLabels([label])

  const currentState = Session.getState()
  expect(currentState.task.items[0].labels["1"]).toBeUndefined()
})

describe("Draw 2D boxes to label2d list", () => {
  // Samples box2D coords to use for tests
  const boxCoords: SimpleRect[] = [
    { x1: 1, y1: 1, x2: 10, y2: 10 },
    { x1: 19, y1: 20, x2: 30, y2: 29 },
    { x1: 4, y1: 5, x2: 23, y2: 24 }
  ]

  test("Add and delete boxes", () => {
    const [label2dHandler] = initializeTestingObjects()
    const canvasSize = new Size2D(100, 100)
    const labelIds: IdType[] = []

    // Draw and check each box
    for (const coords of boxCoords) {
      const labelId = drawBox2D(label2dHandler, canvasSize, coords)
      checkBox2D(labelId, coords)
      labelIds.push(labelId)
    }

    // Delete label
    dispatch(action.deleteLabel(0, labelIds[1]))
    const labelList = Session.label2dList.labelList
    expect(labelList.length).toEqual(2)
    expect(labelList[0].index).toEqual(0)
    expect(labelList[0].labelId).toEqual(labelIds[0])
    expect(labelList[1].index).toEqual(1)
    expect(labelList[1].labelId).toEqual(labelIds[2])
  })

  test("Add boxes with interrupting actions", () => {
    const [label2dHandler] = initializeTestingObjects()
    const canvasSize = new Size2D(100, 100)
    const interrupt = true

    // Draw and check each box
    for (const coords of boxCoords) {
      const labelId = drawBox2D(label2dHandler, canvasSize, coords, interrupt)
      checkBox2D(labelId, coords)
    }
  })

  test("Resize and move boxes", () => {
    const [label2dHandler] = initializeTestingObjects()
    const canvasSize = new Size2D(100, 100)
    const labelIds: IdType[] = []

    // Draw each box
    for (const coords of boxCoords) {
      const labelId = drawBox2D(label2dHandler, canvasSize, coords)
      checkBox2D(labelId, coords)
      labelIds.push(labelId)
    }

    // Resize the second box
    const boxIndex = 1
    const boxId = labelIds[boxIndex]
    const originalCoords = boxCoords[boxIndex]
    let moveCoords = {
      x1: originalCoords.x1,
      y1: originalCoords.y1,
      x2: 16,
      y2: 17
    }
    resizeBox2D(label2dHandler, canvasSize, moveCoords, boxIndex)
    checkBox2D(boxId, { x1: 16, y1: 17, x2: 30, y2: 29 })

    // Flip top left and bottom right corners
    moveCoords = {
      x1: moveCoords.x2,
      y1: moveCoords.y2,
      x2: 42,
      y2: 43
    }
    resizeBox2D(label2dHandler, canvasSize, moveCoords, boxIndex)
    checkBox2D(boxId, { x1: 30, y1: 29, x2: 42, y2: 43 })

    // Move the entire box +4x and -1y
    moveCoords = {
      x1: 32,
      y1: 31,
      x2: 36,
      y2: 32
    }
    moveBox2D(label2dHandler, canvasSize, moveCoords, boxIndex)
    checkBox2D(boxId, { x1: 34, y1: 30, x2: 46, y2: 44 })
  })
})
