// ============================================================================
//
// = CONTEXT
//    TANGO Project - DDC KeithleyElectrometer Support Library
//
// = FILENAME
//    Keithley_487.cpp
//
// = AUTHOR
//    X. Elattaoui
//
// ============================================================================

// ============================================================================
// DEPENDENCIES
// ============================================================================
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <string>
#include <math.h>   //- for ceil
#include <helpers/XString.h>
#include "Keithley_487.h"
#include "KeithleyDDCProtocol.h"

/*
* Valid Range values for a K_487
*/
static const std::string K487_rangeValue[] = {"AUTO ON","2E-9","2E-8","2E-7","2E-6","2E-5","2E-4","2E-3", "AUTO OFF"};
/*
* Range limit : no range for values R8 & R9 (R10 = AUTORANGE OFF)
*/
static short K487_rangeLimit = 8;

/*
* Trigger Mode limit
*/
static short K487_triggerModeLimit = 9;

/*
* Trigger Mode limit (millisec)
*/
static int K487_conversionRate = 360;

// ============================================================================
// Keithley_487::Keithley_487
// ============================================================================
Keithley_487::Keithley_487 (std::string& comLink_device_name):AbstractElectrometerClass(comLink_device_name) 
{
	std::cout << "Keithley_487::Keithley_487 <-" << std::endl;

	std::cout << "Keithley_487::Keithley_487 ->" << std::endl;
}

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

	std::cout << "Keithley_487::~Keithley_487 ->" << std::endl;
}

// ============================================================================
// Keithley_487::init_protocol
// ============================================================================
bool Keithley_487::init_protocol (void)
{
  std::string description("");
  bool success = false;

  try
  {
	  //- build the keithley Electrometer protocol obj
  	_electrometerProtocol = new KeithleyDDCProtocol (_device_proxy_name);
    
    if(_electrometerProtocol)
      success = _electrometerProtocol->build_communicationLink();
  }
  catch(Tango::DevFailed& df)
  {
		description = "FAILED to create proxy on : " + _device_proxy_name;
		
		Tango::Except::re_throw_exception (df,
			(const char*)"COMMUNICATION_ERROR",
			description.c_str(),
			(const char*)"Keithley_487::init_protocol");
  }
  catch(...)
  {
		description = "FAILED to create proxy on : " + _device_proxy_name + ". Caught [...].";
		
		Tango::Except::throw_exception (
			(const char*)"COMMUNICATION_ERROR",
			description.c_str(),
			(const char*)"Keithley_487::init_protocol");
  }
  return success;
}

// ============================================================================
// Keithley_487::autoRange_off
// ============================================================================
void Keithley_487::autoRange_off (void) 
{
	//- send the appropriate command
	KeithleyDDCProtocol* _kddc = dynamic_cast<KeithleyDDCProtocol*>(_electrometerProtocol);
	if(_kddc)
		_kddc->autoRange_OFF_forK486_487();
}

// ============================================================================
// Keithley_487::range_up
// ============================================================================
void Keithley_487::range_up (void) 
{
std::stringstream cmd_to_send;

	// force read of range on instrument to update _range variable 
	this->get_configuration();

	_range += 1;

	if(_range > K487_rangeLimit)
	{
		_range = K487_rangeLimit;

		throw electrometer::ElectrometerException("OUT_OF_RANGE", 
												"Range up limit reached.",
												"Keithley_487::range_up( ).");
	}

	//- build and send the command
	cmd_to_send << _range << std::endl;
	_electrometerProtocol->set_range(cmd_to_send.str());
}

// ============================================================================
// Keithley_487::range_down
// ============================================================================
void Keithley_487::range_down (void) 
{
std::stringstream cmd_to_send;

	// force read of range on instrument to update _range variable 
	this->get_configuration();

	_range -= 1;

	if(_range < 0)
	{
		_range=0;

		throw electrometer::ElectrometerException("OUT_OF_RANGE", 
												"Range down limit reached.",
												"Keithley_487::range_down( ).");
	}
	//- build and send the command
	cmd_to_send << _range << std::endl;
	_electrometerProtocol->set_range(cmd_to_send.str());
}

// ============================================================================
// Keithley_487::get_ElectroMeterRange
// ============================================================================
std::string Keithley_487::get_ElectroMeterRange (void) 
{
	// get config from instrument to update _range variable
	this->get_configuration();

	return _rangeStr;
}

