// todo: Multi-Platform support is not implemented.
// todo: fifo if needed


#ifndef SPECTRUM_M2i40XXSERIES
#define SPECTRUM_M2i40XXSERIES

#define SPCM_MAX_AIRANGE    8
#define SPCM_MAX_AICHANNEL  16

#include <string>
#include <vector>
#include <math.h>
#include "core/states.h"
#include "core/constants.h"
#include "drivers/ADC.h"
#include "GatedData.h"

#if defined __linux__
// ----- linux includes -----
# include <cstdio>
# include <fcntl.h>
# include <sys/ioctl.h>
# include <unistd.h>
# include "include/dlltyp.h"
# include "include/regs.h"
# include "include/spcerr.h"
# include "include/spcm_drv.h"
#endif

#if !(defined __linux__)
# error "sorry, expecting linux"
#endif

#ifndef SPC_DEBUG
# define SPC_DEBUG 0
#endif

/**
  \defgroup spectrum2i40xxseries Spectrum M2i40xx ADC
  \ingroup drivers
  \brief The driver for the Spectrum M2i.40xx series analog to digital converter.

  The M2i.4021/4022 we use supports sampling with up to 20 MS/s on 2/4 channels. This driver requires the GatedSampling option to be installed.
  @{
*/

class SpectrumM2i40xxSeries_error: public ADC_exception
{
public:
    explicit SpectrumM2i40xxSeries_error(const std::string& msg) throw (): ADC_exception(msg) {}
    explicit SpectrumM2i40xxSeries_error(const char* msg) throw (): ADC_exception(msg) {}
    virtual ~SpectrumM2i40xxSeries_error() throw () {}
protected:
    virtual const std::string prefix() const { return "ERROR (SpectrumM2i40xxSeries_error): "; }
};


/**
   \brief The driver class
 */
class SpectrumM2i40xxSeries: public ADC {

    /**
       ttlout triggerline device id
     */
    int device_id;
    /**
       ttlout triggerline ttlmask
     */
    ttlout trigger_line;

    /** stuff concerning fifo acquisition */
    size_t fifobufferno;
    /** stuff concerning fifo acquisition */
    size_t fifobufferlen;
    /** stuff concerning fifo acquisition */
    size_t fifo_minimal_size;
    /** stuff concerning fifo acquisition */
    std::vector<short int*> fifobuffers;
    /** the extra data thread should store things to that buffer */
    short int* fifo_adc_data;
    pthread_attr_t timeout_pt_attrs;
    pthread_t timeout_pt;


# if defined __linux__
  // ----- include the easy ioctl commands from the driver -----
#  include "include/spcioctl.inc"
# endif

    /**
     \brief Stores card settings and information
     */
    class Configuration {
    public:
		/** sampling frequency in Hz */
		double samplefreq;
		/** Data returned by the ADC during the different gating periods is stored in a nested list */
		DataManagementNode* data_structure;
		/** a timeout for acquiring data in s*/
		double timeout;
		/** configured input impedance for each channel in Ohm*/
		double* impedance;
		/** configured input sensitivity for each channel in V*/
		double* sensitivity;
		/** coupling 0=DC else AC */
		int coupling;
		/** external reference clock **/
		int ext_reference_clock;
		/** offsets for each channel in % of sensitivity **/
		int* offset;

		// Information about the card copied from the Spectrum example program

		/** \brief The card's driver handle as returned by the Spectrum driver open function */
		drv_handle hDrv;
		/** \brief The card's ID
		 * Typically 0 if no other card is installed. The device is created as /dev/spcm0
		 */
		int32 lCardID;

		/** \brief The card's type as listed in the manual */
		int32 lCardType;
		/** \brief Serial Number */
		int32 lSerialNumber;
		/** \brief Installed on-board memory in bytes */
		int64 llInstMemBytes;
		/** \brief Bitmap with installed card features */
		int32 lFeatureMap;
		/** \brief Number of channels (analog or digital) */
		int32 lMaxChannels;
		/** \brief Number of bytes for each sample (analog data) */
		int32 lBytesPerSample;
		/** \brief Minimum sampling rate */
		int32 lMinSampleRate;
		/** \brief Maximum sampling rate */
		int32 lMaxSampleRate;
		/** \brief Number of installed modules for data sorting algorithm */
		int32 lModulesCount;

