//###################################################################################
//#						                    Emotiv EPOC Object									              #
//#						          Used To Handle an Emotiv EPOC headset							          #
//#						                          Author : 							                      #
//#						                      Aymeric SUTEAU									                #
//#						                       LISA - ANGERS									                #
//###################################################################################


/* INCLUDES */
#include "Plugin.h"

/* DEFINES */
#define SCALE_FACTOR 0.5

/* GLOBAL VARIABLES */
int iChargeLevel = 0, iMaxChargeLevel = 0, iNbPads = 0;
bool bHeadsetConnected = false;
bool bExpressivTrainingSucceeded = false, bCognitivTrainingSucceeded = false;
bool bNewData = false;
unsigned long uActiveActions = 0;
list<Epoc*> lEpocList;

// Gyro position
float xMinPos = -(float)(GetSystemMetrics(SM_CXSCREEN)) / 2, xMaxPos = (float)(GetSystemMetrics(SM_CXSCREEN)) / 2;
float yMinPos = -(float)(GetSystemMetrics(SM_CYSCREEN)) / 2, yMaxPos = (float)(GetSystemMetrics(SM_CYSCREEN)) / 2;
float xCurrentPos = 0.f, yCurrentPos = 0.f;

// User profiles
Profile *userProfile;
unsigned char* baseProfile = 0;
unsigned int baseProfileSize = 0;
unsigned int profileSize = 0;
unsigned char* profileBuffer = 0;
static bool fileCreated = false;
float secs = 1.0f;

// Definition of raw EEG data channels
EE_DataChannel_t targetChannelList[] = 
{
	ED_COUNTER,
	ED_AF3, ED_F7, ED_F3, ED_FC5, ED_T7, 
	ED_P7, ED_O1, ED_O2, ED_P8, ED_T8, 
	ED_FC6, ED_F4, ED_F8, ED_AF4, ED_GYROX, ED_GYROY, ED_TIMESTAMP, 
	ED_FUNC_ID, ED_FUNC_VALUE, ED_MARKER, ED_SYNC_SIGNAL
};


/*-------------------------- CONSTRUCTOR AND DESTRUCTOR ---------------------------*/
Epoc::Epoc() : Thread()
{
  // Initialize array size for sensor pads quality
  iContactQuality = new int[18];

  // Create new event handler and EmoState buffer
	eEvent = EE_EmoEngineEventCreate();
  eProfile = EE_EmoEngineEventCreate();
	eState = EE_EmoStateCreate();
  SetUserID(0);

  // Add current headset object to the global list
  lEpocList.push_back(this);

  // Safely Disconnect the headset, to be sure that it's not running anymore
  EE_EngineDisconnect();

  // Initialize Affective Suite actions
  SetEngagementBoredom(0.f);
  SetFrustration(0.f);
  SetMeditation(0.f);
  SetInstantaneousExcitement(0.f);
  SetLongTermExcitement(0.f);

  // Initialize Expressiv Suite actions
  SetEyeExpressionType(0);
  SetUpperFaceExpressionType(0);
  SetUpperFaceExpressionPower(0.f);
  SetLowerFaceExpressionType(0);
  SetLowerFaceExpressionPower(0.f);

  // Initialize Cognitiv Suite actions
  SetActionType(0);
  SetActionPower(0.f);

  // Initialize gyroscope
  SetGyroX(0.f);
  SetGyroY(0.f);

  // Initialize user profiles path name and loading / saving status
  SetUserProfileFileName("");
  SetProfileLoadingDone(true);
  SetProfileSavingDone(true);

  // Start thread
  this->start();
  this->bIsRunning = true;
  this->bStatus = false;

  // Raw EEG data acquisition
  readyToCollect = false;
}

Epoc::~Epoc()
{
  // Stop thread
  this->bIsRunning = false;
  this->bStatus = false;
  this->stop();

  // DEBUG: Release connection to EmoEngine
  //Disconnect();

  // Remove current headset object from the global list
  lEpocList.remove(this);

  // Delete dynamic arrays
  delete [] iContactQuality;
  if (baseProfile)
  {
		delete [] baseProfile, baseProfile = 0;
  }
}


/*------------------------------ GETTERS AND SETTERS ------------------------------*/
unsigned int Epoc::GetUserID()
{
  return this->uUserID;
}

