// ============================================================================
//
// = CONTEXT
//    TANGO Project - NovelecProtocol Support Library
//
// = FILENAME
//    NovelecProtocol.cpp
//
// = AUTHOR
//    X. Elattaoui
//
// ============================================================================

// ============================================================================
// DEPENDENCIES
// ============================================================================
#include <iostream>
#include <sstream>
#include <string>
#include <Xstring.h>
#include "NovelecProtocol.h"
#include "TangoSerialLink.h"

//- commands numbers
static const short	STATUS_CMD_NUM	= 1;
static const short	ERRORS_CMD_NUM	= 2;
static const short	MODE_CMD_NUM	= 3;
static const short	ELECTROTYPE_CMD_NUM	= 4;
static const short	POLARITY_CMD_NUM	= 5;
static const short	FREQUENCY_CMD_NUM	= 6;
static const short	GAIN_CMD_NUM		= 7;
static const short	RANGE_CMD_NUM		= 8;
static const short	RESPONSE_LGTH_CMD_NUM = 17;
static const short	CLEAR_ERRORS_CMD_NUM  = 18;
//- the specific new line character
static const string	END_OF_LINE = "\r\n";
//- modes allowed
static const std::string mode_str[]		= {"ERR : UNKNOWN MODE","ZERO V/F","OFFSET","LEAKAGE","TEST","MEASURE"};
static const std::string range_str[5][8]= {	{"1e-11AcC","3e-11AcC","1e-10AcC","3e-10AcC", "OUT OF RANGE","OUT OF RANGE","OUT OF RANGE","OUT OF RANGE"},
											{"1e-10AcC","3e-10AcC","1e-9AcC","3e-9AcC","1e-8AcC","3e-8AcC","1e-7AcC","3e-7AcC"},
											{"1e-8AcC","3e-8AcC","1e-7AcC","3e-7AcC","1e-6AcC","3e-6AcC","1e-5AcC","3e-5AcC"},
											{"1000MOhm","300MOhm","100MOhm","30MOhm","OUT OF RANGE","OUT OF RANGE","OUT OF RANGE","OUT OF RANGE"},
											{"1000KOhm","300KOhm","100KOhm","30KOhm","OUT OF RANGE","OUT OF RANGE","OUT OF RANGE","OUT OF RANGE",}
											};
static const std::string frequency_str[]= {"3 Hz","10 Hz","100 Hz","1000 Hz"};
static const std::string gain_str[]		= {"1","10","100"};


// ============================================================================
// NovelecProtocol::NovelecProtocol
// ============================================================================
NovelecProtocol::NovelecProtocol (std::string& serial_device_name, unsigned short devAddress, unsigned short novelecType)
:	ElectrometerProtocol(),
	_devAdd(devAddress),
	_novType(novelecType)
{
  _commDevName = serial_device_name;
	_rangeParameterNum	= _novType + 2;
	_function = "Not updated";
	_is_measure_mode_on = false;
	_is_explicite_resp_enabled = false;
}

// ============================================================================
// NovelecProtocol::~NovelecProtocol
// ============================================================================
NovelecProtocol::~NovelecProtocol (void)
{
//	std::cout << "NovelecProtocol::~NovelecProtocol <-" << std::endl;

//	std::cout << "NovelecProtocol::~NovelecProtocol ->" << std::endl;
}

// ============================================================================
// NovelecProtocol::build_communicationLink
// ============================================================================
bool NovelecProtocol::build_communicationLink()
{
  if (_commDevName.empty())
    return false;

  _communication_link = new TangoSerialLink (_commDevName);

  if (!_communication_link)
    return false;

  return true;
}

// ============================================================================
// NovelecProtocol::NovelecProtocol
// ============================================================================
void NovelecProtocol::init_MCCE2_for_communication(void)
{
std::stringstream explicite_resp;
std::string tmp("no data");

	//- CMD to enable PROG cmds
	this->switch_MCCE2_OFF();  //- this command is now called in the MCCE2 device !

	//- Clear error registers
	this->clear_registers();

	//- send cmd to have an explicite response :
	explicite_resp << _devAdd << " PROG " << RESPONSE_LGTH_CMD_NUM << " 1" << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(explicite_resp.str());

	//- check only the command response
	check_command(tmp);
	
	//- if no error
	_is_explicite_resp_enabled = true;
}

// ============================================================================
// NovelecProtocol::switch_MCCE2_ON
// ============================================================================
void NovelecProtocol::switch_MCCE2_ON (void) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");

	//- send cmd to have a explicite response :
	cmd_to_send << _devAdd << " MEASURE 1 " << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	
  //- check only the command response
	check_command(tmp);

	_is_measure_mode_on = true;
}

