package ocelot.desktop.util

import com.github.sarxos.webcam.Webcam

import java.awt.Color
import java.awt.image.BufferedImage
import scala.collection.mutable
import scala.concurrent.duration._

object WebcamCapture extends Logging {
  private final val FrameTimeout: Duration = 500.millis
  private final val DeviceTimeout: Duration = 5.seconds
  private final val MaxDistance: Float = 32f
  private final val InvGamma: Float = 1f / 2.2f
  private val instances = new mutable.HashMap[String, WebcamCapture]

  def getInstance(name: String): Option[WebcamCapture] = {
    if (instances.contains(name))
      return Some(instances(name))

    Option(Webcam.getWebcamByName(name)) match {
      case Some(webcam) =>
        val webcamCapture = new WebcamCapture(webcam)
        instances(name) = webcamCapture
        Some(webcamCapture)

      case None =>
        logger.warn(s"No such webcam: $name")
        None
    }
  }

  def getInstance(webcam: Webcam): Option[WebcamCapture] = getInstance(webcam.getName)
  def getDefault: Option[WebcamCapture] = Option(Webcam.getDefault).flatMap(getInstance)

  def cleanup(): Unit = {
    for (instance <- instances.values)
      instance.interrupt()

    for (instance <- instances.values)
      instance.join()
  }

  private def toLinear(value: Int): Float = Math.pow(value / 255f, InvGamma).toFloat
}

class WebcamCapture(private val webcam: Webcam) extends Thread(s"WebcamCaptureThread-${webcam.getName}") with Logging {
  private var frame: Option[BufferedImage] = None
  private var lastUsageTime: Long = -1
  start()

  override def run(): Unit = {
    logger.debug(s"Started thread for webcam '$name'")

    while (!isInterrupted) {
      try {
        if (System.currentTimeMillis() - lastUsageTime >= WebcamCapture.DeviceTimeout.toMillis) {
          webcam.close()

          synchronized {
            wait()
          }
        } else {
          webcam.open()

          val image = Option(webcam.getImage)
          synchronized {
            frame = image
          }

          Thread.sleep(WebcamCapture.FrameTimeout.toMillis)
        }
      } catch {
        case _: InterruptedException => interrupt()
      }
    }

    logger.debug(s"Cleaning up webcam '$name'")
    if (webcam.isOpen)
      webcam.close()
  }

  def ray(x: Float, y: Float): Float = synchronized {
    lastUsageTime = System.currentTimeMillis()
    notify()

    if (frame.isEmpty)
      return 0

    val clampedX = 0f max x min 1f
    val clampedY = 0f max y min 1f
    val frameX = clampedX * (frame.get.getWidth - 1).toFloat
    val frameY = clampedY * (frame.get.getHeight - 1).toFloat
    val color = new Color(frame.get.getRGB(frameX.toInt, frameY.toInt))
    val normalizedDistance = {
      0.2126f * WebcamCapture.toLinear(color.getRed) +
        0.7152f * WebcamCapture.toLinear(color.getGreen) +
        0.0722f * WebcamCapture.toLinear(color.getBlue)
    }

    WebcamCapture.MaxDistance * (1f - normalizedDistance)
  }

  def name: String = webcam.getName
  override def toString: String = name
}