// ============================================================================
// Keithley_487::set_buffer_size()
// ============================================================================
void Keithley_487::set_buffer_size (short size) 
{
std::stringstream cmd_to_send;

  //- check if size is valid
  if(size<0 || size>512)
		throw electrometer::ElectrometerException("OUT_OF_RANGE", 
												"Buffer size value invalid. Please enter a value in the range 0 - 512.",
												"Keithley_487::set_buffer_size( ).");

  //- just for internal use
  _size = size;
	//- send command : size = number of triggers
  cmd_to_send << "N" << size << "X" << std::endl;
	_electrometerProtocol->set_buffer_size(cmd_to_send.str());
}

// ============================================================================
// Keithley_487::set_integrationTime
// ============================================================================
void Keithley_487::set_integrationTime (double seconds) 
{
  double msSeconds = seconds / 1000;

  //- _size set in init_keithley()
  _size = (int)ceil(msSeconds / K487_conversionRate);
}

// ============================================================================
// Keithley_487::set_triggerMode
// ============================================================================
void Keithley_487::set_triggerMode (short trigMod) 
{
  if(trigMod<0 || trigMod>K487_triggerModeLimit)
		throw electrometer::ElectrometerException("OUT_OF_RANGE", 
												"Trigger mode value invalid. Please enter a value in the range 0 - 7.",
												"Keithley_487::set_triggerMode( ).");

  std::stringstream cmd_to_send;

  //- just for internal use
  _trigMod = trigMod;

  cmd_to_send << "T" << trigMod << "X" << std::endl;

  _electrometerProtocol->set_triggerMode(cmd_to_send.str());
}

// ============================================================================
// Keithley_487::get_triggerMode
// ============================================================================
/*std::string Keithley_487::get_triggerMode (void) 
{
	// force read of range on instrument to update _range variable
//	electrometer_status();

	return _trigMod;
}
*/
// ============================================================================
// Keithley_487::get_ElectroMeterMode
// ============================================================================
std::string Keithley_487::get_ElectroMeterMode (void) 
{
	// get config from instrument to update _mode variable
	this->get_configuration();

	return _mode;
}

// ============================================================================
// Keithley_487::setAmperMeterMode
// ============================================================================
void Keithley_487::setAmperMeterMode (void) 
{
	//- send the appropriate command
	KeithleyDDCProtocol* _kddc = dynamic_cast<KeithleyDDCProtocol*>(_electrometerProtocol);
	if(_kddc)
		_kddc->setAmperMeterMode_forK487();
	else
		throw electrometer::ElectrometerException("BAD_CAST", 
												"Unable to switch the electrmometer mode.",
												"Keithley_487::setAmperMeterMode( ).");
}

// ============================================================================
// Keithley_487::init_keithley : command to perform an integration cycle
// ============================================================================
void Keithley_487::init_keithley (void) 
{
  //- set trigger mode
  this->set_triggerMode(_trigMod);
  //- default conversion rate
  _electrometerProtocol->set_conversionRate( );
  //- set buffer size
  this->set_buffer_size(_size);
  //- enable readings from device buffer
  _electrometerProtocol->enable_readingsFromBuffer_K486_487( );
  //- force readings with no prefix
  _electrometerProtocol->disable_readingWithPrefix();
  //- enable SRQ on buffer full
  _electrometerProtocol->enable_SRQBufferFull ();
}

