#include "TECADDON.h"
#include "ADDGLBL.h"
#include <string.h>

// STL
#include <vector>
#include <algorithm>
using namespace std;

AddOn_pa AddOnID;

static UniqueID_t S_lastFrameUsed = BAD_SET_VALUE;
static Boolean_t  S_probedFrameAlwaysOnTop = FALSE;

typedef enum
{
  ProbeType_TrackPosition,
  ProbeType_TrackNode,
  END_ProbeType_e,
  ProbeType_Invalid = BadEnumValue
} ProbeType_e;

static Boolean_t FrameHasTransientData(void)
{
  REQUIRE(TecUtilDataSetIsAvailable());
  REQUIRE(TecUtilFrameGetPlotType() == PlotType_Cartesian2D ||
          TecUtilFrameGetPlotType() == PlotType_Cartesian3D);

  Boolean_t Result = FALSE;

  Set_pa EnabledZones = NULL;
  TecUtilZoneGetEnabled(&EnabledZones);

  if ( EnabledZones )
    {      
      EntIndex_t Zone;
      TecUtilSetForEachMember(Zone, EnabledZones)
        {
          if ( TecUtilZoneGetStrandID(Zone) > 0 )
            {
              Result = TRUE;
              break;
            }
        }      
      TecUtilSetDealloc(&EnabledZones);      
    }

  ENSURE(VALID_BOOLEAN(Result));
  return Result;
}

static void GetSolutionTimes(Strand_t        StrandID,
                             vector<double> &SolutionTimes)
{
  REQUIRE(StrandID > 0);
  Set_pa ZoneSet = NULL;
  TecUtilZoneGetEnabled(&ZoneSet);

  SolutionTimes.clear();

  if ( ZoneSet )
    {      
      EntIndex_t Zone;     
      TecUtilSetForEachMember(Zone, ZoneSet)
        {
          Strand_t CurStrandID = TecUtilZoneGetStrandID(Zone);
          if ( CurStrandID == StrandID )
            {
              double Time = TecUtilZoneGetSolutionTime(Zone);
              SolutionTimes.push_back(Time);
            }
        }
      sort(SolutionTimes.begin(), SolutionTimes.end());
#if 0
      unique(SolutionTimes.begin(), SolutionTimes.end());
#else
      // Make *really* unique
      int ii = 0;
      vector<double> uniqueSolutionTimes;
      uniqueSolutionTimes.push_back(SolutionTimes[0]);
      for ( ii = 1; ii < (int)SolutionTimes.size(); ii++ )
        {
          double Time = SolutionTimes[ii];
          if ( Time != SolutionTimes[ii-1] )
            uniqueSolutionTimes.push_back(Time);
        }
      SolutionTimes.clear();
      for ( ii = 0; ii < (int)uniqueSolutionTimes.size(); ii++ )
        {
          SolutionTimes.push_back(uniqueSolutionTimes[ii]);
        }
#endif

      TecUtilSetDealloc(&ZoneSet);
    }
}

static Set_pa GetZonesAtSolutionTime(Strand_t StrandID,
                                     double   SolutionTime)
{
  REQUIRE(StrandID > 0);
  REQUIRE("SolutionTime can be any value");

  Set_pa ZoneSet = TecUtilSetAlloc(TRUE);
  Set_pa EnabledZones = NULL;
  TecUtilZoneGetEnabled(&EnabledZones);

  if ( EnabledZones &&
       ZoneSet )
    {      
      EntIndex_t Zone;
      TecUtilSetForEachMember(Zone, EnabledZones)
        {
          Strand_t CurStrandID = TecUtilZoneGetStrandID(Zone);
          if ( CurStrandID == StrandID )
            {
              double Time = TecUtilZoneGetSolutionTime(Zone);
              if ( Time == SolutionTime )
                TecUtilSetAddMember(ZoneSet, Zone, TRUE);
            }
        }      
      TecUtilSetDealloc(&EnabledZones);
      if ( TecUtilSetGetMemberCount(ZoneSet) == 0 )
        {
          TecUtilSetDealloc(&ZoneSet);
          ZoneSet = NULL;
        }
    }  
  ENSURE(VALID_REF_OR_NULL(ZoneSet));
  return ZoneSet;
}

#if 0 // quiet compiler since this is not used
static void ClearFrame()
{
  double X, Y, W, H;
  TecUtilFrameGetPosAndSize(&X,&Y,&W,&H);
  TecUtilFrameDeleteTop();
  TecUtilFrameCreateNew(TRUE, X,Y,W,H);
  S_lastFrameUsed = TecUtilFrameGetUniqueID();
}
#endif

static void InstallTimeSeriesPlotProbeCallback(void);

