#include "AWG.h"
#include <iostream>
#include <cmath>
#include <vector>

AWG::AWG() noexcept {	
	this->pCardHandle = nullptr;
	this->cardIdx = -1;
	this->serialNumber = -1;
	this->instMemSize = -1;
	this->bytesPerSample = -1;
	this->maxSampleRate = -1;
}

AWG::~AWG() {
	// TODO: do this
	if (this->isOpen()) { this->close(); }
	this->pCardHandle = nullptr;
	return;
}

void AWG::checkError() {
	/**
	 * printout error if detected.
	 * 
	 */
	if (this->pCardHandle == nullptr) { return; }
	char errorMsg[ERRORTEXTLEN];
	if (
		spcm_dwGetErrorInfo_i32(this->pCardHandle, NULL, NULL, errorMsg)
		!= ERR_OK
	) {
		//std::cout << "AWG error" << std::string(errorMsg) 
		//	<< "\ncard closed\n";
		this->close();
		throw CardException(errorMsg);
	}
}

//void* AWG::getPageAlignedMem(uint64 memSize) {
//	/**
//	 * returns a pointer to a page aligned mem location.
//	 * 
//	 * @param memSize size of memory to allocate, bytes
//	 * @return pointer to start of a page aligned mem location
//	 */
//
//	// for unknown reasons VirtualAlloc/VirtualFree leaks memory if memSize < 4096 (page size)
//	// therefore use _aligned_malloc () to get small amounts of page aligned memory
//	if (memSize >= 4096)
//		return VirtualAlloc(NULL, (size_t)memSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
//	else {
//		void* pMem = _aligned_malloc((size_t)memSize, 4096);
//		if (pMem == NULL)
//			return NULL;
//		memset(pMem, 0, (size_t)memSize);
//		return pMem;
//	}
//}
//
//void AWG::freePageAlignedMem(void* pMem, uint64 memSize) {
//	/**
//	 * frees a page aligned memory space.
//	 * 
//	 * @param pMem pointer to a page aligned memory location
//	 * @param memSize memory size in bytes to deallocate
//	 */
//	if (memSize >= 4096)
//		VirtualFree(pMem, 0, MEM_RELEASE);
//	else
//		_aligned_free(pMem);
//}
//
void AWG::open(int openIndex) {	
	/**
	 * opens a connection to AWG by index.
	 * 
	 * @param openIndex card index, 0 or 1
	 */
	if (!this->isOpen()) {
		std::cout << "card open failed" << std::endl;
		return;
	}

	auto openMsg = "/dev/spcm" + std::to_string(openIndex);
	this->pCardHandle = spcm_hOpen(openMsg.c_str());
	if (pCardHandle == nullptr) {
		std::cout << "card open failed" << std::endl;
		return;
	}

	this->cardIdx = openIndex;
	spcm_dwGetParam_i32(
		this->pCardHandle,
		SPC_PCISERIALNO,
		&this->serialNumber
	);
	spcm_dwGetParam_i64(
		this->pCardHandle,
		SPC_PCISAMPLERATE,
		&this->maxSampleRate
	);
	spcm_dwGetParam_i64(
		this->pCardHandle,
		SPC_PCIMEMSIZE,
		&this->instMemSize
	);
	spcm_dwGetParam_i32(
		this->pCardHandle,
		SPC_MIINST_BYTESPERSAMPLE,
		&this->bytesPerSample
	);

	this->checkError();
}

bool AWG::isOpen() {
	/**
	 * check if a connection to AWG exists.
	 * 
	 * @return true or false
	 */
	if (this->pCardHandle == nullptr) { return false; }
	return true;
	this->checkError();
}

void AWG::close() {
	/**
	 * close the connection to AWG.
	 * 
	 */
	if (!this->isOpen()) { return; }
	spcm_vClose(this->pCardHandle);
	this->pCardHandle = nullptr;
}

