/*
 *  psychlops_devices_nidaqmxbase.cpp
 *  Psychlops Standard Library (Universal)
 *
 *  Last Modified 2009/04/14 by Kenchi HOSOKAWA
 *  (C) 2009 Kenchi HOSOKAWA, Kazushi MARUYA, Takao SATO
 */

#include <iostream>
#include <sstream>
#include <math.h>
#include "../../core/math/psychlops_m_util.h"
#include "psychlops_devices_nidaqmx.h"


#if !defined(___EXTENSIONS___)

namespace Psychlops {
namespace Devices {

	double normalize_through_(double val) {
		return val;
	}

	AnalogInput_NIDAQmx::AnalogInput_NIDAQmx(const char* devname) : api(0) {}
	AnalogInput_NIDAQmx::AnalogInput_NIDAQmx(double freq, const char* devname) : api(0) {}
	AnalogInput_NIDAQmx::~AnalogInput_NIDAQmx() {}
	double AnalogInput_NIDAQmx::get() { return 0; }
	int AnalogInput_NIDAQmx::get(double* buf, int length, double timeout_second) { return 0; }
	double AnalogInput_NIDAQmx::samplingRate() { return 0; }
	void AnalogInput_NIDAQmx::samplingRate(double sample_per_sec) {}


	double AnalogInput_NIDAQmx::getSamplingRate() { return 0; }
	double AnalogInput_NIDAQmx::getLatency() { return 0; }
	double AnalogInput_NIDAQmx::getJitterSD() { return 0; }
	Interval AnalogInput_NIDAQmx::getRange() { Interval i; return 0<=i<=5; }
	bool AnalogInput_NIDAQmx::isAnalog() { return true; }
	void AnalogInput_NIDAQmx::put(double v) { ; }
	void AnalogInput_NIDAQmx::pulse(double v) { ; }
	void AnalogInput_NIDAQmx::keep(double v) { ; }


	AnalogOutput_NIDAQmx::AnalogOutput_NIDAQmx(const char* devname) : api(0) {}
	AnalogOutput_NIDAQmx::~AnalogOutput_NIDAQmx() {}
	void AnalogOutput_NIDAQmx::put(double val) {}
	void AnalogOutput_NIDAQmx::pulse(double val) {}
	void AnalogOutput_NIDAQmx::keep(double val) {}
	void AnalogOutput_NIDAQmx::setVoltage(double val, const char* devname) {}

	double AnalogOutput_NIDAQmx::getLatency() { return 0; }
	double AnalogOutput_NIDAQmx::getJitterSD() { return 0; }
	Interval AnalogOutput_NIDAQmx::getRange() { Interval i; return 0<=i<=5; }
	bool AnalogOutput_NIDAQmx::isAnalog() { return true; }
	double AnalogOutput_NIDAQmx::get() { return 0; }


	NIDAQmx::NIDAQmx()
	{
		ain = 0;
		aout = 0;
	}
	NIDAQmx::~NIDAQmx() {}
	void NIDAQmx::open(const char* devname) {}
	void NIDAQmx::open(double freq, const char* devname) {}
	double NIDAQmx::get() { return 0; }
	void NIDAQmx::put(double val) {}
	void NIDAQmx::keep(double val) {}
	void NIDAQmx::pulse(double val) {}
	int NIDAQmx::get(double* buf, int length, double timeout_second) { return 1; }
	void NIDAQmx::setSamplingRate(double sample_per_sec) {}

	double NIDAQmx::getSamplingRate() { return 0; }
	double NIDAQmx::getLatency() { return 0; }
	double NIDAQmx::getJitterSD() { return 0; }
	Interval NIDAQmx::getRange() { Interval i; return 0<=i<=5; }
	bool NIDAQmx::isAnalog() { return true; }
}
}	/*	<- namespace Psycholops 	*/
#else

#if defined(__APPLE__)
#include "/Applications/National Instruments/NI-DAQmx Base/includes/NIDAQmxBase.h"
#elif defined(__BORLANDC__)
#define WIN32
#include "C:\Program Files\National Instruments\NI-DAQ\DAQmx ANSI C Dev\include\NIDAQmx.h"
#undef WIN32
#else
#include "C:\Program Files\National Instruments\NI-DAQ\DAQmx ANSI C Dev\include\NIDAQmx.h"
#endif

