# ****************************************************************************
# 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.
# ****************************************************************************


behavior pincer_fsm

   script_debug_writes off

   script_variables
      //debug parameters
      bool mDrawSteering = false;

      //parameters for how to initially fly pincer (1st phase, separation)
      string mDirection = "none";
      double mPincerSpeed = 500 * MATH.MPS_PER_NMPH();   //400 kts
      double mPincerOffsetAngle = 70.0;                  //degrees

      //parameters for determining if to chase and how to do so (2nd phase, option A)
      double mEnemyFOVHalfAngle = 50.0;                           //degrees
      double mChaseAngle = 75.0;                                  //degrees
      double mChaseSpeedlnsideFOV = 600 * MATH.MPS_PER_NMPH();    //600 kts
      double mChaseSpeedOutsideFOV = 1200 * MATH.MPS_PER_NMPH();  //1200 kts

      //parameters for determining if to drag (run) and how to do so (2nd phase, option B)
      double mRunDistance = 130 * 1852;               //130nm (meters)
      double mRunAngleLimit = 15.0;                   //degrees
      double mDragSpeed = 800 * MATH.MPS_PER_NMPH();  //800 kts
      double mDragAddedAngle = -1.0;                  //degrees

      // bidding parameters (similar to pursue-target parameters)
      double cMIN_JOB_BID                    =  -MATH.DOUBLE_MAX();
#      double cWEIGHT_CURRENT_TARGET          =     10.0;
#      double cWEIGHT_CLOSING_SPEED_OF        =      0.5; #1.0;
#      double cWEIGHT_SLANT_RANGE_TO          =     -4.0; #-3.0;
#      double cWEIGHT_FUEL                    =      2.0;
#      double cWEIGHT_WEAPONS_IN_FLIGHT       =      0.0;
#      double cWEIGHT_ANY_WEAPONS_ACTIVE      =     20.0;
#      double cWEIGHT_THREAT_WEAPONS_ENVELOPE =  10000.0;
#      double cBASE_SLANT_RANGE_CONSTANT      = 600000.0; //over 300 nm away [How is this used???]
#      double cBASE_CLOSING_SPEED_CONSTANT    =   1050.0; //over 2000 kts closing speed
      double cMAX_SLANT_RANGE                    = 1000000.0;  #over 539 nm away, target ranges beyond this are considered unfavorable
      double cWEIGHT_SLANT_RANGE_TO              =       1.0;
      double cMIN_CLOSING_SPEED                  =   -1050.0;  #threats running away (negative closing) faster are considered unfavorable
      double cWEIGHT_CLOSING_SPEED               =       1.0;  #scale for how closing speed translates to distance
      double cWEIGHT_FUEL                        =       0.0;  #try a value of 2.0 if you care about fuel
      double cWEIGHT_MY_WEAPONS_ACTIVE           =       0.0;  #changes bid if your own weapons are active on target
      double cWEIGHT_PEERS_WEAPONS_ACTIVE        =       0.0;  #changes bid if your peers weapons are active on target
      double cWEIGHT_THREAT_WEAPONS_ENVELOPE     =   10000.0;  #uses the global "mWeaponsEnvelope" array, try value of 10000 if you care about that
      //careful with these two parameters, they are stateful
      double cWEIGHT_CURRENT_TARGET              =       0.0;   #changes bid if you are currently targeting the threat

      //util var, not for user edit
      WsfGeoPoint mPincerPoint;
      WsfDraw     mDraw         = WsfDraw();

      WsfRIPRJob  mCurrentJob;
      WsfTrack    mWorstTrack;
      double      mWorstDist = MATH.DOUBLE_MAX();
      double      mWorstFOV  = MATH.DOUBLE_MAX();
   end_script_variables


   script double GetPincerDragOffset(WsfTrack aTgt)
      if (aTgt.Speed() >= PLATFORM.Speed())
      {
         return 180.0;
      }
      else
      {
         return (180 - MATH.ACos(aTgt.Speed()/PLATFORM.Speed()));
      }
   end_script

   precondition

      if (!PROCESSOR.IsA_TypeOf("WSF_RIPR_PROCESSOR"))
      {
         return Failure("behavior not attached to a RIPR processor!");
      }   //   ((WsfRIPRProcessor)PROCESSOR)

      #writeln_d("precondition pincer");
      if (((WsfRIPRProcessor)PROCESSOR).GetRIPRCommanderProcessor().IsValid())
      {
         mCurrentJob = ((WsfRIPRProcessor)PROCESSOR).GetRIPRCommanderProcessor().GetJobFor(TIME_NOW, ((WsfRIPRProcessor)PROCESSOR));
         if (mCurrentJob.IsValid())
         {
            // If we're supposed to fly towards a point, go for it!
            if (mCurrentJob.Name() == "pincer")
            {
               mPincerPoint = (WsfGeoPoint)mCurrentJob.GetData("ZonePoint");
               mDirection = (string)mCurrentJob.GetData(PLATFORM.Name());
               if (mPincerPoint.IsValid())
               {
                  return true;
               }
               else
               {
                  writeln_d(PLATFORM.Name(), " pincer point not valid!");
                  return Failure("pincer job did not have valid point");
               }
            }
            else
            {
               writeln_d(PLATFORM.Name(), " job not a pincer job: ", mCurrentJob.Name());
            }
         }
         else
         {
            writeln_d(PLATFORM.Name(), " job not valid!");
         }
      }
      else
      {
         writeln_d(PLATFORM.Name(), " commander not found!");
         return Failure("no commander found");
      }
      return Failure("current job not a valid pincer job");
   end_precondition


   ###  FSM states are evaluated after "execute"
   execute
      writeln_d(PLATFORM.Name(), " executing pincer!, T=", TIME_NOW);
      //provide for another channel for pursue-target jobs
      if (((WsfRIPRProcessor)PROCESSOR).NumJobChannels() <= 1)
      {
         ((WsfRIPRProcessor)PROCESSOR).SetNumJobChannels(2);
      }
      ((WsfRIPRProcessor)PROCESSOR).ClearTarget();

      //save off the closest opponent track
      mWorstDist = MATH.DOUBLE_MAX();
      mWorstFOV = MATH.DOUBLE_MAX();
      Array<string> targetNames = (Array<string>)mCurrentJob.GetData("ZoneThreatNameArray");
      foreach(string tgtName in targetNames)
      {
         WsfTrack tgt = GetTrackByName(PLATFORM, tgtName);
         if (tgt.IsValid() && tgt.LocationValid())
         {
            double attackRange = tgt.GroundRangeTo(PLATFORM);
            if (attackRange < mWorstDist)
            {
               //run away, drag threats out
               mWorstDist = attackRange;
               mWorstTrack = tgt;
            }
            double fov = MATH.Fabs(tgt.RelativeBearingTo(PLATFORM));
            if (fov < mWorstFOV)
            {
               mWorstFOV = fov;
            }
         }
      }

