// Room connection and synchronisation.
// Translate local canvas input events to draw messages and send to the room.
// Get back room updates and invoke the local canvas renderer.

import * as canvas from "./canvas.js"
import * as HTML from "./elements.js"
import { connect } from "./room.js"

const TEST_ROOM = "imperial"

const {
  computeErasureIntervals,
  combineErasureIntervals,
} = require("./erasure")

const ERASER_RADIUS = 20
let room = null

function eraseEverythingAtPosition(x, y, radius, room) {
  const mousePos = [x, y]
  room.getPaths().forEach((points, pathID) => {
    const prevPathIntervals =
      (room.erasureIntervals || { [pathID]: {} })[pathID] || {}
    const newPathIntervals = computeErasureIntervals(
      points,
      mousePos,
      radius,
      prevPathIntervals,
    )

    const erasureIntervalsForPath = combineErasureIntervals(
      prevPathIntervals,
      newPathIntervals,
    )

    Object.keys(erasureIntervalsForPath).forEach((pointID) =>
      room.extendErasureIntervals(
        pathID,
        pointID,
        erasureIntervalsForPath[pointID],
      ),
    )
  })
}

const onRoomConnect = (room_) => {
  room = room_

  HTML.connectedRoomID.textContent = room.name
  HTML.connectedRoomInfoContainer.style.display = "block"

  HTML.userIDElem.value = room.ownID || ""
  room.addEventListener("allocateOwnID", ({ detail: id }) => {
    HTML.userIDElem.value = id
  })

  room.addEventListener("userJoin", ({ detail: id }) => {
    if (HTML.connectedPeers.children.length == 0) {
      HTML.connectedPeers.innerHTML = ""
    }

    insertHTMLPeerElement(id)
    updateOverallStatusIcon()
  })

  room.addEventListener("userLeave", ({ detail: id }) => {
    HTML.connectedPeers.removeChild(getPeerById(id))

    if (HTML.connectedPeers.children.length == 0) {
      HTML.connectedPeers.innerHTML = "No peers are connected"
    }
  })

  room.addEventListener("weSyncedWithPeer", ({ detail: id }) => {
    getPeerById(id).children[1].className = "peer-status synced"
    updateOverallStatusIcon()
  })

  room.addEventListener("waitingForSyncStep", ({ detail: id }) => {
    getPeerById(id).children[2].className = "peer-status negotiating"
    updateOverallStatusIcon()
  })

  room.addEventListener("peerSyncedWithUs", ({ detail: id }) => {
    getPeerById(id).children[2].className = "peer-status synced"
    updateOverallStatusIcon()
  })

  room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
    canvas.renderPath(id, points, room.erasureIntervals)
  })

  room.addEventListener(
    "removedIntervalsChange",
    ({ detail: { id, intervals, points } }) => {
      room.erasureIntervals[id] = combineErasureIntervals(
        room.erasureIntervals[id] || {},
        intervals,
      )
      canvas.renderPath(id, points, room.erasureIntervals)
    },
  )
}

const tryRoomConnect = async (roomID) => {
  return await connect(roomID)
    .then(onRoomConnect)
    .catch((err) => alert(`Error connecting to a room:\n${err}`))
}

const tools = {
  PEN: Symbol("pen"),
  ERASER: Symbol("eraser"),
}
let currentTool = tools.PEN
const pathIDsByPointerID = new Map()

HTML.penButton.addEventListener("click", () => {
  if (currentTool == tools.PEN) {
    showElement(HTML.penProperties)
  } else {
    currentTool = tools.PEN
    HTML.penButton.classList.add("selected")
    HTML.eraserButton.classList.remove("selected")
  }
})

HTML.closeButton.forEach((element) => {
  element.addEventListener("click", () => {
    hideElement(element.parentNode.parentNode.parentNode)
  })
})

window.addEventListener("click", (event) => {
  if (event.target == HTML.penProperties) {
    hideElement(HTML.penProperties)
  } else if (event.target == HTML.palette) {
    hideElement(HTML.palette)
    hideElement(HTML.penProperties)
  }
})

HTML.rectangle.addEventListener("click", () => {
  showElement(HTML.palette)
})