#include "../../core/ApplicationInterfaces/psychlops_code_exception.h"


namespace Psychlops {

namespace Devices {

#if defined(__APPLE__)
#if defined(DYLIB)
	struct NIDAQmxAPI {
		enum TaskType { AIVoltage, AOVoltage };

	public:
		NIDAQmxAPI(const char* devname, TaskType dtype, double rate = 1000) { throw new Exception("NIDAQmx: Mac OS X is not supported."); }
		~NIDAQmxAPI() { throw new Exception("NIDAQmx: Mac OS X is not supported."); }
		const double getOneShot() const { throw new Exception("NIDAQmx: Mac OS X is not supported."); return 0; }
		const int getSamples(float64 *f, int32 num, double timeout) const { throw new Exception("NIDAQmx: Mac OS X is not supported."); return 0; }
		void putOneShot(float64 f) const { throw new Exception("NIDAQmx: Mac OS X is not supported."); }
		void setSamplingRate(float64 f) const { throw new Exception("NIDAQmx: Mac OS X is not supported."); }
	};
#else
	int NIDAQmxAPIcount = 0;
	struct NIDAQmxAPI {
		enum TaskType { AIVoltage, AOVoltage };
		TaskHandle taskHandle;
		TaskType type;

	public:
		NIDAQmxAPI(const char* devname, TaskType dtype, double freq) {
			int32 err;
			type = dtype;
			std::stringstream taskname("");
			for(int i=0; i<64; i++) {
				if(*(devname+i)==0) { break; }
				taskname << (*(devname+i)!='/' ? *(devname+i) : '_');
			}
			taskname << ++NIDAQmxAPIcount;
			err = DAQmxBaseCreateTask(taskname.str().c_str(), &taskHandle);
			if(err!=0) throwError();
			switch(type) {
				case AIVoltage:
					err = DAQmxBaseCreateAIVoltageChan( taskHandle, devname, NULL, DAQmx_Val_Diff, 0, 5, DAQmx_Val_Volts, NULL);
					if(err!=0) throwError();
					//setSamplingRate(freq);
					break;
				case AOVoltage:
					err = DAQmxBaseCreateAOVoltageChan( taskHandle, devname, NULL, 0, 5, DAQmx_Val_Volts, NULL);
					if(err!=0) throwError();
					break;
				default:
					throw new Exception("NIDAQmx: Illigal Task Type.");
			}
			DAQmxBaseStartTask (taskHandle);
			if(err!=0) throwError();
		}
		virtual ~NIDAQmxAPI() {
			DAQmxBaseStopTask(taskHandle);
			DAQmxBaseClearTask(taskHandle);
		}
		const double getOneShot() const {
			float64 value[16];
			int32 aaaa, err;
			err = DAQmxBaseReadAnalogF64(taskHandle, 8, 0.01, DAQmx_Val_GroupByChannel, value, 1, &aaaa, NULL);
			if(err!=0) throwError();
			return value[0];
		}
		const int getSamples(float64 *f, int32 num, uInt32 timeout) const {
			int32 aaaa, err;
			err = DAQmxBaseReadAnalogF64(taskHandle, num, timeout, DAQmx_Val_GroupByChannel, f, num, &aaaa, NULL);
			if(err!=0) throwError();
			return aaaa;
		}
		void putOneShot(float64 f) const {
			float64 value[8] = {f,f,f,f,f,f,f,f};
			if(value[0]<0 || value[0]>5) return;
			int32 aaaa, err;
			err = DAQmxBaseWriteAnalogF64(taskHandle, 1, FALSE, 0.1, DAQmx_Val_GroupByChannel, value, &aaaa, NULL);
			if(err!=0) throwError();
		}
		void putSamples(float64 *value, int num) const {
			//if(value[0]<0 || value[0]>5) return;
			int32 aaaa, err;
			err = DAQmxBaseWriteAnalogF64(taskHandle, 1, FALSE, 0.1, DAQmx_Val_GroupByChannel, value, &aaaa, NULL);
			if(err!=0) throwError();
		}
		void setSamplingRate(float64 freq) const {
			int32 err;
			err = DAQmxBaseCfgSampClkTiming(taskHandle, NULL, freq, DAQmx_Val_Rising, DAQmx_Val_ContSamps, (uInt32)ceil(freq));
			if(err!=0) throwError();
		}
		void throwError() const {
			char msg[256];
			DAQmxBaseGetExtendedErrorInfo (msg, 256);
			std::string s(msg);
			throw new Exception(s);
		}

	};
#endif
#else
	struct NIDAQmxAPI {
		enum TaskType { AIVoltage, AOVoltage };
		TaskHandle taskHandle;
		TaskType type;

