
/**
* @file EyeTracking.cpp
*
* @brief The file contains the sources for SMI RED object
**/


/*!
Header include
*/
#include "Plugin.h"


/*!
Global variables
*/
FixationData* fixationData;
SampleData* newSample;

boost::mutex mutexFixation;
boost::mutex mutexSample;

bool bNewFixationData;
bool bNewSampleData;


/*!
Standard constructor and destructor
*/
EyeTracking::EyeTracking()
{
  m_accuracyData = new AccuracyStruct();
	m_systemInfoData = new SystemInfoStruct();
  m_calibrationData = new CalibrationStruct();
  fixationData = new FixationData();
  newSample = new SampleData();

  // Launch update loop thread
  exitRequested = false;
  loopThread = 0;
  loopThread = new boost::thread(boost::bind(&EyeTracking::GoThread, this));

  // Initialize flag for new fixation data
  bNewFixationData = false;
  bNewSampleData = false;
}

EyeTracking::~EyeTracking()
{
  // Stop update loop thread
  exitRequested = true;
  if (loopThread != 0)
    loopThread->join();

  // Close connection to SMI RED
  Disconnect();

  // Free pointers to data structures
  delete(m_accuracyData);
	delete(m_systemInfoData);
	delete(m_calibrationData);

  // Delete class instance for callbacks messages
  delete(fixationData);
  delete(newSample);
}


/*!
Set up calibration
*/
void EyeTracking::SetCalibration(int method, int visualization, int displayDevice, int speed, int autoAccept, int foregroundBrightness, 
                                 int backgroundBrightness, int targetShape, int targetSize, char* targetFilename)
{
  // Set up parameters
  m_calibrationData->method = method;
  m_calibrationData->visualization = visualization;
  m_calibrationData->displayDevice = displayDevice;
  m_calibrationData->speed = speed;
  m_calibrationData->autoAccept = autoAccept;
  m_calibrationData->foregroundBrightness = foregroundBrightness;
  m_calibrationData->backgroundBrightness = backgroundBrightness;
  m_calibrationData->targetShape = targetShape;
  m_calibrationData->targetSize = targetSize;
  strcpy_s(m_calibrationData->targetFilename, targetFilename);

  // Set up calibration
  iV_SetupCalibration(m_calibrationData);
}

bool EyeTracking::Calibrate(int validation)
{
  // Launch calibration process
  if (iV_Calibrate() != RET_SUCCESS)
  {
    MMechostr(0, "EyeTracking::Calibrate -> iV_Calibrate() FAILURE\n");
    return false;
  }
  else
  {
    MMechostr(0, "EyeTracking::Calibrate -> iV_Calibrate() OK\n");
    
    // Check if validation must be done
    if (validation == 0)
    {
      AddCallbacks();
      PostMessage(HScol, SMI_CALIBRATION_DONE_CB, (int)this, (LPARAM)NULL);
      return true;
    }

    // Run validation process
    else
    {
      if (iV_Validate() != RET_SUCCESS)
      {
        MMechostr(0, "EyeTracking::Calibrate -> iV_Validate() FAILURE\n");
        return false;
      }
      else
      {
        // If validation successful, add the callbacks to start the gaze data acquisition
        AddCallbacks();
        PostMessage(HScol, SMI_CALIBRATION_DONE_CB, (int)this, (LPARAM)NULL);
        return true;
      }
    }
  }
}


/*!
Load calibration parameters from a configuration file
*/
// TODO: Fix issue when trying to load calibration from a file (check extension)
bool EyeTracking::LoadCalibrationFromFile(char* fileName)
{
  if (iV_LoadCalibration(fileName) != RET_SUCCESS)
    return false;
  else
    return true;
}


/*!
Save calibration parameters to a configuration file
*/
// TODO: Fix issue when trying to save calibration to a file (check extension)
bool EyeTracking::SaveCalibrationToFile(char* fileName)
{
  if (iV_SaveCalibration(fileName) != RET_SUCCESS)
    return false;
  else
    return true;
}


/*!
Getters
*/
AccuracyStruct* EyeTracking::GetAccuracy()
{
  iV_GetAccuracy(m_accuracyData, 0);
  return m_accuracyData;
}

SystemInfoStruct* EyeTracking::GetSystemInfo()
{
  iV_GetSystemInfo(m_systemInfoData);
  return m_systemInfoData;
}

CalibrationStruct* EyeTracking::GetCalibration()
{
  return m_calibrationData;
}


/*!
Connection and Disconnection
*/
bool EyeTracking::Connect(char* senderIp, int senderPort, char* receiverIp, int receiverPort)
{
  if (iV_Connect(senderIp, senderPort, receiverIp, receiverPort) == RET_SUCCESS)
  {
    PostMessage(HScol, SMI_CONNECTED_CB, (int)this, (LPARAM)NULL);
    return true;
  }
  else
  {
    PostMessage(HScol, SMI_DISCONNECTED_CB, (int)this, (LPARAM)NULL);
    return false;
  }
}

