package wow.doge.mygame.game.entities

import scala.concurrent.duration._
import scala.util.Failure
import scala.util.Success

import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import monix.execution.CancelableFuture
import monix.execution.CancelablePromise
import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.subsystems.events.EntityMovementEvent
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp
import wow.doge.mygame.subsystems.events.Event
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor

object NpcActorSupervisor {
  sealed trait Command
  final case class Move(pos: ImVector3f) extends Command
  private final case class InternalMove(
      move: Move,
      signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
  ) extends Command
  private case object DoneMoving extends Command
  // private case class MovementResponse(response: CancelableFuture[_]) extends Command
  private case class LogError(err: Throwable) extends Command
  private case object NoOp extends Command

  private case class MovementFailed(err: Throwable) extends Command

  final case class Props(
      npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
      npcName: String,
      initialPos: ImVector3f
  ) {
    def behavior =
      Behaviors.setup[Command] { ctx =>
        val npcMovementActor = ctx.spawn(
          (npcMovementActorBehavior),
          s"npc-${npcName}-NpcMovementActor"
        )

        new NpcActorSupervisor(ctx, this, Children(npcMovementActor))
          .idle(State())
      }
  }
  final case class State(
  )
  final case class Children(
      npcMovementActor: ActorRef[NpcMovementActor.Command]
  )
}
class NpcActorSupervisor(
    ctx: ActorContext[NpcActorSupervisor.Command],
    props: NpcActorSupervisor.Props,
    children: NpcActorSupervisor.Children
) {
  import NpcActorSupervisor._
  implicit val timeout = Timeout(1.second)

  val movementTimer = ctx.spawn(
    GenericTimerActor
      .Props(
        children.npcMovementActor,
        NpcMovementActor.MovementTick,
        100.millis
      )
      .behavior,
    s"npc-John-NpcActorTimer"
  )

  def idle(state: State): Behavior[NpcActorSupervisor.Command] =
    Behaviors.setup { _ =>
      ctx.log.debugP("Inside Idle State")
      Behaviors.receiveMessage[Command] {
        case m @ Move(pos) =>
          ctx.ask(
            children.npcMovementActor,
            NpcMovementActor.MoveTo(pos, _)
          ) {
            case Success(signal)    => InternalMove(m, signal)
            case Failure(exception) => LogError(exception)
          }
          Behaviors.same
        case InternalMove(move, signal) =>
          moving(state, move.pos, signal)

        case LogError(err) =>
          ctx.log.warnP(err.getMessage())
          Behaviors.same
        case _ => Behaviors.unhandled
      }
    }

  def moving(
      state: State,
      targetPos: ImVector3f,
      signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
  ): Behavior[NpcActorSupervisor.Command] =
    Behaviors.setup { _ =>
      movementTimer ! GenericTimerActor.Start

      // ctx
      //   .ask(state.npcMovementActor, NpcMovementActor2.MoveTo(targetPos, _))(
      //     _.fold(LogError(_), MovementResponse(_))
      //   )
      ctx.pipeToSelf(signal) {
        case Success(value)     => DoneMoving
        case Failure(exception) => MovementFailed(exception)
      }
      Behaviors.receiveMessagePartial[Command] {
        case LogError(err) =>
          ctx.log.error(err.getMessage())
          Behaviors.same
        case MovementFailed(err) =>
          ctx.self ! LogError(err)
          movementTimer ! GenericTimerActor.Stop
          idle(state)
        case m @ Move(pos) =>
          movementTimer ! GenericTimerActor.Stop
          children.npcMovementActor ! NpcMovementActor.StopMoving
          signal.cancel()
          ctx.ask(
            children.npcMovementActor,
            NpcMovementActor.MoveTo(pos, _)
          ) {
            case Success(signal)    => InternalMove(m, signal)
            case Failure(exception) => MovementFailed(exception)
          }
          Behaviors.same
        case InternalMove(move, signal) =>
          moving(state, targetPos, signal)
        case NoOp => Behaviors.same
        // case MovementResponse(x: CancelableFuture[_]) =>
        // // ctx.pipeToSelf(x)(_.)
        case DoneMoving =>
          movementTimer ! GenericTimerActor.Stop
          idle(state)

      }
    }
}

object NpcMovementActor {

  case object DoneMoving