// ============================================================================
// Keithley_487::electrometer_status
// ============================================================================
std::string Keithley_487::electrometer_status (void)
{
	std::string kconfig("undefined configuration");
	std::string argout("");
	std::string tmp("");

	try
	{
		kconfig = _electrometerProtocol->get_raw_status();
		std::string modelNum = kconfig.substr(0,3);
		if(modelNum.find("487") == std::string::npos)
		{
			set_electroState(ALARM);
			argout = "Invalid error status string received";
			return argout;
		}

		//- IDDC Error : Set when an illegal device dependent command (IDDC) such as HlX is received ("H" is illegal).
		tmp = kconfig.substr(3,1);
		short iddc = XString<short>::convertFromString(tmp);
		if(iddc)
		{
			argout += "IDDC error : illegal device dependent command received.\n";
		}
		//- IDDCO Error : Set when an illegal device-dependent command option (IDDCO) such as T9X is received ("9" is illegal).
		tmp = kconfig.substr(4,1);
		short iddco = XString<short>::convertFromString(tmp);
		if(iddco)
		{
			argout += "IDDCO error : an illegal device-dependent command option received.\n";
		}
		//- Remote Error : Set when a programming command is received when REN is false.
		tmp = kconfig.substr(5,1);
		short remote = XString<short>::convertFromString(tmp);
		if(remote)
		{
			argout += "REMOTE error : programming command is received when REN is false.\n";
		}
		//- Self-Test Error : Set when a self-test failure (RAM and/or ROM) occurs.
		tmp = kconfig.substr(6,1);
		short selfT = XString<short>::convertFromString(tmp);
		if(selfT)
		{
			argout += "SELF-TEST error : Set when a self-test failure (RAM and/or ROM) occurs.\n";
		}
		//- Trigger Overrun Error : Set when a trigger is received when the instrument is still processing a reading from a previous trigger.
		tmp = kconfig.substr(7,1);
		short trigg = XString<short>::convertFromString(tmp);
		if(trigg)
		{
			argout += "Trigger error : Trigger received while instrument is still processing a reading from a previous trigger.\n";
		}
		//- Conflict Error : Set when trying to send a calibration value with the instrument on a measurement 
		//-		range that is too small to accommodate the value..
		tmp = kconfig.substr(8,1);
		short conflict = XString<short>::convertFromString(tmp);
		if(conflict)
		{
			argout += "CONFLICT error : Calibration value with the instrument on a measurement range that is too small.\n";
		}
		//- CAL LOCKED Error : Set when calibrating the instrument with the calibration switch in the locked (disabled) position.
		tmp = kconfig.substr(9,1);
		short calL = XString<short>::convertFromString(tmp);
		if(calL)
		{
			argout += "CAL LOCKED error : Set when calibrating the instrument with the calibration switch in the locked (disabled) position..\n";
		}
		//- Zero Check Error : Set when trying to calibrate the instrument with zero check enabled.
		tmp = kconfig.substr(10,1);
		short zchk = XString<short>::convertFromString(tmp);
		if(zchk)
		{
			argout += "ZERO CHECK error : Set when trying to calibrate the instrument with zero check enabled.\n";
		}
		//- Calibration Error : CALIBRATION - Set when calibration results in a cal constant value that is not within allowable
		//-		limits. Repeated failure may indicate that the Model 486/487 is defective. See service information
		//-		in this manual.
		tmp = kconfig.substr(11,1);
		short calib = XString<short>::convertFromString(tmp);
		if(calib)
		{
			argout += "CALIBRATION error : Set when calibration results in a cal constant value that is not within allowable limits.\n";
		}
		//- E2PROM DEFAULTS Error : Set when power-up checksum test on defaults fail.
		tmp = kconfig.substr(12,1);
		short e2prom = XString<short>::convertFromString(tmp);
		if(e2prom)
		{
			argout += "E2PROM DEFAULTS error : Set when power-up checksum test on defaults fail.\n";
		}
		//- E2pROM CAL CONSTANTS Error : Set when power-up checksum test on cal constants fail.
		tmp = kconfig.substr(13,1);
		short e2prcalL = XString<short>::convertFromString(tmp);
		if(e2prcalL)
		{
			argout += "E2pROM CAL CONSTANTS error : Set when power-up checksum test on cal constants fail.\n";
		}
		//- V-SOURCE CONFLICT Error : Set when trying to send a voltage source value to the Mode1 487 that
		//-		exceeds the maxim um limit of the currently selected voltage sauce range. On the Model 486,
		//-		this bit is always reset to "0".
		tmp = kconfig.substr(14,1);
		short vsconf = XString<short>::convertFromString(tmp);
		if(vsconf)
		{
			argout += "V-SOURCE CONFLICT error : Voltage source value exceeds the maxim um limit of the currently selected voltage sauce range.\n";
		}
		//- V-SOURCE Error : The Model 487, this bit is set when trying to place the voltage source in operate
		//-		while the enabled interlock is open.
		tmp = kconfig.substr(15,1);
		short vsrc = XString<short>::convertFromString(tmp);
		if(vsrc)
		{
			argout += "V-SOURCE error : Trying to place the voltage source in operate while the enabled interlock is open.\n";
		}

		if( argout.empty() )
			argout = "No error.";

		argout = "Keithley Type " + modelNum + " Error Status :\n" + argout;
	}
	catch(...)
	{
		set_electroState(ALARM);
	
		throw electrometer::ElectrometerException("UNKNOWN_ERROR", 
												"Cannot extract device error status.",
												"Keithley_487::electrometer_status( ).");
	}

	set_electroState(ON);
	return argout;
}

