/* **************************************************************************

 Author: Achim Gaedke
 Created: March 2005

****************************************************************************/
#include "core/core.h"
#include "SpinCore-PulseBlaster24Bit.h"
#include <cmath>
#include <typeinfo>
#include <iterator>

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

/*********************************************************************

                    PulseBlaster24BitCommand

*********************************************************************/

PulseBlaster24BitCommand::PulseBlaster24BitCommand(): PulseBlasterCommand() {
}

PulseBlaster24BitCommand::PulseBlaster24BitCommand(const PulseBlaster24BitCommand& orig): PulseBlasterCommand(orig) {}

PulseBlaster24BitCommand::PulseBlaster24BitCommand(PulseBlaster24BitProgram& p): PulseBlasterCommand(p) {}

PulseBlaster24BitCommand::PulseBlaster24BitCommand(PulseBlaster24BitProgram& p,
        int _ttls, double _length)
    :PulseBlasterCommand(p, _ttls, _length)
{}

int PulseBlaster24BitCommand::write_to_file(FILE* out, size_t indent) const {

    if (program==NULL) throw pulse_exception("PulseBlaster24Bit: Command not associated with Program");
    fprintf(out,"%s<instruction ttls=\"0x%06x\" ",
            std::string(indent,' ').c_str(),ttls);
    switch(instruction) {
    case SpinCorePulseBlaster::LOOP:
        fprintf(out,"inst=\"LOOP\" loops=\"%d\"",loop_count);
        break;
    case SpinCorePulseBlaster::LONG_DELAY:
        fprintf(out,"inst=\"LONG_DELAY\" loops=\"%d\"",loop_count);
        break;
    case SpinCorePulseBlaster::BRANCH:
        fprintf(out,"inst=\"BRANCH\" address=\"%" SIZETPRINTFLETTER "\"",std::distance(program->begin(),jump));
        break;
    case SpinCorePulseBlaster::END_LOOP:
        fprintf(out,"inst=\"END_LOOP\" address=\"%" SIZETPRINTFLETTER "\"",std::distance(program->begin(),jump));
        break;
    case SpinCorePulseBlaster::JSR:
        fprintf(out,"inst=\"JSR\" address=\"%" SIZETPRINTFLETTER "\"",std::distance(program->begin(),jump));
        break;
    case SpinCorePulseBlaster::CONTINUE:
        fprintf(out,"inst=\"CONTINUE\"");
        break;
    case SpinCorePulseBlaster::STOP:
        fprintf(out,"inst=\"STOP\"");
        break;
    case SpinCorePulseBlaster::RTS:
        fprintf(out,"inst=\"RTS\"");
        break;
    case SpinCorePulseBlaster::WAIT:
        fprintf(out,"inst=\"WAIT\"");
        break;
    default:
        fprintf(out,"inst=\"%d\" instdata=\"UNKNOWN\"",instruction);
    }
    fprintf(out," length=\"%g\"/>\n",(1.0*length)/program->internal_clock_freq);
    return 1;
}

/*********************************************************************

                    PulseBlaster24BitProgram

*********************************************************************/

PulseBlaster24BitProgram::PulseBlaster24BitProgram(): PulseBlasterProgram() {
    internal_clock_freq=100.0e6;
    minimum_interval=9;
    ttl_device_id=0;
}

PulseBlaster24BitProgram::PulseBlaster24BitProgram(const SpinCorePulseBlaster24Bit& pb24) {
    internal_clock_freq=pb24.clock;
    minimum_interval=pb24.shortest_pulse;
    ttl_device_id=pb24.ttl_device_id;
}

PulseBlaster24BitProgram::PulseBlaster24BitProgram(const PulseBlaster24BitProgram& orig): PulseBlasterProgram() {
    ttl_device_id=orig.ttl_device_id;
    // be sure, there are no old list items left!
    clear();
    const_iterator orig_i;
    for (orig_i=orig.begin(); orig_i!=orig.end(); ++orig_i) {
        const PulseBlaster24BitCommand* the_command=dynamic_cast<const PulseBlaster24BitCommand*>(*orig_i);
        if (the_command==NULL) {
            throw SpinCorePulseBlaster_error("wrong command class or NULL pointer found in program");
        }
        PulseBlaster24BitCommand* new_one=new PulseBlaster24BitCommand(*the_command);
        new_one->program=this;
        push_back(new_one);
    }
    // set correct references...
    orig_i=orig.begin();
    for(iterator i=begin(); i!=end(); ++i) {
        (**i).program=this;
        if ((**i).instruction==SpinCorePulseBlaster::END_LOOP || (**i).instruction==SpinCorePulseBlaster::BRANCH || (**i).instruction==SpinCorePulseBlaster::JSR) {
            (**i).jump=i;
            advance((**i).jump,distance(orig.begin(),(**orig_i).jump)-distance(orig.begin(),orig_i));
        }
        ++orig_i;
    }
}


PulseBlasterCommand* PulseBlaster24BitProgram::create_command(const state& the_state) {
    if (typeid(the_state)==typeid(state_sequent)||typeid(the_state)==typeid(state_parallel))
        throw pulse_exception("PulseBlasterCommands can only be created from states");
    long unsigned int interval = ceil(the_state.length*internal_clock_freq);
    if ( interval  < minimum_interval) {
        fprintf(stderr,"PulseBlaster state too short (%lu < %lu ticks): %e\n", interval, \
							minimum_interval, \
							the_state.length);
        throw pulse_exception("PulseBlaster state too short");
    }
    // all states or-ed together
    int the_ttls=0;
    for (state::const_iterator i=the_state.begin(); i!=the_state.end(); ++i) {
        const ttlout* to=dynamic_cast<const ttlout*>(*i);
        if (to!=NULL && to->id==ttl_device_id) {
            the_ttls|=to->ttls.to_ulong();
        }
    }
    return new PulseBlaster24BitCommand(*this, the_ttls, the_state.length);
}