void Epoc::SetUserID(unsigned int userID)
{
  this->uUserID = userID;
}


// Headset Status
float Epoc::GetSystemUpTime()
{
  return this->fSystemUpTime;
}

void Epoc::SetSystemUpTime(float systemTime)
{
  this->fSystemUpTime = systemTime;
}

int Epoc::GetDongleStatus()
{
  return this->iDongleStatus;
}

void Epoc::SetDongleStatus(int keyStatus)
{
  this->iDongleStatus = keyStatus;
}

int Epoc::GetHeadsetStatus()
{
  return this->iHeadsetStatus;
}

void Epoc::SetHeadsetStatus(int headsetStatus)
{
  this->iHeadsetStatus = headsetStatus;
}

float Epoc::GetWirelessSignal()
{
  return this->fWirelessSignal;
}

void Epoc::SetWirelessSignal(float wirelessSignal)
{
  this->fWirelessSignal = wirelessSignal * 50;    // indicator between 0 and 1
}

float Epoc::GetBatteryPower()
{
  return this->fBatteryPower;
}

void Epoc::SetBatteryPower()
{
  ES_GetBatteryChargeLevel(eState, &iChargeLevel, &iMaxChargeLevel);
  this->fBatteryPower = 100*((float)iChargeLevel / (float)iMaxChargeLevel);    // indicator between 0 and 1
}


// Affectiv Suite
float Epoc::GetEngagementBoredom()
{
  return this->fEngagementBoredom;
}

void Epoc::SetEngagementBoredom(float engagement)
{
  this->fEngagementBoredom = engagement;
}

float Epoc::GetFrustration()
{
  return this->fFrustration;
}

void Epoc::SetFrustration(float frustration)
{
  this->fFrustration = frustration;
}

float Epoc::GetMeditation()
{
  return this->fMeditation;
}

void Epoc::SetMeditation(float meditation)
{
  this->fMeditation = meditation;
}

float Epoc::GetInstantaneousExcitement()
{
  return this->fInstantaneousExcitement;
}

void Epoc::SetInstantaneousExcitement(float instantaneousExcitement)
{
  this->fInstantaneousExcitement = instantaneousExcitement;
}

float Epoc::GetLongTermExcitement()
{
  return this->fLongTermExcitement;
}

void Epoc::SetLongTermExcitement(float longTermExcitement)
{
  this->fLongTermExcitement = longTermExcitement;
}


// Expressiv Suite
unsigned int Epoc::GetEyeExpressionType()
{
  return this->uEyeExpressionType;
}

void Epoc::SetEyeExpressionType(unsigned int eyeExpressionType)
{
  this->uEyeExpressionType = eyeExpressionType;
}

unsigned int Epoc::GetUpperFaceExpressionType()
{
  return this->uUpperFaceExpressionType;
}

void Epoc::SetUpperFaceExpressionType(unsigned int upperFaceExpressionType)
{
  this->uUpperFaceExpressionType = upperFaceExpressionType;
}

float Epoc::GetUpperFaceExpressionPower()
{
  return this->fUpperFaceExpressionPower;
}

void Epoc::SetUpperFaceExpressionPower(float upperFaceExpressionPower)
{
  this->fUpperFaceExpressionPower = upperFaceExpressionPower;
}

unsigned int Epoc::GetLowerFaceExpressionType()
{
  return this->uLowerFaceExpressionType;
}

void Epoc::SetLowerFaceExpressionType(unsigned int lowerFaceExpressionType)
{
  this->uLowerFaceExpressionType = lowerFaceExpressionType;
}

float Epoc::GetLowerFaceExpressionPower()
{
  return this->fLowerFaceExpressionPower;
}

void Epoc::SetLowerFaceExpressionPower(float lowerFaceExpressionPower)
{
  this->fLowerFaceExpressionPower = lowerFaceExpressionPower;
}


// Cognitiv Suite
unsigned int Epoc::GetActionType()
{
  return this->uActionType;
}

void Epoc::SetActionType(unsigned int actionType)
{
  this->uActionType = actionType;
}

float Epoc::GetActionPower()
{
  return this->fActionPower;
}

void Epoc::SetActionPower(float actionPower)
{
  this->fActionPower = actionPower;
}


// Gyro
float Epoc::GetGyroX()
{
  return this->fXGyro;
}

