package objects

import akka.actor.ActorRef
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects.Player
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior}
import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.environment.interaction.RespondsToZoneEnvironment
import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneMap}
import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire, PlanetSideGUID, Vector3}

import scala.concurrent.duration._

class InteractsWithZoneEnvironmentTest extends ActorTest {
  val pool1: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(3, 2, 2, 0, 0))
  val pool2: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(3, 4, 2, 2, 0))
  val pool3: Pool = Pool(EnvironmentAttribute.Lava, DeepSquare(3, 2, 4, 0, 2))
  val zoneEvents: TestProbe = TestProbe()
  val testZone: Zone = {
    val testMap = new ZoneMap(name = "test-map") {
      environment = List(pool1, pool2, pool3)
    }
    new Zone("test-zone", testMap, zoneNumber = 0) {
      override def AvatarEvents: ActorRef = zoneEvents.ref
    }
  }
  testZone.blockMap.addTo(pool1)
  testZone.blockMap.addTo(pool2)
  testZone.blockMap.addTo(pool3)

  "InteractsWithZoneEnvironment" should {
    "not interact with any environment when it does not encroach any environment" in {
      val testProbe = TestProbe()
      val obj = InteractsWithZoneEnvironmentTest.testObject()
      obj.Zone = testZone
      obj.Actor = testProbe.ref
      obj.Position = Vector3(10,10,0)

      obj.zoneInteractions()
      testProbe.expectNoMessage(max = 500 milliseconds)
    }

    "acknowledge interaction when moved into the critical region of a registered environment object" in {
      val testProbe = TestProbe()
      val obj = InteractsWithZoneEnvironmentTest.testObject()
      obj.Zone = testZone
      obj.Actor = testProbe.ref

      obj.Position = Vector3(1, 1, 2.7f)
      obj.zoneInteractions()
      val msg = testProbe.receiveOne(4.seconds)
      msg match {
        case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
        case _ => assert(InteractsWithZoneEnvironmentTest.fail, s"$msg")
      }

      obj.zoneInteractions()
      testProbe.expectNoMessage(max = 500 milliseconds)
    }

    "acknowledge cessation of interaction when moved out of a previous occupied the critical region (just once)" in {
      val testProbe = TestProbe()
      val obj = InteractsWithZoneEnvironmentTest.testObject()
      obj.Zone = testZone
      obj.Actor = testProbe.ref

      obj.Position = Vector3(1, 1, 2.7f)
      obj.zoneInteractions()
      val msg1 = testProbe.receiveOne(4.seconds)
      msg1 match {
        case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
        case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
      }

      obj.Position = Vector3(1,1,5)
      obj.zoneInteractions()
      val msg2 = testProbe.receiveOne(4.seconds)
      msg2 match {
        case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => ()
        case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
      }

      obj.zoneInteractions()
      testProbe.expectNoMessage(max = 500 milliseconds)
    }

    "transition between two different critical regions when the regions have the same attribute" in {
      val testProbe = TestProbe()
      val obj = InteractsWithZoneEnvironmentTest.testObject()
      obj.Zone = testZone
      obj.Actor = testProbe.ref

      obj.Position = Vector3(1, 1, 2.7f)
      obj.zoneInteractions()
      val msg1 = testProbe.receiveOne(4.seconds)
      msg1 match {
        case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
        case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
      }

      obj.Position = Vector3(1, 3, 2.7f)
      obj.zoneInteractions()
      val msg2 = testProbe.receiveOne(4.seconds)
      msg2 match {
        case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
        case _ => assert(false, "")
      }
      testProbe.expectNoMessage()
    }

    "transition between two different critical regions when the regions have different attributes" in {
      val testProbe = TestProbe()
      val obj = InteractsWithZoneEnvironmentTest.testObject()
      obj.Zone = testZone
      obj.Actor = testProbe.ref

      obj.Position = Vector3(1, 1, 2.7f)
      obj.zoneInteractions()
      val msg1 = testProbe.receiveOne(4.seconds)
      msg1 match {
        case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
        case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
      }

      obj.Position = Vector3(3, 1, 2.7f)
      obj.zoneInteractions()
      val msgs = testProbe.receiveN(4, 4.seconds)
      msgs.head match {
        case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => ()
        case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
      }
      msgs(1) match {
        case Vitality.Damage(_) => ()
        case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
      }
      msgs(2) match {
        case AuraEffectBehavior.StartEffect(Aura.Fire, _) => ()
        case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
      }
      msgs(3) match {
        case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Lava, _, _, _) => ()
        case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
      }
    }
  }

  "when interactions are disallowed, end any current interaction" in {
    val testProbe = TestProbe()
    val obj = InteractsWithZoneEnvironmentTest.testObject()
    obj.Zone = testZone
    obj.Actor = testProbe.ref

    obj.Position = Vector3(1, 1, 2.7f)
    obj.zoneInteractions()
    val msg1 = testProbe.receiveOne(max = 250 milliseconds)
    msg1 match {
      case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
      case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
    }

    obj.allowInteraction = false
    val msg2 = testProbe.receiveOne(max = 250 milliseconds)
    msg2 match {
      case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => true
      case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
    }

    obj.zoneInteractions()
    testProbe.expectNoMessage(max = 500 milliseconds)
  }

  "when interactions are allowed, after having been disallowed, engage in any detected interaction" in {
    val testProbe = TestProbe()
    val obj = InteractsWithZoneEnvironmentTest.testObject()
    obj.Zone = testZone
    obj.Actor = testProbe.ref

    obj.allowInteraction = false
    obj.Position = Vector3(1, 1, 2.7f)
    obj.zoneInteractions()
    testProbe.expectNoMessage(max = 500 milliseconds)

    obj.allowInteraction = true
    val msg1 = testProbe.receiveOne(max = 250 milliseconds)
    msg1 match {
      case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
      case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
    }
  }
}

object InteractsWithZoneEnvironmentTest {
  val fail: Boolean = false

  def testObject(): PlanetSideServerObject with InteractsWithZone = {
    val p = new Player(Avatar(1, "test", PlanetSideEmpire.VS, CharacterSex.Male, 1, CharacterVoice.Mute))
    p.GUID = PlanetSideGUID(1)
    p.Spawn()
    p
  }
}