const svg = HTML.wheel.children
for (let i = 1; i < svg.length; i++) {
  svg[i].addEventListener("click", (event) => {
    const paletteColour = event.target.getAttribute("fill")
    HTML.rectangle.style.backgroundColor = paletteColour
    HTML.picker.value = paletteColour
    HTML.labelColour.style.backgroundColor = paletteColour
    canvas.setStrokeColour(paletteColour)
    hideElement(HTML.palette)
  })
}

function showElement(element) {
  element.style.display = "block"
}

function hideElement(element) {
  element.style.display = "none"
}

HTML.picker.addEventListener("change", () => {
  const paletteColour = event.target.value
  HTML.rectangle.style.backgroundColor = paletteColour
  HTML.labelColour.style.backgroundColor = paletteColour
  canvas.setStrokeColour(paletteColour)
})

HTML.eraserButton.addEventListener("click", () => {
  currentTool = tools.ERASER
  HTML.penButton.classList.remove("selected")
  HTML.eraserButton.classList.add("selected")
})

HTML.peerButton.addEventListener("click", () => {
  const peerID = HTML.peerIDElem.value
  if (room == null || peerID == "") {
    return
  }
  room.inviteUser(peerID)
  HTML.peerIDElem.value = ""
})

HTML.roomConnectButton.addEventListener("click", () => {
  const selectedRoomID = HTML.roomIDElem.value
  if (!selectedRoomID || selectedRoomID == room.name) {
    return
  }

  if (room != null) {
    room.disconnect()
    room = null
  }

  canvas.clear()
  HTML.connectedPeers.innerHTML = "No peers are connected"

  tryRoomConnect(selectedRoomID)
})

const insertHTMLPeerElement = (id) => {
  const peerElem = document.createElement("li")
  const peerId = document.createElement("div")
  peerId.style.display = "inline"
  peerId.innerHTML = id

  const ourStatus = document.createElement("div")
  ourStatus.className = "peer-status unsynced"

  const theirStatus = document.createElement("div")
  theirStatus.className = "peer-status unsynced"

  peerElem.appendChild(peerId)
  peerElem.appendChild(ourStatus)
  peerElem.appendChild(theirStatus)

  HTML.connectedPeers.appendChild(peerElem)
}

const getPeerById = (id) => {
  for (const peerElem of HTML.connectedPeers.children) {
    const peerId = peerElem.children[0].innerHTML
    if (peerId == id) {
      return peerElem
    }
  }
}

const updateOverallStatusIcon = () => {
  for (const peerElem of HTML.connectedPeers.children) {
    if (
      !peerElem.children[1].classList.contains("synced") ||
      !peerElem.children[2].classList.contains("synced")
    ) {
      HTML.overallStatusIcon.className = "synchronising"
      HTML.overallStatusIconImage.src = "synchronising.svg"
      return
    }
  }
  HTML.overallStatusIcon.className = "synchronised"
  HTML.overallStatusIconImage.src = "synchronised.svg"
}

canvas.input.addEventListener("strokestart", ({ detail: e }) => {
  if (room == null) {
    return
  }

  const mousePos = [e.offsetX, e.offsetY]

  if (currentTool == tools.PEN) {
    pathIDsByPointerID.set(
      e.pointerId,
      room.addPath([...mousePos, e.pressure, canvas.getStrokeColour()]),
    )
  } else if (currentTool == tools.ERASER) {
    eraseEverythingAtPosition(mousePos[0], mousePos[1], ERASER_RADIUS, room)
  }
})

canvas.input.addEventListener("strokeend", ({ detail: e }) => {
  pathIDsByPointerID.delete(e.pointerId)
})

canvas.input.addEventListener("strokemove", ({ detail: e }) => {
  if (room == null) {
    return
  }

  const mousePos = [e.offsetX, e.offsetY]

  if (currentTool == tools.PEN) {
    room.extendPath(pathIDsByPointerID.get(e.pointerId), [
      ...mousePos,
      e.pressure,
      canvas.getStrokeColour(),
    ])
  } else if (currentTool == tools.ERASER) {
    eraseEverythingAtPosition(mousePos[0], mousePos[1], ERASER_RADIUS, room)
  }
})

tryRoomConnect(TEST_ROOM)