void Epoc::SetGyroX(float xGyro)
{
  this->fXGyro = xGyro;
}

float Epoc::GetGyroY()
{
  return this->fYGyro;
}

void Epoc::SetGyroY(float yGyro)
{
  this->fYGyro = yGyro;
}


// User profiles
void Epoc::SetUserProfileFileName(char *path)
{
  this->cUserProfileFileName = path;
}

char * Epoc::GetUserProfileFileName()
{
  return this->cUserProfileFileName;
}

void Epoc::SetProfileLoadingDone(bool status)
{
  this->bLoadProfileDone = status;
}

bool Epoc::GetProfileLoadingDone()
{
  return this->bLoadProfileDone;
}

void Epoc::SetProfileSavingDone(bool status)
{
  this->bSaveProfileDone = status;
}

bool Epoc::GetProfileSavingDone()
{
  return this->bSaveProfileDone;
}


/*--------------------------------- OTHER METHODS ---------------------------------*/
bool Epoc::Connect()
{
  // Initialize the connection with Emotiv EmoEngine
	if (EE_EngineConnect() != EDK_OK) 
  {
    MMechostr(MSKDEBUG, "Emotiv Engine start up failed...\n");
	  return false;
  }

  // Retrieve the base profile and save it in buffer
	EE_GetBaseProfile(eProfile);
  ProfileToByteArray(eProfile, &baseProfile, &baseProfileSize);

  // Update connection status for thread
  bStatus = true;
  return true;
}

bool Epoc::Disconnect()
{
  // Terminate the connection with EmoEngine
	if (EE_EngineDisconnect() != EDK_OK)
    return false;

  // Free up memory allocated
	EE_EmoStateFree(eState);            // EmoState buffer
	EE_EmoEngineEventFree(eEvent);      // EmoEngine event handler
  EE_EmoEngineEventFree(eProfile);    // User profile event handler 

  // Update connection status for thread
  bStatus = false;
  return true;
}

void Epoc::ReadData()
{
  unsigned int userID;
	unsigned int nSamplesTaken = 0;
  int iSample;

  // New data received from Emotiv EPOC
  DataHandle hData = EE_DataCreate();

  // Initialize data buffer of sampled data
  EE_DataSetBufferSizeInSec(secs);

  // Get next event from EmoEngine
  int state = EE_EngineGetNextEvent(eEvent);

	// New event needs to be handled
	if (state == EDK_OK) 
  {
		EE_Event_t eventType = EE_EmoEngineEventGetType(eEvent);
		EE_EmoEngineEventGetUserId(eEvent, &userID);
    SetUserID(userID);

    // Eventually collect raw EEG data
    if (readyToCollect) 
    {	
			EE_DataUpdateHandle(0, hData);
      nSamplesTaken = 0;
			EE_DataGetNumberOfSample(hData, &nSamplesTaken);

			if (nSamplesTaken != 0)
      {
				double* data = new double[nSamplesTaken];
				for (int sampleIdx = 0; sampleIdx < (int)nSamplesTaken; ++sampleIdx)
        {
          iSample = 0;
					for (int i = 0; i < sizeof(targetChannelList)/sizeof(EE_DataChannel_t); i++)
          {
						EE_DataGet(hData, targetChannelList[i], data, nSamplesTaken);

            // Get only electrode channel values
            if (i >= 1 && i <= 14)
            {
              this->fEEGData[iSample] = data[sampleIdx];
              iSample++;
            }
					}
				}
        // Send new EEG raw data
        PostMessage(HScol, EPOC_RAW_EEG_CB, (int)this, (LPARAM)NULL);

        // Delete dynamic array
				delete[] data;
			}
		}

		switch (eventType) 
    {
      // --- Events related to "Headset Setup"
      case EE_UserAdded:
			  MMechostr(MSKDEBUG, "Event 1: User added...\n");
        SetDongleStatus(1);    // Headset key connected

        // Enable EEG data acquisition
        EE_DataAcquisitionEnable(userID, true);
				readyToCollect = true;
        break;

      case EE_UserRemoved:
        MMechostr(MSKDEBUG, "Event 2: User removed...\n");
        SetDongleStatus(0);    // Headset key disconnected
        break;

      // New emotional state
      case EE_EmoStateUpdated:
      {
        //MMechostr(MSKDEBUG, "Event 3: New emotional state...\n");
        EE_EmoEngineEventGetEmoState(eEvent, eState);
        
        // Retrieve new data related to Headset setup
        HandleHeadsetSetupEvent(eState);
        
        // If the headset is connected, retrieve new data related to Expressiv / Cognitiv / Affectiv suites
        if (bHeadsetConnected) 
        {
          HandleAffectiveSuiteEvent(eState);
          HandleCognitivSuiteEvent(eState);
          HandleExpressivSuiteEvent(eState);
          bNewData = true;   // New data is available (for thread)  
        }
        break;
      }

      // --- Events related to "Expressiv Suite"
      case EE_ExpressivEvent:
        MMechostr(MSKDEBUG, "Event 4: Expressiv event...\n");
        HandleExpressivTrainingEvent(eEvent);
        break;

      // --- Events related to "Cognitiv Setup"
      case EE_CognitivEvent:
        MMechostr(MSKDEBUG, "Event 5: Cognitiv event...\n");
        HandleCognitivTrainingEvent(eEvent);
        break;
    }
  }
  else if (state != EDK_NO_EVENT) 
  {
    MMechostr(MSKDEBUG, "No event !\n");
	}

  // Update headset status
  if (GetHeadsetStatus() == 1 && !bHeadsetConnected) 
  {
    bHeadsetConnected = true;
    PostMessage(HScol, EPOC_CONNECTED_CB, (int)this, (LPARAM)NULL);
  }
  else if (GetHeadsetStatus() == 0 && bHeadsetConnected) 
  {
    bHeadsetConnected = false;
    PostMessage(HScol, EPOC_DISCONNECTED_CB, (int)this, (LPARAM)NULL);
  }

  // Release data handler
  EE_DataFree(hData);
}