bool EyeTracking::Disconnect()
{
  if (iV_Disconnect() != RET_SUCCESS)
    return false;
  else
    return true;
}


/*!
Show tracking window
*/
bool EyeTracking::ShowTrackingWindow()
{
  if (iV_ShowTrackingMonitor() != RET_SUCCESS)
    return false;
  else
    return true;
}


/*!
Create and write line to log file
*/
bool EyeTracking::CreateLogFile(char* logFileName)
{
  // Default: log everything
  if (iV_SetLogger(LOG_LEVEL_ALL_FCT, logFileName) != RET_SUCCESS)
    return false;
  else
    return true;
}

bool EyeTracking::WriteToLog(char* logFileLine)
{
  if (iV_Log(logFileLine))
    return false;
  else
    return true;
}


/*!
Callback functions
*/
int __stdcall EyeTracking::SampleCallbackFunction(SampleStruct sampleData)
{
  // Check if eyes have been detected by SMI RED
  /*if ((sampleData.leftEye.gazeX > 0.0 && sampleData.leftEye.gazeY > 0.0) || (sampleData.rightEye.gazeX > 0.0 && sampleData.rightEye.gazeY > 0.0))
  {
    // Lock mutex
    boost::mutex::scoped_lock lockSample(mutexSample, boost::defer_lock);   // defer_lock makes it initially unlocked
    MMechostr(0, "SampleCallbackFunction --> Mutex locked.\n");

    newSample->leftPupilDiameter = (float)sampleData.leftEye.diam;
    newSample->leftGazeX = (int)sampleData.leftEye.gazeX;
    newSample->leftGazeY = (int)sampleData.leftEye.gazeY;

    newSample->rightPupilDiameter = (float)sampleData.rightEye.diam;
    newSample->rightGazeX = (int)sampleData.rightEye.gazeX;
    newSample->rightGazeY = (int)sampleData.rightEye.gazeY;
    MMechostr(0, "SampleCallbackFunction --> Mutex unlocked.\n");
    bNewSampleData = true;
  }*/
  return 0;
}

int __stdcall EyeTracking::CalibrationUpdated(CalibrationPointStruct calibrationPoint)
{
  return 0;
}

int __stdcall EyeTracking::FixationUpdated(EventStruct eventDataSample)
{
  // Lock mutex
  boost::mutex::scoped_lock lockFixation(mutexFixation, boost::defer_lock);   // defer_lock makes it initially unlocked
  MMechostr(0, "FixationUpdated --> Mutex locked.\n");

  // Convert fixation duration to milliseconds
  fixationData->duration = eventDataSample.duration / 1000.0f;

  // Get position [X,Y]
  fixationData->posX = (int)eventDataSample.positionX;
  fixationData->posY = (int)eventDataSample.positionY;
  MMechostr(0, "FixationUpdated --> Mutex unlocked.\n");
  bNewFixationData = true;
  
  return 0;
}


/*!
Add callback functions for eye tracking
*/
void EyeTracking::AddCallbacks()
{
  // Called when iView X has generated a new raw data sample
  iV_SetSampleCallback(SampleCallbackFunction);

  // Called when a calibration point has changed, the calibration has been finished or aborted
  iV_SetCalibrationCallback(CalibrationUpdated);

  // Called when a real-time detected fixation has ended
  iV_SetEventCallback(FixationUpdated);
}


/*!
Handle new fixation data
*/
void EyeTracking::HandleFixationEvent()
{
  // Lock during operation
  boost::mutex::scoped_lock lockFixation(mutexFixation, boost::defer_lock);   // defer_lock makes it initially unlocked
  MMechostr(0, "GoThread --> Mutex [fixation] locked.\n");

  // Send notification to Scol handle
  PostMessage(HScol, SMI_NEW_FIXATION_CB, (int)this, (LPARAM)fixationData);
  MMechostr(0, "GoThread --> Mutex [fixation] unlocked.\n");

  // Reset flag to detect new data arrival
  bNewFixationData = false;
}


/*!
Handle new sample data
*/
void EyeTracking::HandleSampleEvent()
{
  // Lock during operation
  boost::mutex::scoped_lock lockSample(mutexSample, boost::defer_lock);   // defer_lock makes it initially unlocked
  MMechostr(0, "GoThread --> Mutex [sample] locked.\n");

  // Send notification to Scol handle
  PostMessage(HScol, SMI_NEW_SAMPLE_CB, (int)this, (LPARAM)newSample);
  MMechostr(0, "GoThread --> Mutex [sample] unlocked.\n");

  // Reset flag to detect new data arrival
  bNewFixationData = false;
}


/*!
Handle Boost threading
*/
void EyeTracking::GoThread()
{
  while(!exitRequested)
  {
    // Check arrival of new fixation data
    if (bNewFixationData)
    {
      HandleFixationEvent();
    }

    // Check arrival of new sample data
    if (bNewSampleData)
    {
      //HandleSampleEvent(); 
    }
  }
}
