package wow.doge.mygame.utils.wrappers.jme

import cats.effect.Sync
import com.jme3.{scene => jmes}
import monix.execution.annotations.UnsafeBecauseImpure
import monix.reactive.Observable
import wow.doge.mygame.implicits._
import com.jme3.light.Light

trait NodeDelegate {

  /**
    * Get the underlying wrapped value
    */
  @UnsafeBecauseImpure
  def unsafeDelegate: jmes.Node
}

abstract class NodeWrapper[F[_]: Sync] protected (node: jmes.Node) {
  def children: Observable[jmes.Spatial] = node.observableChildren
  def attachChild(n: jmes.Spatial): F[Unit] = Sync[F].delay(node.attachChild(n))
  def add(wn: Node[F]): F[Unit] =
    Sync[F].delay(node.attachChild(wn.unsafeDelegate))
  def remove(n: jmes.Spatial): F[Unit] =
    Sync[F].delay(node.detachChild(n))
  def remove(wn: Node[F]): F[Unit] =
    Sync[F].delay(node.detachChild(wn.unsafeDelegate))
  def addLight(light: Light) =
    Sync[F].delay {
      node.addLight(light)
    }
  def removeLight(light: Light) =
    Sync[F].delay {
      node.removeLight(light)
    }
  def asSpatial: F[jmes.Spatial] = Sync[F].delay(node)
}
object NodeWrapper {
  implicit class NodeOps[F[_]](private val nw: NodeWrapper[F]) extends AnyVal {
    def +=(n: jmes.Spatial) = nw.attachChild(n)
    def +=(n: Node[F]) = nw.add(n)
    def -=(n: jmes.Spatial) = nw.remove(n)
    def -=(wn: Node[F]) = nw.remove(wn)
    def +=(light: Light) = {
      nw.addLight(light)
    }

    def -=(light: Light) = {
      nw.removeLight(light)
    }
  }
}

final class Node[F[_]: Sync] private (node: jmes.Node)
    extends NodeWrapper[F](node)
    with NodeDelegate {

  /**
    * Get the underlying wrapped value
    */
  @UnsafeBecauseImpure
  def unsafeDelegate = node
}
object Node {
  def apply[F[_]: Sync](name: String) = new Node[F](new jmes.Node(name))
  def apply[F[_]: Sync](n: jmes.Node) = new Node[F](n)
}

final class AppNode[F[_]: Sync] private (node: jmes.Node)
    extends NodeWrapper[F](node)
object AppNode {

  def apply[F[_]: Sync](name: String) = new AppNode[F](new jmes.Node(name))
  def apply[F[_]: Sync](n: jmes.Node) = new AppNode[F](n)

}