// ============================================================================
// NovelecProtocol::switch_MCCE2_OFF
// ============================================================================
void NovelecProtocol::switch_MCCE2_OFF (void) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");

	//- send cmd to have a explicite response :
	cmd_to_send << _devAdd << " MEASURE 0 " << END_OF_LINE << std::endl;

	tmp = _communication_link->write_read(cmd_to_send.str());

  //- check only the command response
	check_command(tmp);

  _is_measure_mode_on = false;
}

// ============================================================================
// NovelecProtocol::get_mode
// ============================================================================
std::string NovelecProtocol::get_mode (void) 
{
std::stringstream cmd_to_send;
std::string argout("no data");
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send command to Novelec device
	cmd_to_send << _devAdd << " READ " << MODE_CMD_NUM << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());

	//- check and extract the response
	argout = check_and_extract_data(tmp, MODE_CMD_NUM);

	//- check what is the response mode type
	if(	_is_explicite_resp_enabled == false )
	{
		short idx	= XString<short>::convertFromString(argout);
		_function	= mode_str[idx];
	}
	else
		_function	= argout;

	return _function;
}

// ============================================================================
// NovelecProtocol::get_value
// ============================================================================
std::string NovelecProtocol::get_value (void) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");
std::string value("-1");
short cmd_num = 13;				//- default value for DEBUG !!!

	//- first get mode
	get_mode(); 
	if( (_function.find("OFFSET") != std::string::npos) || (_function.find("V1") != std::string::npos) )
	{
		cmd_num = 12;
	}
	else
		if( (_function.find("LEAKAGE") != std::string::npos) || (_function.find("V2") != std::string::npos) )
			cmd_num = 11;
		else
			std::cout << "\t\t***WARN : Current mode : \"" << get_mode() << "\" has no value available." << std::endl;

	cmd_to_send << _devAdd << " READ " << cmd_num << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check only the command response
	value = check_and_extract_data(tmp, cmd_num);

	return value;
}

