// Copyright (c) 2020 PSForever
package objects

import akka.actor.Props
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects._
import net.psforever.objects.avatar.{Avatar, Certification}
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl}
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl}
import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretControl}
import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
import net.psforever.types._
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}

import scala.concurrent.duration._

/*
the generator is used to test basic entity repair
essentially, treat it more as a generic entity whose object type is repairable
see GeneratorTest in relation to what the generator does above and beyond that during repair
 */
class RepairableEntityRepairTest extends ActorTest {
  val guid = new NumberPoolHub(new MaxNumberSource(10))
  val zone = new Zone("test", new ZoneMap("test"), 0) {
    override def SetupNumberPools() = {}

    GUID(guid)
  }
  val building = Building("test-building", 1, 1, zone, StructureType.Facility) //guid=1
  val gen      = Generator(GlobalDefinitions.generator)                        //guid=2
  val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
    .copy(certifications = Set(Certification.Engineering))
  val player1 = Player(avatar) //guid=3
  player1.Spawn()
  guid.register(building, 1)
  guid.register(gen, 2)
  guid.register(player1, 3)
  building.Position = Vector3(1, 0, 0)
  building.Zone = zone
  building.Amenities = gen
  gen.Position = Vector3(1, 0, 0)
  gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
  val activityProbe = TestProbe()
  val avatarProbe   = TestProbe()
  val buildingProbe = TestProbe()
  zone.Activity = activityProbe.ref
  zone.AvatarEvents = avatarProbe.ref
  building.Actor = buildingProbe.ref
  val tool = Tool(GlobalDefinitions.nano_dispenser) //4 & 5
  guid.register(tool, 4)
  guid.register(tool.AmmoSlot.Box, 5)
  expectNoMessage(200 milliseconds)
  //we're not testing that the math is correct

  "RepairableEntity" should {
    "handle repairs" in {
      assert(gen.Health == gen.Definition.DefaultHealth) //ideal
      val originalHealth = gen.Health -= 50
      assert(gen.Health < gen.Definition.DefaultHealth)   //damage
      gen.Actor ! CommonMessages.Use(player1, Some(tool)) //repair

      val msg123 = avatarProbe.receiveN(3, 500 milliseconds)
      assert(
        msg123.head match {
          case AvatarServiceMessage(
                "TestCharacter1",
                AvatarAction
                  .SendResponse(PlanetSideGUID(0), InventoryStateMessage(PlanetSideGUID(5), _, PlanetSideGUID(4), _))
              ) =>
            true
          case _ => false
        }
      )
      assert(
        msg123(1) match {
          case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
          case _                                                                                            => false
        }
      )
      assert(
        msg123(2) match {
          case AvatarServiceMessage(
                "TestCharacter1",
                AvatarAction.SendResponse(PlanetSideGUID(0), RepairMessage(PlanetSideGUID(2), _))
              ) =>
            true
          case _ => false
        }
      )
      assert(originalHealth < gen.Health) //generator repaired a bit
    }
  }
}

class RepairableEntityNotRepairTest extends ActorTest {
  val guid = new NumberPoolHub(new MaxNumberSource(10))
  val zone = new Zone("test", new ZoneMap("test"), 0) {
    override def SetupNumberPools() = {}

    GUID(guid)
  }
  val building = Building("test-building", 1, 1, zone, StructureType.Facility) //guid=1
  val gen      = Generator(GlobalDefinitions.generator)                        //guid=2
  val player1 =
    Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=3
  player1.Spawn()
  guid.register(building, 1)
  guid.register(gen, 2)
  guid.register(player1, 3)
  building.Position = Vector3(1, 0, 0)
  building.Zone = zone
  building.Amenities = gen
  gen.Position = Vector3(1, 0, 0)
  gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
  val activityProbe = TestProbe()
  val avatarProbe   = TestProbe()
  val buildingProbe = TestProbe()
  zone.Activity = activityProbe.ref
  zone.AvatarEvents = avatarProbe.ref
  building.Actor = buildingProbe.ref
  val tool = Tool(GlobalDefinitions.nano_dispenser) //4 & 5
  guid.register(tool, 4)
  guid.register(tool.AmmoSlot.Box, 5)
  expectNoMessage(200 milliseconds)
  //we're not testing that the math is correct