  sealed trait Command
  case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command
  case object MovementTick extends Command
  case object StopMoving extends Command
  case class MoveTo(
      target: ImVector3f,
      doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
  ) extends Command

  final class Props[T: CanMove](
      val enqueueR: Function1[() => Unit, Unit],
      val initialPos: ImVector3f,
      // val tickEventBus: GameEventBus[TickEvent],
      val movable: T
  ) {
    def behavior =
      Behaviors.setup[Command] { ctx =>
        new NpcMovementActor(ctx, this).receive(State())
      }
  }

  final case class State()
}
class NpcMovementActor[T](
    ctx: ActorContext[NpcMovementActor.Command],
    props: NpcMovementActor.Props[T]
)(implicit cm: CanMove[T]) {
  import NpcMovementActor._

  def location = cm.location(props.movable)

  def receive(
      state: State
  ): Behavior[NpcMovementActor.Command] =
    Behaviors.receiveMessagePartial {
      case AskPosition(replyTo) =>
        replyTo ! location
        Behaviors.same
      case MoveTo(
            target: ImVector3f,
            replyTo: ActorRef[CancelableFuture[DoneMoving.type]]
          ) =>
        props.enqueueR(() => cm.move(props.movable, target - location))
        val p = CancelablePromise[DoneMoving.type]()
        replyTo ! p.future
        ticking(p, target, state)
    }

  def ticking(
      reachDestination: CancelablePromise[DoneMoving.type],
      targetPos: ImVector3f,
      state: State
  ): Behavior[NpcMovementActor.Command] =
    Behaviors.receiveMessagePartial {
      case StopMoving =>
        ctx.log.debugP(
          "Position at Stop = " + location.toString
        )
        props.enqueueR(() => cm.stop(props.movable))
        receive(state)

      case MovementTick =>
        val dst = ImVector3f.dst(targetPos, location)
        if (dst <= 10f) {
          ctx.self ! StopMoving
          reachDestination.success(DoneMoving)
        } else {
          ctx.log.traceP("Difference = " + dst.toString())
          ctx.log.traceP("Current pos = " + location.toString())
        }
        Behaviors.same
    }
}

object NpcMovementActorNotUsed {
  sealed trait Command
  final case class Props(
      imMovementActorBehavior: Behavior[ImMovementActor.Command],
      npcName: String,
      // movementActor: ActorRef[ImMovementActor.Command],
      mainEventBus: ActorRef[
        EventBus.Command[Event]
      ],
      tickEventBus: GameEventBus[TickEvent]
  ) {

    def behavior: Behavior[Command] =
      Behaviors.setup { ctx =>
        val movementActor = ctx.spawn(
          Behaviors
            .supervise(imMovementActorBehavior)
            .onFailure[Exception](SupervisorStrategy.restart),
          s"npc-${npcName}-MovementActorChild"
        )
        val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] {
          case event: EntityMovementEvent =>
            event match {
              case MovedLeft(name, pressed) =>
                if (name == npcName)
                  movementActor ! ImMovementActor.MovedLeft(pressed)
                Behaviors.same
              case MovedUp(name, pressed) =>
                if (name == npcName)
                  movementActor ! ImMovementActor.MovedUp(pressed)
                Behaviors.same
              case MovedRight(name, pressed) =>
                if (name == npcName)
                  movementActor ! ImMovementActor.MovedRight(pressed)
                Behaviors.same
              case MovedDown(name, pressed) =>
                if (name == npcName)
                  movementActor ! ImMovementActor.MovedDown(pressed)
                Behaviors.same
            }
        }

        val npcMovementEl = ctx.spawn(
          Behaviors
            .supervise(npcMovementElBehavior)
            .onFailure[Exception](SupervisorStrategy.restart),
          s"npc-${npcName}-MovementEventHandler"
        )
        val renderTickElBehavior =
          Behaviors.receiveMessage[RenderTick.type] {
            case RenderTick =>
              movementActor ! ImMovementActor.Tick
              Behaviors.same
          }

        val renderTickEl =
          ctx.spawn(
            renderTickElBehavior,
            s"npc-${npcName}-MovementTickListener"
          )

        mainEventBus ! EventBus.Subscribe(npcMovementEl)
        tickEventBus ! EventBus.Subscribe(renderTickEl)
        Behaviors.receiveMessage { msg => Behaviors.same }
      }

  }
}