/*-------------------------------- EVENTS HANDLING --------------------------------*/
// Event related to "Headset Setup"
void Epoc::HandleHeadsetSetupEvent(EmoStateHandle eState)
{
  // Retrieve general information (system up time, wireless signal, headset status and battery power)
  SetSystemUpTime(ES_GetTimeFromStart(eState));
  SetWirelessSignal(static_cast<float>(ES_GetWirelessSignalStatus(eState)));
  if (GetWirelessSignal() == 0)
    SetHeadsetStatus(0);
  else
    SetHeadsetStatus(1);
  SetBatteryPower();
  
  // Handle bad wireless signal and low battery level
  // Only if neuroheadset and dongle are already connected
  if (GetHeadsetStatus() == 1 && bHeadsetConnected) 
  {
    if (GetWirelessSignal() <= 0.25)
      PostMessage(HScol, EPOC_BAD_SIGNAL_CB, (int)this, (LPARAM)NULL);
    if (GetBatteryPower() <= 0.25)
      PostMessage(HScol, EPOC_LOW_BATTERY_CB, (int)this, (LPARAM)NULL);
  }

  // Retrieve contact quality for all pads
  iNbPads = ES_GetNumContactQualityChannels(eState);
  for (int i=0; i<iNbPads; i++) 
  {
    iContactQuality[i] = static_cast<int>(ES_GetContactQuality(eState, i));
  }

  // New Headset Setup data available
  PostMessage(HScol, EPOC_HEADSET_DATA_CB, (int)this, (LPARAM)NULL);
}