  "RepairableEntity" should {
    "not repair if health is already full" in {
      assert(gen.Health == gen.Definition.DefaultHealth)  //ideal
      gen.Actor ! CommonMessages.Use(player1, Some(tool)) //repair?
      avatarProbe.expectNoMessage(1000 milliseconds)      //no messages
    }
  }
}

class RepairableAmenityTest extends ActorTest {
  val guid = new NumberPoolHub(new MaxNumberSource(10))
  val zone = new Zone("test", new ZoneMap("test"), 0) {
    override def SetupNumberPools() = {}

    GUID(guid)
  }
  val building = Building("test-building", 1, 1, zone, StructureType.Facility) //guid=1
  val term     = Terminal(GlobalDefinitions.order_terminal)                    //guid=2
  val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
    .copy(certifications = Set(Certification.Engineering))
  val player1 = Player(avatar) //guid=3
  player1.Spawn()
  guid.register(building, 1)
  guid.register(term, 2)
  guid.register(player1, 3)
  building.Position = Vector3(1, 0, 0)
  building.Zone = zone
  building.Amenities = term
  term.Position = Vector3(1, 0, 0)
  term.Actor = system.actorOf(Props(classOf[TerminalControl], term), "terminal-control")
  val activityProbe = TestProbe()
  val avatarProbe   = TestProbe()
  val buildingProbe = TestProbe()
  zone.Activity = activityProbe.ref
  zone.AvatarEvents = avatarProbe.ref
  building.Actor = buildingProbe.ref

  val tool = Tool(GlobalDefinitions.nano_dispenser) //4 & 5
  guid.register(tool, 4)
  guid.register(tool.AmmoSlot.Box, 5)
  expectNoMessage(200 milliseconds)
  //we're not testing that the math is correct

  "RepairableAmenity" should {
    "send initialization messages upon restoration" in {
      //the decimator does enough damage to one-shot this terminal from any initial health
      val originalHealth = term.Health = term.Definition.RepairRestoresAt - 1 //initial state manip
      term.Destroyed = true
      assert(originalHealth < term.Definition.RepairRestoresAt)
      assert(term.Destroyed)

      term.Actor ! CommonMessages.Use(player1, Some(tool))
      val msg12345 = avatarProbe.receiveN(5, 500 milliseconds)
      assert(
        msg12345.head match {
          case AvatarServiceMessage(
                "TestCharacter1",
                AvatarAction
                  .SendResponse(PlanetSideGUID(0), InventoryStateMessage(PlanetSideGUID(5), _, PlanetSideGUID(4), _))
              ) =>
            true
          case _ => false
        }
      )
      assert(
        msg12345(1) match {
          case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
          case _                                                                                            => false
        }
      )
      assert(
        msg12345(2) match {
          case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 50, 0)) => true
          case _                                                                                             => false
        }
      )
      assert(
        msg12345(3) match {
          case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 51, 0)) => true
          case _                                                                                             => false
        }
      )
      assert(
        msg12345(4) match {
          case AvatarServiceMessage(
                "TestCharacter1",
                AvatarAction.SendResponse(PlanetSideGUID(0), RepairMessage(PlanetSideGUID(2), _))
              ) =>
            true
          case _ => false
        }
      )
      assert(term.Health > term.Definition.RepairRestoresAt)
      assert(!term.Destroyed)
    }
  }
}

