package wow.doge.mygame.state

import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import com.softwaremill.quicklens._
import wow.doge.mygame.implicits._
import com.jme3.renderer.Camera
import wow.doge.mygame.math.ImVector3f

trait CanMove[T] {
  def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f
  def move(inst: T, direction: ImVector3f): Unit
}

object ImMovementActor {
  sealed trait Command
  // final case class Tick(tpf: Float) extends Command
  final case class Tick(tpf: Float) 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

  final case class Props[T: CanMove](
      app: com.jme3.app.Application,
      movable: T
  )

  /**
    *  Internal state of the actor
    *
    * @param cardinalDir Immutable, can be shared as is
    * @param walkDirection Immutable
    */
  final case class State(
      cardinalDir: CardinalDirection = CardinalDirection()
  )

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

}

class ImMovementActor[T](
    ctx: ActorContext[ImMovementActor.Command],
    props: ImMovementActor.Props[T]
) {
  import ImMovementActor._

  def receive(
      state: ImMovementActor.State
  )(implicit cm: CanMove[T]): 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(tpf) =>
          val walkDir =
            cm.getDirection(props.app.getCamera(), state.cardinalDir)
          if (walkDir != ImVector3f.ZERO) {
            val tmp = walkDir * 2f
            props.app.enqueue(new Runnable {
              override def run(): Unit = {
                cm.move(props.movable, tmp)
              }
            })
          }
          Behaviors.same
        // receive(state = state.modify(_.walkDirection).setTo(walkDir))

      }
    }
}