static void DumpTimeSeriesDataIntoFrame(vector<double> &TimeSeriesPlotData,
                                        int             NumVars)
{  
  REQUIRE(TimeSeriesPlotData.size() > 0);

  Boolean_t IsOk = TRUE;
  StringList_pa VarNames = TecUtilStringListAlloc();
  if ( VarNames )
    {
      TecUtilStringListAppendString(VarNames, "Solution Time");
      for ( int ii = 1; ii < NumVars; ii++ )
        {
          char *VarName;
          TecUtilVarGetName(ii, &VarName);
          CHECK(VALID_REF(VarName));
          TecUtilStringListAppendString(VarNames, VarName);
          TecUtilStringDealloc(&VarName);
        }
    }
  else
    {
      IsOk = FALSE;
    }

  if ( IsOk )
    {
      MouseButtonMode_e LastMouseMode = TecUtilMouseGetCurrentMode();
      // We probed to get here so the mouse mode should be Probe,
      // but we'll get the mouse mode just to be safe
      CHECK(LastMouseMode == Mouse_Probe);

      // Save off the UniqueID for the frame we just probed. If we want
      // to keep this frame always on top, then we use this to pop
      // the frame later.
      UniqueID_t ProbedFrameID = TecUtilFrameGetUniqueID();

      // Use the contouring variable for the dependent variable in the
      // resulting XY plot
      EntIndex_t CVar = BAD_SET_VALUE;
      CVar = TecUtilVarGetNumByAssignment('C');

      // Try popping the last frame we used to dump the data into,
      // if it isn't popped then create a new frame using Tecplot
      // defaults for frame size.
      if ( !TecUtilFramePopByUniqueID(S_lastFrameUsed) )
        {
          TecUtilFrameCreateNew(FALSE, 0,0,0,0);
          S_lastFrameUsed = TecUtilFrameGetUniqueID();          
        }
      
      if ( TecUtilDataSetIsAvailable() )
        {
          // If there's already a dataset in the frame, make sure that
          // it has the proper number of variables
          EntIndex_t NumVarsInThisDataSet = 0;
          TecUtilDataSetGetInfo(NULL, NULL, &NumVarsInThisDataSet);
          if ( NumVarsInThisDataSet > NumVars )
            {
              Set_pa VarSet = TecUtilSetAlloc(TRUE);
              while ( NumVarsInThisDataSet > NumVars )
                {
                  TecUtilSetAddMember(VarSet, NumVarsInThisDataSet--, TRUE);
                }
              TecUtilDataSetDeleteVar(VarSet);
              TecUtilSetDealloc(&VarSet);
            }
          else if ( NumVarsInThisDataSet < NumVars )
            {
              while ( NumVarsInThisDataSet < NumVars )
                {
                  TecUtilDataSetAddVar("Dummy", NULL);
                  NumVarsInThisDataSet++;
                }
            }
          for ( int ii = 0; ii < NumVars; ii++ )
            {
              char *VarName = TecUtilStringListGetString(VarNames, ii+1);
              TecUtilVarRename(ii+1, VarName);
              TecUtilStringDealloc(&VarName);
            }
        }
      else
        {
          TecUtilDataSetCreate("Time Series Plot",
                               VarNames,
                               TRUE); // ResetStyle
        }
      
      
      // Now, create the zone that contains the time series data.        
      LgIndex_t NumTimeSteps = TimeSeriesPlotData.size()/NumVars;

      ArgList_pa ArgList;
      ArgList = TecUtilArgListAlloc();
      TecUtilArgListAppendString(ArgList, SV_NAME, "Time Series Plot Zone");

      // This function call exploits functionality in Tecplot 360 R2 that allows us
      // to overwrite and existing zone.
      TecUtilArgListAppendInt(ArgList, SV_ZONE, 1);

      TecUtilArgListAppendInt(ArgList, SV_IMAX, NumTimeSteps);      

      EntIndex_t newzone;

      if ( TecUtilDataSetAddZoneX(ArgList) )
        {
           Set_pa zones_added = TecUtilSetAlloc(TRUE);
           // new zone is always last zone            
           TecUtilDataSetGetInfo(NULL, &newzone, NULL);

           // Add the data to the new zone
           for ( int CurVar = 0; CurVar < NumVars; CurVar++ )
             {
               FieldData_pa fd = TecUtilDataValueGetWritableRef(newzone, CurVar+1);
               for ( int CurTimeStep = 0; CurTimeStep < NumTimeSteps; CurTimeStep++ )
                 {
                   // Data is stored in the array by variable then by time:
                   // e.g. V1t1 V2t1, V3t1, V1t2, V2t2, V3t3
                   double Value = TimeSeriesPlotData[NumVars*CurTimeStep+CurVar];
                   TecUtilDataValueSetByRef(fd,
                                            CurTimeStep+1,
                                            Value);
                 }
             }       
       
           TecUtilSetAddMember(zones_added, newzone, TRUE);
           TecUtilStateChanged(StateChange_ZonesAdded,
                               (ArbParam_t)zones_added);
           TecUtilSetDealloc(&zones_added);
        }
      TecUtilArgListDealloc(&ArgList);

      // Switch the plot type to XY Line and setup the proper line map
      // to be active. The one we activate is the one that matches the
      // contouring variable in the probed frame.
      TecUtilFrameSetPlotType(PlotType_XYLine);
      if ( CVar >= 0 )
        {
          int NumLineMaps = TecUtilLineMapGetCount();
          EntIndex_t LineMapToActivate = BAD_SET_VALUE;
          for ( int ii = 0; ii < NumLineMaps; ii++ )
            {
              EntIndex_t YVar;
              TecUtilLineMapGetAssignment(ii+1,
		                                      NULL,  //Zone
		                                      NULL,  //XVar
		                                      &YVar,
		                                      NULL,  //XAxisNum
		                                      NULL,  //YAxisNum
		                                      NULL); //FunctionDependency
              // Add 1 to CVar because the dataset in this frame (the TimeSeries frame)
              // has an addition variable (solution time) which comes before any other variables
              if ( YVar == CVar+1 )
                {
                  LineMapToActivate = ii+1;
                  break;
                }
            }
          
          if ( LineMapToActivate != BAD_SET_VALUE )
            {
              Set_pa LineMapSet = TecUtilSetAlloc(TRUE);              
              TecUtilSetAddMember(LineMapSet, LineMapToActivate, TRUE);
              // Make sure the line map references the zone we just created.
              TecUtilLineMapSetAssignment(SV_ZONE, LineMapSet, 0.0, newzone);
              TecUtilLineMapSetActive(LineMapSet, AssignOp_Equals);
              TecUtilSetDealloc(&LineMapSet);
            }
        }
      TecUtilViewDataFit();

      // We may have changed the mouse mode by creating a frame or some other
      // action, be sure to put the mouse mode back.
      if ( TecUtilMouseGetCurrentMode() != LastMouseMode )
        TecUtilMouseSetMode(LastMouseMode);

      // If we want the Transient frame (probed frame) always on top,
      // pop it now.
      if ( S_probedFrameAlwaysOnTop )
        {
          TecUtilFramePopByUniqueID(ProbedFrameID);
          // We need to reinstall the probe callback, since it's just
          // a one shot deal.
          InstallTimeSeriesPlotProbeCallback();
        }
    }
  if ( VarNames )
    TecUtilStringListDealloc(&VarNames);
}