void AWG::reset() {
	/**
	 * resets the AWG card, equivalent to a power reset.
	 *
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_CARD_RESET
	);
	this->checkError();
}

int AWG::getCardIdx() {
	return this->cardIdx;
}

int AWG::getSerialNumber() {
	return this->serialNumber;
}

int64 AWG::getInstMemSize() {
	return this->instMemSize;
}

int AWG::getBytesPerSample() {
	return this->bytesPerSample;
}

int64 AWG::getMaxSampleRate() {
	return this->maxSampleRate;
}

void AWG::setSampleRate(int64 sampleRate) {
	if (!this->isOpen()) { return; }
	if (sampleRate < 0 or sampleRate > this->maxSampleRate) {
		std::cout << sampleRate << "exceeds max sample rate" << std::endl;
		return;
	}
	spcm_dwSetParam_i64(
		this->pCardHandle,
		SPC_SAMPLERATE,
		sampleRate
	);
}

int64 AWG::getSampleRate() {
	if (!this->isOpen()) { return 0; }
	int64 sr;
	spcm_dwGetParam_i64(
		this->pCardHandle,
		SPC_SAMPLERATE,
		&sr
	);
	this->checkError();
	return sr;
}

void AWG::setActiveChannels(std::set<int> channels) {
	/**
	 * activate channels for next run, resets last set.
	 * 
	 * @param channels vector of channel index (0-3)
	 */
	if (!this->isOpen()) { return; }
	int64 chMask = 0;
	for (auto ch : channels) {
		chMask = chMask | int64(pow(2,ch)); 
	}
	spcm_dwSetParam_i64(
		this->pCardHandle,
		SPC_CHENABLE,
		chMask
	);
	this->checkError();
	this->activeChannels = channels;
}

void AWG::setChannelAmp(int ch, int amp) {
	/**
	 * set channel amplitude.
	 * 
	 * @param ch channel index
	 * @param amp amplitude (mV)
	 */
	if (!this->isOpen()) { return; }
	const auto regStep = SPC_AMP1 - SPC_AMP0;
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_AMP0 + ch * regStep,
		amp
	);
	this->checkError();
}

void AWG::setChannelStopLvl(int ch, CHANNEL_STOPLVL stopLvl) {
	/**
	 * set channel stop level.
	 * 
	 * @param ch channel index
	 * @param stopLvl options: ZERO,LOW,HIGH,HOLDLAST
	 */
	if (!this->isOpen()) { return; }
	const auto regStep = SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL;
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_CH0_STOPLEVEL + ch * regStep,
		int32(stopLvl)
	);
	this->checkError();
}

void AWG::toggleChannelOutput(int ch, bool enable) {
	/**
	 * toggle channel output.
	 * 
	 * @param ch channel index
	 * @param enable true or false
	 */
	if (!this->isOpen()) { return; }
	if (ch < 0 or ch > 3) {
		std::cout << ch << "is not an allowed channel index"
			<< std::endl;
		return;
	}
	const auto regStep = SPC_ENABLEOUT1 - SPC_ENABLEOUT0;
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_ENABLEOUT0 + ch * regStep,
		enable
	);
	this->checkError();
}

void AWG::setChannel(int ch, int amp, CHANNEL_STOPLVL stopLvl, bool enable) {
	/**
	 * channel macro set amplitude, stop level, and enables output.
	 * 
	 * @param ch channel index
	 * @param amp amplitude (mV)
	 * @param stopLvl options: ZERO, LOW, HIGH, HOLDLAST
	 */
	this->setChannelAmp(ch, amp);
	this->setChannelStopLvl(ch, stopLvl);
	this->toggleChannelOutput(ch, enable);
}

int AWG::getChannelCount() {
	if (!this->isOpen()) { return 0; }
	int32 numCh;
	spcm_dwGetParam_i32(
		this->pCardHandle,
		SPC_CHCOUNT,
		&numCh
	);
	this->checkError();
	return numCh;
}

std::set<int> AWG::getChannelActivated() {
	return this->activeChannels;	
}

void AWG::setChannelDiffMode(int chPair, bool enable) {
	/**
	 * set channel 0(2) and channel 1(3) to differential output mode.
	 *
	 * @param chPair 0 for channel 0,1, 1 for channel 2,3
	 * @param enable true or false
	 */
	if (!this->isOpen()) { return; }
	if (chPair != 0 && chPair != 1) {
		std::cout << "unable to parse input chPair: "
			<< chPair << std::endl;
		return;
	}
	if (enable) {
		if (chPair == 0 && this->activeChannels.contains(1)) {
			std::cout << "channel 1 must disabled for chPair=0"
				"in differential output mode" << std::endl;
			return;
		}
		if (chPair == 1 && this->activeChannels.contains(3)) {
			std::cout << "channel 3 must disabled for chPair=1"
				"in differential output mode" << std::endl;
			return;
		}
	}
	const auto regStep = SPC_DIFF2 - SPC_DIFF0;
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_DIFF0 + chPair * regStep,
		enable
	);
	this->checkError();
}