// Event related to "Expressiv Suite"
void Epoc::HandleExpressivSuiteEvent(EmoStateHandle eState)
{
  // Retrieve upper and lower face current actions
	EE_ExpressivAlgo_t upperFaceType = ES_ExpressivGetUpperFaceAction(eState);
	EE_ExpressivAlgo_t lowerFaceType = ES_ExpressivGetLowerFaceAction(eState);

	float upperFaceAmp = ES_ExpressivGetUpperFaceActionPower(eState);
	float lowerFaceAmp = ES_ExpressivGetLowerFaceActionPower(eState);

  // Update eye related actions
  if (ES_ExpressivIsBlink(eState))
    SetEyeExpressionType(1);
  else if (ES_ExpressivIsLeftWink(eState))
    SetEyeExpressionType(2);
  else if (ES_ExpressivIsRightWink(eState))
    SetEyeExpressionType(4);
  else if (ES_ExpressivIsLookingLeft(eState))
    SetEyeExpressionType(8);
  else if (ES_ExpressivIsLookingRight(eState))
    SetEyeExpressionType(16);
  else if (ES_ExpressivIsLookingUp(eState))
    SetEyeExpressionType(32);
  else if (ES_ExpressivIsLookingDown(eState))
    SetEyeExpressionType(64);
  else
    SetEyeExpressionType(0);

  // Update upper face actions
	if (upperFaceAmp > 0.0)
  {
		switch (upperFaceType)
    {
			case EXP_EYEBROW:
        SetUpperFaceExpressionType(1);
        break;

			case EXP_FURROW:
        SetUpperFaceExpressionType(2);
        break;

			default:
        break;
		}
	}
  SetUpperFaceExpressionPower(100*upperFaceAmp);

  // Update lower face actions
	if (lowerFaceAmp > 0.0)
  {
		switch (lowerFaceType)
    {
			case EXP_CLENCH:
        SetLowerFaceExpressionType(1);
        break;

			case EXP_SMILE:
        SetLowerFaceExpressionType(2);
        break;

			case EXP_LAUGH:
        SetLowerFaceExpressionType(4);
        break;

			case EXP_SMIRK_LEFT:
        SetLowerFaceExpressionType(8);
        break;

			case EXP_SMIRK_RIGHT:
        SetLowerFaceExpressionType(16);
        break;

			default:
        break;
		}
	}
  SetLowerFaceExpressionPower(100*lowerFaceAmp);

  // New Expressiv data available
  PostMessage(HScol, EPOC_EXPRESSIV_DATA_CB, (int)this, (LPARAM)NULL);
}


// Event related to "Affectiv Suite"
void Epoc::HandleAffectiveSuiteEvent(EmoStateHandle eState)
{
  // Query whether the signal is too noisy for Affectiv detection to be active
  if (ES_AffectivIsActive(eState, AFF_ENGAGEMENT_BOREDOM))
    SetEngagementBoredom(100*ES_AffectivGetEngagementBoredomScore(eState));

  if (ES_AffectivIsActive(eState, AFF_FRUSTRATION))
    SetFrustration(100*ES_AffectivGetFrustrationScore(eState));

  if (ES_AffectivIsActive(eState, AFF_MEDITATION))
    SetMeditation(100*ES_AffectivGetMeditationScore(eState));

  if (ES_AffectivIsActive(eState, AFF_EXCITEMENT)) 
  {
    SetInstantaneousExcitement(100*ES_AffectivGetExcitementShortTermScore(eState));
    SetLongTermExcitement(100*ES_AffectivGetExcitementLongTermScore(eState));
  }

  // New Affectiv data available
  PostMessage(HScol, EPOC_AFFECTIV_DATA_CB, (int)this, (LPARAM)NULL);
}


// Event related to "Cognitiv Suite"
void Epoc::HandleCognitivSuiteEvent(EmoStateHandle eState)
{
  // Get information about current action (type and power)
	EE_CognitivAction_t actionType = ES_CognitivGetCurrentAction(eState);
	float actionPower = ES_CognitivGetCurrentActionPower(eState);

  // Query whether the signal is too noisy for Cognitiv detection to be active
  if (ES_CognitivIsActive(eState))
    SetActionType(actionType);
  SetActionPower(100*actionPower);

  // New Cognitiv data available
  PostMessage(HScol, EPOC_COGNITIV_DATA_CB, (int)this, (LPARAM)NULL);
}


