package wow.doge.mygame.subsystems.events

import scala.concurrent.duration._
import scala.reflect.ClassTag

import akka.actor.typed.ActorRef
import akka.actor.typed.LogOptions
import akka.actor.typed.Props
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import com.typesafe.scalalogging.{Logger => SLogger}
import monix.bio.IO
import org.slf4j.event.Level
import wow.doge.mygame.AppError
import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.Event
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.types.AkkaScheduler

class EventsModule(
    scheduler: AkkaScheduler,
    spawnProtocol: ActorRef[SpawnProtocol.Command]
) {
  import EventsModule._
  implicit val s = scheduler.value
  implicit val sp = spawnProtocol
  implicit val timeout = Timeout(1.second)

  val eventBusLogger = SLogger[EventBus[_]]

  val playerEventBus: IO[AppError, GameEventBus[PlayerEvent]] =
    createEventBus[PlayerEvent]()

  // val playerCameraEventBusTask =
  //   createEventBus[PlayerCameraEvent](Level.DEBUG)

  val tickEventBus: IO[AppError, GameEventBus[TickEvent]] =
    createEventBus[TickEvent](Level.TRACE)

  val mainEventBus: IO[AppError, GameEventBus[Event]] = createEventBus[Event]()

  def createEventBus[T: ClassTag](
      logLevel: Level = Level.DEBUG,
      busName: Option[String] = None
  )(implicit name: sourcecode.Name) =
    spawnProtocol
      .askL(
        SpawnProtocol.Spawn[EventBus.Command[T]](
          Behaviors.logMessages(
            logOptions = LogOptions()
              .withLevel(logLevel)
              .withLogger(eventBusLogger.underlying),
            Behaviors
              .supervise(EventBus[T]())
              .onFailure[Exception](
                SupervisorStrategy.restart.withLimit(2, 100.millis)
              )
          ),
          busName.fold(name.value)(identity),
          Props.empty,
          _
        )
      )
      .onErrorHandleWith(TimeoutError.from)

}

object EventsModule {
  type GameEventBus[T] = ActorRef[EventBus.Command[T]]
}