void AWG::setChannelDoubleMode(int chPair, bool enable) {
	/**
	 * set channel 0(2) and channel 1(3) to differential output mode.
	 *
	 * @param chPair 0 for channel 0,1, 1 for channel 2,3
	 * @param enable true or false
	 */
	if (!this->isOpen()) { return; }
	if (chPair != 0 && chPair != 1) {
		std::cout << "unable to parse input chPair: "
			<< chPair << std::endl;
		return;
	}
	if (enable) {
		if (chPair == 0 && this->activeChannels.contains(1)) {
			std::cout << "channel 1 must disabled for chPair=0"
				"in double output mode" << std::endl;
			return;
		}
		if (chPair == 1 && this->activeChannels.contains(3)) {
			std::cout << "channel 3 must disabled for chPair=1"
				"in double output mode" << std::endl;
			return;
		}
	}
	const auto regStep = SPC_DOUBLEOUT2 - SPC_DOUBLEOUT0;
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_DOUBLEOUT0 + chPair * regStep,
		enable
	);
	this->checkError();
}

void AWG::writeSetup() {
	/**
	 * write current setup to card without starting hardware,
	 * some settings may not be changed while card is running.
	 * 
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_CARD_WRITESETUP
	);
	this->checkError();
}

void AWG::cardRun() {
	/**
	 * starts the card with all selected settings.
	 * 
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_CARD_START
	);
	this->checkError();
}

void AWG::cardStop() {
	/**
	 * stops the card, no effect if not running.
	 * 
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_CARD_STOP
	);
	this->checkError();
}

void AWG::waitReady() {
	/**
	 * wait until card finished current output run.
	 * TODO: check if this blocks.
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_CARD_WAITREADY
	);
	this->checkError();
}

void AWG::setTimeOut(int time) {
	/**
	 * set a timeout time in ms, 0 to disable.
	 *
	 * @param time
	 */
	if (!this->isOpen()) { return; }
	if (time < 0) {
		std::cout << time << "is an invalid timeout time" << std::endl;
	}
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_TIMEOUT,
		time
	);
	this->checkError();
}

int AWG::getTimeOut() {
	if (!this->isOpen()) { return 0; }
	int32 time;
	spcm_dwGetParam_i32(
		this->pCardHandle,
		SPC_TIMEOUT,
		&time
	);
	return time;
}

void AWG::printCardStatus() {
	if (!this->isOpen()) { return; }
	int32 status;
	spcm_dwGetParam_i32(
		this->pCardHandle,
		SPC_M2STATUS,
		&status
	);
	std::cout << STATUS_NAMES.at(status) << std::endl;
}

void AWG::toggleTrigger(bool enable) {
	/**
	 * toggle the trigger detection.
	 * 
	 * @param enable
	 */
	if (!this->isOpen()) { return; }
	auto command = enable 
		? M2CMD_CARD_ENABLETRIGGER
		: M2CMD_CARD_DISABLETRIGGER;
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		command
	);
	this->checkError();
}

void AWG::forceTrigger() {
	/**
	 * sends a software trigger, equivalent to an actual trigger.
	 * 
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_CARD_FORCETRIGGER
	);
	this->checkError();
}

void AWG::waitTrigger() {
	/**
	 * wait until trigger event.
	 * TODO: check if this blocks.
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_CARD_WAITTRIGGER
	);
	this->checkError();
}

void AWG::setTrigMaskOr(std::initializer_list<TRIGGER_MASK> trigMasks) {
	/**
	 * program the OR trigger mask.
	 *
	 * @param trigMasks 1 or more TRIGGER_MASK
	 */
	if (!this->isOpen()) { return; }
	auto mask = 0;
	for (auto m : trigMasks) {
		mask = mask | int32(m);
	}
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_TRIG_ORMASK,
		mask
	);
	this->checkError();
}

void AWG::setTrigMaskOr(std::vector<TRIGGER_MASK> trigMasks) {
    /**
     * program the OR trigger mask.
     *
     * @param trigMasks 1 or more TRIGGER_MASK
     */
    if (!this->isOpen()) { return; }
    auto mask = 0;
    for (auto m : trigMasks) {
        mask = mask | int32(m);
    }
    spcm_dwSetParam_i32(
        this->pCardHandle,
        SPC_TRIG_ORMASK,
        mask
    );
    this->checkError();
}