		/** \brief Library version */
		int32 lLibVersion;
		/** \brief Kernel version */
		int32 lKernelVersion;

		/** \brief Main control firmware version */
		int32 lCtrlFwVersion;
		/** \brief Base hardware version */
		int32 lBaseHwVersion;
		/** \brief Module hardware version */
		int32 lModHwVersion;
		/** \brief Module firmware version */
		int32 lModFwVersion;


		// current settings
		/** \brief Bitmap for currently enabled channels */
		channel_array qwSetChEnableMap;
		/** \brief Programmed memory size */
		int llSetMemsize;
		/** \brief Number of used channels for this run */
		int lSetChannels;
		/** \brief Currently active oversampling factor */
		int32 lOversampling;


		// Analog Input settings
		/** \brief Resolution of analog input channel */
		int32   lResolution;
		int32   lMaxADCValue;                   // maximum range, normally 2^(Resolution-1) but can be limited
		int32   lPathCount;                     // number of input paths
		int32   lRangeCount;                    // number of analog input ranges
		int32   lRangeMin[SPCM_MAX_AIRANGE];    // analog input ranges
		int32   lRangeMax[SPCM_MAX_AIRANGE];    // ...
		bool    bInputTermAvailable;            // input termination available
		bool    bDiffModeAvailable;             // differential mode available
		bool    bACCouplingAvailable;           // AC/DC coupling softwar selectable
		bool    bBWLimitAvailable;              // bandwidth limit available
		bool    bOffsPercentMode;               // offset programmed in percent of range



		/** print data for debug purpose  */
		void print(FILE* f);

		bool bOpenCard();
		char* PrintInfo();

		Configuration() {
			samplefreq=0;
			timeout=0;
			impedance = NULL;
			sensitivity = NULL;
			offset = NULL;
			coupling=0;
			ext_reference_clock=0;
			data_structure=NULL;
			hDrv = NULL;

			lCardID = 0;
			lSetChannels = 0;
			lBytesPerSample = 0;
		}

		Configuration(const Configuration& orig) {
			samplefreq=orig.samplefreq;
			timeout=orig.timeout;
			impedance=orig.impedance;
			sensitivity=orig.sensitivity;
			coupling=orig.coupling;
			ext_reference_clock=orig.ext_reference_clock;
			qwSetChEnableMap = orig.qwSetChEnableMap;
			lSetChannels = orig.lSetChannels;
			if (orig.data_structure==NULL)
				data_structure=NULL;
			else
				data_structure=new DataManagementNode(*orig.data_structure);
		}

		~Configuration() {
			if (data_structure!=NULL)
				delete data_structure;
		}
    };

	/**
	 \brief Default settings for the ADC card.
	 These settings are copied and modified at need to derive effective settings
	*/
	Configuration default_settings;

	/**
	 \brief The settings actually used for a measurement.
	 Once a measurement is started, user settings may override default settings
	*/
	Configuration* effective_settings;

	short int* split_adcdata_recursive(short int* data, const DataManagementNode& structure, adc_results& result_splitted);

	/**
	 \brief Collects the settings required for the current job.
	 */
	void collect_config_recursive(state_sequent& exp, SpectrumM2i40xxSeries::Configuration& settings);

public:

	SpectrumM2i40xxSeries(const ttlout& t_line, int ext_reference_clock=(int)100e6);

	virtual void sample_after_external_trigger(double rate, size_t samples, double sensitivity=5.0, size_t resolution=14);

	virtual void set_daq(state & exp);


	/**
	 for syncronization purposes with sample clock
	*/
	//double get_sample_clock_frequency() const;

	/**
	 get results from transient recorder
	 timeout: 0.0 return immediately
	*/
	virtual result* get_samples(double timeout=0.0);

	/**
	 timeout issues during fifo acquisition
	*/
	//int TimeoutThread();

	virtual ~SpectrumM2i40xxSeries();
};

/** @{
 */

#endif