int CompareDouble(const double Val1,
                  const double Val2,
                  const int    Precision)
{
  const double TEN = 10.0;
  int intVal1 = TRUNC(Val1 * pow(TEN, Precision));
  int intVal2 = TRUNC(Val2 * pow(TEN, Precision));

  int Result = 0;
  if ( intVal1 > intVal2 )
    Result = 1;
  else if ( intVal1 < intVal2 )
    Result = -1;  

  return Result;
}

#define USE_SMART_PROBE_AT_POSITION_METHOD

static Boolean_t SmarterProbeAtPosition(double      XPos,
                                        double      YPos,
                                        double      ZPos,
                                        double    **VValues,
                                        EntIndex_t *CurZone,
                                        Set_pa      SourceZones,  
                                        Boolean_t   GetZoneOnly,
                                        Boolean_t   IsNearestPoint)
{
  REQUIRE("XPos, YPos, ZPos, may be any value");
  REQUIRE(VALID_REF_OR_NULL(VValues));
  REQUIRE(VALID_REF_OR_NULL(CurZone));  
  REQUIRE(VALID_REF_OR_NULL(SourceZones));
  REQUIRE(VALID_BOOLEAN(GetZoneOnly));
  REQUIRE(VALID_BOOLEAN(IsNearestPoint));
  REQUIRE(TecUtilFrameGetPlotType() == PlotType_Cartesian2D ||
          TecUtilFrameGetPlotType() == PlotType_Cartesian3D);

  EntIndex_t NumVars;
  TecUtilDataSetGetInfo((char **)NULL,
                        (EntIndex_t *)NULL,
                         &NumVars);
  double *LocalVValues = NULL;
  if ( VValues )
    *VValues = (double *)malloc(NumVars*sizeof(double));
  else
    LocalVValues = (double *)malloc(NumVars*sizeof(double));

  Boolean_t IsOk = FALSE;

  LgIndex_t    ICell, JCell, KCell;
  IJKPlanes_e  Plane;                  
  EntIndex_t   SourceZone;

  TecUtilDataLoadBegin();

  IsOk = TecUtilProbeAtPosition(XPos, YPos, ZPos,
                                &ICell,
                                &JCell,
                                &KCell,
                                &Plane,
                                CurZone == NULL ? &SourceZone : CurZone,
                                FALSE, //StartWithLocalCell
                                LocalVValues ? LocalVValues : *VValues,
                                SourceZones, // SourceZones - NULL means to check all zones
                                TecUtilFrameGetPlotType() == PlotType_Cartesian3D, // SearchVolume
                                GetZoneOnly,
                                IsNearestPoint);
  if ( !IsOk )
    {
      // If the above call failed, it's 99% because we're in 3D and doing a volume probe, but
      // due to slight round off issues, the XYZ position is slightly outside the bounds of any
      // zone. So, redo the probe as a surface probe.
      //
      // We need to then ensure that the XYZ position of this probe is within an acceptible
      // delta of the original probe. If it's not, then we need to call it a failure.
      IsOk = TecUtilProbeAtPosition(XPos, YPos, ZPos,
                                    &ICell,
                                    &JCell,
                                    &KCell,
                                    &Plane,
                                    CurZone == NULL ? &SourceZone : CurZone,
                                    FALSE, //StartWithLocalCell
                                    LocalVValues ? LocalVValues : *VValues,
                                    SourceZones,  // SourceZones - NULL means to check all zones
                                    FALSE, // SearchVolume
                                    GetZoneOnly,
                                    IsNearestPoint);
      if ( IsOk )
        {
          PlotType_e PlotType = TecUtilFrameGetPlotType();

          EntIndex_t XVarNum = TecUtilVarGetNumByAssignment('X');
          EntIndex_t YVarNum = TecUtilVarGetNumByAssignment('Y');
          EntIndex_t ZVarNum = BAD_SET_VALUE;          
          if ( PlotType == PlotType_Cartesian3D )
            ZVarNum = TecUtilVarGetNumByAssignment('Z');

          double NewXPos = TecUtilProbeFieldGetValue(XVarNum);
          double NewYPos = TecUtilProbeFieldGetValue(YVarNum);
          double NewZPos = 0;
          if ( PlotType == PlotType_Cartesian3D )
            NewZPos = TecUtilProbeFieldGetValue(ZVarNum);

          // Make sure that the values are "close enough". We should probably
          // come up with a better comparison than this, like checking the values
          // based on average cell width or something of that sort.
          IsOk = CompareDouble(NewXPos, XPos, 10) == 0 &&
                 CompareDouble(NewYPos, YPos, 10) == 0 &&
                 CompareDouble(NewZPos, ZPos, 10) == 0;
        }
    }
  TecUtilDataLoadEnd();

  if ( !IsOk )
    {
      if ( VValues )
        {
          free(*VValues);
          *VValues = NULL;
        }
    }
  if ( LocalVValues )
    free(LocalVValues);
  
  ENSURE(VALID_BOOLEAN(IsOk));
  ENSURE(IMPLICATION(IsOk && VValues, VALID_REF(*VValues)));
  return IsOk;
}