// ============================================================================
// Keithley_487::get_configuration
// ============================================================================
std::string Keithley_487::get_configuration (void)
{ 
	std::string _kstatus("undefined status");
	std::string argout("undefined status");
	std::string tmp("");

	//- read keithley status from HW	
	_kstatus = _electrometerProtocol->get_DDC_configuration();

	//- build status
	try
	{
		std::string modelNum = _kstatus.substr(0,3);
		//- if not expected data (here model number)
		if(modelNum.find("487") == std::string::npos)
		{
			set_electroState(ALARM);
			argout = "Invalid status string received";
			return argout;
		}
		argout = "Keithley Type : " + modelNum + "\n";
		//- Display Intensity ('A')
		tmp = _kstatus.substr(_kstatus.find('A')+1,1);
    short dintensity = XString<short>::convertFromString(tmp);
		if(!dintensity)
			argout += "Display Intensity : NORMAL\n";
		else
		if(dintensity == 1)
			argout += "Display Intensity : DIM\n";
		else
		if(dintensity == 2)
			argout += "Display Intensity : OFF\n";
		//- Reading Source ('B')
		tmp = _kstatus.substr(_kstatus.find('B'),1);
    short rsource = XString<short>::convertFromString(tmp);
		if(!rsource)
			argout += "Reading Source : A/D Reading\n";
		else
		if(rsource == 1)
			argout += "Reading Source : One Data Store Reading\n";
		else
		if(rsource == 2)
			argout += "Reading Source : All Data Store Reading\n";
		else
		if(rsource == 3)
			argout += "Reading Source : Max Data Store Reading\n";
		else
		if(rsource == 4)
			argout += "Reading Source : Min Data Store Reading\n";
		//- Zero check state ('C')
		tmp = _kstatus.substr(_kstatus.find('C')+1,1);
		if(XString<short>::convertFromString(tmp))
			argout += "Zero Check : Enabled\n";
		else
			argout += "Zero Check : Disabled\n";
		//- V/I Ohms ('F')
		tmp = _kstatus.substr(_kstatus.find('F')+1,1);
		if(XString<short>::convertFromString(tmp))
		{
			argout += "V/I Ohms : Enabled\n";
			_mode = "V/I Ohms";
		}
		else
		{
			argout += "V/I Ohms : Disabled\n";
			_mode = "CURRent";
		}
		//- Data Format ('G')
		tmp = _kstatus.substr(_kstatus.find('G')+1,1);
    short dformat = XString<short>::convertFromString(tmp);
		if(!dformat)
			argout += "Data Format : Reading With Prefix (ASCII)\n";
		else
		if(dformat == 1)
			argout += "Data Format : Reading Without Prefix (ASCII)\n";
		else
		if(dformat == 2)
			argout += "Data Format : Reading and Buffer Location With Prefix (ASCII)\n";
		else
		if(dformat == 3)
			argout += "Data Format : Reading and Buffer Location Without Prefix (ASCII)\n";
		else
		if(dformat == 4)
			argout += "Data Format : Binary Reading - precision, bytes reversed for Intel CPUs\n";
		else
		if(dformat == 5)
			argout += "Data Format : Binary Reading - precision, bytes in normal order for Motorola CPUs\n";
		else
		if(dformat == 6)
			argout += "Data Format : Binary Reading - counts and exponent, bytes reversed for Intel CPUs\n";
		else
		if(dformat == 7)
			argout += "Data Format : Binary Reading - counts and exponent, bytes in normal order for Motorola CPUs\n";
		//- Self Test ('J')
		tmp = _kstatus.substr(_kstatus.find('J')+1,1);
    short stest = XString<short>::convertFromString(tmp); 
		if(!stest)
			argout += "Self Test : No Errors\n";
		else
		if(stest == 1)
			argout += "Self Test : ROM Error\n";
		else
		if(stest == 2)
			argout += "Self Test : RAM Error\n";
		else
		if(stest == 3)
			argout += "Self Test : ROM and RAM Error\n";
		//- EOI & Bus Hold Off ('K')
		tmp = _kstatus.substr(_kstatus.find('K')+1,1);
    short ebhoff = XString<short>::convertFromString(tmp);
		if(!ebhoff)
			argout += "EOI & Bus Hold Off : EOI and Hold-Off\n";
		else
		if(ebhoff == 1)
			argout += "EOI & Bus Hold Off : No EOI and Hold-Off\n";
		else
		if(ebhoff == 2)
			argout += "EOI & Bus Hold Off : EOI and no Hold-Off\n";
		else
		if(ebhoff == 3)
			argout += "EOI & Bus Hold Off : No EOI and no Hold-Off\n";
		//- SRQ ('M')
		tmp = _kstatus.substr(_kstatus.find('M')+1,3);
    short srqInfo = XString<short>::convertFromString(tmp);
		if(!srqInfo)
			argout += "SRQ : Disabled\n";
		else
		if(srqInfo == 1)
			argout += "SRQ : Reading Overflow\n";
		else
		if(srqInfo == 2)
			argout += "SRQ : Data Store Full\n";
		else
		if(srqInfo == 4)
			argout += "SRQ : Data Store 1/2 full\n";
		else
		if(srqInfo == 8)
			argout += "SRQ : Reading Done\n";
		else
		if(srqInfo == 16)
			argout += "SRQ : Ready\n";
		else
		if(srqInfo == 32)
			argout += "SRQ : Error\n";
		else
		if(srqInfo == 128)
			argout += "SRQ : Voltage Source Error\n";
		//- Data Store Size ('N')
		tmp = _kstatus.substr(_kstatus.find('N')+1,3);
		if(!XString<short>::convertFromString(tmp))
			argout += "Data Store Size : Wrap Around\n";
		else
			argout += "Data Store Size : " + tmp + "\n";
		//- Filters ('P')
		tmp = _kstatus.substr(_kstatus.find('P')+1,1);
    short filters = XString<short>::convertFromString(tmp);
		if(!filters)
			argout += "Filters : Both Filters Disabled\n";
		else
		if(filters == 1)
			argout += "Filters : Digital Filter Enabled, Analog Filter Disabled\n";
		else
		if(filters == 2)
			argout += "Filters : Digital Filter Disabled, Analog Filter Enabled\n";
		else
		if(filters == 3)
			argout += "Filters : Both Filters Enabled\n";
		//- Range ('Rmn')
		//- check AutoRange
		tmp = _kstatus.substr(_kstatus.find('R')+1,2);
		if(tmp[0] == '0')
			argout += "Range : AutoRange Disabled\n";
		else
			argout += "Range : AutoRange Enabled\n";
		//- range value
		_range = atoi(&tmp[1]);
		_rangeStr = K487_rangeValue[_range];
		argout   += _rangeStr + "\n";
		//- Integration Period ('S')
		tmp = _kstatus.substr(_kstatus.find('S')+1,1);
		if(!XString<short>::convertFromString(tmp))
			argout += "Integration Period : Fast (4-1/2d)\n";
		else
			argout += "Integration Period : Line Cycle (5-1/2d)\n";
    //- Trigger ('T')
		tmp = _kstatus.substr(_kstatus.find('T')+1,1);
    _trigMod = XString<short>::convertFromString(tmp);
		if(!_trigMod)
			argout += "Trigger : Multiple On Talk\n";
		else
		if(_trigMod == 1)
			argout += "Trigger : One-shot On Talk\n";
		else
		if(_trigMod == 2)
			argout += "Trigger : Multiple On Get\n";
		else
		if(_trigMod == 3)
			argout += "Trigger : One-shot On Get)\n";
		else
		if(_trigMod == 4)
			argout += "Trigger : Multiple On X\n";
		else
		if(_trigMod == 5)
			argout += "Trigger : One-shot On X\n";
		else
		if(_trigMod == 6)
			argout += "Trigger : Multiple On External Trigger\n";
		else
		if(_trigMod == 7)
			argout += "Trigger : One-shot On External Trigger\n";
		else
		if(_trigMod == 8)
			argout += "Trigger : Multiple On Operate\n";
		else
		if(_trigMod == 9)
			argout += "Trigger : One-shot On Operate\n";
		//- Relative ('Z') (baseline suppress)
		tmp = _kstatus.substr(_kstatus.find('Z')+1,1);
		if(!XString<short>::convertFromString(tmp))
			argout += "Relative : Current Rel Disabled\n";
		else
			argout += "Relative : Current Rel Enabled\n";
	}
	catch(std::out_of_range)
	{
		set_electroState(ALARM);

		throw electrometer::ElectrometerException("OUT_OF_RANGE", 
												"Cannot extract device status [find or substr failed !].",
												"Keithley_487::get_configuration( ).");
	}

	set_electroState(ON);
	return argout;
}