/*------------------------------------ TRAINING -----------------------------------*/
// Events related to "Expressiv Suite" training
void Epoc::HandleExpressivTrainingEvent(EmoEngineEventHandle eEvent)
{
  int signatureAvailable = 0;
  unsigned int u_timeOut = 0;
	EE_ExpressivEvent_t eventType = EE_ExpressivEventGetType(eEvent);

	switch (eventType) 
  {
    // Training started
		case EE_ExpressivTrainingStarted:
			MMechostr(MSKDEBUG, "Expressiv training for user [%u] STARTED!\n", GetUserID());
      PostMessage(HScol, EPOC_EXPRESSIV_TRAINING_STARTED_CB, (int)this, (LPARAM)NULL);
			break;

    // Training succeeded
		case EE_ExpressivTrainingSucceeded:
      MMechostr(MSKDEBUG, "Expressiv training for user [%u] SUCCEEDED!\n", GetUserID());
      PostMessage(HScol, EPOC_EXPRESSIV_TRAINING_SUCCEEDED_CB, (int)this, (LPARAM)NULL);
      
			// Update status
      bExpressivTrainingSucceeded = true;
      break;

    // Training failed
		case EE_ExpressivTrainingFailed:
      MMechostr(MSKDEBUG, "Expressiv training for user [%u] FAILED!\n", GetUserID());
      PostMessage(HScol, EPOC_EXPRESSIV_TRAINING_FAILED_CB, (int)this, (LPARAM)NULL);
			break;

    // Training completed
		case EE_ExpressivTrainingCompleted:
		  MMechostr(MSKDEBUG, "Expressiv training for user [%u] COMPLETED!\n", GetUserID());
      PostMessage(HScol, EPOC_EXPRESSIV_TRAINING_COMPLETED_CB, (int)this, (LPARAM)NULL);

      // Switch from Universal to Trained Signature if possible
      // NOTE: At least 2 trainings (NEUTRAL + another expression) are required to be able to use trained signature
      EE_ExpressivGetTrainedSignatureAvailable(GetUserID(), &signatureAvailable);
      if (signatureAvailable)
        EE_ExpressivSetSignatureType(GetUserID(), EXP_SIG_TRAINED);
      break;

    // Training rejected
    case EE_ExpressivTrainingRejected:
      MMechostr(MSKDEBUG, "Expressiv training for user [%u] REJECTED!\n", GetUserID());
      PostMessage(HScol, EPOC_EXPRESSIV_TRAINING_REJECTED_CB, (int)this, (LPARAM)NULL);
      break;

    // Training erased
    case EE_ExpressivTrainingDataErased:
      MMechostr(MSKDEBUG, "Expressiv training for user [%u] ERASED!\n", GetUserID());
      PostMessage(HScol, EPOC_EXPRESSIV_TRAINING_ERASED_CB, (int)this, (LPARAM)NULL);
      break;

    // Not handled cases
		case EE_ExpressivTrainingReset:
    case EE_ExpressivNoEvent:
		default:
			break;
	}
}


bool Epoc::StartExpressivSuiteTraining(EE_ExpressivAlgo_t eExpression)
{
  // Set Expressiv training action
  if (EE_ExpressivSetTrainingAction(GetUserID(), eExpression) == EDK_OK) 
  {
    if (EE_ExpressivSetTrainingControl(GetUserID(), EXP_START) == EDK_OK) 
      return true;
  }
  return false;
}


bool Epoc::EraseExpressivSuiteTraining(EE_ExpressivAlgo_t eExpression)
{
  // Set Expressiv training action
  if (EE_ExpressivSetTrainingAction(GetUserID(), eExpression) == EDK_OK) 
  {
    // Erase training for a specific expression
    if (EE_ExpressivGetTrainingAction(GetUserID(), &eExpression) == EDK_OK) 
    {
      if (EE_ExpressivSetTrainingControl(GetUserID(), EXP_ERASE) == EDK_OK) 
        return true;
    }
  }
  return false;
}