static Boolean_t ExtractDataOverTime(Strand_t    StrandID,
                                     double      XPos,
                                     double      YPos,
                                     double      ZPos,
                                     Boolean_t   GetNearestPoint,
                                     LgIndex_t   PointIndex,
                                     ProbeType_e ProbeType)
{
  REQUIRE(StrandID > 0);
  REQUIRE("XPos, YPos, ZPos can be any value");
  REQUIRE(VALID_BOOLEAN(GetNearestPoint));
  REQUIRE(VALID_ENUM(ProbeType, ProbeType_e));
  REQUIRE(IMPLICATION(ProbeType == ProbeType_TrackNode, PointIndex >= 1));
  REQUIRE(IMPLICATION(ProbeType == ProbeType_TrackNode, GetNearestPoint));
  REQUIRE(TecUtilDataSetIsAvailable());
  REQUIRE(TecUtilFrameGetPlotType() == PlotType_Cartesian2D ||
          TecUtilFrameGetPlotType() == PlotType_Cartesian3D);

  vector<double> SolutionTimes;  
  GetSolutionTimes(StrandID, SolutionTimes);

  Boolean_t IsOk        = TRUE;
  Boolean_t Interrupted = FALSE;

  if ( SolutionTimes.size() > 0 )
    {
      TecUtilProbeAtPosSequenceBeginX(NULL);
      int ii = 0;

      EntIndex_t NumVars;
      TecUtilDataSetGetInfo((char **)NULL,
                            (EntIndex_t *)NULL,
                             &NumVars);
      // Store the solution time and probed data for each solution time in a vector.
      // The vector is organized in "point" format:
      //   V1t1, V2t1, V3t1, V1t2, V2t2, V3t3...
      vector<double> TimeSeriesPlotData;

      TecUtilStatusStartPercentDone("Extracting Data Over Time",
                                    TRUE,
                                    TRUE);

      int NumSolutionTimes = SolutionTimes.size();
      for ( ii = 0; ii < NumSolutionTimes; ii++ )
        {    
          if ( TecUtilInterruptCheck() )
            {
              Interrupted = TRUE;
              break;
            }

          int PercentDone = (int)((double)(((double)ii+1)/(double)NumSolutionTimes)*100);
          TecUtilStatusCheckPercentDone(PercentDone);
                      
          Set_pa SourceZones = GetZonesAtSolutionTime(StrandID, SolutionTimes[ii]);          

          if ( SourceZones )
            {              
              char Msg[4096];
              sprintf(Msg, "Collecting Data at Time %f...", SolutionTimes[ii]);
              TecUtilStatusSetPercentDoneText(Msg);
              // We can't handle the multiple zone case yet. Probably need to
              // create a ProbeData array for each Strand.
              CHECK(TecUtilSetGetMemberCount(SourceZones) == 1);

              double *VValues;
              Boolean_t AddVarData = FALSE;
              if ( ProbeType == ProbeType_TrackNode )
                {
                  AddVarData = TRUE;
                  VValues = (double *)malloc(NumVars * sizeof(double));
                  /*
                   * TODO: Need to add checks that the PointIndex is valid for the
                   *       target zone. Also, if there are multiple zones at the
                   *       solution time, we need to have a method to consistently
                   *       select the same relative zone. Not really sure how to
                   *       handle that. Perhaps we could check IJK dimensions?
                   */
                  EntIndex_t Zone = TecUtilSetGetNextMember(SourceZones, TECUTILSETNOTMEMBER);
                  for ( int CurVar = 0; CurVar < NumVars; CurVar++ )
                    {                      
                      FieldData_pa fd = TecUtilDataValueGetReadableRef(Zone, CurVar+1);
                      VValues[CurVar] = TecUtilDataValueGetByRef(fd, PointIndex);                      
                    }
                }
              else
                {
#if defined USE_SMART_PROBE_AT_POSITION_METHOD
                  AddVarData = SmarterProbeAtPosition(XPos, YPos, ZPos,
                                                      &VValues,
                                                      NULL, //CurZone
                                                      SourceZones,  
                                                      FALSE, //GetZoneOnly
                                                      FALSE); // IsNearestPoint - FALSE because the point has already been
                                                          // determined by the original probe.
#else              
                  VValues = (double *)malloc(NumVars*sizeof(double));              
       
                  if ( VValues )
                    {
                      // Not a Nearest Point probe, so we maintain the X,Y,Z position
                      // rather than following a particular point index through space.
                      LgIndex_t    ICell, JCell, KCell;
                      IJKPlanes_e  Plane;                  
                      EntIndex_t   SourceZone;

                      TecUtilDataLoadBegin();
                      AddVarData = TecUtilProbeAtPosition(XPos, YPos, ZPos,
                                                          &ICell,
                                                          &JCell,
                                                          &KCell,
                                                          &Plane,
                                                          &SourceZone,
                                                          FALSE,
                                                          VValues,
                                                          SourceZones,
                                                          TecUtilFrameGetPlotType() == PlotType_Cartesian3D, // SearchVolume
                                                          FALSE,
                                                          FALSE); // GetNearestPoint - Set to FALSE since the point
                                                                  // has already been determined by the orginal probe.
                      TecUtilDataLoadEnd();
                    }
#endif
                }
              if ( AddVarData )
                {
                  // Adding the solution time into this array as well, so add 1 to NumVars.
                  TimeSeriesPlotData.push_back(SolutionTimes[ii]);
                  for ( int CurVar = 0; CurVar < NumVars; CurVar++ )
                    TimeSeriesPlotData.push_back(VValues[CurVar]);
                }
              TecUtilSetDealloc(&SourceZones);
              free(VValues);
            }
        }      
      TecUtilProbeAtPosSequenceEnd();

      if ( !Interrupted && TimeSeriesPlotData.size() > 0 )
        {
          // Add one to NumVars to make room for the solution time
          DumpTimeSeriesDataIntoFrame(TimeSeriesPlotData, NumVars+1);
        }
      else
        {
          IsOk = FALSE;
        }
      TecUtilStatusFinishPercentDone();
    }
  else
    {
      IsOk = FALSE;
    }

  ENSURE(VALID_BOOLEAN(IsOk));
  return IsOk;
}




