package ocelot.desktop.node.nodes

import ocelot.desktop.audio._
import ocelot.desktop.graphics.IconSource
import ocelot.desktop.inventory.SyncedInventory
import ocelot.desktop.inventory.item.TapeItem
import ocelot.desktop.node.{EntityNode, LabeledEntityNode, PositionalSoundSourcesNode, WindowedNode}
import ocelot.desktop.ui.event.BrainEvent
import ocelot.desktop.windows.TapeDriveWindow
import totoro.ocelot.brain.entity.tape.{AudioPacketDfpwm, Dfpwm}
import totoro.ocelot.brain.entity.{TapeDrive, TapeDriveState}
import totoro.ocelot.brain.event.{TapeDriveAudioEvent, TapeDriveStopEvent}

class TapeDriveNode(val tapeDrive: TapeDrive)
    extends EntityNode(tapeDrive)
    with SyncedInventory
    with LabeledEntityNode
    with PositionalSoundSourcesNode
    with WindowedNode[TapeDriveWindow] {

  override def icon: IconSource = IconSource.Nodes.TapeDrive

  override def rotatable: Boolean = true

  private lazy val soundTapeRewind: SoundSource = SoundSource.fromBuffer(
    SoundBuffers.MachineTapeRewind,
    SoundCategory.Records,
    looping = true,
  ).build()

  private lazy val streams: (SoundStream, SoundSource) = Audio.newStream(SoundCategory.Records)
  private def stream: SoundStream = streams._1
  private def source: SoundSource = streams._2

  private val codec = new Dfpwm

  eventHandlers += {
    // FIXME: if the tape drive rapidly switches from Playing to another state and back to Playing,
    //        we may get TapeDriveStopEvent and TapeDriveAudioEvent during the same brain update cycle.
    //        however, since `source.stop()` defers removing the source until the next GUI update,
    //        it can potentially remove the newly queued buffers too.
    case BrainEvent(_: TapeDriveStopEvent) => source.stop()

    case BrainEvent(TapeDriveAudioEvent(_, pkt @ AudioPacketDfpwm(volume, frequency, _))) if !Audio.isDisabled =>
      // FIXME: OpenAL does not enjoy mixing buffers with different sample rates.
      //        yet if the playback speed changes, it will affect the frequency.
      //        thus we'll be enqueueing a buffer with a different sample rate than the previous buffers.
      //        we'll get errors.
      val samples = SoundSamples(pkt.decode(codec), frequency, SoundSamples.Format.Mono8)
      stream.enqueue(samples)
      source.volume = volume / 127f
  }

  override def update(): Unit = {
    super.update()

    val isRewinding =
      tapeDrive.state.state == TapeDriveState.State.Rewinding || tapeDrive.state.state == TapeDriveState.State.Forwarding

    if (!isRewinding && soundTapeRewind.playing) {
      soundTapeRewind.stop()
    } else if (isRewinding && !soundTapeRewind.playing && !Audio.isDisabled) {
      soundTapeRewind.play()
    }
  }

  override def dispose(): Unit = {
    super.dispose()

    soundTapeRewind.stop()
  }

  // -------------------------------- PositionalSoundSourcesNode --------------------------------

  override def soundSources: Seq[SoundSource] = super.soundSources ++ Seq(source)

  // -------------------------------- Inventory --------------------------------

  override type I = TapeItem

  override def brainInventory: TapeDrive = tapeDrive

  // -------------------------------- Windowed --------------------------------

  override protected def createWindow(): TapeDriveWindow = new TapeDriveWindow(this)
}
