/*
 *  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 <math.h>
#include <sstream>

#include "../../core/math/psychlops_m_util.h"
#include "psychlops_devices_nidaqmxbase.h"


#define ___EXTENSIONS___

#if !defined(___EXTENSIONS___)

namespace Psychlops {
namespace Devices {

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

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


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

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

	double AnalogOutput_NIDAQmxBase::getLatency() { return 0; }
	double AnalogOutput_NIDAQmxBase::getJitterSD() { return 0; }
	Interval AnalogOutput_NIDAQmxBase::getRange() { Interval i; return 0<=i<=5; }
	bool AnalogOutput_NIDAQmxBase::isAnalog() { return true; }
	double AnalogOutput_NIDAQmxBase::get() { return 0; }
}
}	/*	<- 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-DAQmx Base\Include\NIDAQmxBase.h"
#undef WIN32
#else
#include "C:\Program Files\National Instruments\NI-DAQmx Base\Include\NIDAQmxBase.h"
#endif

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


namespace Psychlops {

namespace Devices {

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

	public:
		NIDAQmxBaseAPI(const char* devname, TaskType dtype, double rate = 1000) { throw new Exception("NIDAQmx: Mac OS X is not supported."); }
		~NIDAQmxBaseAPI() { 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 NIDAQmxBaseAPIcount = 0;
	struct NIDAQmxBaseAPI {
		enum TaskType { AIVoltage, AOVoltage };
		TaskHandle taskHandle;
		TaskType type;

	public:
		NIDAQmxBaseAPI(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 << ++NIDAQmxBaseAPIcount;
			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 ~NIDAQmxBaseAPI() {
			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


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

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


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




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

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

}

}	/*	<- namespace Psycholops 	*/

#endif