static void STDCALL TimeSeriesPlotProbeCallback(Boolean_t IsNearestPoint)
{
  TecUtilLockStart(AddOnID);
  REQUIRE(TecUtilDataSetIsAvailable());
  REQUIRE(TecUtilFrameGetPlotType() == PlotType_Cartesian2D ||
          TecUtilFrameGetPlotType() == PlotType_Cartesian3D);
  REQUIRE(FrameHasTransientData());

  PlotType_e PlotType = TecUtilFrameGetPlotType();
  
  EntIndex_t XVarNum = TecUtilVarGetNumByAssignment('X');
  EntIndex_t YVarNum = TecUtilVarGetNumByAssignment('Y');
  EntIndex_t ZVarNum = BAD_SET_VALUE;
  if ( PlotType == PlotType_Cartesian3D )
    ZVarNum = TecUtilVarGetNumByAssignment('Z');

  double XPos = TecUtilProbeFieldGetValue(XVarNum);
  double YPos = TecUtilProbeFieldGetValue(YVarNum);
  double ZPos = 0;
  if ( PlotType == PlotType_Cartesian3D )
    ZPos = TecUtilProbeFieldGetValue(ZVarNum);  

  Boolean_t  IsOk = TRUE;
  EntIndex_t SourceZone;
  if ( TecUtilProbeFieldGetCZType() == CZType_FieldDataZone )
    {
      SourceZone = TecUtilProbeFieldGetZone();
    }
  else // Hit a COB. We need to get the zone we're in.
    {
      // If we hit a COB, probe again, to determine the source zone.
      Set_pa ActiveZones = NULL;
      TecUtilZoneGetActive(&ActiveZones);
      IsOk = SmarterProbeAtPosition(XPos, YPos, ZPos,
                                    NULL,
                                    &SourceZone,
                                    ActiveZones,
                                    TRUE, //GetZoneOnly
                                    IsNearestPoint);
      TecUtilSetDealloc(&ActiveZones);  
    }
  
  LgIndex_t   PointIndex = -1;
  ProbeType_e ProbeType = ProbeType_TrackPosition;
  if ( IsNearestPoint )
    {
      Boolean_t TrackNode = 
        TecUtilDialogMessageBox("Nearest point probe. Press \"Yes\" to track this node "
                                "over time. Press \"No\" to track this XY[Z] position over time.",
                                MessageBox_YesNo);
      if ( TrackNode )
        {
          ProbeType = ProbeType_TrackNode;
          PointIndex = TecUtilProbeGetPointIndex();
        }
    }

  if ( IsOk )
	  {
      Strand_t Strand = TecUtilZoneGetStrandID(SourceZone);  
      if ( Strand > 0 )
        IsOk = ExtractDataOverTime(Strand,
                                   XPos,
                                   YPos,
                                   ZPos,
                                   IsNearestPoint,
                                   PointIndex,
                                   ProbeType);
	  }
  else
	  {
      // Due to slight round off, the probe could fail. 
      if ( TecUtilFrameGetPlotType() == PlotType_Cartesian3D )
        TecUtilDialogErrMsg("Probe failed. Try a \"nearest point\" probe or try probing within the volume.");
      else
        TecUtilDialogErrMsg("Probe failed. Try a \"nearest point\" probe.");
		}



  TecUtilLockFinish(AddOnID);
}


