package wow.doge.mygame.game.subsystems.input

import scala.concurrent.duration._

import akka.actor.typed.ActorRef
import cats.effect.concurrent.Ref
import com.jme3.input.InputManager
import com.jme3.input.KeyInput
import com.jme3.input.MouseInput
import com.jme3.input.controls.KeyTrigger
import com.jme3.input.controls.MouseAxisTrigger
import monix.bio.UIO
import monix.{eval => me}
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.utils.IOUtils._

object GameInputHandler {

  class Props(
      inputManager: InputManager,
      playerEventBus: GameEventBus[PlayerEvent]
      // playerCameraEventBus: GameEventBus[PlayerCameraEvent]
      // tickEventBus: GameEventBus[TickEvent]
  ) {
    def begin =
      for {
        _ <- UIO(setupMovementKeys(inputManager))
        // _ <- UIO(setupAnalogMovementKeys)
        _ <- UIO(setupCameraKeys())
        _ <- toIO(
          me.Task.parSequence(
            Seq(
              playerMovementInputEventsGenerator(
                inputManager,
                playerEventBus
              ).completedL,
              // generateAnalogMovementEvents(
              //   inputManager,
              //   playerEventBus
              // ).completedL,
              // cameraMovementEventsGenerator(
              //   inputManager,
              //   playerEventBus
              // ).completedL,
              Ref
                .of[me.Task, Boolean](false)
                .flatMap(value => cursorToggle(value))
            )
          )
        ).startAndForget
      } yield ()

    def setupMovementKeys(inputManager: InputManager) =
      inputManager.withEnumMappings(PlayerMovementInput) {
        case PlayerMovementInput.WalkRight =>
          new KeyTrigger(KeyInput.KEY_D) :: Nil
        case PlayerMovementInput.WalkLeft =>
          new KeyTrigger(KeyInput.KEY_A) :: Nil
        case PlayerMovementInput.WalkForward =>
          new KeyTrigger(KeyInput.KEY_W) :: Nil
        case PlayerMovementInput.WalkBackward =>
          new KeyTrigger(KeyInput.KEY_S) :: Nil
        case PlayerMovementInput.Jump =>
          new KeyTrigger(KeyInput.KEY_SPACE) :: Nil
      }

    def setupAnalogMovementKeys() =
      inputManager.withEnumMappings(PlayerAnalogMovementInput) {
        case PlayerAnalogMovementInput.TurnRight =>
          Seq(new KeyTrigger(KeyInput.KEY_D))
        case PlayerAnalogMovementInput.TurnLeft =>
          Seq(new KeyTrigger(KeyInput.KEY_A))
      }

    def setupCameraKeys() =
      inputManager.withEnumMappings(PlayerCameraInput) {
        case PlayerCameraInput.CameraRotateLeft =>
          Seq(
            new KeyTrigger(KeyInput.KEY_LEFT),
            new MouseAxisTrigger(MouseInput.AXIS_X, false)
          )
        case PlayerCameraInput.CameraRotateRight =>
          Seq(
            new KeyTrigger(KeyInput.KEY_RIGHT),
            new MouseAxisTrigger(MouseInput.AXIS_X, true)
          )
        case PlayerCameraInput.CameraRotateUp =>
          Seq(
            new KeyTrigger(KeyInput.KEY_UP),
            new MouseAxisTrigger(MouseInput.AXIS_Y, false)
          )
        case PlayerCameraInput.CameraRotateDown =>
          Seq(
            new KeyTrigger(KeyInput.KEY_DOWN),
            new MouseAxisTrigger(MouseInput.AXIS_Y, true)
          )
      }

    def cursorToggle(toggleRef: Ref[me.Task, Boolean]) =
      for {
        _ <- me.Task(
          inputManager.withMapping(
            MiscInput.ToggleCursor,
            new KeyTrigger(KeyInput.KEY_Z)
          )
        )
        _ <-
          inputManager
            .enumEntryObservableAction(MiscInput.ToggleCursor)
            .doOnNext(action =>
              action.binding match {
                case MiscInput.ToggleCursor =>
                  if (action.value) for {
                    value <- toggleRef.getAndUpdate(!_)
                    _ <- me.Task(inputManager.setCursorVisible(value))
                  } yield ()
                  else me.Task.unit
                // case _ => me.Task.unit
              }
            )
            .completedL
      } yield ()

  }

