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



processor GCI_QUANTUM_TASKER WSF_QUANTUM_TASKER_PROCESSOR

      update_interval 10.0 sec

      script_variables
         ##########################################################################
         ##  user parameters, change these at will
         ##########################################################################
         bool    mDrawClusters           = false;
         bool    mRequireTasking         = false;
         int     mMaxTaskPerformers      =  1;
         string  mClusterMethod          =  "H_TREE_MAX";         //valid: K_MEANS, H_TREE_MIN, H_TREE_MAX
         string  mDistanceFunction       =  "POSITION_VELOCITY";  //valid: POSITION_VELOCITY, POSITION_ONLY, 2D_POSITION
         double  mClusterDistanceLimit   =  50*1852;       // ~50 nautical miles
         int     mNumClusters            =  -1; //not used unless positive (over-rides distance limit)
         double  mClusterJobPriority     =  1;  //if set to zero or less, then number of cluster occupants will be the priority

         ##########################################################################
         ##  script variables, used by methods below, do not change
         ##########################################################################
         WsfDraw                 draw             = WsfDraw();
         WsfClusterManager       cManager;
      end_script_variables


      on_initialize
         cManager = WsfClusterManager.Create(); // creates a cluster manager owned by this script
         cManager.SetClusterMethod(mClusterMethod);
         cManager.SetDistanceLimit(mClusterDistanceLimit);
         if (mNumClusters > 0)
         {
            cManager.SetNumClustersToCreate(mNumClusters);
         }
         else
         {
            cManager.SetDistanceFunction(mDistanceFunction);
         }
      end_on_initialize

      ############################################################################
      ##  utility method used to draw lines around tracks in a cluster
      ##  lines are drawn according to the convex hull of the cluster members
      ############################################################################
      script void draw_cluster_hull(WsfCluster cluster)
         Array<WsfGeoPoint> pts = cluster.ConvexHull();
         if (pts.Size() <= 0)
         {
            return;
         }
         WsfGeoPoint first = pts.Get(0);
         draw.SetDuration(PROCESSOR.UpdateInterval());
         draw.SetColor(1.0,0.5,0.0);  //orange?
         draw.SetLineStyle("solid");
         draw.SetLineSize(2);
         draw.BeginPolyline();
         for (int j = 0; j < pts.Size(); j = j + 1 )
         {
            WsfGeoPoint pt = pts.Get(j);
            draw.Vertex(pt);
         }
         draw.Vertex(first);
         draw.End();
      end_script


      script Array<WsfQuantumTask> GciCommanderTaskGeneration (Array<WsfLocalTrack> TRACKS, Array<WsfAssetPerception> ASSETS )

         //create weapon tasks for enemy track groups (clusters)
         Array<WsfQuantumTask> tasks = Array<WsfQuantumTask>();
         writeln_d(PLATFORM.Name(), " executing GciCommanderTaskGeneration(), T=", TIME_NOW);

         ##########################################################################
         ##   update the cluster manager with the current master track list
         ##########################################################################
         WsfLocalTrackList localTracks = PLATFORM.MasterTrackList();
         Array<WsfTrack> trackArray = Array<WsfTrack>();
         foreach (WsfLocalTrack lt in localTracks)
         {
            if (mRequireTasking == true && TasksReceivedForTarget(lt.TrackId()).Count() <= 0)
            {
               continue;
            }
            if (lt.IsValid() && (!lt.SideValid() || lt.Side() != PLATFORM.Side()))
            {
               lt.SetBearing( lt.Heading() );  #useful for the cluster processing
               trackArray.PushBack(lt);
            }
         }
         cManager.UpdateClusters(TIME_NOW,trackArray);

         ##########################################################################
         ##  generate a task for each clusters
         ##########################################################################
         int NumClusters = cManager.Count();
         for (int i = 0; i < NumClusters; i = i + 1 )
         {
            WsfCluster cluster = cManager.Entry(i);
            int clusterId = cluster.Id();

            if (mDrawClusters == true)
            {
               draw_cluster_hull(cluster);
            }

            if( cluster.Count() > 0 )
            {
               double ClusterPriority = mClusterJobPriority;
               if (ClusterPriority <= 0)
               {
                  ClusterPriority = (double)cluster.Count();
               }
               WsfQuantumTask qt = WsfQuantumTask.Construct(ClusterPriority, "CLUSTER", cluster.Entry(0));   //include a track with the task
               qt.SetTaskType("CLUSTER");
               qt.UniqueId(cluster.Id());
               qt.SetAuxData( "clusterIndex", i );   //for evaluation below
               //TODO local tracks in the quantum task???
               qt.SetAuxData( "maxPerformers", mMaxTaskPerformers );
               qt.SetAuxData( "clusterId", cluster.Id() );
               qt.SetAuxData( "ClusterMeanPoint", WsfGeoPoint(cluster.MeanLocation()) );
               qt.SetAuxData( "ClusterBearing", cluster.Bearing() );
               qt.SetAuxData( "ClusterBearingValid", cluster.BearingValid() );
               qt.SetAuxData( "ClusterNumMembers", cluster.Count() );
               Array<string> arr = Array<string>();
               for (int i = 0; i < cluster.Count(); i = i + 1 )
               {
                  arr.PushBack(cluster.Entry(i).TargetName());
                  writeln_d("job cluster member: ", cluster.Entry(i).TargetName());
               }
               qt.SetAuxData( "ClusterMemberNames", arr );
               tasks.PushBack(qt);
               writeln_d("cluster task generated for: ", cluster.Id());
            }
         }
         return tasks;
      end_script


      script double GciCommanderEvaluation ( WsfQuantumTask TASK, WsfAssetPerception ASSET)

         //simplistic evaluation for now
         //TODO : iterate over all perceived assets that are subordinates of this lead and...
         //       evaluate each entry separately & make a composite value
         writeln_d("evaluating ", ASSET.Name(), " and task type ", TASK.TaskType());
         if (ASSET.Type() == "FLIGHT_LEAD")
         {
            WsfPlatform lead = WsfSimulation.FindPlatform(ASSET.Index());
            if (lead.IsValid())
            {
               writeln_d("calculating value");
               WsfGeoPoint subCentroid = lead.SubordinatesCentroid();   //cheating, TODO: use asset perception
               int clusterIndex = TASK.AuxDataInt("clusterIndex");
               WsfCluster cluster = cManager.Entry(clusterIndex);
               double dist = cluster.MeanLocation().GroundRangeTo(subCentroid);
               #double count = (double)lead.Subordinates().Count();

               //count available weapons
               double weaponCount = 0;
               Array<WsfAssetPerception> assets = PLATFORM.PerceivedAssets();
               WsfPlatformList subs = lead.Subordinates();
               foreach(WsfPlatform sub in subs)
               {
                  foreach (WsfAssetPerception asset in assets)
                  {
                     if (asset.Index()==sub.Index())
                     {
                        for (int j=0; j<asset.SystemCount(); j+=1)
                        {
                           if (asset.SystemKind(j) == "weapon")
                           {
                              weaponCount += asset.SystemQuantityRemaining(j);
                           }
                        }
                        break;
                     }
                  }
               }
               writeln_d("T=", TIME_NOW, ", ", lead.Name(), " evaluation: ", (weaponCount/dist));
               #return (count/dist);
               return (weaponCount/dist);
            }
         }
         return 0.0;
      end_script

     #show_task_messages
      script_debug_writes    off
      update_interval        10.0 sec
      reallocation_strategy  dynamic
      generator              custom          GciCommanderTaskGeneration
      evaluator              custom          GciCommanderEvaluation
      allocator              optimal_profit
     #allocator_extra_tasks  optimal_profit
     #allocator              greedy_isolated
     #allocator_extra_tasks  greedy_isolated
end_processor