// ============================================================================
// NovelecProtocol::unable_zeroVF_func
// ============================================================================
void NovelecProtocol::unable_zeroVF_func (void) 
{
std::stringstream cmd_to_send;
std::string cmdNumber(" 1");			//- PROG 1 -> = PROG FUNCTION
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

  //- send cmd to have a explicite response :
	cmd_to_send << _devAdd << " PROG 1 " << cmdNumber << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check only the command response
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::unable_offset_zeroV1_func
// ============================================================================
void NovelecProtocol::unable_offset_zeroV1_func (void) 
{
std::stringstream cmd_to_send;
std::string cmdNumber(" 2");			//- PROG 1 -> = PROG FUNCTION
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send cmd to have a explicite response :
	cmd_to_send << _devAdd << " PROG 1 " << cmdNumber << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check only the command response
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::unable_leakage_zeroV2_func
// ============================================================================
void NovelecProtocol::unable_leakage_zeroV2_func (void) 
{
std::stringstream cmd_to_send;
std::string cmdNumber(" 3");			//- PROG 1 -> = PROG FUNCTION
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send cmd to have a explicite response :
	cmd_to_send << _devAdd << " PROG 1 " << cmdNumber << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check only the command response
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::unable_test_func
// ============================================================================
void NovelecProtocol::unable_test_func (void) 
{
std::stringstream cmd_to_send;
std::string cmdNumber(" 4");			//- PROG 1 -> = PROG FUNCTION
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send cmd to have a explicite response :
	cmd_to_send << _devAdd << " PROG 1 " << cmdNumber << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check only the command response
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::unable_measure_func
// ============================================================================
void NovelecProtocol::unable_measure_func (void) 
{
std::stringstream cmd_to_send;
std::string cmdNumber(" 5");			//- PROG 1 -> = PROG FUNCTION
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send cmd to have a explicite response :
	cmd_to_send << _devAdd << " PROG 1 " << cmdNumber << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check only the command response
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::get_range
// ============================================================================
std::string NovelecProtocol::get_range (void) 
{
std::stringstream cmd_to_send;
std::string argout("no data");
std::string tmp("no data");

	//- send command to Novelec device
	cmd_to_send << _devAdd << " READ " << RANGE_CMD_NUM << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check and extract the response
	argout = check_and_extract_data(tmp, RANGE_CMD_NUM);

	//- check what is the response mode type
	short idx = -1;
	if(	_is_explicite_resp_enabled == false )
	{
		idx	= XString<short>::convertFromString(argout);
		argout		= range_str[_novType-1][idx];
	}

	return argout;
}

// ============================================================================
// NovelecProtocol::set_range
// ============================================================================
void NovelecProtocol::set_range (std::string value) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send command to Novelec device
	cmd_to_send << _devAdd << " PROG " << _rangeParameterNum << " " << value << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- the novelec device send ACK/NAK response after a received command
	check_command(tmp);

}

// ============================================================================
// NovelecProtocol::get_polarity
// ============================================================================
std::string NovelecProtocol::get_polarity (void) 
{
std::stringstream cmd_to_send;
std::string argout("no data");
std::string tmp("no data");

	//- send command to Novelec device
	cmd_to_send << _devAdd << " READ " << POLARITY_CMD_NUM << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check and extract the response
	argout = check_and_extract_data(tmp, POLARITY_CMD_NUM);

	//- check what is the response mode type
	if(	_is_explicite_resp_enabled == false )
	{
		short idx	= XString<short>::convertFromString(argout);
		if(idx < 0)
			argout = "negative";
		else
			argout = "positive";
	}

	return argout;
}

// ============================================================================
// NovelecProtocol::set_polarity
// ============================================================================
void NovelecProtocol::set_polarity (std::string newPolarity) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send command to Novelec device
	cmd_to_send << _devAdd << " PROG 2" << " " << newPolarity << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- the novelec device send ACK/NAK response after a received command
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::get_frequency
// ============================================================================
std::string NovelecProtocol::get_frequency (void) 
{
std::stringstream cmd_to_send;
std::string argout("no data");
std::string tmp("");

	//- send command to Novelec device
	cmd_to_send << _devAdd << " READ " << FREQUENCY_CMD_NUM << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	
	//- check and extract the response
	argout = check_and_extract_data(tmp, FREQUENCY_CMD_NUM);

	//- check what is the response mode type
	if(	_is_explicite_resp_enabled == false )
	{
		short idx	= XString<short>::convertFromString(argout);
		argout		= frequency_str[idx];
	}

	return argout;
}

// ============================================================================
// NovelecProtocol::set_frequency
// ============================================================================
void NovelecProtocol::set_frequency (std::string newFrequency) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send command to Novelec device
	cmd_to_send << _devAdd << " PROG 9" << " " << newFrequency << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- the novelec device send ACK/NAK response after a received command
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::get_gain
// ============================================================================
std::string NovelecProtocol::get_gain (void) 
{
std::stringstream cmd_to_send;
std::string argout("no data");
std::string tmp("no data");

	//- send command to Novelec device
	cmd_to_send << _devAdd << " READ " << GAIN_CMD_NUM << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check and extract the response
	argout = check_and_extract_data(tmp, GAIN_CMD_NUM);

	//- check what is the response mode type
	if(	_is_explicite_resp_enabled == false )
	{
		short idx	= XString<short>::convertFromString(argout);
		argout		= gain_str[idx];
	}
	else
		//- erase wildcard char : response received is as this "*10xx"	
		argout		= argout.erase(argout.find('*'),1);

	return argout;
}

// ============================================================================
// NovelecProtocol::set_gain
// ============================================================================
void NovelecProtocol::set_gain (std::string newGain) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");

  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send command to Novelec device
	cmd_to_send << _devAdd << " PROG 8" << " " << newGain << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());

	//- the novelec device send ACK/NAK response after a received command
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::get_errors
// ============================================================================
std::string NovelecProtocol::get_errors (void) 
{
std::stringstream cmd_to_send;
std::string argout("no data");
std::string tmp("no data");

	//- send command to Novelec device
	cmd_to_send << _devAdd << " READ " << ERRORS_CMD_NUM << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check and extract the response
	argout = check_and_extract_data(tmp, ERRORS_CMD_NUM);

	return argout;
}

// ============================================================================
// NovelecProtocol::get_electrotype
// ============================================================================
std::string NovelecProtocol::get_electrotype (void) 
{
std::stringstream cmd_to_send;
std::string argout("no data");
std::string tmp("no data");

	//- send command to Novelec device
	cmd_to_send << _devAdd << " READ " << ELECTROTYPE_CMD_NUM << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check and extract the response
	argout = check_and_extract_data(tmp, ELECTROTYPE_CMD_NUM);

	//- check what is the response mode type
	if(	_is_explicite_resp_enabled == true )
		argout = argout.substr(argout.find(".")+1);

	return argout;
}

// ============================================================================
// NovelecProtocol::reset
// ============================================================================
void NovelecProtocol::reset (void) 
{
std::stringstream cmd_to_send;
	
	//- send command
	cmd_to_send << _devAdd << " RESET" << END_OF_LINE << std::endl;
	_communication_link->write(cmd_to_send.str());
	//- there is no response from the device !!!
}

// ============================================================================
// NovelecProtocol::local
// ============================================================================
void NovelecProtocol::local (void) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");
	
  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send command
	cmd_to_send << _devAdd << " LOCAL" << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	
	//- check the response
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::remote
// ============================================================================
void NovelecProtocol::remote (void) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");
	
  //- check if the MCCE2 is not in MEASURE mode to get/change any settings
	this->is_allowed();

	//- send command
	cmd_to_send << _devAdd << " REMOTE" << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	
	//- check the response
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::get_raw_status
// ============================================================================
std::string NovelecProtocol::get_raw_status (void) 
{
std::stringstream cmd_to_send;
std::string argout("no data");
std::string tmp("no data");

	//- send command to Novelec device
	cmd_to_send << _devAdd << " READ " << STATUS_CMD_NUM << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	//- check and extract the response
	argout = check_and_extract_data(tmp, STATUS_CMD_NUM);

	return argout;
}

// ============================================================================
// NovelecProtocol::clear_registers
// ============================================================================
void NovelecProtocol::clear_registers (void) 
{
std::stringstream cmd_to_send;
std::string tmp("no data");
	
	//- send command
	cmd_to_send << _devAdd << " PROG " << CLEAR_ERRORS_CMD_NUM << " 1 " << END_OF_LINE << std::endl;
	tmp = _communication_link->write_read(cmd_to_send.str());
	
	//- check the response
	check_command(tmp);
}

// ============================================================================
// NovelecProtocol::is_allowed
// ============================================================================
void NovelecProtocol::is_allowed (void) 
{
	if(this-> _is_measure_mode_on)
		throw electrometer::ElectrometerException("COMMAND_NOT_ALLOWED", 
    "Cannot change parameter(s) when MEASURE mode enabled : call MCCE2_OFF command.",
											"NovelecProtocol::check_command( ).");
}

// ============================================================================
// NovelecProtocol::check_command
//	This method read the device response and check and throw an 
//	Electrometer Exception if the device response is a NAK.
//	Else, it returns the extracted response
// ============================================================================
void NovelecProtocol::check_command (std::string response) 
{
std::string data;

	//- A correct response is :
	//			-> "address ACK " : if command well understood
	//- An invalid response is: "address NAK ..."
	if(response.find("NAK") != std::string::npos)
	{
		throw electrometer::ElectrometerException("COMMAND_NOT_UNDERSTOOD", 
											"Bad formatted string command sent -> NAK received !",
											"NovelecProtocol::check_command( ).");
	}
	else
		if(response.find("ACK") != std::string::npos)
			data = response;
		else //- must not exist !!!
			throw electrometer::ElectrometerException("INVALID_DATA", 
													"Invalid string received from Novelec device.",
													"NovelecProtocol::check_command( ).");
	
}

// ============================================================================
// NovelecProtocol::check_and_extract_data
//	This method read the device response and check and throw an 
//	Electrometer Exception if the device response is a NAK.
//	Else, it returns the extracted response
// ============================================================================
std::string NovelecProtocol::check_and_extract_data (std::string response, short cmd_sent) 
{
std::string data;
std::string cmd_sentStr;

	cmd_sentStr = XString<short>::convertToString(cmd_sent);

	//- A correct response is :
	//			-> "address AWR ReadNum = data " : if there is a returned value
	//- An invalid response is: "address NAK ..."

	if(response.find("NAK") != std::string::npos)
	{
		throw electrometer::ElectrometerException("COMMAND_NOT_UNDERSTOOD", 
											"Bad formatted string command sent -> NAK received !",
											"NovelecProtocol::check_and_extract_data( ).");
	}
	else
		if(response.find("AWR") != std::string::npos)
		{
			//- check if it is the received answer of the command sent
			if(response.find(cmd_sentStr) != std::string::npos)
			{
				//- extract data in the device response
				data = response.substr(response.find("=")+1);

				/*
				*	check if the response is a short or explicite response
				*
				*	To do so, check if char at idx 18 is a space or a letter :
				*		if it is a space -> this is a short response
				*			and must be converted to return the explicite response
				*		else it is an explicite response and can be returned as this
				*/
				//- extract char
				response = response.substr(18,1);
				
				//- check what is this char a 'space' or 'letter'
				if(response.find(" ") != std::string::npos)
					_is_explicite_resp_enabled = false;
				else
					_is_explicite_resp_enabled = true;

				//- erase first and last space char
				data.erase(data.find_first_of(" "),1);
				data.erase(data.find_last_of(" "));
			}
			else
				throw electrometer::ElectrometerException("SYNCHRONISATION_LOST", 
														"The received response is not for the command previously sent.",
														"NovelecProtocol::check_and_extract_data( ).");
		}
		else //- must not exist !!!
			throw electrometer::ElectrometerException("INVALID_DATA", 
													"Invalid string received from Novelec device.",
													"NovelecProtocol::check_and_extract_data( ).");

	return data;
}