package wow.doge.mygame

import com.jme3.app.SimpleApplication
import com.jme3.app.state.AppStateManager
import scala.reflect.ClassTag
import com.jme3.app.state.AppState
import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.simsilica.es.EntityData
import com.simsilica.es.EntityComponent
import com.simsilica.es.EntityId
import akka.actor.typed.ActorRef
import akka.util.Timeout
import akka.actor.typed.Scheduler
import monix.eval.Task
import com.jme3.input.InputManager
import com.jme3.input.controls.Trigger
import com.jme3.input.controls.InputListener
import com.jme3.math.Vector3f
import wow.doge.mygame.math.ImVector3f
import com.jme3.scene.Geometry
import wow.doge.mygame.state.CardinalDirection
import wow.doge.mygame.state.CanMove
import com.jme3.renderer.Camera

package object implicits {
  implicit class StateManagerExt(val sm: AppStateManager) extends AnyVal {
    def state[S <: AppState]()(implicit c: ClassTag[S]): S =
      sm.getState(c.runtimeClass.asInstanceOf[Class[S]])

  }

  implicit class SimpleApplicationExt(val sa: SimpleApplication)
      extends AnyVal {
    def stateManager() = sa.getStateManager()
  }

  implicit class NodeExt(val n: Node) extends AnyVal {
    def child(s: Spatial): Node = {
      n.attachChild(s)
      n
    }
  }

  implicit class EntityDataExt(val ed: EntityData) extends AnyVal {

    def query = new EntityQuery(ed)

    // def entities[T <: EntityComponent](entities: Seq[T])
  }

  implicit class EntityExt(val e: EntityId) extends AnyVal {
    def withComponents(classes: EntityComponent*)(implicit
        ed: EntityData
    ): EntityId = {
      ed.setComponents(e, classes: _*)
      e
    }
  }

  implicit class ActorRefExt[Req](val a: ActorRef[Req]) extends AnyVal {
    import akka.actor.typed.scaladsl.AskPattern._
    def askT[Res](
        replyTo: ActorRef[Res] => Req
    )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = {
      Task.deferFuture(a.ask(replyTo)(timeout, scheduler))
    }
  }
  // def ?[Res](replyTo: ActorRef[Res] => Req)(implicit timeout: Timeout, scheduler: Scheduler): Future[Res] = {
  //     ask(replyTo)(timeout, scheduler)
  //   }

  implicit class InputManagerExt(val inputManager: InputManager)
      extends AnyVal {
    def withMapping(mapping: String, triggers: Trigger*): InputManager = {
      inputManager.addMapping(mapping, triggers: _*)
      inputManager
    }

    def withListener(listener: InputListener, mappings: String*) = {
      inputManager.addListener(listener, mappings: _*)
      inputManager
    }
  }

  implicit class Vector3fExt(val v: Vector3f) extends AnyVal {
    def +=(that: Vector3f) = v.addLocal(that)
    def *=(that: Vector3f) = v.multLocal(that)
    def -=(that: Vector3f) = v.subtractLocal(that)
    def /=(that: Vector3f) = v.divideLocal(that)
    def unary_- = v.negateLocal()
    def immutable = ImVector3f(v.x, v.y, v.z)
  }

  implicit class ImVector3fExt(val v: ImVector3f) extends AnyVal {
    def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z)
    def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z)
    def *(f: Float): ImVector3f =
      // v.copy(v.x * f, v.y * f, v.x * f)
      v * ImVector3f(f, f, f)
    def -(that: ImVector3f) = v.copy(v.x - that.x, v.y - that.y, v.z - that.z)
    def /(that: ImVector3f) = v.copy(v.x / that.x, v.y / that.y, v.z / that.z)
    def unary_- = v.copy(-v.x, -v.y, -v.z)
    // def unary_-(that: ImVector3f) = this.copy(this.x, this.y, this.z)
    def mutable = new Vector3f(v.x, v.y, v.z)
  }

  // implicit val implVector3fForVector3 = new Vector3[Vector3f] {
  //   override def +(implicit v: Vector3f, that: com.jme3.math.Vector3f): Unit =
  //     v += that

  //   override def *(implicit v: Vector3f, that: Vector3f): Unit = v *= that

  //   override def -(implicit v: Vector3f, that: Vector3f): Unit = v -= that

  //   override def /(implicit v: Vector3f, that: Vector3f): Unit = v /= that

  // }

  // implicit val implImVector3fForVector3 = new Vector3[ImVector3f] {
  //   override def +(implicit v: ImVector3f, that: ImVector3f): Unit =
  //     v + that

  //   override def *(implicit v: ImVector3f, that: ImVector3f): Unit = v * that

  //   override def -(implicit v: ImVector3f, that: ImVector3f): Unit = v - that

  //   override def /(implicit v: ImVector3f, that: ImVector3f): Unit = v / that

  // }

  // def test[T](v: T)(implicit ev: Vector3[T]) = {
  //   import ev._
  //   ev.+
  // }

  implicit val implCanMoveForGeom = new CanMove[Geometry] {
    override def getDirection(
        cam: Camera,
        cardinalDir: CardinalDirection
    ): ImVector3f = {
      val camDir =
        cam.getDirection().immutable * 0.6f
      val camLeft = cam.getLeft().immutable * 0.4f

      val zero = ImVector3f.ZERO
      val dir = cardinalDir
      val walkDir = {
        val mutWalkDir = new Vector3f()
        if (dir.left) {
          // ctx.log.trace("left")
          mutWalkDir += (zero + camLeft).mutable
        }
        if (dir.right) {
          // ctx.log.trace("right")
          mutWalkDir += (zero + -camLeft).mutable
        }
        if (dir.up) {
          // ctx.log.trace("up")
          mutWalkDir += (zero + camDir).mutable
        }
        if (dir.down) {
          // ctx.log.trace("down")
          mutWalkDir += (zero + -camDir).mutable
        }
        mutWalkDir.immutable
      }
      walkDir
    }
    override def move(geom: Geometry, direction: ImVector3f): Unit = {
      val v = geom.getLocalTranslation()
      geom.setLocalTranslation(v += direction.mutable)
    }
  }
}
