package wow.doge.mygame.game.entities

import akka.actor.typed.ActorRef
import akka.actor.typed.DispatcherSelector
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.renderer.Camera
import com.jme3.scene.CameraNode
import com.jme3.scene.Geometry
import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.jme3.scene.shape.Box
import com.softwaremill.tagging._
import io.odin.Logger
import monix.bio.IO
import monix.bio.Task
import wow.doge.mygame.AppError
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
import wow.doge.mygame.game.GameApp
import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.types._
import wow.doge.mygame.utils.wrappers.jme._

object PlayerController {
  sealed trait Error
  case class CouldNotCreatePlayerModel(reason: AssetManager.Error) extends Error

  object Tags {
    sealed trait PlayerNode
    sealed trait PlayerCameraNode
    sealed trait PlayerCameraPivotNode
  }

  type PlayerNode = Node @@ Tags.PlayerNode
  type PlayerCameraNode = CameraNode @@ Tags.PlayerCameraNode
  type PlayerCameraPivotNode =
    Node @@ Tags.PlayerCameraPivotNode

  class Props(
      gameApp: GameApp,
      enqueueR: Function1[() => Unit, Unit],
      rootNode: RootNode,
      loggerL: Logger[Task],
      physicsSpace: PhysicsSpace,
      initialPlayerPos: ImVector3f,
      playerEventBus: GameEventBus[PlayerEvent],
      playerPhysicsControl: BetterCharacterControl,
      // appScheduler: monix.execution.Scheduler,
      scheduler: AsyncScheduler,
      playerNode: PlayerNode,
      cameraNode: PlayerCameraNode,
      cameraPivotNode: PlayerCameraPivotNode,
      tickEventBus: GameEventBus[TickEvent],
      camera: Camera
  ) {
    val playerActorBehavior = {
      val movementActorBeh = new ImMovementActor.Props(
        enqueueR,
        camera
      ).behavior(playerPhysicsControl)
      new PlayerActorSupervisor.Props(
        playerEventBus,
        tickEventBus,
        movementActorBeh,
        scheduler
      ).behavior
    }
    val playerActor =
      gameApp.spawnGameActor(
        playerActorBehavior,
        // Some("playerActorSupervisor"),
        props = DispatcherSelector.default()
      )
    val create: IO[AppError, ActorRef[PlayerActorSupervisor.Command]] =
      (for {
        // playerActor <-
        //   // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")
        //   gameApp.spawnGameActor(
        //     playerActorBehavior,
        //     Some("playerActorSupervisor"),
        //     props = DispatcherSelector.default()
        //   )
        pa <- playerActor
        _ <- (for {
            _ <- rootNode += playerNode
            _ <- physicsSpace += playerNode
            _ <- physicsSpace += playerPhysicsControl
            _ = cameraPivotNode += cameraNode
            _ <- rootNode += cameraPivotNode
          } yield ()).mapError(AppError.AppNodeError)
      } yield pa)

  }

  object Defaults {
    def defaultMesh = {
      val b = Box(1, 1, 1)
      val geom = Geometry("playerGeom", b)
      geom
    }

    def defaultCamerNode(
        cam: Camera,
        // playerNode: Node,
        // cameraPivotNode: Node,
        playerPos: ImVector3f
    ) =
      new CameraNode("CameraNode", cam)
        // .withControlDir(ControlDirection.SpatialToCamera)
        .withLocalTranslation(ImVector3f(0, 1.5f, 10))
        .withLookAt(playerPos, ImVector3f.UnitY)

    def defaultPlayerNode(
        playerPos: ImVector3f,
        playerModel: Node,
        // camNode: CameraNode,
        playerPhysicsControl: BetterCharacterControl
    ) =
      Node("PlayerNode")
        .withChildren(playerModel)
        .withLocalTranslation(playerPos)
        .withControl(playerPhysicsControl)
        .taggedWith[Tags.PlayerNode]

    def defaultNpcNode(
        // assetManager: AssetManager,
        npcModel: Spatial,
        initialPos: ImVector3f,
        npcPhysicsControl: BetterCharacterControl,
        npcName: String
    ) =
      // Either
      //   .catchNonFatal(
      Node(npcName)
        .withChildren(
          // defaultMesh.withMaterial(defaultTexture(assetManager))
          npcModel
        )
        .withLocalTranslation(initialPos)
        .withControl(npcPhysicsControl)
    // )

    def defaultPlayerPhysicsControl =
      new BetterCharacterControl(1.5f, 6f, 1f)
        .withJumpForce(ImVector3f(0, 5f, 0))
  }
}