static Boolean_t OkToCreateTimeSeriesPlot()
{
  Boolean_t Result = TecUtilDataSetIsAvailable() &&
                     (TecUtilFrameGetPlotType() == PlotType_Cartesian2D ||
                      TecUtilFrameGetPlotType() == PlotType_Cartesian3D) &&
                     FrameHasTransientData();
  ENSURE(VALID_BOOLEAN(Result));
  return Result;
}


static void InstallTimeSeriesPlotProbeCallback(void)
{  
  TecUtilLockStart(AddOnID);

  REQUIRE(OkToCreateTimeSeriesPlot());

  TecUtilProbeInstallCallback(TimeSeriesPlotProbeCallback,
                              "Click to create time series plot. Use <Ctrl>+click for nearest point probe.");
  TecUtilProbeAllowCOBs();

  TecUtilLockFinish(AddOnID);
}


static void ResetLastFrameUsed(void)
{
  S_lastFrameUsed = BAD_SET_VALUE;
}

static void STDCALL KeepProbedFrameOnTopMenuCallback(ArbParam_t ClientData)
{
  S_probedFrameAlwaysOnTop = !S_probedFrameAlwaysOnTop;
  ENSURE(VALID_BOOLEAN(S_probedFrameAlwaysOnTop));
}

static Boolean_t STDCALL GetKeepProbedFrameOnTopStateCallback(ArbParam_t ClientData)
{
  ENSURE(VALID_BOOLEAN(S_probedFrameAlwaysOnTop));
  return S_probedFrameAlwaysOnTop;
}

static Boolean_t STDCALL OkToSendTimeSeriesDataToNewFrameSensitivityCallback(ArbParam_t ClientData)
{
  TecUtilLockStart(AddOnID);
  //
  // If the S_lastFrameUsed UniqueID exists in the current layout, then
  // the user should have the ability to press them menu option, which
  // resets the S_lastFrameUsed value so a new frame is created when
  // creating a time series plot.
  //
  Boolean_t LastFrameUsedExists = FALSE;
  TecUtilFrameLightweightPopStart();
  do 
    {
      if ( !LastFrameUsedExists &&
           TecUtilFrameGetUniqueID() == S_lastFrameUsed )
        {
          LastFrameUsedExists = TRUE;
        }
    } while ( TecUtilFrameLightweightPopNext() );
  TecUtilFrameLightweightPopEnd();        

  TecUtilLockFinish(AddOnID);

  ENSURE(VALID_BOOLEAN(LastFrameUsedExists));
  return LastFrameUsedExists;
}

static void STDCALL SendTimeSeriesDataToNewFrameMenuCallback(ArbParam_t ClientData)
{
  ResetLastFrameUsed();
}

static Boolean_t STDCALL OkToCreateTimeSeriesPlotSensitivityCallback(ArbParam_t ClientData)
{
  return OkToCreateTimeSeriesPlot();
}

