# ****************************************************************************
# CUI
#
# The Advanced Framework for Simulation, Integration, and Modeling (AFSIM)
#
# The use, dissemination or disclosure of data in this file is subject to
# limitation or restriction. See accompanying README and LICENSE for details.
# ****************************************************************************

script_struct WeaponData
   script_variables
      string type           = "";
      double rangeMin       = 0;      #meters
      double rangeMax       = 0;      #meters
      bool   onlyUseInRange = true;
      double averageSpeed   = 0;      #meters/second
      double maxTimeFlight  = 0;      #seconds, note: should == rangeMax / averageSpeed
      int    numActiveMax   = 0;
      bool   domainAir      = false;
      bool   domainLand     = false;
      double maxFiringAngle = 0;
   end_script_variables
end_script_struct

script_variables
   Map<string, struct> gWeaponDefs =  Map<string, struct>();

   gWeaponDefs["MEDIUM_RANGE_MISSILE"] = struct.New("WeaponData");
   gWeaponDefs["MEDIUM_RANGE_MISSILE"]->type           = "MEDIUM_RANGE_MISSILE";
   gWeaponDefs["MEDIUM_RANGE_MISSILE"]->rangeMin       = 50;       # (meters)
   gWeaponDefs["MEDIUM_RANGE_MISSILE"]->rangeMax       = 111120;   # ~60 nm (meters)
   gWeaponDefs["MEDIUM_RANGE_MISSILE"]->averageSpeed   = 1657.283; #mach 5 (m/s)
   gWeaponDefs["MEDIUM_RANGE_MISSILE"]->maxTimeFlight  = 67.05;    #for 60 nm range (seconds)
   gWeaponDefs["MEDIUM_RANGE_MISSILE"]->numActiveMax   = 2;
   gWeaponDefs["MEDIUM_RANGE_MISSILE"]->domainAir      = true;
   gWeaponDefs["MEDIUM_RANGE_MISSILE"]->domainLand     = false;
   gWeaponDefs["MEDIUM_RANGE_MISSILE"]->maxFiringAngle = 45.0;

   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"] = struct.New("WeaponData");
   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"]->type           = "MEDIUM_RANGE_RADAR_MISSILE";
   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"]->rangeMin       = 50;         # (meters)
   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"]->rangeMax       = 111120;     # ~60 nm (meters)
   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"]->averageSpeed   = 1657.283;   #mach 5 (m/s)
   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"]->maxTimeFlight  = 67.05;      #for 60 nm range (seconds)
   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"]->numActiveMax   = 2;
   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"]->domainAir      = true;
   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"]->domainLand     = false;
   gWeaponDefs["MEDIUM_RANGE_RADAR_MISSILE"]->maxFiringAngle = 45.0;

end_script_variables

#returns a 'WeaponData' struct
script struct GetWeaponData(string aType)
   if (gWeaponDefs.Exists(aType))
   {
      return gWeaponDefs.Get(aType);
   }
   else
   {
      return struct.New("WeaponData");
   }
end_script


include_once processors/quantum_agents/common/common_platform_script.txt

script_debug_writes off

script bool WeaponCapableAvailableAgainstThreat(WsfWeapon weapon, WsfTrack track)
   writeln_d("        checking weapon ", weapon.Name(), " valid=", weapon.IsValid());
   if (weapon.IsNull() || !weapon.IsValid() || track.IsNull() || !track.IsValid())
   {
      writeln_d("weapon or track is not valid!");
      return false;
   }
   if ((weapon.QuantityRemaining()-weapon.WeaponsPendingFor(WsfTrackId())) <= 0)
   {
      writeln_d("no unassigned weapons left to fire!");
      return false;
   }

   #check manually input user data first
   struct weaponData = GetWeaponData(weapon.Type());
   if (weaponData->type == weapon.Type())
   {
      if ((track.AirDomain()  && !weaponData->domainAir) ||
          (track.LandDomain() && !weaponData->domainLand)  )
      {
         writeln_d("weapon not capable against target domain!");
         return false;
      }
   }
   else
   {
      writeln_d("could not find weapon type ", weapon.Type() ," in weapon database; query returned type ", weaponData->type);
      #check if it has a launch computer of the necessary type
      WsfLaunchComputer lcPtr = weapon.LaunchComputer();
      if (lcPtr.IsValid())
      {
         if (track.AirDomain() && lcPtr.IsA_TypeOf("WSF_AIR_TO_AIR_LAUNCH_COMPUTER"))
         {
            return true;
         }
         else if (track.LandDomain() && lcPtr.IsA_TypeOf("WSF_ATG_LAUNCH_COMPUTER"))
         {
            return true;
         }
      }
      writeln_d("nor could an applicable launch computer be found!");
      return false;   #dont have weapon data
   }
   return true;
