package wow.doge.mygame.subsystems.movement

import akka.actor.typed.Behavior
import akka.actor.typed.PostStop
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import com.jme3.math.Vector3f
import com.jme3.renderer.Camera
import com.jme3.scene.Geometry
import com.softwaremill.quicklens._
import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._

final case class CardinalDirection(
    left: Boolean = false,
    right: Boolean = false,
    up: Boolean = false,
    down: Boolean = false
)

sealed trait RotateDir
object RotateDir {
  case object Left extends RotateDir
  case object Right extends RotateDir
}

object ImMovementActor {
  sealed trait Command
  case object Tick extends Command

  sealed trait Movement extends Command
  final case class MoveLeft(pressed: Boolean) extends Movement
  final case class MoveUp(pressed: Boolean) extends Movement
  final case class MoveRight(pressed: Boolean) extends Movement
  final case class MoveDown(pressed: Boolean) extends Movement
  case object Jump extends Movement

  class Props(
      val enqueueR: Function1[() => Unit, Unit],
      // playerMovementEventBus: ActorRef[
      //   EventBus.Command[PlayerMovementEvent]
      // ]
      val camera: Camera
  ) {
    def behavior[T: CanMove](movable: T): Behavior[Command] =
      Behaviors.setup(ctx =>
        new ImMovementActor(ctx, this, movable).receive(State(), new Vector3f)
      )
  }

  /**
    *  Internal state of the actor
    *
    * @param cardinalDir The four directions the character can move
    */
  final case class State(cardinalDir: CardinalDirection = CardinalDirection())

}

class ImMovementActor[T](
    ctx: ActorContext[ImMovementActor.Command],
    props: ImMovementActor.Props,
    val movable: T
)(implicit cm: CanMove[T]) {
  import ImMovementActor._

  def receive(
      state: ImMovementActor.State,
      walkDirBuf: Vector3f
  ): Behavior[Command] =
    Behaviors
      .receiveMessage[Command] {
        case m: Movement =>
          m match {
            case MoveLeft(pressed) =>
              stopIfNotPressed(pressed)
              receive(
                state = state.modify(_.cardinalDir.left).setTo(pressed),
                walkDirBuf
              )
            case MoveUp(pressed) =>
              stopIfNotPressed(pressed)
              receive(
                state = state.modify(_.cardinalDir.up).setTo(pressed),
                walkDirBuf
              )
            case MoveRight(pressed) =>
              stopIfNotPressed(pressed)
              receive(
                state = state.modify(_.cardinalDir.right).setTo(pressed),
                walkDirBuf
              )
            case MoveDown(pressed) =>
              stopIfNotPressed(pressed)
              receive(
                state = state.modify(_.cardinalDir.down).setTo(pressed),
                walkDirBuf
              )
            case Jump =>
              cm.jump(movable)
              Behaviors.same
          }

        case Tick =>
          val camDir =
            props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f)
          val camLeft =
            props.camera.getLeft().clone().normalizeLocal.multLocal(0.4f)
          val dir = state.cardinalDir
          walkDirBuf.set(0, 0, 0)
          if (dir.up) {
            ctx.log.traceP("up")
            walkDirBuf += camDir
          }
          if (dir.left) {
            ctx.log.traceP("left")
            walkDirBuf += camLeft
          }
          if (dir.right) {
            ctx.log.traceP("right")
            walkDirBuf += -camLeft
          }
          if (dir.down) {
            ctx.log.traceP("down")
            walkDirBuf += -camDir
          }
          walkDirBuf *= 25f *= (1f / 144)
          cm.move(movable, walkDirBuf.immutable, 20f)
          Behaviors.same
      }
      .receiveSignal {
        case (_, PostStop) =>
          ctx.log.debugP("Stopped")
          Behaviors.same
      }

  def stopIfNotPressed(pressed: Boolean) = if (!pressed) cm.stop(movable)

  def getDirection2(
      cardinalDir: CardinalDirection
      // debug: sourcecode.Text[String] => sourcecode.Text[String]
  ) = {
    val camDir =
      props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f)
    val camLeft = props.camera.getLeft().clone().normalizeLocal.multLocal(0.4f)
    val dir = cardinalDir
    val walkDir = {
      val mutWalkDir = new Vector3f()
      if (dir.up) {
        ctx.log.traceP("up")
        mutWalkDir += camDir
      }
      if (dir.left) {
        ctx.log.traceP("left")
        mutWalkDir += camLeft
      }
      if (dir.right) {
        ctx.log.traceP("right")
        mutWalkDir += -camLeft
      }
      if (dir.down) {
        ctx.log.traceP("down")
        mutWalkDir += -camDir
      }
      mutWalkDir.immutable
    }
    walkDir
  }
}