static void STDCALL CreateTimeSeriesPlotMenuCallback(ArbParam_t ClientData)
{
  TecUtilLockStart(AddOnID);
  REQUIRE(OkToCreateTimeSeriesPlot());

  InstallTimeSeriesPlotProbeCallback();
  
  TecUtilLockFinish(AddOnID);
}

/**
 * This function is called when the
 * $!ADDONCOMMAND macro command is
 * processed.
 */
static Boolean_t STDCALL MacroCommandCallback(char *MacroCommandString,  /* IN */
                                              char **ErrMsg)             /* OUT (only if returning FALSE) */
{

  Boolean_t IsOk = TRUE;

  /* 
   * MacroCommandString is the add-on macro command string needing processing.
   *
   * *ErrMsg is an error message string which must be allocated and set by this
   * function if and only if the return value is FALSE.
   */
  
  TecUtilLockStart(AddOnID);
  
  /*
   * TODO: Process the macro command.
   *
   * Example:
   *
   * $!ADDONCOMMAND ADDONID='Time Series Plot' COMMAND='MYCOMMAND'
   */  

  if (!IsOk)
    {
      /*
       * Some kind of error, so inform the user about it.
       */
  
      *ErrMsg = TecUtilStringAlloc(1000,"String for Error Message");
      strcpy(*ErrMsg,"Error processing macro command");
    }
  else
    {
      /* Ignore the *ErrMsg parameter */
    }

  TecUtilLockFinish(AddOnID);
  return (IsOk);
}


/**
 */
static void STDCALL StateChangeCallback(StateChange_e StateChange)
{
  
  switch (StateChange)
    {
   /*
    * This function will be called by Tecplot
    * each time a state change occurs.
    *
    *
    * NOTE:
    *
    * Some State changes also have some supplemental "state"
    * information that can be retrieved if you desire.
    * Comments in the case statements below identify these
    * state changes.  To retrieve the supplemental information
    * use the functions TecUtilStateChangeGetXXXXX. You may
    * only call these functions during the scope of this
    * callback.  Once control returns from this call the
    * supplemental information will become unaccessible.
    *
    */

      /*   State Change                Supplemental information */
      case StateChange_VarsAltered:     /* set of altered variables */
      case StateChange_VarsAdded:       /* set of added variables */
      case StateChange_ZonesDeleted:    /* set of deleted zones */
      case StateChange_ZonesAdded:      /* set of added zones */
      case StateChange_NodeMapsAltered: /* set of node maps altered */                
      case StateChange_MouseModeUpdate: /* the new mouse mode */                
      case StateChange_Style:           /* Style Parameters P1,P2,P3,P4,P5,P6 */
      case StateChange_View:            /* View action (View_e) */
      case StateChange_Streamtrace:     /* Streamtrace action (Streamtrace_e) */
      case StateChange_AuxDataAltered:  /* Name, Auxiliary Location (AuxDataLocation_e), Var/Zone/or Map Num */
      case StateChange_AuxDataAdded:    /* Name, Auxiliary Location (AuxDataLocation_e), Var/Zone/or Map Num */
      case StateChange_AuxDataDeleted:  /* Name, Auxiliary Location (AuxDataLocation_e), Var/Zone/or Map Num */
      case StateChange_VarsDeleted:     /* set of deleted variables (zero based set) */
      case StateChange_VariableLockOn:  /* Locker name, Variable Num, VarLockMode */
      case StateChange_VariableLockOff: /* Unlocker name, Variable Num */
      case StateChange_DataSetLockOn:   /* Locker name */
      case StateChange_DataSetLockOff:  /* Unlocker name */

    /* State changes which do not have any supplemental "state" information. */
      case StateChange_TecplotIsInitialized:/* Tecplot is finished initializing */
      case StateChange_FrameDeleted:        /* A frame was delete */                
      case StateChange_NewTopFrame:         /* A new frame has become the current frame */                
      case StateChange_Text:                /* One or more text elements has changed */
      case StateChange_Geom:                /* One or more geometry elements has changed */
      case StateChange_DataSetReset:        /* A new dataset has been loaded */
      case StateChange_NewLayout:           /* The current layout has been cleared and reset */
      case StateChange_CompleteReset:       /* Anything could have happened */
      case StateChange_LineMapAssignment:   /* A line mapping definition has been altered (includes zone and axis information) */
      case StateChange_ContourLevels:       /* The contour levels have been altered */
      case StateChange_ModalDialogLaunch:   /* A modal dialog has been launched */
      case StateChange_ModalDialogDismiss:  /* A modal dialog has been dismissed */
      case StateChange_QuitTecplot:         /* Tecplot is about to exit */
      case StateChange_ZoneName:            /* The name of a zone has been altered */
      case StateChange_VarName:             /* The name of a variable has been altered */
      case StateChange_LineMapName:           /* The name of an X-Y mapping has been altered */
      case StateChange_LineMapAddDeleteOrReorder: /* The set of existing X-Y mappings has been altered */
      case StateChange_ColorMap:            /* The color mapping has been altered */
      case StateChange_ContourVar:          /* The contour variable has been reassigned */
      case StateChange_NewAxisVariables:    /* The axis variables have been reassigned */
      case StateChange_PickListCleared:     /* All picked objects are unpicked */
      case StateChange_PickListGroupSelect: /* A group of objects has been added to the pick list */
      case StateChange_PickListSingleSelect:/* A single object has been added to or removed from the pick list */
      case StateChange_PickListStyle:       /* An action has been performed on all of the objects in the pick list */
      case StateChange_DataSetFileName:     /* The current data set has been saved to a file */
      case StateChange_DataSetTitle:        /* The current data set title has been changed */
      case StateChange_DrawingInterrupted:  /* The user has interrupted the drawing */
      case StateChange_ImageExported:       /* An image frame was exported */


    /* Version 9 and later Note: If you are using modeless dialogs, you should
       trap the following state changes and take appropriate
       action when print preview is launched and dismissed.

       Usually you will either disable or close your dialog
       when print preview is launched. */

      case StateChange_PrintPreviewLaunch:  /* Modeless dialogs should close or disable themselves */
      case StateChange_PrintPreviewDismiss: /* Modeless dialogs can re-launch or enable themselves */


      case StateChange_SuspendInterface:    /* Replaces StateChange_DrawGraphicsOn */
      case StateChange_UnsuspendInterface:  /* Replaces StateChange_DrawGraphicsOff */
        {
          /* TODO: Add code to handle state changes.... */
        } break;
      default: break;
    } /* end switch */
}