// Events related to "Cognitiv Suite" training
void Epoc::HandleCognitivTrainingEvent(EmoStateHandle cognitivEvent)
{
  unsigned int u_timeOut = 0;
	EE_CognitivEvent_t eventType = EE_CognitivEventGetType(cognitivEvent);

	switch (eventType)
  {
    // Training started
		case EE_CognitivTrainingStarted:
      MMechostr(MSKDEBUG, "Cognitiv training for user [%u] STARTED!\n", GetUserID());
      PostMessage(HScol, EPOC_COGNITIV_TRAINING_STARTED_CB, (int)this, (LPARAM)NULL);
			break;

    // Training succeeded
		case EE_CognitivTrainingSucceeded:
      MMechostr(MSKDEBUG, "Cognitiv training for user [%u] SUCCEEDED!\n", GetUserID());
      PostMessage(HScol, EPOC_COGNITIV_TRAINING_SUCCEEDED_CB, (int)this, (LPARAM)NULL);

      // Update status
      bCognitivTrainingSucceeded = true;
			break;
      
    // Training failed
		case EE_CognitivTrainingFailed:
      MMechostr(MSKDEBUG, "Cognitiv training for user [%u] FAILED!\n", GetUserID());
      PostMessage(HScol, EPOC_COGNITIV_TRAINING_FAILED_CB, (int)this, (LPARAM)NULL);
			break;

    // Training completed
    case EE_CognitivTrainingCompleted:
      MMechostr(MSKDEBUG, "Cognitiv training for user [%u] COMPLETED!\n", GetUserID());
      PostMessage(HScol, EPOC_COGNITIV_TRAINING_COMPLETED_CB, (int)this, (LPARAM)NULL);
			break;

    // Training erased
    case EE_CognitivTrainingDataErased:
      MMechostr(MSKDEBUG, "Cognitiv training for user [%u] ERASED!\n", GetUserID());
      PostMessage(HScol, EPOC_COGNITIV_TRAINING_ERASED_CB, (int)this, (LPARAM)NULL);
			break;

    // Training rejected
    case EE_CognitivTrainingRejected:
      MMechostr(MSKDEBUG, "Cognitiv training for user [%u] REJECTED!\n", GetUserID());
      PostMessage(HScol, EPOC_COGNITIV_TRAINING_REJECTED_CB, (int)this, (LPARAM)NULL);
      break;

    // Not handled cases
		case EE_CognitivTrainingReset:
    case EE_CognitivAutoSamplingNeutralCompleted:
		case EE_CognitivSignatureUpdated:
		case EE_CognitivNoEvent:
		default:
			break;
	}
}


bool Epoc::StartCognitivSuiteTraining(EE_CognitivAction_t eAction)
{
  // Add current action to the list of Cognitiv active actions
  unsigned long uActiveActions = 0;
  uActiveActions |= eAction;
  EE_CognitivSetActiveActions(GetUserID(), uActiveActions);

  // Set Cognitiv training action
  if (EE_CognitivSetTrainingAction(GetUserID(), eAction) == EDK_OK) 
  {
    // Start training
    if (EE_CognitivSetTrainingControl(GetUserID(), COG_START) == EDK_OK)
      return true;
  }
  return false;
}


bool Epoc::EraseCognitivSuiteTraining(EE_CognitivAction_t eAction)
{
  // Retrieve list of active actions
  EE_CognitivGetActiveActions(GetUserID(), &uActiveActions);

  // Delete current action from the list of Cognitiv active actions
  uActiveActions &= (~eAction);

  // Update list of active actions
  EE_CognitivSetActiveActions(GetUserID(), uActiveActions);

  // Set Cognitiv training action
  if (EE_CognitivSetTrainingAction(GetUserID(), eAction) == EDK_OK)
  {
    // Erase training for a specific action
    if (EE_CognitivGetTrainingAction(GetUserID(), &eAction) == EDK_OK)
    {
      if (EE_CognitivSetTrainingControl(GetUserID(), COG_ERASE) == EDK_OK)
        return true;
    }
  }
  return false;
}


/*--------------------------------- USER PROFILES ---------------------------------*/
// Get user profile byte stream
bool Epoc::ProfileToByteArray(EmoEngineEventHandle eProfile, unsigned char** profileBuffer, unsigned int* profileSize) 
{
	if (*profileBuffer) 
		delete [] *profileBuffer, *profileBuffer = 0;
	
	// Query the size of the profile byte stream
	bool ok = (EE_GetUserProfileSize(eProfile, profileSize) == EDK_OK);
	if (ok && *profileSize > 0) 
  {
		// Copy the content of profile byte stream into local buffer
		*profileBuffer = new unsigned char[*profileSize];
		ok = (EE_GetUserProfileBytes(eProfile, *profileBuffer, *profileSize) == EDK_OK);
		if (!ok) 
			delete [] *profileBuffer, *profileBuffer = 0;
	}
	return ok;
}


// Load and Save profile (new methods)
bool Epoc::LoadProfile(char *fileName)
{
  // Create binary file for user profile
  if (!fileCreated)
  {
    userProfile = new Profile(fileName);
    fileCreated = true;
  }

  // Get binary data from file
  if (userProfile->ReadBinaryData()) {
    // Assign it to current user
    if (EE_SetUserProfile(GetUserID(), (unsigned char*)userProfile->GetFileData(), (unsigned int)userProfile->GetFileLength()) == EDK_OK) {
      MMechostr(MSKDEBUG, ">>> User profile [%s] loaded...\n", fileName);
      return true;
    }
    else {
      MMechostr(MSKDEBUG, ">>> Error when loading user profile [%s] !\n", fileName);
      return false;
    }
  }
  return false;
}

