package wow.doge.mygame.game.entities

import akka.actor.typed.ActorRef
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import cats.implicits._
import com.jme3.asset.AssetManager
import com.jme3.bullet.BulletAppState
import com.jme3.bullet.PhysicsSpace
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.math.FastMath
import com.jme3.renderer.Camera
import com.jme3.scene.CameraNode
import com.jme3.scene.Geometry
import com.jme3.scene.Node
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.game.GameAppTags
import wow.doge.mygame.game.SimpleAppExt
import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.state.MyMaterial
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.utils.AkkaUtils

object PlayerControllerTags {
  sealed trait PlayerTag
  sealed trait PlayerCameraNode
  sealed trait PlayerCameraPivotNode
}

object PlayerController {
  sealed trait Error
  case class CouldNotCreatePlayerNode(reason: String) extends Error
  case class GenericError(reason: String) extends Error

  class Props(
      enqueueR: Function1[() => Unit, Unit],
      rootNode: Node @@ GameAppTags.RootNode,
      loggerL: Logger[Task],
      physicsSpace: PhysicsSpace,
      initialPlayerPos: ImVector3f = ImVector3f.ZERO,
      spawnProtocol: ActorRef[SpawnProtocol.Command],
      playerEventBus: GameEventBus[PlayerEvent],
      playerPhysicsControl: BetterCharacterControl,
      appScheduler: monix.execution.Scheduler,
      playerNode: Node @@ PlayerControllerTags.PlayerTag,
      cameraNode: CameraNode @@ PlayerControllerTags.PlayerCameraNode,
      cameraPivotNode: Node @@ PlayerControllerTags.PlayerCameraPivotNode,
      tickEventBus: GameEventBus[TickEvent],
      camera: Camera
  )(implicit timeout: Timeout, scheduler: Scheduler) {
    val create: IO[Error, Unit] =
      (for {
        playerActor <- AkkaUtils.spawnActorL(
          spawnProtocol,
          "playerActorSupervisor",
          new PlayerActorSupervisor.Props(
            playerEventBus,
            // playerCameraEventBus,
            tickEventBus,
            new ImMovementActor.Props(
              enqueueR,
              playerPhysicsControl,
              camera
            ).behavior,
            // wireWith(PlayerCameraEventListener.apply _)
            // PlayerCameraEventListener()
            new PlayerCameraActor.Props(
              cameraPivotNode,
              enqueueR,
              playerNode.getWorldTranslation _
            ).behavior
          ).behavior(playerPhysicsControl)
        )
        _ <- Task(rootNode += playerNode)
        _ <- IO {
          physicsSpace += playerNode
          physicsSpace += playerPhysicsControl
          // rootNode += cameraNode
          cameraPivotNode += cameraNode
          // playerNode += cameraPivotNode
          rootNode += cameraPivotNode

        }

      } yield ())
        .onErrorHandleWith(e => IO.raiseError(GenericError(e.getMessage())))
        .executeOn(appScheduler)
  }

  def apply(
      app: SimpleAppExt,
      modelPath: os.RelPath,
      cam: Camera
  )(assetManager: AssetManager, bulletAppState: BulletAppState) = {
    lazy val playerPos = ImVector3f.ZERO
    lazy val playerPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
      .withJumpForce(ImVector3f(0, 5f, 0))
    lazy val playerNode = new Node("PlayerNode")
      .withChildren(
        assetManager
          .loadModel(modelPath)
          .asInstanceOf[Node]
          .withRotate(0, FastMath.PI, 0)
      )
      .withLocalTranslation(playerPos)
      .withControl(playerPhysicsControl)

    {
      bulletAppState.physicsSpace += playerNode
      bulletAppState.physicsSpace += playerPhysicsControl
    }

    {
      app.rootNode += playerNode
    }

    playerPhysicsControl
  }

  object Defaults {
    def defaultMesh = {
      val b = Box(1, 1, 1)
      val geom = Geometry("playerGeom", b)
      geom
    }
    def defaultTexture(assetManager: AssetManager) =
      MyMaterial(
        assetManager = assetManager,
        path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
      )

    //  new CameraControl(cam) {
    //     override def controlUpdate(tpf: Float) = {
    //       this.getCamera().setRotation(spatial.getWorldRotation())
    //       cameraPivotNode.setLocalTranslation(
    //         playerNode.getWorldTranslation()
    //       )
    //       this.getCamera().setLocation(spatial.getWorldTranslation())
    //     }
    //   }

    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.UNIT_Y)

    def defaultPlayerNode(
        assetManager: AssetManager,
        modelPath: os.RelPath,
        playerPos: ImVector3f,
        // camNode: CameraNode,
        playerPhysicsControl: BetterCharacterControl
    ) =
      Either
        .catchNonFatal(
          Node("PlayerNode")
            .withChildren(
              // camNode,
              assetManager
                .loadModel(modelPath)
                .asInstanceOf[Node]
                .withRotate(0, FastMath.PI, 0)
            )
            .withLocalTranslation(playerPos)
            .withControl(playerPhysicsControl)
        )
        .map(_.taggedWith[PlayerControllerTags.PlayerTag])
        .leftMap(e => PlayerController.CouldNotCreatePlayerNode(e.getMessage()))

    def defaultNpcNode(
        assetManager: AssetManager,
        // modelPath: os.RelPath,
        initialPos: ImVector3f,
        npcPhysicsControl: BetterCharacterControl,
        npcName: String
    ) =
      Either
        .catchNonFatal(
          Node(npcName)
            .withChildren(
              // assetManager
              //   .loadModel(modelPath)
              //   .asInstanceOf[Node]
              //   .withRotate(0, FastMath.PI, 0)
              defaultMesh.withMaterial(defaultTexture(assetManager))
            )
            .withLocalTranslation(initialPos)
            .withControl(npcPhysicsControl)
        )
    // .map(_.taggedWith[PlayerControllerTags.PlayerTag])
    // .leftMap(e => PlayerController.CouldNotCreatePlayerNode(e.getMessage()))

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

object Methods {}

// camNode <- IO(
//         _cameraNode.getOrElse(defaultCamerNode(camera, initialPlayerPos))
//       )
//       playerPhysicsControl <- IO(
//         _playerPhysicsControl
//           .getOrElse(defaultPlayerPhysicsControl)
//           .taggedWith[PlayerTag]
//       )
//       playerNode <- IO.fromEither(
//         _playerNode.fold(
//           defaultPlayerNode(
//             assetManager,
//             modelPath,
//             initialPlayerPos,
//             camNode,
//             playerPhysicsControl
//           )
//         )(_.asRight)
//       )