class RepairableTurretWeapon extends ActorTest {
  val guid = new NumberPoolHub(new MaxNumberSource(10))
  val zone = new Zone("test", new ZoneMap("test"), 0) {
    override def SetupNumberPools() = {}
    GUID(guid)
  }
  val building      = Building("test-building", 1, 1, zone, StructureType.Facility) //guid=1
  val activityProbe = TestProbe()
  val avatarProbe   = TestProbe()
  val vehicleProbe  = TestProbe()
  val buildingProbe = TestProbe()
  zone.Activity = activityProbe.ref
  zone.AvatarEvents = avatarProbe.ref
  zone.VehicleEvents = vehicleProbe.ref
  building.Actor = buildingProbe.ref

  val turret = new FacilityTurret(GlobalDefinitions.manned_turret) //2, 5, 6
  turret.Actor = system.actorOf(Props(classOf[FacilityTurretControl], turret), "turret-control")
  turret.Zone = zone
  turret.Position = Vector3(1, 0, 0)
  val turretWeapon = turret.Weapons.values.head.Equipment.get.asInstanceOf[Tool]

  val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
    .copy(certifications = Set(Certification.Engineering))
  val player1 = Player(avatar) //guid=3
  player1.Spawn()
  player1.Position = Vector3(2, 2, 2)
  val player1Probe = TestProbe()
  player1.Actor = player1Probe.ref

  guid.register(building, 1)
  guid.register(turret, 2)
  guid.register(player1, 3)
  guid.register(turretWeapon, 5)
  guid.register(turretWeapon.AmmoSlot.Box, 6)
  building.Position = Vector3(1, 0, 0)
  building.Zone = zone
  building.Amenities = turret

  val tool = Tool(GlobalDefinitions.nano_dispenser) //7 & 8
  guid.register(tool, 7)
  guid.register(tool.AmmoSlot.Box, 8)

  "RepairableTurretWeapon" should {
    "handle repairs and restoration" in {
      turret.Health = turret.Definition.RepairRestoresAt - 1 //initial state manip
      turret.Destroyed = true                                //initial state manip
      assert(turret.Health < turret.Definition.RepairRestoresAt)
      assert(turret.Destroyed)

      turret.Actor ! CommonMessages.Use(player1, Some(tool))
      val msg12345 = avatarProbe.receiveN(5, 500 milliseconds)
      val msg4     = vehicleProbe.receiveOne(500 milliseconds)
      assert(
        msg12345.head match {
          case AvatarServiceMessage(
                "TestCharacter1",
                AvatarAction
                  .SendResponse(PlanetSideGUID(0), InventoryStateMessage(PlanetSideGUID(8), _, PlanetSideGUID(7), _))
              ) =>
            true
          case _ => false
        }
      )
      assert(
        msg12345(1) match {
          case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
          case _                                                                                            => false
        }
      )
      //msg12345(2) and msg12345(3) are related to RepairableAmenity
      assert(
        msg12345(4) match {
          case AvatarServiceMessage(
                "TestCharacter1",
                AvatarAction.SendResponse(PlanetSideGUID(0), RepairMessage(PlanetSideGUID(2), _))
              ) =>
            true
          case _ => false
        }
      )
      assert(
        msg4 match {
          case VehicleServiceMessage("test", VehicleAction.EquipmentInSlot(_, PlanetSideGUID(2), 1, t))
              if t eq turretWeapon =>
            true
          case _ => false
        }
      )
      assert(turret.Health > turret.Definition.RepairRestoresAt)
      assert(!turret.Destroyed)
    }
  }
}

class RepairableVehicleRepair extends ActorTest {
  val guid = new NumberPoolHub(new MaxNumberSource(10))
  val zone = new Zone("test", new ZoneMap("test"), 0) {
    override def SetupNumberPools() = {}
    GUID(guid)
  }
  val avatarProbe = TestProbe()
  zone.AvatarEvents = avatarProbe.ref

  val atv = Vehicle(GlobalDefinitions.quadassault) //guid=1, 2, 3
  atv.Actor = system.actorOf(Props(classOf[VehicleControl], atv), "vehicle-control")
  atv.Position = Vector3(1, 0, 0)
  val atvWeapon = atv.Weapons(1).Equipment.get.asInstanceOf[Tool]