void AWG::setTrigMaskAnd(std::initializer_list<TRIGGER_MASK> trigMasks) {
	/**
	 * program the AND trigger mask.
	 *
	 * @param trigMasks 1 or more TRIGGER_MASK
	 */
	if (!this->isOpen()) { return; }
	int mask = 0;
	for (auto m : trigMasks) {
		mask = mask | int32(m);
	}
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_TRIG_ANDMASK,
		mask
	);
	this->checkError();
}

void AWG::setTrigMaskAnd(std::vector<TRIGGER_MASK> trigMasks) {
	/**
	 * program the AND trigger mask.
	 *
	 * @param trigMasks 1 or more TRIGGER_MASK
	 */
	if (!this->isOpen()) { return; }
	int mask = 0;
	for (auto m : trigMasks) {
		mask = mask | int32(m);
	}
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_TRIG_ANDMASK,
		mask
	);
	this->checkError();
}

void AWG::setTrigMode(int trigChannel, TRIGGER_MODE trigMode) {
	/**
	 * sets the trigger mode of trigger channel.
	 * 
	 * @param trigChannel 0 or 1 (labeled EXT0 and EXT1 on board)
	 * @param trigMode see TRIGGER_MODE for options
	 */
	if (!this->isOpen()) { return; }
	if (trigChannel != 0 and trigChannel != 1) {
		std::cout << "invalid trigger channel: " 
			<< trigChannel << std::endl;
		return;
	}
	auto trigRegister = trigChannel
		? SPC_TRIG_EXT1_MODE
		: SPC_TRIG_EXT0_MODE;

	spcm_dwSetParam_i32(
		this->pCardHandle,
		trigRegister,
		int32(trigMode)
	);
	this->checkError();
}

void AWG::setTrigTerm(int term) {
	/**
	 * sets the trigger input termination.
	 *
	 * \param term 0 for high impedance, 1 for 50 Ohm.
	 */

	if (!this->isOpen()) { return; }
	if (term != 0) {
		std::cout << "invalid trigger input termination setting: "
			<< term << std::endl;
		return;
	}
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_TRIG_TERM,
		term
	);
	this->checkError();
}

int AWG::getTrigTerm() {
	if (!this->isOpen()) { return -1; }
	int32 term;
	spcm_dwGetParam_i32(
		this->pCardHandle,
		SPC_TRIG_TERM,
		&term
	);
	this->checkError();
	return term;
}

void AWG::setTrigCoupling(int channel, int coupling) {
	/**
	 * sets trigger coupling to DC or AC.
	 * 
	 * @param coupling 1 for AC, 0 for DC
	 */
	if (!this->isOpen()) { return; }
	if (coupling != 0 and coupling != 1) {
		std::cout << "invalid trigger coupling setting :" << coupling
			<< std::endl;
		return;
	}
	auto trigChannelReg = channel ? 
		SPC_TRIG_EXT1_ACDC : SPC_TRIG_EXT0_ACDC;
	spcm_dwSetParam_i32(
		this->pCardHandle,
		trigChannelReg,
		coupling
	);
	this->checkError();
}

int AWG::getTrigCoupling(int channel) {
	if (!this->isOpen()) { return -1; }
	auto trigChannelReg = channel ? 
		SPC_TRIG_EXT1_ACDC : SPC_TRIG_EXT0_ACDC;
	int32 coupling;
	spcm_dwGetParam_i32(
		this->pCardHandle,
		trigChannelReg,
		&coupling
	);
	this->checkError();
	return coupling;
}

void AWG::setTrigLvl(int channel, int level) {
	/**
	 * set trigger level.
	 * 
	 * @param channel trigger channel
	 * @param level -10000mV to 10000mV
	 */
	if (!this->isOpen()) { return; }
	if (level < -10000 or level > 10000) {
		std::cout << "invalid trigger level: " << level << std::endl;
		return;
	}
	auto trigChannelReg = channel ?
		SPC_TRIG_EXT1_LEVEL0 : SPC_TRIG_EXT0_LEVEL0;
	spcm_dwSetParam_i64(
		this->pCardHandle,
		trigChannelReg,
		level
	);
	this->checkError();
}