// old/unused
object MovementActor {
  sealed trait Command
  case object Tick extends Command

  sealed trait Movement extends Command
  final case class MovedLeft(pressed: Boolean) extends Movement
  final case class MovedUp(pressed: Boolean) extends Movement
  final case class MovedRight(pressed: Boolean) extends Movement
  final case class MovedDown(pressed: Boolean) extends Movement

  class Props(val app: com.jme3.app.Application, val geom: Geometry)

  /**
    *  Internal state of the actor
    *
    * @param cardinalDir Immutable, can be shared as is
    * @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor
    */
  final case class State(
      cardinalDir: CardinalDirection = CardinalDirection(),
      walkDirection: Vector3f = Vector3f.UNIT_X
  )

  def apply(props: Props): Behavior[Command] =
    Behaviors.setup(ctx => new MovementActor(ctx, props).receive(State()))

}
class MovementActor(
    ctx: ActorContext[MovementActor.Command],
    props: MovementActor.Props
) {
  import MovementActor._
  import com.softwaremill.quicklens._
  def receive(state: MovementActor.State): Behavior[Command] =
    Behaviors.receiveMessage { msg =>
      msg match {
        case m: Movement =>
          m match {
            case MovedLeft(pressed) =>
              receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
            case MovedUp(pressed) =>
              receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
            case MovedRight(pressed) =>
              receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
            case MovedDown(pressed) =>
              receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
          }

        case Tick =>
          val camDir =
            props.app.getCamera.getDirection().clone().multLocal(0.6f)
          val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f)
          val walkDir = state.walkDirection.set(0, 0, 0)
          // val walkDir = new Vector3f
          val dir = state.cardinalDir
          if (dir.up) {
            ctx.log.debugP("up")
            // ctx.log.debugP(Thread.currentThread().getName())
            // walkDir.addLocal(0, 0, -1)
            walkDir += camDir
          }
          if (dir.left) {
            ctx.log.debugP("left")
            // walkDir.addLocal(-1, 0, 0)
            walkDir.addLocal(camLeft)
          }
          if (dir.right) {
            ctx.log.debugP("right")
            // walkDir.addLocal(1, 0, 0)
            walkDir.addLocal(camLeft.negateLocal())
          }
          if (dir.down) {
            ctx.log.debugP("down")
            walkDir.addLocal(camDir.negateLocal())
            // walkDir.addLocal(0, 0, 1)
          }
          // (dir.up, dir.down, dir.left, dir.right) match {
          //   case (true, false, true, false) =>
          //   case _                          =>
          // }

          walkDir.multLocal(2f)

          // walkDir.multLocal(100f)
          // .multLocal(tpf)

          // val v = props.geom.getLocalTranslation()
          // props.geom.setLocalTranslation(
          //   (v += walkDir)
          // )
          props.app.enqueue(new Runnable {
            override def run(): Unit = {
              // geom.setLocalTranslation(walkDir)

              val v = props.geom.getLocalTranslation()
              props.geom.setLocalTranslation(
                (v += walkDir)
              )
            }
          })
          Behaviors.same
        // receive(state = state.modify(_.walkDirection).setTo(walkDir))

      }
    }
}