end_script


#could return -1 for an invalid max range
#(from a launch computer or weapon struct not found)
script double MaxRange(WsfPlatform shooter, WsfWeapon weapon, WsfTrack track)
   WsfLaunchComputer lcPtr = weapon.LaunchComputer();
   if (lcPtr.IsValid() &&
       lcPtr.IsA_TypeOf("WSF_AIR_TO_AIR_LAUNCH_COMPUTER"))
   {
      double rmax = lcPtr.LookupResult(track)[0];
      return rmax;
   }
   else
   {
      struct weaponData = GetWeaponData(weapon.Type());
      if (weaponData->type == weapon.Type())
      {
         return weaponData->rangeMax;
      }
      else
      {
         return -1.0;
      }
   }
end_script

#if beyond max range: returns time to hit if launched at max range
#if before min range: returns time to hit if launched at min range
#could return -1 for an invalid max range
#(from a launch computer or weapon struct not found)
script double TimeToHit(WsfPlatform shooter, WsfWeapon weapon, WsfTrack track)
   double range = shooter.SlantRangeTo(track);
   WsfLaunchComputer lcPtr = weapon.LaunchComputer();
   if (lcPtr.IsValid() &&
       lcPtr.IsA_TypeOf("WSF_AIR_TO_AIR_LAUNCH_COMPUTER"))
   {
      Array<double> arr = lcPtr.LookupResult(track);
      double rmax    = arr[0];
      double rmaxTOF = arr[1];
      double rmin    = arr[4];
      double rminTOF = arr[5];
      if (range >= rmax)
      {
         return rmaxTOF;
      }
      else if (range<=rmin)
      {
         return rminTOF;
      }
      else
      {
         # in between, interpolate
         double scale = (range-rmin)/(rmax-rmin);
         double tof = scale*(rmaxTOF-rminTOF) + rminTOF;
         return tof;
      }
   }
   else
   {
      struct weaponData = GetWeaponData(weapon.Type());
      if (weaponData->type == weapon.Type())
      {
         if (range > weaponData->rangeMax)
         {
            range = weaponData->rangeMax;
         }
         else if (range < weaponData->rangeMin)
         {
            range = weaponData->rangeMin;
         }
         double tof = range / weaponData->averageSpeed;
         return tof;
      }
      else
      {
         return -1.0;
      }
   }
end_script

# Calculate time for the weapon to fly a given range
#if beyond max range: returns time to hit if launched at max range
#if before min range: returns time to hit if launched at min range
#could return -1 for an invalid max range
#(from a launch computer or weapon struct not found)
script double TimeToRange(double range, WsfWeapon weapon, WsfTrack track)
   WsfLaunchComputer lcPtr = weapon.LaunchComputer();
   if (lcPtr.IsValid() &&
       lcPtr.IsA_TypeOf("WSF_AIR_TO_AIR_LAUNCH_COMPUTER"))
   {
      Array<double> arr = lcPtr.LookupResult(track);
      double rmax    = arr[0];
      double rmaxTOF = arr[1];
      double rmin    = arr[4];
      double rminTOF = arr[5];
      if (range >= rmax)
      {
         return rmaxTOF;
      }
      else if (range<=rmin)
      {
         return rminTOF;
      }
      else
      {
         # in between, interpolate
         double scale = (range-rmin)/(rmax-rmin);
         double tof = scale*(rmaxTOF-rminTOF) + rminTOF;
         return tof;
      }
   }
   else
   {
      struct weaponData = GetWeaponData(weapon.Type());
      if (weaponData->type == weapon.Type())
      {
         if (range > weaponData->rangeMax)
         {
            range = weaponData->rangeMax;
         }
         else if (range < weaponData->rangeMin)
         {
            range = weaponData->rangeMin;
         }
         double tof = range / weaponData->averageSpeed;
         return tof;
      }
      else
      {
         return -1.0;
      }
   }
end_script