int AWG::getTrigLvl(int channel) {
	if (!this->isOpen()) { return 0; }
	auto trigChannelReg = channel ?
		SPC_TRIG_EXT1_LEVEL0 : SPC_TRIG_EXT0_LEVEL0;
	int32 level;
	spcm_dwGetParam_i32(
		this->pCardHandle,
		trigChannelReg,
		&level
	);
	this->checkError();
	return level;
}

void AWG::setTrigRearmLvl(int channel, int level) {
	/**
	 * sets rearm trigger level.
	 * 
	 * @param channel trigger channel
	 * @param level -10000mV to +10000mV
	 */
	if (!this->isOpen()) { return; }
	auto trigChannelReg = channel ?
		SPC_TRIG_EXT1_LEVEL1 : SPC_TRIG_EXT0_LEVEL1;
	spcm_dwSetParam_i64(
		this->pCardHandle,
		trigChannelReg,
		level
	);
	this->checkError();
}

int64 AWG::getTrigRearmLvl(int channel) {
	if (!this->isOpen()) { return 0; }
	auto trigChannelReg = channel ?
		SPC_TRIG_EXT1_LEVEL1 : SPC_TRIG_EXT0_LEVEL1;
	int64 level;
	spcm_dwGetParam_i64(
		this->pCardHandle,
		trigChannelReg,
		&level
	);
	return level;
}

//void AWG::setReplayMode(REPLAY_MODE mode) {
//	/**
//	 * set the data replay mode.
//	 * 
//	 * a macro for sequence replay mode is setup in
//	 * initReplayModeSeq(), if you wish to use other modes, you must
//	 * consult the manual for mode specific settings.
//	 * 
//	 * @param mode SINGLE, MULTI, GATE, SINGLERESTART, SEQUENCE,
//	 * FIFO_{SINGLE, MULTI, GATE}.
//	 * 
//	 */
//	if (!this->isOpen()) { return; }
//	spcm_dwSetParam_i32(
//		this->pCardHandle,
//		SPC_CARDMODE,
//		mode
//	);
//	this->checkError();
//}
//
//void AWG::setMemSize(int64 sampleSize) {
//	/**
//	 * set memory size in samples in SINGLE, SINGLE_RESTART,
//	 * MULTI, and GATE mode.
//	 * Please consult the manual for # of channel dependent
//	 * minimum/maximum sample size considerations.
//	 * 
//	 * @param sampleSize size of sample
//	 *
//	 */
//	if (!this->isOpen()) { return; }
//	if (sampleSize < 0 or sampleSize > 2e9) {
//		std::cout << "invalid sampleSize: " << sampleSize << std::endl;
//		return;
//	}
//	spcm_dwSetParam_i64(
//		this->pCardHandle,
//		SPC_MEMSIZE,
//		sampleSize
//	);
//	this->checkError();
//}
//
//int64 AWG::getMemsize() {
//	int64 ms;
//	spcm_dwGetParam_i64(
//		this->pCardHandle,
//		SPC_MEMSIZE,
//		&ms
//	);
//	this->checkError();
//	return ms;
//}
//
//void AWG::setLoop(int nLoop) {
//	/**
//	 * set number of replay loops in SINGLE, SINGLE_RESTART,
//	 * MULTI, and GATE mode.
//	 * 
//	 * @param nLoop
//	 */
//	if (!this->isOpen()) { return; }
//	if (nLoop < 0) {
//		std::cout << "invalid number of loop: " << nLoop << std::endl;
//		return;
//	}
//	spcm_dwSetParam_i32(
//		this->pCardHandle,
//		SPC_LOOPS,
//		nLoop
//	);
//}
//
//int64 AWG::getLoop() {
//	if (!this->isOpen()) { return -1; }
//	int64 nl;
//	spcm_dwGetParam_i64(
//		this->pCardHandle,
//		SPC_LOOPS,
//		&nl
//	);
//	return nl;
//}

void AWG::initReplayModeSeq(int nSeg) {
	/**
	 * initialize sequence replay mode.
	 * 
	 * @param nSeg number of segments memory is divided into
	 */
	if (!this->isOpen()) { return; }
	if (nSeg % 2 != 0) {
		std::cout << "invalid number of segments for SEQUENCE mode: "
			<< nSeg << std::endl;
		return;
	}
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_CARDMODE,
		int32(REPLAY_MODE::SEQUENCE)
	);
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_SEQMODE_MAXSEGMENTS,
		nSeg
	);
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_SEQMODE_STARTSTEP,
		0
	);
	this->checkError();
}