void Epoc::SaveProfile(char* fileName)
{
  EmoEngineEventHandle eProfileTemp = EE_ProfileEventCreate();
  if (EE_GetUserProfile(GetUserID(), eProfile) == EDK_OK) 
  {
	  if (ProfileToByteArray(eProfile, &profileBuffer, &profileSize))
    {
      // Write data twice : first for training data, second for missing data bytes (which are written at the end of the application when using API methods)
      MMechostr(MSKDEBUG, ">>> Profile size = %u\n", profileSize);
      userProfile->WriteBinaryData((char*)profileBuffer, profileSize);
      userProfile->WriteBinaryData((char*)profileBuffer, profileSize);

      if (profileBuffer)
			  delete [] profileBuffer, profileBuffer = 0;
	  }
    else
      MMechostr(MSKDEBUG, ">>> Couldn't get profile byte array...\n");
  }
  EE_EmoEngineEventFree(eProfileTemp);
}


/*-------------------------------------- GYRO -------------------------------------*/
bool Epoc::UpdateGyroPosition()
{
  int gx = 0, gy = 0;
  if (EE_HeadsetGetGyroDelta(GetUserID(), &gx, &gy) == EDK_OK) 
  {
    EE_HeadsetGyroRezero(GetUserID());

    // Update current position on the screen
    xCurrentPos += (float)SCALE_FACTOR * (float)gx;
    yCurrentPos += (float)SCALE_FACTOR * (float)gy;

    // Check limit thresholds
    // X axis
    if (xCurrentPos >= xMaxPos)
      xCurrentPos = xMaxPos;
    else if (xCurrentPos <= xMinPos)
      xCurrentPos = xMinPos;

    // Y axis
    if (yCurrentPos >= yMaxPos)
      yCurrentPos = yMaxPos;
    else if (yCurrentPos <= yMinPos)
      yCurrentPos = yMinPos;

    // Scale values between 0 and 1
    SetGyroX(xCurrentPos / xMaxPos);
    SetGyroY(yCurrentPos / yMaxPos);
    return true;
  }
  return false;
}


/*-------------------------------- THREAD HANDLING --------------------------------*/
void Epoc::run()
{
  try
  {
    while (bIsRunning)
    {
      // If connection has been established with EmoEngine
      if (bStatus)
      {
        // Parse data received from EmoEngine
        ReadData();

        // Accept / reject expressiv training
        if (bExpressivTrainingSucceeded)
        {
          EE_ExpressivSetTrainingControl(GetUserID(), EXP_ACCEPT);    // EXP_ACCEPT to accept training, EXP_REJECT to reject
          //EE_ExpressivSetTrainingControl(GetUserID(), EXP_REJECT);
          bExpressivTrainingSucceeded = false;
        }

        // Accept / reject cognitiv Training
        if (bCognitivTrainingSucceeded)
        {
          EE_CognitivSetTrainingControl(GetUserID(), COG_ACCEPT);     // COG_ACCEPT to accept training, COG_REJECT to reject
          //EE_CognitivSetTrainingControl(GetUserID(), COG_REJECT);
          bCognitivTrainingSucceeded = false;
        }

        // New position from gyro (if headset connected and delta position are greater than 0)
        if (bNewData && GetHeadsetStatus())
        {
          UpdateGyroPosition();
          bNewData = false;
        }

        // Load profile
        if (!GetProfileLoadingDone())
        {
          LoadProfile(GetUserProfileFileName());
          SetProfileLoadingDone(true);
        }

        // Save profile
        if (!GetProfileSavingDone())
        {
          SaveProfile(GetUserProfileFileName());
          SetProfileSavingDone(true);
        }
      }

      // Sleep thread during 1ms to wait for the next data acquisition
      this->sleep(1);
    }

    // Release connection to EmoEngine
    Disconnect();
  }
  catch (ThreadException ex)
  {
    MMechostr(MSKDEBUG, "error thread : %s\n", ex.getMessage().c_str());
  }
}
