package wow.doge.mygame.state

import akka.actor.typed.scaladsl.Behaviors
import ammonite.Main
import akka.actor.typed.ActorRef
import com.jme3.app.state.AppState
import ammonite.runtime.Storage.Folder
import ammonite.main.Defaults
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.ActorContext
import ammonite.util.Res.Success
import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
import scala.util.Try
import cats.implicits._

object ScriptActor {
  sealed trait Result
  final case class AppStateResult(state: AppState) extends Result
  final case class Error(reason: String) extends Result

  sealed trait Command
  final case class Compile(path: os.Path, sender: ActorRef[Result])
      extends Command

  final case class CompileAny(
      path: os.Path,
      result: ActorRef[Either[Error, Any]]
  ) extends Command

  def defaultScalaRunner() =
    ammonite
      .Main(
        storageBackend = new Folder(
          // os.pwd / "target"
          Defaults.ammoniteHome,
          isRepl = false
        )
      )

  def defaultKotlinRunner(): ScriptEngine = {
    val manager = new ScriptEngineManager()
    val engine = manager.getEngineByExtension("main.kts")
    engine
  }

  def apply(
      scalaRunner: Main = defaultScalaRunner(),
      kotlinRunner: ScriptEngine = defaultKotlinRunner()
      // parent: ActorRef[ScriptStoringActor.Command]
  ): Behavior[ScriptActor.Command] =
    Behaviors.setup(ctx =>
      new ScriptActor(scalaRunner, kotlinRunner, ctx).receiveMessage
    )

  sealed trait ScriptType
  case object ScalaType extends ScriptType
  case object KotlinType extends ScriptType
  case object GroovyType extends ScriptType

  def determineScriptType(path: os.Path): Either[Error, ScriptType] =
    path.toString match {
      case s if s.endsWith(".sc")       => Right(ScalaType)
      case s if s.endsWith(".main.kts") => Right(KotlinType)
      case s if s.endsWith(".groovy")   => Right(GroovyType)
      case _                            => Left(Error("Unknown script type"))
    }

  def runScala(path: os.Path, scalaRunner: ammonite.Main): Either[Error, Any] =
    scalaRunner
      .runScript(
        path,
        Seq.empty
      )
      ._1 match {
      case ammonite.util.Res.Exception(t, msg) => Left(Error(msg))

      case Success(obj) => Right(obj)

      case _ => Left(Error("Failed to run script"))
    }

  def runKotlin(path: os.Path, kotlinRunner: ScriptEngine): Either[Error, Any] =
    Try(kotlinRunner.eval(os.read(path))).toEither.leftMap(t =>
      Error(t.getMessage())
    )

  def runGroovy(path: os.Path): Either[Error, Any] =
    Left(Error("Not implemented yet"))

}

class ScriptActor(
    val scalaRunner: Main,
    val kotlinRunner: ScriptEngine,
    // parent: ActorRef[ScriptStoringActor.Command],
    context: ActorContext[ScriptActor.Command]
) {
  import ScriptActor._

  def receiveMessage: Behavior[Command] =
    Behaviors.receiveMessage { msg =>
      msg match {
        case Compile(path, sender) =>
          context.log.debug(s"Received $path")
          val res = getScript(path)
          sender ! res
          Behaviors.same
        // case CompileScripts(sender, paths) =>
        case CompileAny(path, requester) =>
          context.log.debug(s"Received $path")
          val res = getScript2(path)
          context.log.debug(s"result = $res")
          requester ! res
          // parent ! ScriptStoringActor.Put(path, res)
          Behaviors.same
      }
    }

  def getScript(path: os.Path) = {
    val res = determineScriptType(path) match {
      case Right(ScalaType)  => runScala(path, scalaRunner)
      case Right(KotlinType) => runKotlin(path, kotlinRunner)
      case Right(GroovyType) => runGroovy(path)
      case l @ Left(err)     => l
    }

    res match {
      case Left(err) => err
      case Right(obj) =>
        obj match {
          case s: MyBaseState => AppStateResult(s)
          case _              => Error("Class in script does not match known types")
        }
    }
  }

  def getScript2(path: os.Path): Either[Error, Any] = {
    val res = determineScriptType(path) match {
      case Right(ScalaType)  => runScala(path, scalaRunner)
      case Right(KotlinType) => runKotlin(path, kotlinRunner)
      case Right(GroovyType) => runGroovy(path)
      case l @ Left(err)     => l
    }

    // res match {
    //   case Left(err) => err
    //   case Right(obj) =>
    //     obj match {
    //       case s: MyBaseState => AppStateResult(s)
    //       case _              => Error("Class in script does not match known types")
    //     }
    // }
    res
  }

}
