package ocelot.desktop.node

import ocelot.desktop.OcelotDesktop
import ocelot.desktop.audio._
import ocelot.desktop.geometry.Vector2D
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.inventory.SyncedInventory
import ocelot.desktop.node.ComputerAwareNode._
import ocelot.desktop.node.Node.Size
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.BrainEvent
import ocelot.desktop.ui.event.handlers.DiskActivityHandler
import ocelot.desktop.ui.widget.ComputerErrorMessageLabel
import ocelot.desktop.util.Messages
import totoro.ocelot.brain.Settings
import totoro.ocelot.brain.entity.traits.{Entity, Environment, WorkspaceAware}
import totoro.ocelot.brain.event._

import java.util.Calendar
import scala.collection.mutable.ArrayBuffer

abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceAware)
  extends EntityNode(entity)
    with SyncedInventory
    with DiskActivityHandler
    with OcelotLogParticleNode
    with ShiftClickNode {

  // access should be synchronized because messages are added in the update thread
  private val messages = ArrayBuffer.empty[(Float, ComputerErrorMessageLabel)]

  private def addErrorMessage(message: ComputerErrorMessageLabel): Unit = messages.synchronized {
    messages += ((0f, message))
  }

  private lazy val soundCardSounds: (SoundStream, SoundSource) = Audio.newStream(SoundCategory.Records)
  private def soundCardStream: SoundStream = soundCardSounds._1
  private def soundCardSource: SoundSource = soundCardSounds._2

  eventHandlers += {
    case BrainEvent(event: MachineCrashEvent) =>
      val message = Messages.lift(event.message) match {
        case Some(message) =>
          logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message code ${event.message}: $message")
          message

        case None =>
          logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message: ${event.message}")
          event.message
      }

      addErrorMessage(new ComputerErrorMessageLabel(this, message))

    case BrainEvent(event: BeepEvent) if !Audio.isDisabled =>
      BeepGenerator.newBeep(".", event.frequency, event.duration).play()

    case BrainEvent(event: BeepPatternEvent) if !Audio.isDisabled =>
      BeepGenerator.newBeep(event.pattern, 1000, 200).play()

    case BrainEvent(event: SoundCardAudioEvent) if !Audio.isDisabled =>
      val samples = SoundSamples(event.data, Settings.get.soundCardSampleRate, SoundSamples.Format.Mono8)
      soundCardStream.enqueue(samples)
      soundCardSource.volume = event.volume

    case BrainEvent(_: SelfDestructingCardBoomEvent) =>
      OcelotDesktop.updateThreadTasks.add(() => {
        SoundSource.MinecraftExplosion.play()
        destroy()
      })
  }

  override def update(): Unit = {
    messages.synchronized {
      messages.mapInPlace { case (t, message) => (t + ErrorMessageMoveSpeed * UiHandler.dt, message) }
      messages.filterInPlace(_._1 <= 1f)
    }

    super.update()
  }

  private def drawMessageParticles(g: Graphics): Unit = messages.synchronized {
    for ((time, message) <- messages.reverseIterator) {
      message.position = message.initialPosition + Vector2D(0, -MaxErrorMessageDistance * time)
      message.alpha = 1 - time
      message.draw(g)
    }
  }

  override def drawParticles(g: Graphics): Unit = {
    super.drawParticles(g)
    drawMessageParticles(g)
  }

  protected def drawOverlay(g: Graphics): Unit = {
    if (HolidaySprite.isEmpty)
      return

    val holidayOverlaySize = Size * 2
    val offset = holidayOverlaySize / 4

    g.sprite(
      HolidaySprite.get,
      position.x - offset,
      position.y - offset,
      holidayOverlaySize,
      holidayOverlaySize
    )
  }

  override def draw(g: Graphics): Unit = {
    super.draw(g)

    drawOverlay(g)
  }
}

object ComputerAwareNode {
  private val MaxErrorMessageDistance: Float = 50f
  private val ErrorMessageMoveSpeed: Float = 0.5f

  private var HolidaySprite: Option[String] = None

  {
    var dayOfYear = Calendar.getInstance().get(Calendar.DAY_OF_YEAR)
    val maxDuration = 5

    if (dayOfYear >= 365 - maxDuration)
      dayOfYear -= 365

    HolidaySprite =
      Array(
        (1, "Christmas"),
        (45, "Valentines"),
        (305, "Halloween")
      )
      .find(holiday => dayOfYear >= holiday._1 - maxDuration && dayOfYear <= holiday._1 + maxDuration)
      .map(holiday => s"nodes/holidays/${holiday._2}")
  }
}
