package wow.doge.mygame.launcher

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._

import cats.effect.concurrent.Deferred
import javafx.application.Platform
import monix.bio.Task
import monix.catnap.CancelableF
import monix.eval.{Task => ETask}
import monix.reactive.Observable
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.scene.control.Button
import scalafx.stage.StageStyle
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.implicits.JavaFXMonixObservables._
import wow.doge.mygame.utils.IOUtils._

object Launcher {
  sealed trait LauncherResult
  object LauncherResult {
    case object LaunchGame extends LauncherResult
    case object Exit extends LauncherResult
  }

  class Props(
      val schedulers: Schedulers,
      val signal: Deferred[Task, LauncherResult]
  ) {
    val create = Task(new Launcher(this))
  }
}
class Launcher private (props: Launcher.Props) {
  import Launcher._

  private lazy val launchButton = new Button {
    text = "Launch"
  }

  private lazy val launchAction =
    launchButton
      .observableAction()
      .doOnNext(_ => toTask(props.signal.complete(LauncherResult.LaunchGame)))

  private lazy val exitButton = new Button {
    text = "Exit"
  }

  private lazy val exitAction =
    exitButton
      .observableAction()
      .doOnNext(_ => toTask(props.signal.complete(LauncherResult.Exit)))

  private lazy val _scene =
    DefaultUI.scene(launchButton, exitButton)

  private lazy val _stage = new PrimaryStage {
    scene = _scene
  }

  private lazy val internal = new JFXApp {
    stage = _stage
    stage.initStyle(StageStyle.Undecorated)
    // ResizeHelper.addResizeListener(stage)
  }

  private lazy val sceneDragObservable = {
    lazy val mpo = _scene.observableMousePressed()
    lazy val mdo = _scene.observableMouseDragged()

    mpo.mergeMap(pressEvent =>
      mdo.doOnNext(dragEvent =>
        ETask(
          _stage.setX(dragEvent.screenX - pressEvent.sceneX)
        ) >>
          ETask(
            _stage.setY(
              dragEvent.screenY - pressEvent.sceneY
            )
          )
      )
    )
  }

  def init(delay: FiniteDuration = 2000.millis) =
    for {
      _ <- Task(Platform.setImplicitExit(false))

      fib <- Task(internal.main(Array.empty)).start
      _ <- Task.sleep(500.millis)
      sceneDragFib <- toIO(sceneDragObservable.completedL).start
      fib2 <- toIO(
        Observable(launchAction, exitAction).merge
          .doOnNext(_ =>
            ETask(internal.stage.close()).executeOn(props.schedulers.fx)
          )
          .completedL
      ).start
      c <- CancelableF[Task](fib.cancel >> fib2.cancel >> sceneDragFib.cancel)
    } yield (c)

}
