package ocelot.desktop.windows

import ocelot.desktop.audio.{Audio, ClickSoundSourceFactory, SoundBuffers, SoundCategory, SoundSource}
import ocelot.desktop.color.{Color, RGBAColorNorm}
import ocelot.desktop.geometry.{Padding2D, Size2D}
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.graphics.IconSource.Window.Tape.TapeButtonIconSource
import ocelot.desktop.inventory.item.TapeItem
import ocelot.desktop.node.nodes.TapeDriveNode
import ocelot.desktop.ui.layout.{AlignItems, Layout, LinearLayout}
import ocelot.desktop.ui.widget.IconButton.{DefaultModel, ReadOnlyModel}
import ocelot.desktop.ui.widget.slot.SlotWidget
import ocelot.desktop.ui.widget.traits.HoverHighlight
import ocelot.desktop.ui.widget.window.PanelWindow
import ocelot.desktop.ui.widget.{IconButton, PaddingBox, Widget}
import ocelot.desktop.util.Orientation
import ocelot.desktop.windows.TapeDriveWindow.{TapeButtonSound, TapeEject, TapeInsert}
import totoro.ocelot.brain.entity.TapeDriveState

class TapeDriveWindow(val tapeDriveNode: TapeDriveNode) extends PanelWindow {
  private val playbackOverlayColor: RGBAColorNorm = RGBAColorNorm(1, 1, 1, 0.01f)

  override protected def title: String = s"Tape Drive ${tapeDriveNode.tapeDrive.node.address}"
  override def titleMaxLength: Int = 35

  override def minimumSize: Size2D = Size2D(352, 182)
  override def maximumSize: Size2D = minimumSize

  setInner(new PaddingBox(
    new Widget {
      override protected val layout: Layout =
        new LinearLayout(this, orientation = Orientation.Vertical, gap = 16, alignItems = AlignItems.Center)

      // Screen
      children :+= new Widget {
        override def minimumSize: Size2D = Size2D(292, 26)
        override def maximumSize: Size2D = minimumSize

        override def draw(g: Graphics): Unit = {
          // Screen background
          g.sprite(
            IconSource.Window.Tape.Screen,
            bounds,
          )

          // A barely noticeable overlay showing the playback progress
          // Btw Computronics doesn't have this feature, so I won't ruin the canon and make it too annoying
          val playedPart = tapeDriveNode.tapeDrive.position.toFloat / tapeDriveNode.tapeDrive.size.toFloat

          if (playedPart > 0) {
            val offset = 2

            g.rect(
              position.x + offset,
              position.y + offset,
              (width - offset * 2) * playedPart,
              height - offset * 2,
              playbackOverlayColor,
            )
          }

          // Screen text
          g.background = Color.Transparent

          var text: String = null

          if (tapeDriveNode.tapeDrive.tape.isDefined) {
            g.foreground = Color.White
            text = tapeDriveNode.tapeDrive.storageName

            if (text.isEmpty)
              text = "Unnamed tape"
          } else {
            g.foreground = Color.Red
            text = "No tape"
          }

          // Clipping text if it's too long
          val textMaxLength = 32

          if (text.length > textMaxLength)
            text = text.take(textMaxLength - 1) + "…"

          // Finally drawing it
          val textWidth = text.map(g.font.charWidth(_)).sum
          val textHeight = 16

          g.text(position.x + width / 2 - textWidth / 2, position.y + height / 2 - textHeight / 2, text)
        }
      }

      // Slot
      children :+= new SlotWidget[TapeItem](tapeDriveNode.Slot(0)) {
        override def onItemAdded(): Unit = {
          super.onItemAdded()

          if (!Audio.isDisabled)
            TapeInsert.play()
        }

        override def onItemRemoved(removedItem: TapeItem, replacedBy: Option[TapeItem]): Unit = {
          super.onItemRemoved(removedItem, replacedBy)

          if (!Audio.isDisabled)
            TapeEject.play()
        }
      }

      // Buttons
      children :+= new Widget {
        override protected val layout: Layout = new LinearLayout(this, gap = 0)

        def addButton(
          source: TapeButtonIconSource,
          pressedState: TapeDriveState.State,
          isToggle: Boolean = true,
        ): Unit = {
          children :+= new IconButton(
            source.Released,
            source.Pressed,
            sizeMultiplier = 2,
            mode =
              if (isToggle)
                IconButton.Mode.Switch
              else
                IconButton.Mode.Regular,
            model = {
              if (isToggle)
                ReadOnlyModel(tapeDriveNode.tapeDrive.state.state == pressedState)
              else
                DefaultModel(false)
            },
          ) with HoverHighlight {
            override def onPressed(): Unit = {
              if (isToggle) {
                tapeDriveNode.tapeDrive.state.switchState(pressedState)
              }
            }

            override def onClicked(): Unit = {
              if (!isToggle) {
                tapeDriveNode.tapeDrive.state.switchState(pressedState)
              }
            }

            override protected def clickSoundSource: ClickSoundSourceFactory = TapeButtonSound
          }
        }

        addButton(IconSource.Window.Tape.Back, TapeDriveState.State.Rewinding)
        addButton(IconSource.Window.Tape.Play, TapeDriveState.State.Playing)
        addButton(IconSource.Window.Tape.Stop, TapeDriveState.State.Stopped, isToggle = false)
        addButton(IconSource.Window.Tape.Forward, TapeDriveState.State.Forwarding)
      }
    },
    Padding2D(top = 10, left = 18),
  ))
}

object TapeDriveWindow {
  private object TapeButtonSound extends ClickSoundSourceFactory {
    override lazy val press: SoundSource.Factory =
      SoundSource.fromBuffer(SoundBuffers.MachineTapeButtonPress, SoundCategory.Environment)

    override lazy val release: SoundSource.Factory =
      SoundSource.fromBuffer(SoundBuffers.MachineTapeButtonRelease, SoundCategory.Environment)
  }

  private lazy val TapeInsert: SoundSource.Factory =
    SoundSource.fromBuffer(SoundBuffers.MachineTapeInsert, SoundCategory.Environment)

  private lazy val TapeEject: SoundSource.Factory =
    SoundSource.fromBuffer(SoundBuffers.MachineTapeEject, SoundCategory.Environment)
}