		public:
		NIDAQmxAPI(const char* devname, TaskType dtype, double rate = 1000) {
			int32 err;
			type = dtype;
			char taskname[64];
			for(int i=0; i<64; i++) {
				if(*(devname+i)==0) { taskname[i] = 0; break; }
				taskname[i] = (*(devname+i)!='/' ? *(devname+i) : '_');
			}
			err = DAQmxCreateTask (taskname, &taskHandle);
			if(err!=0) throwError();
			switch(type) {
				case AIVoltage:
					err = DAQmxCreateAIVoltageChan( taskHandle, devname, NULL, DAQmx_Val_Diff, -5, 5, DAQmx_Val_Volts, NULL);
					if(err!=0) throwError();
					//setSamplingRate(rate);
					break;
				case AOVoltage:
					err = DAQmxCreateAOVoltageChan( taskHandle, devname, NULL, 0, 5, DAQmx_Val_Volts, NULL);
					if(err!=0) throwError();
					break;
				default:
					throw new Exception("NIDAQmx: Illigal Task Type.");
			}
			err = DAQmxStartTask( taskHandle );
			if(err!=0) throwError();
		}
		virtual ~NIDAQmxAPI() {
			DAQmxStopTask(taskHandle);
			DAQmxClearTask(taskHandle);
		}
		const double getOneShot() const {
			float64 value[16];
			int32 aaaa, err;
			err = DAQmxReadAnalogF64(taskHandle, 1, 0.01, DAQmx_Val_GroupByChannel, value, 1, &aaaa, NULL);
			if(err!=0) throwError();
			return value[0];
		}
		const int getSamples(float64 *f, int32 num, uInt32 timeout) const {
			int32 aaaa, err;
			err = DAQmxReadAnalogF64(taskHandle, num, timeout, DAQmx_Val_GroupByChannel, f, num, &aaaa, NULL);
			if(err!=0) throwError();
			return aaaa;
		}
		void putOneShot(float64 f) const {
			float64 value[8] = {f,f,f,f,f,f,f,f};
			if(value[0]<0 || value[0]>5) return;
			int32 aaaa, err;
			err = DAQmxWriteAnalogF64(taskHandle, 1, FALSE, 0.01, DAQmx_Val_GroupByChannel, value, &aaaa, NULL);
			if(err!=0) throwError();
		}
		void setSamplingRate(float64 f) const {
			int32 err;
			err = DAQmxCfgSampClkTiming(taskHandle, NULL, f, DAQmx_Val_Rising, DAQmx_Val_ContSamps, (int)ceil(f));
			if(err!=0) throwError();
		}
		void throwError() const {
			char msg[256];
			DAQmxGetExtendedErrorInfo (msg, 256);
			std::string s(msg);
			throw new Exception(s);
		}
	};
#endif

/*
	double normalize_exponential(double val) {
		double sign = (val<0 ? -1 : 1);
		return sign * factor*pow(Math::abs(val), exponent) + intercept;
	}
*/


	double normalize_through_(double val) {
		return val;
	}



	AnalogInput_NIDAQmx::AnalogInput_NIDAQmx(const char* devname) : api(0) {
		normalize = &normalize_through_;
		api = new NIDAQmxAPI(devname, NIDAQmxAPI::AIVoltage, 10000.0);
	}
	AnalogInput_NIDAQmx::AnalogInput_NIDAQmx(double freq, const char* devname) : api(0) {
		normalize = &normalize_through_;
		api = new NIDAQmxAPI(devname, NIDAQmxAPI::AIVoltage, freq);
	}
	AnalogInput_NIDAQmx::~AnalogInput_NIDAQmx() {
		if(api!=0) delete api;
	}
	double AnalogInput_NIDAQmx::get() {
		return normalize(api->getOneShot());
	}
	int AnalogInput_NIDAQmx::get(double* buf, int length, double timeout_second) {
		return api->getSamples(buf, length, timeout_second);
	}
	double AnalogInput_NIDAQmx::samplingRate() {
		return 0;
	}
	void AnalogInput_NIDAQmx::samplingRate(double sample_per_sec) {
		api->setSamplingRate(sample_per_sec);
	}


