#include <cmath>
#include <stack>
#include <cerrno>
#include <cstring>
#include "pthread.h"
#include "core/core.h"
#include "core/stopwatch.h"
#include "core/result.h"
#include "core/xml_states.h"
#include "Spectrum-MI40xxSeries.h"

#include <sstream>

#ifndef SPC_DEBUG
# define SPC_DEBUG 0
#endif
#ifndef SIZETPRINTFLETTER
#  ifndef __LP64__
#    define SIZETPRINTFLETTER "u"
#  else
#    define SIZETPRINTFLETTER "lu"
#  endif
#endif

void SpectrumMI40xxSeries::Configuration::print(FILE* f) {
    fprintf(f, "Spectrum MI40xx Series Configuration:\n");
    fprintf(f, "  Sample Frequency %g Hz, Acquisition Timeout %g\n",samplefreq, timeout);
    if (data_structure!=NULL)
        data_structure->print(f,2);
    else
        fprintf(f, "  No Data to acquire\n");

    for (int i=0; i < lSetChannels; i++) {
        fprintf(f, "  Channel %i: Impedance %f Ohm, Sensitivity %f V, Coupling %s\n", i, impedance[i], sensitivity[i], (coupling==0)?"DC":"AC");
    }
}


SpectrumMI40xxSeries::SpectrumMI40xxSeries(const ttlout& t_line, float impedance, int ext_reference_clock) {
    // print neat string to inform the user
    fprintf(stderr, "Initializing ADC card\n");

    // to be configured
    device_id=0;
    trigger_line=t_line;

    default_settings.qwSetChEnableMap = channel_array(ADC_MI_DEFAULT_CHANNELS);
    default_settings.lSetChannels = default_settings.qwSetChEnableMap.count();
    default_settings.impedance = new double[default_settings.lSetChannels];
    default_settings.sensitivity = new double[default_settings.lSetChannels];
    default_settings.offset = new int[default_settings.lSetChannels];

    for(int i = 0; i < default_settings.lSetChannels; i++) {
        default_settings.impedance[i] = ADC_MI_DEFAULT_IMPEDANCE;

        default_settings.sensitivity[i] = ADC_MI_DEFAULT_SENSITIVITY; // Set sensitivity in Volts to the default (maximum) value
        default_settings.offset[i] = ADC_MI_DEFAULT_OFFSET; // Set offsets in % of sensitivity to default (0)
    }


    default_settings.ext_reference_clock=ext_reference_clock; // Hz
    effective_settings=NULL;

#     if defined __linux__
    // ----- open driver -----
    deviceno = open ("/dev/spc0", O_RDWR);
    if (deviceno == -1) {
        std::string error_message="could not open /dev/spc0";
        if (errno==0) throw SpectrumMI40xxSeries_error( error_message+" (unknown error)");
        else throw SpectrumMI40xxSeries_error( error_message+" ("+strerror(errno)+")");
    }
#     endif
#     if defined __CYGWIN__
    // open library dll
    spectrum_driver_dll = LoadLibrary("spectrum.dll");
    if (spectrum_driver_dll==NULL)
        throw SpectrumMI40xxSeries_error("could not open driver dll");
    deviceno=0;
    // load driver functions
#     define load_spectrum_function(name) name = ( name##_type)GetProcAddress(spectrum_driver_dll, #name); \
if (name == NULL) { FreeLibrary(spectrum_driver_dll); \
 throw SpectrumMI40xxSeries_error("failed to load function" #name "" ); \
}
    load_spectrum_function(SpcInitPCIBoards);
    load_spectrum_function(SpcGetParam);
    load_spectrum_function(SpcSetParam);
    load_spectrum_function(SpcGetData);

    // find spectrum boards
    boardcount=0;
    SpcInitPCIBoards(&boardcount,&PCIVersion);
    if (boardcount<=0) {
        FreeLibrary(spectrum_driver_dll);
        throw SpectrumMI40xxSeries_error("no spectrum boards found");
    }

#     endif
    /* calculate the sample number that can be saved without fifo */
    int memory_size=0;
    SpcGetParam(deviceno,SPC_PCIMEMSIZE,&memory_size);
    fifo_minimal_size=memory_size/(sizeof(short int)*2);

    /* get the device type */
    SpcGetParam(deviceno, SPC_PCITYP, &cardType);
    if (cardType != TYP_MI4020 && cardType != TYP_MI4021 && cardType != TYP_MI4022 && cardType != TYP_MI4030 && cardType != TYP_MI4031 && cardType != TYP_MI4032) {
        throw SpectrumMI40xxSeries_error("Board type not supported");
    }

    /* make a check, whether spectrum board is running */
    int actual_status=0;
    SpcGetParam(deviceno, SPC_STATUS, &actual_status);
    if (actual_status!=SPC_READY) {
        fprintf(stderr, "Warning: Spectrum board was is running before reset.\n");
    }
#if SPC_DEBUG
    fprintf(stderr,"External reference clock: %i\n",default_settings.ext_reference_clock);
#endif
    int ErrorOccurred=1;
    SpcSetParam(deviceno, SPC_COMMAND,        SPC_RESET)==ERR_OK &&       // reset device first
    SpcSetParam (deviceno, SPC_GATE,          1)==ERR_OK &&             // Gated Triggering
    SpcSetParam (deviceno, SPC_PLL_ENABLE,    1)==ERR_OK &&             // Internal PLL enabled for clock
    SpcSetParam (deviceno, SPC_CLOCKOUT,      0)==ERR_OK &&             // No clock output
    SpcSetParam (deviceno, SPC_REFERENCECLOCK, default_settings.ext_reference_clock)==ERR_OK &&     // external reference clock
    SpcSetParam (deviceno, SPC_EXTERNALCLOCK, 0)==ERR_OK &&             //  but no external sample clock
    SpcSetParam (deviceno, SPC_CLOCK50OHM,    0)==ERR_OK &&             // clock input NOT with 50Ohm impedance
    SpcSetParam (deviceno, SPC_TRIGGERMODE,   TM_TTLPOS)==ERR_OK &&     // ttl trigger is used
    SpcSetParam (deviceno, SPC_TRIGGEROUT,    0)==ERR_OK &&             // No trigger output
    SpcSetParam (deviceno, SPC_TRIGGER50OHM,  0)==ERR_OK &&             // Trigger to 1MOhm, necessary for steep slopes
    (ErrorOccurred=0);                                                  // done, really no error occurred

    // ----- driver error: request error and end program -----
    if (ErrorOccurred!=0) {
        int32   lErrorCode, lErrorReg, lErrorValue;
        SpcGetParam (deviceno, SPC_LASTERRORCODE,   &lErrorCode);
        SpcGetParam (deviceno, SPC_LASTERRORREG,    &lErrorReg);
        SpcGetParam (deviceno, SPC_LASTERRORVALUE,  &lErrorValue);
        char error_message[256];
        snprintf(error_message, sizeof(error_message),"Configuration error %d in register %d at value %d", lErrorCode, lErrorReg, lErrorValue);
        throw SpectrumMI40xxSeries_error(error_message);
    }

    /* print lines with useful information: */
    fprintf(stderr, "Spectrum MI40xx series board with %d byte memory\n", memory_size);
    fprintf(stderr,
            " impedance set to %g Ohm\n expecting trigger on id=%d ttls=0x%lx\n external clock frequency set to %g Hz\n",
            impedance,
            t_line.id,
            t_line.ttls.to_ulong(),
            (float)ext_reference_clock );
}

void SpectrumMI40xxSeries::sample_after_external_trigger(double rate, size_t samples, double sensitivity, size_t resolution) {
    throw SpectrumMI40xxSeries_error("SpectrumMI40xxSeries::sample_after_external_trigger is not jet implemented");
}



void SpectrumMI40xxSeries::collect_config_recursive(state_sequent& exp, SpectrumMI40xxSeries::Configuration& settings) {

    /* start with dummy node */
    DataManagementNode* new_branch=new DataManagementNode(NULL);
    DataManagementNode* where_to_append=new_branch;
    double parent_timeout=settings.timeout;
    settings.timeout=0.0;

    /* loop over all states and sequences */
    for (state_sequent::iterator i=exp.begin(); i!=exp.end(); ++i) {

        state* a_state=dynamic_cast<state*>(*i);
        if (a_state==NULL)
            throw SpectrumMI40xxSeries_error("state_atom not expected in sate_sequent");
        if (dynamic_cast<state_parallel*>(*i)!=NULL)
            throw SpectrumMI40xxSeries_error("state_parallel handling is not jet implemented");
        state_sequent* a_sequence=dynamic_cast<state_sequent*>(a_state);
        if (a_sequence!=NULL) {
            // found a sequence
            DataManagementNode* tmp_structure=settings.data_structure;
            settings.data_structure=where_to_append;
            collect_config_recursive(*a_sequence, settings);
            settings.data_structure=tmp_structure;
        //} /* end working on sequence */
        //else {
            // found a state, not a sequence
            settings.timeout+=a_state->length;
#if SPC_DEBUG
            fprintf(stderr,"SETTINGS %e %e\n",settings.timeout,a_state->length);
#endif
            // collect analogin sections in state
            std::list<analogin*> inputs;
            /* loop over all device definitions in a state */
            state::iterator j=a_state->begin();
            while (j!=a_state->end()) {
                analogin* input=dynamic_cast<analogin*>(*j);
                if (input!=NULL && input->id==device_id) {
                    /* collect appropiate analogin sections, forget others */
                    if (input->samples<=0 || input->sample_frequency<=0)
                        delete input;
                    else
                        inputs.push_back(input);
                    j=a_state->erase(j);
                } else
                    ++j;
            }
            if (!inputs.empty()) {
                /* evaluate the found analogin definitions */
                if (inputs.size()>1) {
                    while (!inputs.empty()) {
                        delete inputs.front();
                        inputs.pop_front();
                    }
                    throw ADC_exception("can not handle more than one analogin section per state");
                }
                /* save sampling frequency */
                if (settings.samplefreq<=0)
                    settings.samplefreq=inputs.front()->sample_frequency;
                else if (settings.samplefreq != inputs.front()->sample_frequency) {
                    while (!inputs.empty()) {
                        delete inputs.front();
                        inputs.pop_front();
                    }
                    throw ADC_exception("Sorry, but gated sampling requires same sampling frequency in all analogin sections");
                }

                /* save channel mask and number of channels */
                if (settings.lSetChannels > 0 && settings.qwSetChEnableMap.to_ulong() > 0) {
                    if (settings.qwSetChEnableMap != inputs.front()->channels) {
                        fprintf(stderr, "Warning! different channels enabled in input %lu and in config %lu, setting to default \n",
                                settings.qwSetChEnableMap.to_ulong(),
                                inputs.front()->channels.to_ulong());
                        settings.qwSetChEnableMap = channel_array(ADC_MI_DEFAULT_CHANNELS);
                        settings.lSetChannels = settings.qwSetChEnableMap.count();
                    }
                } else {
                    settings.qwSetChEnableMap = inputs.front()->channels;
                    settings.lSetChannels = inputs.front()->nchannels;
                }

                /* save sensitivity */
                if (settings.sensitivity != NULL) { // if sensitivity is set, make sure it's valid (i.e. the same for all inputs)
                    for (int k = 0; k < inputs.front()->nchannels; k++) {
                        if (settings.sensitivity[k] != inputs.front()->sensitivity[k]) {
                            fprintf(stderr, "Warning! different sensitivity specified (here %f, elsewhere %f), choosing higher voltage\n",
                                    settings.sensitivity[k],
                                    inputs.front()->sensitivity[k]);
                            if (settings.sensitivity[k] < inputs.front()->sensitivity[k]) {
                                settings.sensitivity[k] = inputs.front()->sensitivity[k];
                            }
                        }
                    }
                } else {
                    settings.sensitivity = inputs.front()->sensitivity;
                }
                // check if sensitivity is valid
                for (int k = 0; k < inputs.front()->nchannels; k++) {
                    bool sensAllowed = false;
                    for (int l = 0; l < ADC_MI_ALLOWED_SENSITIVITY_LENGTH; l++) {
                        if (settings.sensitivity[k] == ADC_MI_ALLOWED_SENSITIVITY[l] ) {
                            sensAllowed = true;
                            break;
                        }
                    }
                    if (!sensAllowed) {
                        fprintf(stderr, "Warning! Illegal sensitivity specified for channel %i: %f", k, inputs.front()->sensitivity[k]);
                        settings.sensitivity[k] = ADC_MI_DEFAULT_SENSITIVITY;
                    }
                }

                /* save impedance */
                if (settings.impedance != NULL) {
                    for (int k = 0; k < inputs.front()->nchannels; k++) {
                        if (settings.impedance[k] != inputs.front()->impedance[k]) {
                            fprintf(stderr, "Warning! different impedance specified (here %f, elsewhere %f), setting to default\n",
                                    settings.impedance[k],
                                    inputs.front()->impedance[k]);
                            settings.impedance[k] = ADC_MI_DEFAULT_IMPEDANCE;
                        }
                        if (settings.impedance[k] != ADC_MI_DEFAULT_IMPEDANCE && settings.impedance[k] != ADC_MI_ALLOWED_IMPEDANCE) {
                            fprintf(stderr, "Warning! Illegal impedance specified for channel %i: %f",k, inputs.front()->impedance[k]);
                            settings.offset[k] = 0;
                        }
                    }
                } else {
                    settings.impedance = inputs.front()->impedance;
                }

                /* save offsets */
                if (settings.offset != NULL) {
                    for (int k = 0; k < inputs.front()->nchannels; k++) {
                        if (settings.offset[k] != inputs.front()->offset[k]) {
                            fprintf(stderr, "Warning! different impedance specified (here %i, elsewhere %i), setting to default\n",
                                    settings.offset[k],
                                    inputs.front()->offset[k]);
                            settings.offset[k] = ADC_MI_DEFAULT_OFFSET;
                        }
                        if (inputs.front()->offset[k] > 100 || inputs.front()->offset[k] < -100) {
                            fprintf(stderr, "Warning! Illegal offset specified for channel %i: %i", k, inputs.front()->offset[k]);
                            settings.offset[k] = 0;
                        }
                    }
                } else {
                    settings.offset = inputs.front()->offset;
                }

                if (inputs.front()->samples%4 != 0) {
                    throw ADC_exception("Number of samples must be a multiple of four");
                }

		    // calculate the time required
		    double delayed_gating_time=0.0;
		    // the gating time has an offset, which was found to be 1.5 dwelltimes for <2.5MHz and 4.5 dwelltimes for >=2.5MHz
		    double gating_time;
		    
#if SPC_DEBUG
		    fprintf(stderr, "Channels: %lu\n", settings.qwSetChEnableMap.to_ulong());
#endif
		    
		    /* check if channel mask is legal for the card */
	        if (this->IsChannelMaskLegal(inputs.front()->channels.to_ulong())) {
    		    settings.qwSetChEnableMap = inputs.front()->channels;
			    settings.lSetChannels = inputs.front()->nchannels;
		    } else {
		        throw SpectrumMI40xxSeries_error("Selected channels combination not allowed for this card type");
		    }
		    /* apply proper timing */
		    if ( settings.qwSetChEnableMap.to_ulong()==(CHANNEL0|CHANNEL1) || settings.qwSetChEnableMap.to_ulong()==(CHANNEL0|CHANNEL1|CHANNEL2|CHANNEL3) )  {
#if SPC_DEBUG		    
                fprintf(stderr, "Default Channels\n");
#endif
		        if (settings.samplefreq<2.5e6) {
		            // if sampling rate is <2.5MHz, there is another data handling mode,
		            // see MI4021 manual page 79: "Accquisition Delay: -6 Samples"
		            // it might be necessary to add 0.1 dwelltime to shift the sampling start a little more...
		            
		            // edit by Stefan Reutter @2013-06, it seems that the MI4021 cards actually have a second 
		            // threshold at 500 kHz that is not mentioned in the manual.
		            // this can be tested by disabling the if below and switching over the threshold
		            gating_time=(inputs.front()->samples)/settings.samplefreq;
		            if (settings.samplefreq >= 5e5) {
    		            delayed_gating_time=ceil(1e8*6.0/settings.samplefreq)/1e8;
		            } else {
    		            delayed_gating_time=0.0;
		            }
		        } else {
		            gating_time=(inputs.front()->samples+3)/settings.samplefreq;
		            delayed_gating_time=0.0;
		        }
		    }
		    // disabled the more exotic channel setup as it is untested with the updated timings and probably not used anyways
		    /* else if (settings.qwSetChEnableMap.to_ulong()==CHANNEL0 || settings.qwSetChEnableMap.to_ulong()==(CHANNEL0|CHANNEL2) )  {
#if SPC_DEBUG		    
                fprintf(stderr, "Weird Channels\n");
#endif
		        if (settings.samplefreq<5e6) {
		            gating_time=(inputs.front()->samples+1.5)/settings.samplefreq;
		            delayed_gating_time=ceil(1e8*6.0/settings.samplefreq)/1e8;
        		} else {
		            gating_time=(inputs.front()->samples+4.5)/settings.samplefreq;
		            delayed_gating_time=-ceil(1e8*7.0/settings.samplefreq)/1e8;

            } */else {
		        throw SpectrumMI40xxSeries_error("Selected channels combination not allowed");
		    }
		
		    gating_time=ceil(1e8*gating_time)/1e8; 
		    double time_required=delayed_gating_time+gating_time;
		    // check time requirements
		    if (a_state->length < gating_time) {
#if SPC_DEBUG	
			    fprintf(stderr, "state is shorter than acquisition time %e time required, %e state time\n", gating_time, a_state->length);
#endif
			    // update the state length if it's shorter than the gate. this is usually due to rounding to 10 ns for the pulseblaster
		        if (ceil(1e8*(a_state->length + 3.0f/settings.samplefreq))/1e8 < time_required) { // 
		            std::stringstream parameter_inf;
		            parameter_inf << "state is   (" << inputs.front()->samples << " samples, " << settings.samplefreq << " sampling rate, " << time_required << " time required, " << a_state->length << " state time)" << std::endl;
				    throw ADC_exception(parameter_inf.str());
			    } else {
			        a_state->length = time_required;
			    }
		    }
            // if necessary, add the gating pulse delay...
            if (delayed_gating_time>0.0) {
                state* delayed_gating_state=new state(*a_state);
                delayed_gating_state->length=delayed_gating_time;
                // insert before me
                exp.insert(i,(state_atom*)delayed_gating_state);
		    } else if (delayed_gating_time < 0.0)  {
                /*
		        For +samples delays
                    1. get the previous state
                    2. if the length is not long enough (6*dw) add the state before
                    3. split the state(s) so that we have the gating on 6*dw before the actual recording
                */
                double rest_length = delayed_gating_time;
                state_sequent::iterator i_prev;
                state* prev_state;
                i_prev = i;
                do {
                    i_prev--;
                    prev_state = dynamic_cast<state*>(*(i_prev));
                    rest_length -= prev_state->length;
                    fprintf(stderr, "DW rest_length: %g\n", rest_length);
                    fprintf(stderr, "DW state_length: %g\n", prev_state->length);
                    if (rest_length >= 0)
                        prev_state->push_back(trigger_line.copy_new()); // add trigger to this state
                    else { // split final state
                        state* prev_state_1 = prev_state->copy_flat(); //create copy of current state
                        prev_state_1->length += rest_length; // adjust 1st part length
                        exp.insert(i_prev,(state_atom*) prev_state_1); // insert AFTER prev_state
                        prev_state->length = -rest_length; // adjust 2nd part length
                        prev_state->push_back(trigger_line.copy_new()); // add trigger to 2nd part
                        break;
                    }

                } while (i_prev!=exp.begin() || rest_length > 0.0);
            }
# if SPC_DEBUG
            fprintf(stderr, "sequence after pre_trigger correction:\n");
            xml_state_writer().write_states(stderr, exp);
# endif
			
		    // adapt the pulse program for gated sampling
		    if (a_state->length == gating_time) {
		        // state has proper length
		        a_state->push_back(trigger_line.copy_new());
	        } else {
# if SPC_DEBUG
	            fprintf(stderr, "state too long, length %e, time required %e\n", a_state->length, time_required);
# endif
		        // state is too long... 
		        // create new one with proper time and gated sampling pulse
		        state* gated_sampling_pulse=new state(*a_state);
		        gated_sampling_pulse->length=gating_time;
		        gated_sampling_pulse->push_back(trigger_line.copy_new());
		        // insert gate pulse state before remaining (original) state
		        exp.insert(i,(state_atom*)gated_sampling_pulse);
		        // shorten this state
		        a_state->length-=time_required;
		    }
		    
# if SPC_DEBUG
                fprintf(stderr, "sequence after correcting trigger state:\n");
                xml_state_writer().write_states(stderr, exp);
# endif
		    
		    /* save sampleno */
		    DataManagementNode* new_one=new DataManagementNode(new_branch);
		    new_one->n=inputs.front()->samples;
		    new_one->child=NULL;
		    new_one->next=where_to_append->next;
		    where_to_append->next=new_one;
		    where_to_append=new_one;
		    while (!inputs.empty()) {delete inputs.front(); inputs.pop_front();}
	    } /* !inputs.empty() */



        } /* end working on state */


    } /* i */

    /* something happened? */
    if (new_branch->next!=NULL) {
        /* make dummy node to a loop */
        new_branch->n=exp.repeat;
        new_branch->child=new_branch->next;

        /* if possible, append it */
        if (settings.data_structure!=NULL) {
            new_branch->parent=settings.data_structure->parent;
            new_branch->next=settings.data_structure->next;
            settings.data_structure->next=new_branch;
        }
        else {
            new_branch->parent=NULL;
            new_branch->next=NULL;
            settings.data_structure=new_branch;
        }
    }
    else
        delete new_branch;

    settings.timeout*=exp.repeat;
    settings.timeout+=parent_timeout;
#ifdef SPC_DEBUG
    fprintf(stderr,"setting.timout %g\n",settings.timeout);
#endif
    return;
}

static void* SpectrumMI40xxSeries_TimeoutThread(void* data) {
	int *ret = new int;
	*ret = ((SpectrumMI40xxSeries*)data)->TimeoutThread();
    return (void*) ret;
}

void SpectrumMI40xxSeries::set_daq(state & exp) {

    // cleanup?!
    if (!fifobuffers.empty()) {
        fprintf(stderr, "normally there should be a cleanup at the end of acquisition\n");
        for (std::vector<short int*>::iterator i=fifobuffers.begin(); i!=fifobuffers.end(); ++i) free(*i);
        fifobuffers.clear();
    }

    /* what could be done: PCIBIT_GATE, PCIBIT_MULTI */
    int features;
    SpcGetParam(deviceno,SPC_PCIFEATURES,&features);

    state_sequent* exp_sequence=dynamic_cast<state_sequent*>(&exp);
    if (exp_sequence==NULL)
        throw ADC_exception("Spectrum-MI40xxSeries::set_daq only working on sequences");

# if SPC_DEBUG
    fprintf(stderr, "working on sequence:\n");
    xml_state_writer().write_states(stderr, *exp_sequence);
# endif

    /* find out what to do */
    Configuration* conf=new Configuration();
    collect_config_recursive(*exp_sequence, *conf);
    if (conf->samplefreq<=0) conf->samplefreq=default_settings.samplefreq;
    if (conf->impedance == NULL) conf->impedance = default_settings.impedance;
    if (conf->sensitivity  == NULL) conf->sensitivity = default_settings.sensitivity;
    if (conf->offset  == NULL) conf->offset = default_settings.offset;

    if (conf->lSetChannels == 0) {
        conf->lSetChannels = default_settings.lSetChannels;
        conf->qwSetChEnableMap = default_settings.qwSetChEnableMap;
    }

    size_t sampleno=(conf->data_structure==NULL)?0:conf->data_structure->size();

    /* nothing to do! */
    if (sampleno==0) {
        delete conf;
        effective_settings=NULL;
        return;
    }


    if (sampleno<16 || sampleno%16!=0) {
        delete conf;
        throw SpectrumMI40xxSeries_error("Total number of samples must be multiple of 16 and at least 16");
    }

    effective_settings=conf;

#if SPC_DEBUG
    /* settings for this experiment */
    conf->print(stderr);
#endif

    /* make a check, whether spectrum board is running */
    int actual_status=0;
    SpcGetParam (deviceno, SPC_STATUS, &actual_status);
    if (actual_status!=SPC_READY) {
        fprintf(stderr, "Warning: Spectrum board was/is running before starting data aquisition.\n");
        SpcSetParam (deviceno, SPC_COMMAND,      SPC_STOP);     // stop the board
    }

    /* and the dirty things there...
       ----- setup board for recording -----
    */
    for (unsigned int j=0; j<(unsigned int)effective_settings->lSetChannels; j++) {
        SpcSetParam (deviceno, SPC_AMP0 + 100*j,    (int)floor(effective_settings->sensitivity[j]*1000));          // +/- 5V input range
        SpcSetParam (deviceno, SPC_50OHM0 + 100*j,  ((effective_settings->impedance[j]==50.0)?1:0));             // 1 = 50 Ohm input impedance, 0 = 1MOhm input impedance
        SpcSetParam (deviceno, SPC_OFFS0 +  100*j, effective_settings->offset[j]); // set offset to zero
    }

    SpcSetParam (deviceno, SPC_CHENABLE, effective_settings->qwSetChEnableMap.to_ulong()); // Enable channels for recording
    int activated_channels;
    SpcGetParam (deviceno, SPC_CHENABLE, &activated_channels);

    SpcSetParam (deviceno, SPC_SAMPLERATE,          (int)floor(effective_settings->samplefreq));      // Samplerate

    // the MI4021 card doesn't accept all sampling frequency settings. check if the sampling rate is set correctly. if not, throw an exception
    int setSamplingRate = 0;
    SpcGetParam (deviceno, SPC_SAMPLERATE,          &setSamplingRate);
    if (setSamplingRate != (int)floor(effective_settings->samplefreq)) {
        char parameter_info[16];
        snprintf(parameter_info,sizeof(parameter_info), "%d", setSamplingRate);
        throw ADC_exception(std::string("DAC sampling rate not available. Try setting to: ")+parameter_info);
    }

  // decide for aquisition mode and start it
  int16 nErr=ERR_OK;
  if (sampleno<fifo_minimal_size) {

#if SPC_DEBUG
        fprintf(stderr, "expecting %" SIZETPRINTFLETTER " samples in normal mode\n",sampleno);
#endif

      SpcSetParam (deviceno, SPC_MEMSIZE,             sampleno);      // Memory size
	  
      // ----- start the board -----
      nErr = SpcSetParam (deviceno, SPC_COMMAND,      SPC_START);     // start the board
  } else {

#if SPC_DEBUG
        fprintf(stderr, "expecting %" SIZETPRINTFLETTER " samples in fifo mode\n",sampleno);
#endif
        // todo: fifo should really write directly to recursive gated sampling structures
        fifo_adc_data=(short int*)malloc(sampleno*sizeof(short int)*2);
        if (fifo_adc_data==NULL) {
            throw SpectrumMI40xxSeries_error("could not allocate adc data memory for fifo recording");
        }
        // ToDo: do some magic calculations
        fifobufferno=16;
        fifobufferlen=1<<20;
#if SPC_DEBUG
        fprintf(stderr, "configuring for %" SIZETPRINTFLETTER " buffers, each of size %" SIZETPRINTFLETTER "\n", fifobufferno, fifobufferlen);
#endif
        SpcSetParam(deviceno,SPC_FIFO_BUFFERS,fifobufferno);
        SpcSetParam(deviceno,SPC_FIFO_BUFLEN,fifobufferlen);
        // allocate FIFO buffers
        fifobuffers.resize(fifobufferno,(short int*)NULL);
        for (size_t i=0; i!=fifobufferno; ++i) {
            void* newbuffer;
#if SPC_DEBUG
            fprintf(stderr, "allocating fifobuffer %" SIZETPRINTFLETTER "...", i);
#endif
            newbuffer=malloc(fifobufferlen);
            if (newbuffer==NULL) {
                // free the buffers, if there is not enough memory
                for (size_t j=0; j!=i; ++j) free(fifobuffers[j]);
                fifobuffers.clear();
                throw SpectrumMI40xxSeries_error("could not allocate buffers for fifo mode");
            }
#if SPC_DEBUG
            fprintf(stderr, "and registering with ADC driver....");
#endif
            fifobuffers[i]=(short int*)newbuffer;
            // todo: check for errors
#ifndef _LINUX64
            SpcSetParam (deviceno, SPC_FIFO_BUFADR0+i, (int32) newbuffer);
#else
            // 64 bit Linux needs SetAdr function, 32 bit linux can also use this if driver build > 1093
            SpcSetAdr (deviceno, SPC_FIFO_BUFADR0+i, newbuffer);
#endif
#if SPC_DEBUG
            fprintf(stderr, "success.\n");
#endif
        }
#if SPC_DEBUG
        fprintf(stderr, "starting fifo thread\n");
#endif
        // to do: append a new state to overcome problems with fifo...
        // need another thread, that collects the data
        pthread_attr_init(&timeout_pt_attrs);
        pthread_create(&timeout_pt, &timeout_pt_attrs, SpectrumMI40xxSeries_TimeoutThread,(void*)this);
    }
    // ----- driver error: request error and end program -----
    if (nErr != ERR_OK) {
        if (effective_settings!=NULL) delete effective_settings;
        effective_settings=NULL;
        for (std::vector<short int*>::iterator i=fifobuffers.begin(); i!=fifobuffers.end(); ++i) free(*i);
        fifobuffers.clear();
        // create an error message
        int32   lErrorCode, lErrorReg, lErrorValue;
        SpcGetParam (deviceno, SPC_LASTERRORCODE,   &lErrorCode);
        SpcGetParam (deviceno, SPC_LASTERRORREG,    &lErrorReg);
        SpcGetParam (deviceno, SPC_LASTERRORVALUE,  &lErrorValue);
        char error_message[256];
        snprintf(error_message, sizeof(error_message),"Configuration error %d in register %d at value %d", lErrorCode, lErrorReg, lErrorValue);
        throw SpectrumMI40xxSeries_error(error_message);
    }

}

double SpectrumMI40xxSeries::get_sample_clock_frequency() const {

    if (effective_settings==NULL ||
            effective_settings->samplefreq<=0 ||
            effective_settings->data_structure==NULL ||
            effective_settings->data_structure->size()==0)
        return 0;

    return effective_settings->samplefreq;
}

int SpectrumMI40xxSeries::TimeoutThread() {
    // now collect data
    assert(effective_settings!=NULL);
    size_t sampleno=effective_settings->data_structure->size();
    size_t buff_number_expected=(sampleno*2*sizeof(short int)+fifobufferlen-1)/fifobufferlen;
    SpcSetParam(deviceno, SPC_FIFO_BUFMAXCNT, buff_number_expected);
    size_t buff_number=0;
    short int* buff_pointer=fifo_adc_data;

    // start fifo aquisition
#if SPC_DEBUG
    fprintf(stderr, "SPC_FIFOSTART\n");
#endif
    SpcSetParam(deviceno, SPC_COMMAND, SPC_FIFOSTART);

    do {
        // wait for the buffers
        pthread_testcancel();
#if SPC_DEBUG
        fprintf(stderr, "reading buffer no %" SIZETPRINTFLETTER "/%" SIZETPRINTFLETTER "\n", buff_number+1, buff_number_expected);
#endif
        if (buff_number+1==buff_number_expected) {
            // the last one is special, copy only expected bytes
            memcpy(buff_pointer, fifobuffers[buff_number%(fifobuffers.size())], sampleno*2*sizeof(short int)-fifobufferlen*buff_number);
            break;
        }
        else
            buff_pointer=(short int*)mempcpy(buff_pointer, fifobuffers[buff_number%(fifobuffers.size())], fifobufferlen);
        SpcSetParam(deviceno,SPC_FIFO_BUFREADY, buff_number%(fifobuffers.size()));
        buff_number++;
        int buff_available;
        SpcGetParam(deviceno, SPC_FIFO_BUFCOUNT, &buff_available);
        if (((size_t)buff_available)>buff_number) {
            // get also the next buffer...
#if SPC_DEBUG
            fprintf(stderr, "read  %" SIZETPRINTFLETTER " buffers, %d buffers gained by the hardware sofar\n", buff_number, buff_available);
#endif
            continue;
        }

        int adc_status;
#if SPC_DEBUG
        fprintf(stderr, "SPC_STATUS\n");
#endif
        SpcGetParam (deviceno, SPC_STATUS, &adc_status);
        if (adc_status==SPC_READY) {
            // this must be a timeout!
            //pthread_cancel(timeout_pt);
            //pthread_join(timeout_pt,NULL);
            //pthread_attr_destroy(&timeout_pt_attrs);
            free(fifo_adc_data);
            fifo_adc_data=NULL;
            throw SpectrumMI40xxSeries_error("timeout occured while collecting data");
            return 0;
        }
#if SPC_DEBUG
        fprintf(stderr, "waiting for next fifo buffer\n");
        fprintf(stderr, "SPC_FIFOWAIT\n");
#endif
        SpcSetParam(deviceno, SPC_COMMAND, SPC_FIFOWAIT);
    }
    while (buff_number<buff_number_expected);
#if SPC_DEBUG
    fprintf(stderr, "SPC_STOP\n");
#endif
    SpcSetParam(deviceno, SPC_COMMAND, SPC_STOP);

    return 1;
}

short int* SpectrumMI40xxSeries::split_adcdata_recursive(short int* data, const DataManagementNode& structure, adc_results& result_splitted) {
    short nchannels = effective_settings->lSetChannels;
    if (structure.child==NULL) {
        // simple case: do real work
        // todo: Channel selection
        short int* datachunk=(short int*)malloc(sizeof(short int)*structure.n*nchannels);
        if (datachunk==NULL) {
            throw SpectrumMI40xxSeries_error("not enough memory to create results");
        }
        // todo: error checking
        memcpy(datachunk, data, sizeof(short int)*structure.n*nchannels);
        data+=structure.n*nchannels;
        adc_result* the_result=new adc_result(0, structure.n, datachunk, effective_settings->samplefreq,nchannels);
        result_splitted.push_back(the_result);
        if (structure.next!=NULL)
            data=split_adcdata_recursive(data, *(structure.next), result_splitted);
    }
    else
        for (size_t i=0; i<structure.n; ++i) {
            data=split_adcdata_recursive(data, *(structure.child), result_splitted);
        }
    return data;

}

result* SpectrumMI40xxSeries::get_samples(double _timeout) {

    if (core::term_signal!=0) return NULL;
    size_t sampleno=(effective_settings==NULL || effective_settings->data_structure==NULL)?0:effective_settings->data_structure->size();
    if (sampleno==0) return new adc_result(1,0,NULL);
    short nchannels=(effective_settings==NULL)?0:effective_settings->lSetChannels;
    if (nchannels==0) throw SpectrumMI40xxSeries_error("No channels specified");

//    double timeout=_timeout;

#if SPC_DEBUG
    fprintf(stderr,"effective_settings in get_samples: %g\n",effective_settings->timeout);
#endif
    // allocate a lot of space
    // todo: use GatedSampling buffers directly!

    if (fifobuffers.empty()) {
#if SPC_DEBUG
        printf("ADC not in FIFO mode\n");
#endif
        short int* adc_data=(short int*)malloc(sampleno*sizeof(short int)*nchannels);
        if (adc_data==NULL) {
            for (std::vector<short int*>::iterator i=fifobuffers.begin(); i!=fifobuffers.end(); ++i) free(*i);
            fifobuffers.clear();
            throw SpectrumMI40xxSeries_error("could not allocate adc data memory");
        }

        // simple version: standard acquisition, wait and get...
#if SPC_DEBUG
        fprintf(stderr, "fetching %" SIZETPRINTFLETTER " samples in normal mode (timeout is %g)\n",sampleno,effective_settings->timeout);
#endif
        stopwatch adc_timer;
        adc_timer.start();
        int adc_status, i;
        double elapsed_time=0;
        /*
        The gate-end signal can only be recognized at an eigth samples alignement.
        The board can record up to 7 more samples, the timeout has to be allowed to be longer
        */
        double post_gate_maxtime=(1e8*7.5/effective_settings->samplefreq)/1e8;
        i = 0;
        SpcGetParam(deviceno, SPC_STATUS, &adc_status);
        /*
        Sometimes the timeout is not enough, the reason could be that the timer
        starts running before the actual start of the pulse program because the OS is not loading and starting
               the pulseblaster immediatly. It is not possible to
        synchronize the timers for now.
        Thus, we add a generous 1s to the timeout to be on the safe side.
        */
        while (core::term_signal==0 && adc_status!=SPC_READY && elapsed_time<=(effective_settings->timeout + post_gate_maxtime + 1) ) {
            timespec sleeptime;
            sleeptime.tv_nsec=10*1000*1000; // 10 ms
            sleeptime.tv_sec=0;
            nanosleep(&sleeptime,NULL);
            SpcGetParam(deviceno, SPC_STATUS, &adc_status);
            elapsed_time=adc_timer.elapsed();
#if SPC_DEBUG
            fprintf(stderr, "(nofifo) loop %i (adc_status, t, t_out, post_gate, core): %i %g %g %g %i\n",i,adc_status, elapsed_time, effective_settings->timeout, post_gate_maxtime, core::term_signal);
#endif
            i++;
        }
#if SPC_DEBUG
        fprintf(stderr, "(nofifo) loop stopped  (adc_status, t, t_out, core): %i %g %g %i\n",adc_status, elapsed_time, effective_settings->timeout, core::term_signal);
#endif
        /* card ready to transfer samples */
        if (adc_status!=SPC_READY) {
            free(adc_data);
#if SPC_DEBUG
            fprintf(stderr, "ERROR: adc_status not ready (%i) \n",adc_status );
#endif
            throw SpectrumMI40xxSeries_error("timeout occured while collecting data");
        }
        SpcSetParam(deviceno, SPC_COMMAND, SPC_STOP);
        if (core::term_signal!=0) {
#if SPC_DEBUG
            fprintf(stderr, "received core::term_signal %i\n",core::term_signal);
#endif
            free(adc_data);
            return NULL;
        }

# if defined __linux__
        size_t data_length=SpcGetData (deviceno, 0, 0, nchannels * sampleno, 2, (dataptr) adc_data);
# if SPC_DEBUG
        fprintf(stderr, "SpcGetData returned %" SIZETPRINTFLETTER " bytes, got %" SIZETPRINTFLETTER " out of %" SIZETPRINTFLETTER " expected samples\n", data_length, data_length/sizeof(short int)/2, sampleno);
# endif
        if (nchannels*sizeof(short int)*sampleno!=data_length) {
            free(adc_data);
            throw SpectrumMI40xxSeries_error("wrong amount of data from adc card");
        }
        // current position in adc data array
# elif defined __CYGWIN__
        int16 read_result=SpcGetData (deviceno, 0, 0, nchannels * sampleno,(void*) adc_data);
# if SPC_DEBUG
        fprintf(stderr, "SpcGetData returned %d\n", read_result);
# endif
        if (read_result!=0) {
            free(adc_data);
            throw SpectrumMI40xxSeries_error("wrong amount of data from adc card");
        }
# endif
        short int* data_position=adc_data;
        // produced results
        adc_results* the_results=new adc_results(0);
        data_position=split_adcdata_recursive(data_position, *(effective_settings->data_structure), *the_results);
        if (data_position==0 || (size_t)(data_position-adc_data)!=(sampleno*nchannels)) {
            fprintf(stderr,"something went wrong while spliting data\n");
        }
        free(adc_data);
        delete effective_settings;
        effective_settings=NULL;
        return the_results;
    }
    else {
#if SPC_DEBUG
        printf("ADC in FIFO mode!\n");
        fprintf(stderr, "fetching %" SIZETPRINTFLETTER " samples in fifo mode (timeout is %g)\n",sampleno,effective_settings->timeout);
#endif
        // FIFO method
        stopwatch timer;
        timer.start();
        do {
            int32 lStatus;
            timespec sleeptime;
            sleeptime.tv_sec=0;
            sleeptime.tv_nsec=100*1000*1000;
            nanosleep(&sleeptime,NULL);
            //pthread_testcancel();
            SpcGetParam (deviceno, SPC_STATUS, &lStatus);
#if SPC_DEBUG
            fprintf(stderr, "while loop FIFO (lStatis, timer, effective_timeout): %i %g %g\n",lStatus, timer.elapsed(), effective_settings->timeout );
#endif
            if (lStatus == SPC_READY) break;
        } while (timer.elapsed()<effective_settings->timeout); //rosi _timeout
        SpcSetParam(deviceno, SPC_COMMAND, SPC_STOP);

#if SPC_DEBUG
        fprintf(stderr, "waiting for read thread terminating\n");
#endif
        /* wait for read thread */
        pthread_cancel(timeout_pt);
        pthread_join(timeout_pt,NULL);
        pthread_attr_destroy(&timeout_pt_attrs);
        /* free it's resources */
#if SPC_DEBUG
        fprintf(stderr, "freeing fifo data buffers\n");
#endif
        for (std::vector<short int*>::iterator i=fifobuffers.begin(); i!=fifobuffers.end(); ++i) free(*i);
        fifobuffers.clear();

        adc_results* the_results=NULL;
        if (fifo_adc_data!=NULL) {
            // split data to (small) pieces
            short int* data_position=fifo_adc_data;
            the_results=new adc_results(0);
            data_position=split_adcdata_recursive(data_position, *(effective_settings->data_structure), *the_results);
            if (data_position==0 || (size_t)(data_position-fifo_adc_data)!=(sampleno*nchannels)) {
                fprintf(stderr,"something went wrong while spliting data from fifo mode\n");
            }
            free(fifo_adc_data);
            fifo_adc_data=NULL;
        }
        else
            fprintf(stderr,"something went wrong while collecting data with fifo mode\n");

        delete effective_settings;
        effective_settings=NULL;
        return the_results;
    }
}

/* check whether a channel mask is legal for this board */
bool SpectrumMI40xxSeries::IsChannelMaskLegal(int mask) {
    if ( (cardType == TYP_MI4020 || cardType == TYP_MI4030) && mask != CHANNEL0) {
        return false;
    } else if ( (cardType == TYP_MI4021 || cardType == TYP_MI4031)  && mask != (CHANNEL0 | CHANNEL1) && mask != CHANNEL0) {
        return false;
    } else if ((cardType == TYP_MI4022 || cardType == TYP_MI4032) && mask != (CHANNEL0 | CHANNEL1 | CHANNEL2 | CHANNEL3) && mask != (CHANNEL0 | CHANNEL1) && mask != (CHANNEL0 | CHANNEL2) && mask != CHANNEL0) {
        return false;
    }
    return true;
}

SpectrumMI40xxSeries::~SpectrumMI40xxSeries() {
    SpcSetParam (deviceno, SPC_COMMAND,      SPC_STOP);     // stop the board
# if defined __linux__
    close (deviceno);
# elif defined __CYGWIN__
    FreeLibrary(spectrum_driver_dll);
# endif
}

