package ocelot.desktop.ui.widget.window

import ocelot.desktop.ColorScheme
import ocelot.desktop.color.{Color, RGBAColorNorm}
import ocelot.desktop.geometry.{Padding2D, Rect2D, Size2D}
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.node.Node.Size
import ocelot.desktop.node.{NodeRegistry, NodeTypeGroup, NodeTypeWidget}
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.layout.{AlignItems, JustifyContent, LinearLayout}
import ocelot.desktop.ui.widget._
import ocelot.desktop.util.animation.{ColorAnimation, UnitAnimation}
import ocelot.desktop.util.{DefaultSlotItemsFillable, DrawUtils, Logging, Orientation}

import scala.collection.immutable.ArraySeq

class NodeSelector extends Window with Logging {
  private val nodesPerRow = 5

  override protected val layout = new LinearLayout(this)

  private val rowsWidget: Widget = new Widget {
    override protected val layout = new LinearLayout(this, Orientation.Vertical)
  }

  private val nodeGroupTitleHeight = 35f
  private val inner: Widget = new PaddingBox(rowsWidget, Padding2D(0, 4, 4, 4))
  private val scrollView = new ScrollView(inner)
  private val outer: Widget = new PaddingBox(scrollView, Padding2D.equal(4))
  children +:= outer

  private var heightAnimation: UnitAnimation = _
  private var animationTime: Float = 0f

  private val FinalRingColor: RGBAColorNorm = ColorScheme("NodeSelectorRing")
  private val colorAnimation: ColorAnimation = new ColorAnimation(FinalRingColor.withAlpha(0f), _speed = 3f)

  override protected def dragRegions: Iterator[Rect2D] = Iterator()

  private def workspaceView: WorkspaceView = windowPool.parent.get.asInstanceOf[WorkspaceView]

  private def rows: ArraySeq[Widget] = rowsWidget.children

  // noinspection ScalaUnusedSymbol
  private def rows_=(rows: ArraySeq[Widget]): Unit = rowsWidget.children = rows

  private var rowCount = 0

  private def addNodeWidget(widget: Widget): Unit = {
    val row = {
      if (rows.lastOption.exists(row => row.isInstanceOf[Row] && row.children.length < nodesPerRow)) {
        rows.last
      } else {
        val row = new Row(rows.length)
        rows :+= row
        rowCount += 1

        row
      }
    }

    row.children :+= widget
  }

  private def addNodeGroupTitle(group: NodeTypeGroup): Unit = {
    rows :+= new Widget {
      override protected val layout =
        new LinearLayout(this, Orientation.Vertical, JustifyContent.Center, AlignItems.Center)

      children :+= new Label {
        override def text: String = group.name
        override def color: Color = ColorScheme("NodeSelectorRing")
      }

      // TODO: replace this with PaddingBox when layout will be fixed
      override def minimumSize: Size2D = Size2D(Size * nodesPerRow, nodeGroupTitleHeight)
      override def maximumSize: Size2D = minimumSize
    }
  }

  private def initNodeTypes(): Unit = {
    NodeRegistry.groups.foreach(group => {
      // Group title
      addNodeGroupTitle(group)

      // Nodes from group
      group.types.foreach(nodeType => {
        addNodeWidget(new NodeTypeWidget(nodeType) {
          override def onClick(): Unit = {
            val node = nodeType.make()

            node match {
              case fillable: DefaultSlotItemsFillable => fillable.fillSlotsWithDefaultItems()
              case _ =>
            }

            workspaceView.addNode(node)
            close()
          }
        })
      })
    })
  }

  size = maximumSize

  def finalHeight: Float = (rowCount * Size + NodeRegistry.groups.length * nodeGroupTitleHeight + 16).min(500)

  def ringColor: Color = colorAnimation.color

  override def update(): Unit = {
    super.update()
    if (isClosed) return

    colorAnimation.update()
    heightAnimation.update()

    if (isClosing) {
      animationTime -= UiHandler.dt
    } else {
      animationTime += UiHandler.dt
    }

    width = inner.maximumSize.width + 8
    height = heightAnimation.value * finalHeight

    if (isClosing && size.height < 10) {
      state = Window.State.Closed
    }

    if (isOpening && (size.height - finalHeight).abs < 1) {
      state = Window.State.Open
    }
  }

  override def draw(g: Graphics): Unit = {
    if (isClosed) return

    g.rect(position.x, position.y, size.width, size.height,
      ColorScheme("NodeSelectorBackground").mapA(_ * colorAnimation.color.toRGBANorm.a))
    DrawUtils.ring(g, position.x, position.y, size.width, size.height, thickness = 1, color = colorAnimation.color)

    super.draw(g)
  }

  override def onOpening(): Window.State.Value = {
    super.onOpening()

    colorAnimation.goto(FinalRingColor)

    heightAnimation = UnitAnimation.easeOutQuad(0.25f)
    heightAnimation.goUp()
    animationTime = 0.016f * 4f * scrollView.offset.y / Size

    Window.State.Opening
  }

  override def onClosing(): Window.State.Value = {
    super.onClosing()

    colorAnimation.goto(FinalRingColor.copy(a = 0f))

    heightAnimation = UnitAnimation.easeInQuad(0.25f)
    heightAnimation.time = 1
    heightAnimation.goDown()
    animationTime = 0.25f + 0.016f * 4f * scrollView.offset.y / Size

    Window.State.Closing
  }

  override def onUnfocused(): Unit = {
    super.onUnfocused()
    close()
  }

  private class Row(rowIdx: Int) extends Widget {
    override protected val layout = new LinearLayout(this, Orientation.Horizontal)

    override protected def drawChildren(g: Graphics): Unit = {
      for ((child, i) <- children.zipWithIndex) {
        val idx = rowIdx * nodesPerRow + i
        val timeOffset = idx.toFloat * 0.016f

        g.save()
        val sx = child.position.x + child.size.width / 2
        val sy = child.position.y + child.size.height / 2
        g.translate(sx, sy)
        g.scale(((animationTime - timeOffset) / 0.1f).max(0f).min(1f))
        g.translate(-sx, -sy)
        child.draw(g)
        g.restore()
      }
    }
  }

  initNodeTypes()
}