PulseBlasterCommand* PulseBlaster24BitProgram::create_command(const PulseBlasterCommand* orig) {
    PulseBlaster24BitCommand* new_command=NULL;
    if (orig==NULL) {
        new_command=new PulseBlaster24BitCommand();
        new_command->program=this;
        new_command->length=minimum_interval;
    }
    else {
        const PulseBlaster24BitCommand* command24Bit=dynamic_cast<const PulseBlaster24BitCommand*>(orig);
        if (command24Bit==NULL) throw pulse_exception("wrong PulseBlasterCommand class");
        new_command=new PulseBlaster24BitCommand(*command24Bit);
    }
    return new_command;
}


int PulseBlaster24BitProgram::write_to_file(FILE* out, size_t indent) const {
    std::string indent_string(indent,' ');
    fprintf(out,"%s<PulseBlaster24BitProgram>\n",indent_string.c_str());

    for(const_iterator i=begin(); i!=end(); ++i) {
        if (*i==NULL) throw SpinCorePulseBlaster_error("NULL pointer found in command list");
        (**i).write_to_file(out, indent+2);
    }
    fprintf(out,"%s</PulseBlaster24BitProgram>\n",indent_string.c_str());
    return 1;
}


/*********************************************************************

                    SpinCorePulseBlaster24Bit

*********************************************************************/

void SpinCorePulseBlaster24Bit::write_command(unsigned char* data, int flags, opcode inst, unsigned int inst_data, size_t delay) {
    if (inst>8)
        throw SpinCorePulseBlaster_error("instruction code not known");

    // Output, Control Word 1st Byte
    data[0]=(flags&0xff0000)>>16;
    // Output, Control Word 2nd Byte
    data[1]=(flags&0xff00)>>8;
    // Output, Control Word 3rd Byte
    data[2]=flags&0xff;
    // Data Field 1st Byte
    data[3]=(inst_data&0x0ff000)>>12;
    // Data Field 2nd Byte
    data[4]=(inst_data&0xff0)>>4;
    // Data Field 3rd Byte and opcode
    data[5]=(inst_data&0xf)<<4|(inst&0xf);
    // Delay Count 1st Byte
    data[6]=(delay&0xff000000)>>24;
    // Delay Count 2nd Byte
    data[7]=(delay&0xff0000)>>16;
    // Delay Count 3rd Byte
    data[8]=(delay&0xff00)>>8;
    // Delay Count 4th Byte
    data[9]=(delay&0xff);
    /* *** BUG-FIX (ToDo: clean solution) ***
       there is a nasty error in pulseblaster, affecting all states with 4th byte
       equal 0xff and delay >255. In this case reduce state for 10ns.
    */
    if (0 && data[9]==0xff && delay>0xff)
        data[9]=0xfe;
}



void SpinCorePulseBlaster24Bit::write_command(unsigned char* data, const PulseBlasterCommand& command) {
    const PulseBlaster24BitCommand* command24Bit=dynamic_cast<const PulseBlaster24BitCommand*>(&command);
    if (command24Bit==NULL)
        throw SpinCorePulseBlaster_error("found wrong command class in PulseBlasterProgram");

    if (command24Bit->program==NULL) throw SpinCorePulseBlaster_error("Command not associated with Program");
    unsigned int inst_data=0;
    switch(command24Bit->instruction) {
    case SpinCorePulseBlaster::CONTINUE:
    case SpinCorePulseBlaster::STOP:
    case SpinCorePulseBlaster::WAIT:
    case SpinCorePulseBlaster::RTS:
        // no parameter
        break;
    case SpinCorePulseBlaster::LOOP:
        inst_data=command24Bit->loop_count-1;
        break;
    case SpinCorePulseBlaster::LONG_DELAY:
        inst_data=command24Bit->loop_count-2;
        break;
    case SpinCorePulseBlaster::BRANCH:
    case SpinCorePulseBlaster::END_LOOP:
    case SpinCorePulseBlaster::JSR:
        inst_data=std::distance(command24Bit->program->begin(),command24Bit->jump);
        break;
    default:
        throw SpinCorePulseBlaster_error("instruction code not known");
    }
    if (command24Bit->length<3)
        throw SpinCorePulseBlaster_error("delay length too small!");
    unsigned int delay=(unsigned int)command24Bit->length-3;
    write_command(data,command24Bit->ttls,command24Bit->instruction,inst_data,delay);
}

void SpinCorePulseBlaster24Bit::write_to_device(const PulseBlaster24BitProgram& p) {
    std::string program;
    //Begin pulse program
    for (PulseBlaster24BitProgram::const_iterator c=p.begin(); c!=p.end(); ++c) {
        char command[10];
        write_command((unsigned char*)command,**c);
        program.append(command, (size_t)10);
    }
    set_program(program);
    // End of programming registers and pulse program
    set_initialized();
}

PulseBlasterProgram* SpinCorePulseBlaster24Bit::create_program(state& exp) {
    PulseBlaster24BitProgram* prog=new PulseBlaster24BitProgram();
    // the user's code
    prog->append_sequence(exp);
    return prog;
}

void SpinCorePulseBlaster24Bit::run_pulse_program(const PulseBlasterProgram& p) {
    const PulseBlaster24BitProgram* prog=dynamic_cast<const PulseBlaster24BitProgram*>(&p);
    if (prog==NULL)
        throw SpinCorePulseBlaster_error("found wrong program class in SpinCorePulseBlaster24Bit method");
    write_to_device(*prog);
    start();
}