/**
 * When Tecplot first loads an add-on, it makes a 
 * call to initialize the add-on. This function
 * must be named InitTecAddOn, as shown below.
 */
EXPORTFROMADDON void STDCALL InitTecAddOn(void)
{


  /*
   * NOTE:  TecUtilLockOn MUST be used for InitTecAddOn instead
   *        of TecUtilLockStart because AddonID has yet to be
   *        established.  TecUtilLockOn is in effect an "anonymous"
   *        locking of Tecplot (old style).
   */

  TecUtilLockOn();

  /*
   * The function TecUtilAddOnRegister() is the
   * only function that is REQUIRED to be called from
   * the initialization function.
   *
   * The information you give Tecplot by calling
   * this function will show up in the Help/About Add-ons
   * dialog box.
   */

  /*
   * Note that if your add-on requires a specific version of Tecplot,
   * you would check for that here using TecUtilGetTecplotVersion()
   */

  AddOnID = TecUtilAddOnRegister(110,
                                 ADDON_NAME,
                                 "V"ADDON_VERSION"("TecVersionId") "ADDON_DATE,
                                 "Tecplot, Inc.");
  
  TecUtilMacroAddCommandCallback(ADDON_NAME,
                                 MacroCommandCallback);

  ArgList_pa ArgList;
  ArgList = TecUtilArgListAlloc();
  TecUtilArgListAppendFunction(ArgList, SV_CALLBACKFUNCTION,       (const void *)StateChangeCallback);
  TecUtilArgListAppendInt(ArgList,      SV_STATECHANGEMODE,        StateChangeMode_v100);
  TecUtilArgListAppendInt(ArgList,      SV_STATECHANGECALLBACKAPI, StateChangeCallbackAPI_ChangeOnly);
  TecUtilStateChangeAddCallbackX(ArgList);
  TecUtilArgListDealloc(&ArgList);
  
  Menu_pa ToolsMenu = TecUtilMenuGetStandard(StandardMenu_Tools);

  if ( ToolsMenu )
    {
      Menu_pa TimeSeriesMenu = TecUtilMenuInsertSubMenu(ToolsMenu,
                                                        MENU_POSITION_LAST,
                                                        "Time Series Plot");
      Menu_pa CurMenu = TecUtilMenuInsertOption(TimeSeriesMenu,
                                                MENU_POSITION_LAST,
                                                "Probe To Create Time Series Plot",
                                                CreateTimeSeriesPlotMenuCallback,
                                                0);
      TecUtilMenuRegisterSensitivityCallback(CurMenu,
		                                         OkToCreateTimeSeriesPlotSensitivityCallback,
		                                         0);
      CurMenu = TecUtilMenuInsertOption(TimeSeriesMenu,
                                        MENU_POSITION_LAST,
                                        "Send Time Series Data To New Frame",
                                        SendTimeSeriesDataToNewFrameMenuCallback,
                                        0);
      TecUtilMenuRegisterSensitivityCallback(CurMenu,
                                             OkToSendTimeSeriesDataToNewFrameSensitivityCallback,
                                             0);
      TecUtilMenuInsertToggle(TimeSeriesMenu,
                              MENU_POSITION_LAST,
                              "Keep Probed Frame On Top",
                              KeepProbedFrameOnTopMenuCallback,
                              0,
                              GetKeepProbedFrameOnTopStateCallback,
                              0);
    }



  /*
   * See note on TecUtilLockOn at start of this function.
   */
  TecUtilLockOff();
}