script bool InRangeToFire(WsfPlatform shooter, WsfWeapon weapon, WsfTrack track, double percentRangeMax, double percentRangeMin)

   ##do not check this again
   #if ( ! WeaponCapableAvailableAgainstThreat(weapon, track) )
   #{
   #   return false;
   #}

   WsfLaunchComputer lcPtr = weapon.LaunchComputer();

   if (lcPtr.IsValid() &&
       lcPtr.IsA_TypeOf("WSF_AIR_TO_AIR_LAUNCH_COMPUTER"))
   {
      writeln_d("            using air-to-air launch computer");

      # The returned array contains:  Rmax, RmaxTOF, Rne, RneTOF, Rmin, RminTOF
      # in that order.  -1.0 means "not valid".

      Array<double> returnedValues = lcPtr.LookupResult(track);

      # Now have to consider whether we have enough information to continue with a weapon shot:
      double theRmax    = returnedValues[0]; #"Rmax";
      double theRmaxTOF = returnedValues[1]; #"RmaxTOF";
      double theRne     = returnedValues[2]; #"Rne";
      double theRneTOF  = returnedValues[3]; #"RneTOF";
      double theRmin    = returnedValues[4]; #"Rmin";
      double theRminTOF = returnedValues[5]; #"RminTOF";
      double range      = shooter.GroundRangeTo(track.CurrentLocation());

      # Check for track range less than Rmin * scaleFactor, if not, return.
      # But do not check for min range constraint at all unless we are likely to be needing it.
      if (range < 5000)
      {
         if (theRmin == -1.0)
         {
            writeln_d("    Engagement did not shoot since Rmin was not valid.");
            return false;
         }
         double RminConstraint = theRmin * percentRangeMin;
         if (range < RminConstraint)
         {
            writeln_d("    Engagement did not shoot since inside the k * Rmin constraint distance.");
            writeln_d("    Range versus Rmin constraint = ", range, ", ", RminConstraint);
            return false;
         }
      }

      # Check for track range less than Rne, if so, FORCE a weapon fire.
      bool forceWeaponFire = false;
      if (range < theRne)
      {
         writeln_d("    Engagement is forcing a weapon fire due to inside Rne.");
         writeln_d("    Range versus Rne constraint = ", range, ", ", theRne);
         return true;
      }

      if (forceWeaponFire == false)
      {
         ######################################TRY THIS######################################
         WsfPlatform plat = WsfSimulation.FindPlatform( track.TargetName() );
         if (plat.IsValid() && plat.CategoryMemberOf("fighter"))
         {
            #theRmax = theRne;
            theRmax = (theRmax + theRne)/2.0;  #for highly maneuverable fighter targets
         }
         ####################################END TRY THIS####################################
         # Check for track range less than k * Rmax, if not, return.
         if (theRmax == -1.0)
         {
            writeln_d("    Engagement did not shoot since Rmax was not valid.");
            return false;
         }
         #double RmaxConstraint = theRmax * DefaultPercentRangeMax;
         if (range > (theRmax * percentRangeMax))
         {
            writeln_d("    Engagement did not shoot since outside the k * Rmax constraint distance.");
            writeln_d("    Range versus Rmax constraint = ", range, ", ", (theRmax * percentRangeMax));
            return false;
         }
      }

      writeln_d("    Engagement meets constraints for firing a weapon (continue).");
   }
   else if (lcPtr.IsValid() &&
            lcPtr.IsA_TypeOf("WSF_ATG_LAUNCH_COMPUTER"))
   {
      writeln_d("            using air-to-ground launch computer");
      if (lcPtr.CanIntercept(track))
      {
         #intercept works, this weapon is a candidate
      }
      else
      {
         return false;
      }
   }
   else
   {
      struct weaponData = GetWeaponData(weapon.Type());

      writeln_d("            using input WeaponData struct values");
      #check our own ranges & angles --> hacky!!!
     #extern double EffectiveRange (WsfPlatform, WsfTrack);
      double effectiveRange     = EffectiveRange(shooter, track);
      double absRelativeBearing = MATH.Fabs(shooter.RelativeBearingTo( track ));

      if ((weaponData->rangeMin * percentRangeMin) > effectiveRange)
      {
         writeln_d("            target too close");
         return false;
      }
      if (absRelativeBearing > weaponData->maxFiringAngle)
      {
         writeln_d("            target firing angle too large");
         return false;
      }
      if (weaponData->rangeMax * percentRangeMax < effectiveRange)
      {
         writeln_d("            target too far away");
         return false;
      }

      double range = shooter.SlantRangeTo(track);
      #double closingSpeed = PLATFORM.ClosingSpeedOf(track);
      double relBearing = track.RelativeBearingTo(shooter);
      if (relBearing > 90.0)
      {
        if (track.Speed() > weaponData->averageSpeed)
        {
          return false;
        }
        double speedDiff = weaponData->averageSpeed - track.Speed();
        if ((range/speedDiff) > weaponData->maxTimeFlight)
        {
          return false;
        }
      }
   }
   return true;
end_script