  def methodName(implicit enclosing: sourcecode.Enclosing) =
    enclosing.value.split(" ")(0).split("""\.""").last

  def playerMovementInputEventsGenerator(
      inputManager: InputManager,
      playerEventBus: GameEventBus[PlayerEvent]
  ) = {
    val name = methodName
    inputManager
      .enumObservableAction(PlayerMovementInput)
      // .dump("O")
      .doOnNext { action =>
        action.binding match {
          case PlayerMovementInput.WalkLeft =>
            me.Task(
              playerEventBus ! EventBus.Publish(
                PlayerMovementEvent.PlayerMovedLeft(pressed = action.value),
                name
              )
            )
          case PlayerMovementInput.WalkRight =>
            me.Task(
              playerEventBus ! EventBus.Publish(
                PlayerMovementEvent.PlayerMovedRight(pressed = action.value),
                name
              )
            )
          case PlayerMovementInput.WalkForward =>
            me.Task(
              playerEventBus ! EventBus.Publish(
                PlayerMovementEvent.PlayerMovedForward(pressed = action.value),
                name
              )
            )
          case PlayerMovementInput.WalkBackward =>
            me.Task(
              playerEventBus ! EventBus.Publish(
                PlayerMovementEvent.PlayerMovedBackward(pressed = action.value),
                name
              )
            )
          case PlayerMovementInput.Jump =>
            if (action.value)
              me.Task(
                playerEventBus ! EventBus.Publish(
                  PlayerMovementEvent.PlayerJumped,
                  name
                )
              )
            else me.Task.unit
        }
      }
  }

  def generateAnalogMovementEvents(
      inputManager: InputManager,
      playerEventBus: GameEventBus[PlayerEvent]
  ) =
    // val name = "analogMovementEventsGenerator"
    inputManager
      .enumAnalogObservable(PlayerAnalogMovementInput)
      .sample(1.millis)
  // .doOnNext(analogEvent =>
  //   analogEvent.binding match {
  //     case PlayerAnalogMovementInput.TurnRight =>
  //       me.Task(
  //         playerMovementEventBus ! EventBus.Publish(
  //           PlayerMovementEvent.PlayerTurnedRight,
  //           name
  //         )
  //       )
  //     case PlayerAnalogMovementInput.TurnLeft =>
  //       me.Task(
  //         playerMovementEventBus ! EventBus.Publish(
  //           PlayerMovementEvent.PlayerTurnedLeft,
  //           name
  //         )
  //       )
  //   }
  // )

  def cameraMovementEventsGenerator(
      inputManager: InputManager,
      playerEventBus: ActorRef[EventBus.Command[PlayerEvent]]
  ) = {
    val name = methodName
    inputManager
      .enumAnalogObservable(PlayerCameraInput)
      .sample(1.millis)
      .doOnNext(analogEvent =>
        analogEvent.binding match {
          case PlayerCameraInput.CameraRotateLeft =>
            me.Task(
              playerEventBus ! EventBus.Publish(
                PlayerCameraEvent.CameraLeft,
                name
              )
            )
          case PlayerCameraInput.CameraRotateRight =>
            me.Task(
              playerEventBus ! EventBus.Publish(
                PlayerCameraEvent.CameraRight,
                name
              )
            )
          case PlayerCameraInput.CameraRotateUp =>
            me.Task(
              playerEventBus ! EventBus.Publish(
                PlayerCameraEvent.CameraMovedUp,
                name
              )
            )
          case PlayerCameraInput.CameraRotateDown =>
            me.Task(
              playerEventBus ! EventBus.Publish(
                PlayerCameraEvent.CameraMovedDown,
                name
              )
            )
        }
      )
  }
}