  val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
    .copy(certifications = Set(Certification.Engineering))
  val player1 = Player(avatar) //guid=4
  player1.Spawn()
  player1.Position = Vector3(2, 2, 2)
  val player1Probe = TestProbe()
  player1.Actor = player1Probe.ref

  guid.register(atv, 1)
  guid.register(atvWeapon, 2)
  guid.register(atvWeapon.AmmoSlot.Box, 3)
  guid.register(player1, 4)
  atv.Zone = zone

  val tool = Tool(GlobalDefinitions.nano_dispenser) //5 & 6
  guid.register(tool, 5)
  guid.register(tool.AmmoSlot.Box, 6)

  "RepairableVehicle" should {
    "handle repairs" in {
      val originalHealth = atv.Health = atv.Definition.DamageDestroysAt + 1 //initial state manip
      assert(atv.Health == originalHealth)

      atv.Actor ! CommonMessages.Use(player1, Some(tool))
      val msg123 = avatarProbe.receiveN(3, 500 milliseconds)
      assert(
        msg123.head match {
          case AvatarServiceMessage(
                "TestCharacter1",
                AvatarAction
                  .SendResponse(PlanetSideGUID(0), InventoryStateMessage(PlanetSideGUID(6), _, PlanetSideGUID(5), _))
              ) =>
            true
          case _ => false
        }
      )
      assert(
        msg123(1) match {
          case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 0, _)) => true
          case _                                                                                            => false
        }
      )
      assert(
        msg123(2) match {
          case AvatarServiceMessage(
                "TestCharacter1",
                AvatarAction.SendResponse(PlanetSideGUID(0), RepairMessage(PlanetSideGUID(1), _))
              ) =>
            true
          case _ => false
        }
      )
      assert(atv.Health > originalHealth)
    }
  }
}

class RepairableVehicleRestoration extends ActorTest {
  /*
  no messages are dispatched, in this case, because most vehicles are flagged to not be repairable if destroyed
   */
  val guid = new NumberPoolHub(new MaxNumberSource(10))
  val zone = new Zone("test", new ZoneMap("test"), 0) {
    override def SetupNumberPools() = {}
    GUID(guid)
  }
  val avatarProbe = TestProbe()
  zone.AvatarEvents = avatarProbe.ref

  val atv = Vehicle(GlobalDefinitions.quadassault) //guid=1, 2, 3
  atv.Actor = system.actorOf(Props(classOf[VehicleControl], atv), "vehicle-control")
  atv.Position = Vector3(1, 0, 0)
  val atvWeapon = atv.Weapons(1).Equipment.get.asInstanceOf[Tool]

  val player1 =
    Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=4
  player1.Spawn()
  player1.Position = Vector3(2, 2, 2)
  val player1Probe = TestProbe()
  player1.Actor = player1Probe.ref

  guid.register(atv, 1)
  guid.register(atvWeapon, 2)
  guid.register(atvWeapon.AmmoSlot.Box, 3)
  guid.register(player1, 4)
  atv.Zone = zone

  val tool = Tool(GlobalDefinitions.nano_dispenser) //5 & 6
  guid.register(tool, 5)
  guid.register(tool.AmmoSlot.Box, 6)

  "RepairableVehicle" should {
    "will not restore a destroyed vehicle to working order" in {
      atv.Health = atv.Definition.DamageDestroysAt - 1 //initial state manip
      atv.Destroyed = true                             //initial state manip
      assert(atv.Health <= atv.Definition.DamageDestroysAt)
      assert(atv.Destroyed)

      atv.Actor ! CommonMessages.Use(player1, Some(tool))
      avatarProbe.expectNoMessage(500 milliseconds)
      assert(atv.Health == 0) //set to zero explicitly
      assert(atv.Destroyed)
    }
  }
}

object RepairableTest {}
