package wow.doge.mygame.game.entities

import scala.concurrent.duration._
import scala.util.Random

import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.TimerScheduler
import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level
import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.subsystems.events.EventBus
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.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor

object PlayerActorSupervisor {
  sealed trait Command
  final case class Props(
      playerEventBus: GameEventBus[PlayerEvent],
      tickEventBus: GameEventBus[TickEvent],
      imMovementActorBehavior: Behavior[ImMovementActor.Command],
      playerCameraActorBehavior: Behavior[PlayerCameraActor.Command]
  ) {
    def behavior[T: CanMove](movable: T) =
      Behaviors.logMessages(
        LogOptions()
          .withLevel(Level.TRACE)
          .withLogger(
            Logger[PlayerActorSupervisor[T]].underlying
          ),
        Behaviors.setup[Command] { ctx =>
          ctx.log.info("Hello from PlayerActor")

          // spawn children actors
          lazy val movementActor =
            ctx.spawn(
              Behaviors
                .supervise(imMovementActorBehavior)
                .onFailure[Exception](SupervisorStrategy.restart),
              "playerMovementActorChild"
            )

          val playerCameraActor =
            ctx.spawn(playerCameraActorBehavior, "playerCameraActor")

          val playerCameraEl = ctx.spawn(
            PlayerCameraEventListener(playerCameraActor),
            "playerCameraActorEl"
          )

          ctx.spawn(
            PlayerMovementActor
              .Props(
                movementActor,
                playerCameraActor,
                playerEventBus,
                tickEventBus
              )
              .behavior,
            "playerMovementAcor"
          )

          //init actors
          playerEventBus ! EventBus.Subscribe(playerCameraEl)

          new PlayerActorSupervisor(
            ctx,
            this,
            Children(movementActor)
          ).receive
        }
      )

  }

  case class Children(
      movementActor: ActorRef[ImMovementActor.Command]
  )
}
class PlayerActorSupervisor[T: CanMove](
    ctx: ActorContext[PlayerActorSupervisor.Command],
    props: PlayerActorSupervisor.Props,
    children: PlayerActorSupervisor.Children
) {
  import PlayerActorSupervisor._
  def receive =
    Behaviors.receiveMessage[Command] {
      case _ =>
        // children.movementActor ! ImMovementActor.MovedDown(true)
        Behaviors.same
    }
}

object PlayerMovementActor {
  sealed trait Command
  final case class Props(
      movementActor: ActorRef[ImMovementActor.Command],
      playerCameraActor: ActorRef[PlayerCameraActor.Command],
      playerEventBus: GameEventBus[PlayerEvent],
      tickEventBus: GameEventBus[TickEvent]
  ) {

    def behavior: Behavior[Command] =
      Behaviors.setup { ctx =>
        val playerMovementEl = ctx.spawn(
          Behaviors
            .supervise(PlayerMovementEventListener(movementActor))
            .onFailure[Exception](SupervisorStrategy.restart),
          "playerMovementEventHandler"
        )
        val renderTickElBehavior =
          Behaviors.receiveMessage[RenderTick.type] {
            case RenderTick =>
              movementActor ! ImMovementActor.Tick
              // playerCameraActor ! PlayerCameraActor.Tick
              Behaviors.same
          }
        val renderTickEl =
          ctx.spawn(renderTickElBehavior, "playerMovementTickListener")

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

object GenericTimerActor {
  sealed trait Command
  final case object Start extends Command
  final case object Stop extends Command
  private case object Send extends Command
  case class TimerKey(seed: Long)

  case class Props[T](
      target: ActorRef[T],
      messageToSend: T,
      timeInterval: FiniteDuration
  ) {
    val behavior = Behaviors.withTimers[Command] { timers =>
      new GenericTimerActor(timers, TimerKey(Random.nextLong()), this).idle
    }
  }
}
class GenericTimerActor[T](
    timers: TimerScheduler[GenericTimerActor.Command],
    timerKey: GenericTimerActor.TimerKey,
    props: GenericTimerActor.Props[T]
) {
  import GenericTimerActor._

  val idle: Behavior[Command] =
    Behaviors.receiveMessage {
      case Start =>
        timers.startTimerWithFixedDelay(timerKey, Send, props.timeInterval)
        active
      case _ => Behaviors.unhandled

    }

  val active: Behavior[Command] =
    Behaviors.receiveMessagePartial {
      case Send =>
        props.target ! props.messageToSend
        Behaviors.same
      case Stop =>
        timers.cancel(timerKey)
        idle
    }
}