	double AnalogInput_NIDAQmx::getSamplingRate() { return 0; }
	double AnalogInput_NIDAQmx::getLatency() { return 0; }
	double AnalogInput_NIDAQmx::getJitterSD() { return 0; }
	Interval AnalogInput_NIDAQmx::getRange() { Interval i; return 0<=i<=5; }
	bool AnalogInput_NIDAQmx::isAnalog() { return true; }
	void AnalogInput_NIDAQmx::put(double v) { ; }
	void AnalogInput_NIDAQmx::pulse(double v) { ; }
	void AnalogInput_NIDAQmx::keep(double v) { ; }






	AnalogOutput_NIDAQmx::AnalogOutput_NIDAQmx(const char* devname) : api(0) {
		denormalize = &normalize_through_;
		api = new NIDAQmxAPI(devname, NIDAQmxAPI::AOVoltage, 10.0);
	}
	AnalogOutput_NIDAQmx::~AnalogOutput_NIDAQmx() {
		if(api!=0) delete api;
	}
	void AnalogOutput_NIDAQmx::put(double val) {
		api->putOneShot(denormalize(val));
	}
	void AnalogOutput_NIDAQmx::pulse(double val) {
		api->putOneShot(denormalize(val));
	}
	void AnalogOutput_NIDAQmx::keep(double val) {
		api->putOneShot(denormalize(val));
	}
	void AnalogOutput_NIDAQmx::setVoltage(double val, const char* devname) {
		AnalogOutput_NIDAQmx mx(devname);
		mx.put(val);
	}



	double AnalogOutput_NIDAQmx::getLatency() { return 0; }
	double AnalogOutput_NIDAQmx::getJitterSD() { return 0; }
	Interval AnalogOutput_NIDAQmx::getRange() { Interval i; return 0<=i<=5; }
	bool AnalogOutput_NIDAQmx::isAnalog() { return true; }
	double AnalogOutput_NIDAQmx::get() { return 0; }





	NIDAQmx::NIDAQmx()
	{
		ain = 0;
		aout = 0;
	}
	NIDAQmx::~NIDAQmx()
	{
		if(ain!=0) delete ain;
		if(aout!=0) delete aout;
	}
	void NIDAQmx::open(const char* devname)
	{
		ain = new NIDAQmxAPI(devname, NIDAQmxAPI::AIVoltage, 10000.0);;
		aout = new NIDAQmxAPI(devname, NIDAQmxAPI::AOVoltage, 10.0);
	}
	void NIDAQmx::open(double freq, const char* devname)
	{
		ain = new NIDAQmxAPI(devname, NIDAQmxAPI::AIVoltage, freq);;
		aout = new NIDAQmxAPI(devname, NIDAQmxAPI::AOVoltage, 10.0);
	}
	double NIDAQmx::get()
	{
		return ain!=0 ? (ain->getOneShot()) : 0;
	}
	void NIDAQmx::put(double val) {
		if(aout!=0) aout->putOneShot(val);
	}
	void NIDAQmx::pulse(double val) {
		if(aout!=0) aout->putOneShot(val);
	}
	void NIDAQmx::keep(double val) {
		if(aout!=0) aout->putOneShot(val);
	}
	int NIDAQmx::get(double* buf, int length, double timeout_second) {
		return ain!=0 ? ain->getSamples(buf, length, timeout_second) : 0;
	}
	void NIDAQmx::setSamplingRate(double sample_per_sec) {
		ain->setSamplingRate(sample_per_sec);
	}

	double NIDAQmx::getSamplingRate() { return 0; }
	double NIDAQmx::getLatency() { return 0; }
	double NIDAQmx::getJitterSD() { return 0; }
	Interval NIDAQmx::getRange() { Interval i; return 0<=i<=5; }
	bool NIDAQmx::isAnalog() { return true; }



}

}	/*	<- namespace Psycholops 	*/


#endif