#      if (mDrawSteering == true)
#      {
#         WsfGeoPoint pt = PLATFORM.Location();
#         pt.Extrapolate(heading, 185200);
#         mDraw.SetLayer("behavior_pincer");
#         mDraw.SetDuration(((WsfRIPRProcessor)PROCESSOR).UpdateInterval());
#         mDraw.SetColor(0.0, 1.0, 0.75); //green-blue
#         mDraw.SetLineSize(1);
#         mDraw.BeginLines();
#            mDraw.Vertex(PLATFORM.Location());
#            mDraw.Vertex(pt);
#            mDraw.Vertex(PLATFORM.Location());
#            mDraw.Vertex(mPincerPoint);
#         mDraw.End();
#      }
   end_execute


   script bool ShouldChase()
      int allOnCap = (int)mCurrentJob.GetData("all_on_cap");
      if (allOnCap > 0)
      {
         return false;
      }
      int other_dragging_count = 0;
      if (mDirection == "right")
      {
         other_dragging_count = (int)mCurrentJob.GetData("left_drag");
      }
      else if (mDirection == "left")
      {
         other_dragging_count = (int)mCurrentJob.GetData("right_drag");
      }
      double diff = 0;
      if (mWorstTrack.IsValid() && mWorstTrack.HeadingValid())
      {
         double b1 = mWorstTrack.Heading();
         double b2 = mWorstTrack.TrueBearingTo(PLATFORM.Location());
         diff = MATH.NormalizeAngleMinus180_180(b2 - b1);
      }
      if (other_dragging_count > 0 && MATH.Fabs(diff) > mEnemyFOVHalfAngle)
      {
         //the other pincer group is being engaged, turn in now
         writeln_d(PLATFORM.Name(), " chasing because other pair is dragging");
         return true;
      }
      else if ((mDirection == "right" && diff < -mChaseAngle) ||
               (mDirection == "left"  && diff >  mChaseAngle)   )
      {
         //we have already flanked, so now we can chase (regardless of other side dragging or not)
         writeln_d(PLATFORM.Name(), " chasing because past chase angle");
         return true;
      }
      return false;
   end_script


  #show_state_transitions
   #first default state, start here and go back to here if confused
   state SEPARATE

      next_state DRAG
         int allOnCap = (int)mCurrentJob.GetData("all_on_cap");
         if (  (allOnCap <= 0) &&
               (mWorstDist < mRunDistance) &&
               (mWorstTrack.IsValid()) &&
               (mWorstTrack.HeadingValid()) &&
               (MATH.Fabs(mWorstTrack.TrueBearingTo(PLATFORM)-mWorstTrack.Heading()) < mRunAngleLimit))
         {
            return true;
         }
         return false;
      end_next_state

      next_state CHASE
         return ShouldChase();
      end_next_state

      next_state SEPARATE
         double separate_angle = 0;
         if (mDirection == "left")
         {
            separate_angle = -mPincerOffsetAngle;
         }
         else if (mDirection == "right")
         {
            separate_angle = mPincerOffsetAngle;
         }
         double heading = MATH.NormalizeAngle0_360(PLATFORM.TrueBearingTo(mPincerPoint) + separate_angle);
         PLATFORM.GoToSpeed(mPincerSpeed);
         PLATFORM.TurnToHeading(heading);
         return true;
      end_next_state
   end_state


   state DRAG
      next_state SEPARATE
         if ( (!mWorstTrack.IsValid()) ||
              (mWorstDist > (mRunDistance*1.1)) ||
              (!mWorstTrack.HeadingValid()) ||
              (MATH.Fabs(mWorstTrack.TrueBearingTo(PLATFORM)-mWorstTrack.Heading()) > (mRunAngleLimit*1.1)))
         {
            return true;
         }
         return false;
      end_next_state

      next_state DRAG
         writeln_d("dragging!");
         //try to maintain distance
         PLATFORM.GoToSpeed(mDragSpeed);
         double dragOffset = GetPincerDragOffset(mWorstTrack);
         double heading;
         if (mDirection == "right")
         {
            if (mDragAddedAngle > 0)
            {
               heading = MATH.NormalizeAngle0_360(heading + mDragAddedAngle);
            }
            else
            {
               heading = MATH.NormalizeAngle0_360(PLATFORM.TrueBearingTo(mPincerPoint) + dragOffset);
            }
         }
         else if (mDirection == "left")
         {
            if (mDragAddedAngle > 0)
            {
               heading = MATH.NormalizeAngle0_360(heading - mDragAddedAngle);
            }
            else
            {
               heading = MATH.NormalizeAngle0_360(PLATFORM.TrueBearingTo(mPincerPoint) - dragOffset);
            }
         }
         PLATFORM.TurnToHeading(heading);
         return true;
      end_next_state

      on_entry
         if (mDirection == "right")
         {
            int count = (int)mCurrentJob.GetData("right_drag");
            mCurrentJob.SetData("right_drag",count+1);
            writeln_d(PLATFORM.Name(), " increased right_drag to: ", count+1);
         }
         else if (mDirection == "left")
         {
            int count = (int)mCurrentJob.GetData("left_drag");
            mCurrentJob.SetData("left_drag",count+1);
            writeln_d(PLATFORM.Name(), " increased left_drag to: ", count+1);
         }
      end_on_entry

      on_exit
         if (mDirection == "right")
         {
            int count = (int)mCurrentJob.GetData("right_drag");
            mCurrentJob.SetData("right_drag",count-1);
            writeln_d(PLATFORM.Name(), " decreased right_drag to: ", count-1);
         }
         else if (mDirection == "left")
         {
            int count = (int)mCurrentJob.GetData("left_drag");
            mCurrentJob.SetData("left_drag",count -1);
            writeln_d(PLATFORM.Name(), " decreased left_drag to: ", count-1);
         }
      end_on_exit
   end_state

   state CHASE

      next_state SEPARATE
         return ! ShouldChase();
      end_next_state

      next_state CHASE
         writeln_d("chasing!");
         if (mWorstFOV <= mEnemyFOVHalfAngle)
         {
            PLATFORM.GoToSpeed(mChaseSpeedlnsideFOV);
         }
         else
         {
            PLATFORM.GoToSpeed(mChaseSpeedOutsideFOV);
         }
         double heading = 0;
         if (mWorstTrack.IsValid())
         {
            heading = PLATFORM.TrueBearingTo(mWorstTrack);
            ((WsfRIPRProcessor)PROCESSOR).SetTarget(mWorstTrack);
         }
         else
         {
            heading = PLATFORM.TrueBearingTo((WsfGeoPoint)mCurrentJob.GetData("ZonePoint"));
         }
         PLATFORM.TurnToHeading(heading);
         return true;
      end_next_state
   end_state

end_behavior