void AWG::setSeqModeStep(
	uint32 step,
	uint64 segment,
	uint64 nextStep,
	uint64 nLoop,
	SEQ_LOOPCONDITION condition) {
	/**
	 * configure a step in sequence replay mode.
	 * 
	 * @param step step to configure
	 * @param segment memory segment step is associated with
	 * @param nextStep index of next step after end loop condition is met
	 * @param condition end loop condition
	 */
	if (!this->isOpen()) { return; }
	int64 mask = 
		(uint64(condition) << 32)
		| (nLoop << 32)
		| (nextStep << 16)
		| segment;
	spcm_dwSetParam_i64(
		this->pCardHandle,
		SPC_SEQMODE_STEPMEM0 + step,
		mask
	);
}

void AWG::writeSeqModeSegment(
	void* pDataBuffer,
	int size,
	int segment) {
	/**
	 * write buffered data into a memory segment in sequence replay mode.
	 * 
	 * @param pDataBuffer pointer to data memory, must be page aligned
	 * @param size size of data buffer in number of samples
	 * @param segment memory segment to write to
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_SEQMODE_WRITESEGMENT,
		segment
	);
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_SEQMODE_SEGMENTSIZE,
		size
	);
	this->prepDataTransfer(pDataBuffer, size);
	this->startDataTransfer();
	this->checkError();
}

void AWG::prepDataTransfer(
	void* dataBuffer,
	uint64 bufferLen,
	BUFFER_TYPE bufType,
	TRANSFER_DIR dir,
	uint32 notifySize,
	uint64 brdMemOffs
) {
	/**
	 * prepares the board for data transfer.
	 * 
	 * @param dataBuffer pointer to the data buffer
	 * @param bufferLen length of data buffer, bytes
	 * @param bufType buffer type, use only DATA for replay
	 * @param dir direction of data transfer
	 * @param notifySize number of bytes after which an event is sent
	 * @param brdMemOffs offset for transfer in board memory
	 */
	if (!this->isOpen()) { return; }
	spcm_dwDefTransfer_i64(
		this->pCardHandle,
		int32(bufType),
		int32(dir),
		notifySize,
		dataBuffer,
		brdMemOffs,
		bufferLen
	);
	this->checkError();
}

void AWG::startDataTransfer() {
	/**
	 * starts data transfer for a defined buffer.
	 *
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_DATA_STARTDMA
	);
	this->checkError();
}

void AWG::waitDataTransfer() {
	/**
	 * wait for data transfer to complete, blocks, 
	 * timeout is considered.
	 * 
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_DATA_WAITDMA
	);
	this->checkError();
}

void AWG::stopDataTransfer() {
	/**
	 * stops a running data transfer.
	 * 
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_M2CMD,
		M2CMD_DATA_STOPDMA
	);
	this->checkError();
}

void AWG::setClockMode(CLOCK_MODE cm) {
	/**
	 * set the clock mode.
	 * 
	 * @param cm INTPLL or EXTREFCLK
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_CLOCKMODE,
		int32(cm)
	);
	this->checkError();
}

int32 AWG::getClockMode() {
	if (!this->isOpen()) { return -1; }
	int32 cm;
	spcm_dwGetParam_i32(
		this->pCardHandle,
		SPC_CLOCKMODE,
		&cm
	);
	this->checkError();
	return cm;
}

void AWG::setRefClkFreq(int64 frequency) {
	/**
	 * if using EXTREFCLK mode, set the external clock frequency.
	 * 
	 * @param frequency Hz
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i64(
		this->pCardHandle,
		SPC_REFERENCECLOCK,
		frequency
	);
	this->checkError();
}

void AWG::setClockOut(bool enable) {
	/**
	 * enable/disable clock output.
	 * 
	 * @param enable true/false
	 */
	if (!this->isOpen()) { return; }
	spcm_dwSetParam_i32(
		this->pCardHandle,
		SPC_CLOCKOUT,
		enable
	);
	this->checkError();
}

int64 AWG::getClockOutFreq() {
	/**
	 * Read out frequency of internally synthesized clock.
	 * 
	 * @return frequency (Hz)
	 */
	if (!this->isOpen()) { return -1; }
	int64 freq;
	spcm_dwGetParam_i64(
		this->pCardHandle,
		SPC_CLOCKOUTFREQUENCY,
		&freq
	);
	this->checkError();
	return freq;
}