#include <QShortcut>
#include <QFileDialog>
#include <QDateTime>

#include <fstream>
#include <cmath>
using namespace::std;

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    cout.open(LogStreamDevice(ui->log));

    new QShortcut(QKeySequence(Qt::Key_Escape), this, SLOT(on_actionQuit_triggered()));

    //setup timers
    previewTimer = new QTimer(this);
    connect(previewTimer, SIGNAL(timeout()), this, SLOT(updatePreview()));
    statusTimer = new QTimer(this);
    connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));

    //initialize
    fx3Open();
    lastMode = CLASSIC_MODE;
    classicRepetitions = 1;
    patternRepetitions = 100;
    loadSettings( "config.txt" );
    settingsChanged();

    //connect all settings
    connect(ui->gateMode, SIGNAL(currentIndexChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->rolling, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->repetitions, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->spadoffOff, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->spadoffOn, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->rechargeOff, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->rechargeOn, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->gateOff, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->gateOn, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->numPeriods, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->spadoffIdle, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->rechargeIdle, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->gateIdle, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->gateLength, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->holdoff, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->autoHoldoff, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->shiftMode, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));

    connect(ui->uFrameCount, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->frameCount, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->bitsPerPixel, SIGNAL(currentIndexChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->uFrameDelay, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->frameDelay, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->resetBeforeFrame, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->softRate, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->writeSequence, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));

    connect(ui->finePerCoarse, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->stepSize, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));
    connect(ui->totalSteps, SIGNAL(valueChanged(int)), this, SLOT(settingsChanged()));

    //connect more ui
    connect(ui->contrastMin, SIGNAL(valueChanged(int)), this, SLOT(contrastChanged()));
    connect(ui->contrastMax, SIGNAL(valueChanged(int)), this, SLOT(contrastChanged()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_actionQuit_triggered()
{
    ofstream config("config.txt");
    writeSettings(config);
    qApp->quit();
}

void MainWindow::on_intFreq_currentIndexChanged( int refClkIndex )
{
    if( refClkIndex < 0 || refClkIndex > 7 ) return;

    uint32_t configWords[8] = {
        0x30020109, //20 MHz
        0x30050107, //25 MHz
        0x30060105, //33 MHz
        0x30030104, //40 MHz
        0x30000103, //50 MHz
        0x30070102, //66 MHz
        0x30040304, //80 MHz
        0x30010101  //100 MHz
    };
    uint32_t prg = configWords[refClkIndex];
    cout << "Clock program word: 0x" << hex << prg << dec << endl;
    fx3.sendWord(1,prg);

    double refPeriod[8] = {
        50.0,40.0,33.333,25.0,20.0,16.666,12.5,10.0
    };
    ui->refPeriod->setText(QString::number(refPeriod[refClkIndex]));

    fx3ReadStatus();
    settingsChanged();
}

void MainWindow::on_useExt_clicked(bool state)
{
    fx3SyncClock(state);
}

void MainWindow::on_fineShiftInc_clicked()
{
    if( !(fx3.incShift(1)&0x20000000) ) {
        ui->fineShiftPos->setValue(ui->fineShiftPos->value()+1);
    }
    fx3ReadStatus();
}

void MainWindow::on_fineShiftDec_clicked()
{
    if( !(fx3.decShift(1)&0x20000000) ) {
        ui->fineShiftPos->setValue(ui->fineShiftPos->value()-1);
    }
    fx3ReadStatus();
}

void MainWindow::on_fineShiftReset_clicked()
{
    on_useExt_clicked(ui->useExt->isChecked());
    on_fineShiftResetPos_clicked();
}

void MainWindow::on_fineShiftResetPos_clicked()
{
    ui->fineShiftPos->setValue(0);
}

void MainWindow::on_folderButton_clicked()
{
    ui->folderDisp->setText(QFileDialog::getExistingDirectory(this,"Choose save folder", ui->folderDisp->text()));
}

void MainWindow::settingsChanged()
{
    int32_t refClkIndex = ui->intFreq->currentIndex();
    if( refClkIndex < 0 || refClkIndex > 7 ) return;
    uint32_t maxPeriods[8] = {
        1, 1, 2, 2, 3, 4, 4, 7
    };
    ui->numPeriods->setMaximum(maxPeriods[refClkIndex]);
    uint32_t baseSegments[8] = {
        10, 8, 6, 5, 4, 3, 5, 2
    };
    uint32_t segments = ui->numPeriods->value()*baseSegments[refClkIndex];
    if( refClkIndex == 6 ) { //80 MHz does not divide 200 MHz (use even multiple for pattern)
        segments = (ui->numPeriods->value()+1)/2*baseSegments[refClkIndex];
    }
    ui->patternInput->setSegments(segments);

    if( ui->rolling->isChecked() ) {
        ui->repetitions->setValue(1);
        ui->repetitions->setEnabled(false);
    }
    else {
        if( !ui->repetitions->isEnabled() || lastMode != ui->gateMode->currentIndex() ) {
            ui->repetitions->setEnabled(true);
            lastMode = (GateMode)ui->gateMode->currentIndex();
            if( ui->gateMode->currentIndex() == CLASSIC_MODE ) {
                ui->repetitions->setValue(classicRepetitions);
            }
            else {
                ui->repetitions->setValue(patternRepetitions);
            }
        }
        else {
            if( ui->gateMode->currentIndex() == CLASSIC_MODE ) {
                classicRepetitions = ui->repetitions->value();
            }
            else {
                patternRepetitions = ui->repetitions->value();
            }
        }
    }

    //Read classic timing
    uint32_t classicTiming[6];
    classicTiming[0] = ui->spadoffOff->value();
    classicTiming[1] = ui->spadoffOn->value();
    classicTiming[2] = ui->rechargeOff->value();
    classicTiming[3] = ui->rechargeOn->value();
    classicTiming[4] = ui->gateOff->value();
    classicTiming[5] = ui->gateOn->value();
    ui->spadoffOffNs->setText(QString::number(classicTiming[0]*5+5));
    ui->spadoffOnNs->setText(QString::number(classicTiming[1]*5+5));
    ui->rechargeOffNs->setText(QString::number(classicTiming[2]*5+5));
    ui->rechargeOnNs->setText(QString::number(classicTiming[3]*5+5));
    ui->gateOffNs->setText(QString::number(classicTiming[4]*5+5));
    ui->gateOnNs->setText(QString::number(classicTiming[5]*5+5));

    uint32_t maxClassic = 0;
    for( uint32_t i = 0; i < 6; ++i ) {
        if( classicTiming[i] > maxClassic ) {
            maxClassic = classicTiming[i];
        }
    }

    if( ui->gateMode->currentIndex() == CLASSIC_MODE ) {
        ui->gateLength->setEnabled(true);
        ui->gateLength->setMinimum(maxClassic+1);
        ui->gateLength->setMaximum(200000);
        double duty = 100.0*fabs(classicTiming[4]-classicTiming[5])/((double)ui->gateLength->value());
        ui->estDuty->setText(QString::number(duty,'f',1));
    }
    else {
        ui->gateLength->setEnabled(false);
        ui->gateLength->setMinimum(2);
        ui->gateLength->setValue(segments);
    }
    ui->gateLengthNs->setText(QString::number(ui->gateLength->value()*5));

    if( ui->autoHoldoff->isChecked() ) {
        ui->holdoff->setEnabled(false);
    }
    else {
        ui->holdoff->setEnabled(true);
    }

    //Max 1ms shutter
    ui->repetitions->setMaximum(200000/ui->gateLength->value());
    ui->totalGateUs->setText(QString::number(ui->gateLength->value()*ui->repetitions->value()*0.005));

    //Recording settings
    ui->uFrameDelayUs->setText(QString::number(ui->uFrameDelay->value()*0.01));
    ui->frameDelayUs->setText(QString::number(ui->frameDelay->value()*0.01));

    uint64_t cycPerUFrame = 512 + ui->uFrameDelay->value();
    if( !ui->rolling->isChecked() )
        cycPerUFrame += ui->gateLength->value()*ui->repetitions->value()/2;
    uint64_t cycPerFrame = cycPerUFrame*ui->uFrameCount->value() + ui->frameDelay->value();
    if( ui->resetBeforeFrame->isChecked() )
        cycPerFrame += 512;
    ui->fpsDisp->setText(QString::number(100000000.0/cycPerFrame));
    uint64_t bitsPerFrame = 512*128*(1<<ui->bitsPerPixel->currentIndex()) + 32;
    if( ui->softRate->isChecked() )
        bitsPerFrame += 32;
    ui->mbpsDisp->setText(QString::number(bitsPerFrame/(8388608.0)*100000000.0/cycPerFrame));
    ui->sizeDisp->setText(QString::number(bitsPerFrame/(8388608.0)*ui->frameCount->value()));

    ui->stripHeader->setEnabled(ui->writeSequence->isChecked());
    ui->padBytes->setEnabled(ui->writeSequence->isChecked() && ui->bitsPerPixel->currentIndex()<3);

    //FLIM settings
    ui->coarseStart->setMaximum(segments*4-1);
    uint32_t numSteps = ui->totalSteps->value()*ui->stepSize->value();
    double shiftNs = 1.25*(numSteps-1)/ui->finePerCoarse->value();
    ui->totalShift->setText(QString::number(shiftNs));
    ui->totalData->setText(QString::number(bitsPerFrame/(8388608.0)*ui->frameCount->value()*ui->totalSteps->value()));
}

void MainWindow::applySettings( uint32_t flimShift )
{
    int32_t refClkIndex = ui->intFreq->currentIndex();
    if( refClkIndex < 0 || refClkIndex > 7 ) return;

    unsigned defstate = 0;
    if( ui->spadoffIdle->isChecked() ) {
        defstate |= 1;
    }
    if( ui->rechargeIdle->isChecked() ) {
        defstate |= 2;
    }
    if( ui->gateIdle->isChecked() ) {
        defstate |= 4;
    }
    fx3.shutterIdleState( defstate );

    unsigned length = ui->gateLength->value();
    unsigned repetitions = ui->repetitions->value();
    unsigned holdoff = 0;
    if( !ui->autoHoldoff->isChecked() ) {
        holdoff = ui->holdoff->value();
    }

    if( ui->gateMode->currentIndex() == CLASSIC_MODE ) {
        unsigned spadoff_on_value = ui->spadoffOn->value();
        unsigned spadoff_off_value = ui->spadoffOff->value();
        unsigned recharge_on_value = ui->rechargeOn->value();
        unsigned recharge_off_value = ui->rechargeOff->value();
        unsigned gate_on_value = ui->gateOn->value();
        unsigned gate_off_value = ui->gateOff->value();

        fx3.set_classic_shutter( length-2, repetitions-1, spadoff_on_value, spadoff_off_value,
            recharge_on_value, recharge_off_value, gate_on_value, gate_off_value );
    }
    else {
        if( ui->autoHoldoff->isChecked() ) {
            holdoff = 1;
            repetitions += 1;
        }
        uint64_t spadoff, recharge, gate;
        ui->patternInput->getRotatedPattern( spadoff, recharge, gate, ui->coarseStart->value() + flimShift );
        fx3.set_laser_shutter( length-2, repetitions-1, spadoff, recharge, gate );
    }

    fx3.setShutterHoldoff( holdoff );

    fx3.setContShutter(ui->rolling->isChecked());

    fx3.setMaxCount( ui->uFrameCount->value() );
    fx3.setImageCount( ui->frameCount->value()-1 );
    fx3.setBitWidth( 1<<ui->bitsPerPixel->currentIndex() );
    wordsPerFrame = 512*128*(1<<ui->bitsPerPixel->currentIndex())/32+1;
    if( ui->softRate->isChecked() )
        wordsPerFrame += 1;
    fx3.setImageLength(wordsPerFrame);
    fx3.setFrameDelay( ui->uFrameDelay->value() );
    fx3.setInterFrameDelay( ui->frameDelay->value() );
    fx3.setSoftRate( ui->softRate->isChecked() );
    fx3.setResetBeforeFrame( ui->resetBeforeFrame->isChecked() );
    fx3.setShiftMode( ui->shiftMode->isChecked() );

    fx3.descramblerOn();

    fx3.setMemIncDelay(ui->readoutLatency->value());
    fx3.updateTiming(ui->readoutPattern->value());

    uint64_t cycPerUFrame = 512 + ui->uFrameDelay->value();
    if( !ui->rolling->isChecked() )
        cycPerUFrame += ui->gateLength->value()*ui->repetitions->value()/2;
    uint64_t cycPerFrame = cycPerUFrame*ui->uFrameCount->value() + ui->frameDelay->value();
    if( ui->resetBeforeFrame->isChecked() )
        cycPerFrame += 512;
    frameTimeout = cycPerFrame/100000;
}

void MainWindow::on_previewButton_clicked()
{
    if(!ui->previewButton->isChecked())
    {
        previewTimer->stop();
    }
    else
    {
        updatePreview();
    }
}

void MainWindow::on_saveButton_clicked()
{
    QString filename = QFileDialog::getSaveFileName(this,"Choose save file name",ui->folderDisp->text()+"/live.png");
    if(filename.isNull()) return;
    ui->liveView->saveImage(filename);
}

void MainWindow::on_loadSettingsButton_clicked()
{
    QString filename = QFileDialog::getOpenFileName(this,"Choose settings file",ui->folderDisp->text());
    if(filename.isNull()) return;
    loadSettings(filename.toStdString());
}

void MainWindow::on_recordButton_clicked()
{
    recSequence();
}

void MainWindow::recSequence(uint32_t flimShift)
{
    QString prefix = ui->folderDisp->text() + "/" + QDateTime::currentDateTime().toString("yyMMdd_hhmmsszzz");

    applySettings(flimShift);
    fx3.clrMem();
    fx3.setMultiFrameMode( true );

    uint64_t recordSize = ui->frameCount->value()*wordsPerFrame + 1;

    uint32_t* receiveBuffer = new uint32_t[recordSize];
    if( !receiveBuffer ) {
        cerr << "Could not allocate memory." << endl;
        return;
    }

    timeval start, end;
    gettimeofday(&start,0);
    fx3.startCap(); //acquire images
    fx3.sendWord( 3, 6 ); //push out data
    int64_t total = fx3.receive( recordSize, receiveBuffer, 200+frameTimeout*ui->frameCount->value() );
    gettimeofday(&end,0);

    cout << prefix.toStdString() << endl;
    cout << total/256.0 << "k of memory bytes received" << endl;
    double secs = end.tv_sec-start.tv_sec + (end.tv_usec-start.tv_usec)/1000000.0;
    cout << "Elapsed time: " << secs << " seconds. (" << (total/(256.0*1024.0))/secs << "MB/s)" << endl;

    if( fx3GetLinkQuality() ) {
        ui->statusBar->showMessage( "Connection error. Please check cable and retry.", 3000 );
        cout << "Problems with USB connection detected. Possible data corruption." << endl;
        cout << "FPGA reset, data discarded. Check your USB cable." << endl;
        fx3Reset();
        delete[] receiveBuffer;
        return;
    }

    bool writeSequence = ui->writeSequence->isChecked();
    bool stripHeader = ui->stripHeader->isChecked();
    uint32_t bpp = 1<<ui->bitsPerPixel->currentIndex();
    bool padBytes = ui->padBytes->isChecked() && bpp < 8;
    bool writeHeader = ui->writeHeader->isChecked();
    ofstream sequence, headers;
    if( writeSequence ) {
        sequence.open( (prefix+"_sequence.bin").toStdString(), ios::out|ios::binary );
        if( !sequence ) {
            cout << "Could not open " + prefix.toStdString() + "_seq.bin" << endl;
            writeSequence = false;
        }
    }
    if( writeHeader ) {
        headers.open( (prefix+"_headers.csv").toStdString(), ios::out );
        if( !headers ) {
            cout << "Could not open " + prefix.toStdString() + "_headers.csv" << endl;
            writeHeader = false;
        }
    }
    if( ui->writeSettings->isChecked() ) {
        ofstream settings( (prefix+"_settings.txt").toStdString(), ios::out );
        if( !settings ) {
            cout << "Could not open " + prefix.toStdString() + "_settings.txt" << endl;
        }
        else {
            writeSettings(settings);
            settings.close();
            cout << "Wrote settings to " + prefix.toStdString() + "_settings.txt" << endl;
        }
    }

    uint32_t headerSize = wordsPerFrame&0xf;
    uint32_t offset = 0, frameCount = 0;
    while( offset+wordsPerFrame <= total ) {
        uint32_t frameNumber = receiveBuffer[offset+headerSize-1];
        if( writeHeader ) {
            headers << frameNumber;
            for( uint32_t i = 1; i < headerSize; ++i ) {
                headers << ',' << receiveBuffer[offset+i-1];
            }
            headers << endl;
        }
        if( writeSequence ) {
            if( !stripHeader ) {
                sequence.write( (char*)&receiveBuffer[offset], headerSize*4 );
            }
            if( !padBytes ) {
                sequence.write( (char*)&receiveBuffer[offset+headerSize], (wordsPerFrame-headerSize)*4 );
            }
            else {
                uint8_t* index = (uint8_t*)&receiveBuffer[offset+headerSize];
                for( uint32_t row = 0; row < 128; ++row ) {
                    for( uint32_t col = 0; col < 512; ++col ) {
                        uint8_t value = 0;
                        switch( bpp ) {
                            case 1:
                                if( (col&7) == 0 )
                                    value = (index[row*64+col/8]&0x01)<<7;
                                else if( (col&7) == 1 )
                                    value = (index[row*64+col/8]&0x02)<<6;
                                else if( (col&7) == 2 )
                                    value = (index[row*64+col/8]&0x04)<<5;
                                else if( (col&7) == 3 )
                                    value = (index[row*64+col/8]&0x08)<<4;
                                else if( (col&7) == 4 )
                                    value = (index[row*64+col/8]&0x10)<<3;
                                else if( (col&7) == 5 )
                                    value = (index[row*64+col/8]&0x20)<<2;
                                else if( (col&7) == 6 )
                                    value = (index[row*64+col/8]&0x40)<<1;
                                else if( (col&7) == 7 )
                                    value = (index[row*64+col/8]&0x80);
                                break;
                            case 2:
                                if( (col&0x3) == 0 )
                                    value = (index[row*128+col/4]&0x03)<<6;
                                else if( (col&0x3) == 1 )
                                    value = (index[row*128+col/4]&0x0C)<<4;
                                else if( (col&0x3) == 2 )
                                    value = (index[row*128+col/4]&0x30)<<2;
                                else
                                    value = (index[row*128+col/4]&0xC0);
                                break;
                            case 4:
                                if( (col&0x1) == 0 )
                                    value = (index[row*256+col/2]&0x0f)<<4;
                                else
                                    value = (index[row*256+col/2]&0xf0);
                                break;
                        }
                        sequence.write( (char*)&value, 1 );
                    }
                }
            }
        }
        offset += wordsPerFrame;
        frameCount += 1;
    }

    ui->statusBar->showMessage( QString("%1 frames received.").arg(frameCount), 3000 );
    cout << frameCount << " frames received." << endl;
    if( writeHeader ) {
        headers.close();
        cout << "Wrote headers to " + prefix.toStdString() + "_headers.csv" << endl;
    }
    if( writeSequence ) {
        sequence.close();
        cout << "Wrote sequence to " + prefix.toStdString() + "_sequence.bin" << endl;
    }

    delete[] receiveBuffer;
}

void MainWindow::on_doFLIM_clicked()
{
    bool useExt = ui->useExt->isChecked();

    //Reset shift DCM
    uint32_t dcmPos = 0;
    fx3SyncClock(useExt);
    uint32_t dcmOffset = ui->finePerCoarse->value()/2;
    fx3.decShift(dcmOffset);

    uint32_t numSteps = ui->totalSteps->value();
    uint32_t stepSize = ui->stepSize->value();
    uint32_t finePerCoarse = ui->finePerCoarse->value();
    for( uint32_t step = 0; step < numSteps; ++step ) {
        uint32_t dcmTarget = step*stepSize%finePerCoarse;
        //Shift DCM to next value
        uint32_t dcmStatus = 0;
        if( dcmTarget > dcmPos ) {
            dcmStatus = fx3.incShift(dcmTarget-dcmPos);
        }
        else {
            dcmStatus = fx3.decShift(dcmPos-dcmTarget);
        }
        dcmPos = dcmTarget;
        if( (dcmStatus&0x20000000) ) {
            ui->statusBar->showMessage("DCM shift limit reach. Acquisition aborted.");
        }

        //Record sequence
        uint32_t flimShift = step*stepSize/finePerCoarse;
        recSequence(flimShift);
    }
}

void MainWindow::updatePreview()
{
    fx3GetPreview();
    fx3ReadStatus();
    if(ui->previewButton->isChecked())
    {
        previewTimer->start(200);
    }
}

void MainWindow::contrastChanged()
{
    ui->contrastLabel->setText(QString("%1 : %2").arg(ui->contrastMin->value()).arg(ui->contrastMax->value()));
}

void MainWindow::updateStatus()
{
    fx3ReadStatus(false);
    if( ui->tabWidget->currentIndex() == 1 || ui->tabWidget->currentIndex() == 3 ) {
        statusTimer->start(200);
    }
}

void MainWindow::on_tabWidget_currentChanged(int index)
{
    if( index == 0 ) {
        if(ui->previewButton->isChecked()) {
            updatePreview();
        }
    }
    else {
        previewTimer->stop();
    }

    if( index == 1 || index == 3 ) {
        updateStatus();
    }
    else {
        statusTimer->stop();
    }
}

void MainWindow::on_capFalling_clicked(bool checked)
{
    fx3.sendWord(5,checked?1:0);
}

void MainWindow::on_capCal_clicked()
{
    fx3.sendWord(5,0x40000001);
}

void MainWindow::on_capReset_clicked()
{
    fx3.sendWord(5,0x50000001);
}

void MainWindow::on_capInc_clicked()
{
    fx3.sendWord(5,0x10000101);
}

void MainWindow::on_capDec_clicked()
{
    fx3.sendWord(5,0x10000001);
}

void MainWindow::on_voidWarranty_clicked(bool checked)
{
    static bool really = false;
    static bool voided = false;
    if( checked ) {
        if( really ) {
            voided = true;
        }
        else {
            really = true;
            ui->voidWarranty->setText("Really?");
            ui->voidWarranty->setChecked(false);
        }
    }
    if( voided ) {
        ui->voidWarranty->setText("Warranty void");
        ui->voidWarranty->setChecked(true);
        ui->readoutLatency->setEnabled(true);
        ui->readoutPattern->setEnabled(true);
        ui->capFalling->setEnabled(true);
        ui->capCal->setEnabled(true);
        ui->capReset->setEnabled(true);
        ui->capInc->setEnabled(true);
        ui->capDec->setEnabled(true);
    }
}

void MainWindow::writeSettings(ostream& config)
{
    //config << "nextscan " << nextscan << endl;
    config << "window " << pos().x() << ' ' << pos().y() << ' ' << size().width() << ' ' << size().height();
    config << ' ' << ui->tabWidget->currentIndex() << endl;
    uint64_t spadoff, recharge, gate;
    ui->patternInput->getPattern( spadoff, recharge, gate, false );
    config << "spadoff " << (ui->spadoffIdle->isChecked()?"1 ":"0 ") << spadoff;
    config << ' ' << ui->spadoffOn->value() << ' ' << ui->spadoffOff->value() << endl;
    config << "recharge " << (ui->rechargeIdle->isChecked()?"1 ":"0 ") << recharge;
    config << ' ' << ui->rechargeOn->value() << ' ' << ui->rechargeOff->value() << endl;
    config << "gate " << (ui->gateIdle->isChecked()?"1 ":"0 ") << gate;
    config << ' ' << ui->gateOn->value() << ' ' << ui->gateOff->value() << endl;
    config << "gating " << ui->gateMode->currentIndex() << (ui->rolling->isChecked()?" 1 ":" 0 ");
    config << (ui->gateMode->currentIndex() == CLASSIC_MODE ? ui->gateLength->value() : ui->numPeriods->value());
    config << ' ' << ui->repetitions->value() << endl;
    config << "clocking " << ui->intFreq->currentIndex() << (ui->useExt->isChecked()?" 1 ":" 0 ");
    config << classicRepetitions << ' ' << patternRepetitions << endl;
    config << "holdoff " << ui->holdoff->value() << (ui->autoHoldoff->isChecked()?" 1 ":" 0 ") << endl;
    config << "imaging " << ui->bitsPerPixel->currentIndex() << ' ' << ui->uFrameCount->value() << ' ';
    config << ui->frameCount->value() << ' ' << ui->uFrameDelay->value();
    config << ' ' << ui->frameDelay->value() << ' ' << (ui->resetBeforeFrame->isChecked()?"1 ":"0 ") << endl;
    config << "readout " << (ui->softRate->isChecked()?"1 ":"0 ") << (ui->shiftMode->isChecked()?"1 ":"0 ");
    config << (ui->writeSequence->isChecked()?"1 ":"0 ") << (ui->stripHeader->isChecked()?"1 ":"0 ") << (ui->padBytes->isChecked()?"1 ":"0 ");
    config << (ui->writeHeader->isChecked()?"1 ":"0 ") << (ui->writeSettings->isChecked()?"1 ":"0 ");
    config << "flim " << ui->coarseStart->value() << ' ' << ui->finePerCoarse->value() << ' ';
    config << ui->stepSize->value() << ' ' << ui->totalSteps->value() << endl;
    config << "basepath " << ui->folderDisp->text().toStdString() << endl;
}

void MainWindow::loadSettings( const string& filename )
{
    ifstream config( filename );
    while( config ) {
        string setting;
        config >> setting;
        if( setting == "window" ) {
            int32_t x, y, w, h, t;
            config >> x >> y >> w >> h >> t;
            move(x,y);
            resize(w,h);
            ui->tabWidget->setCurrentIndex(t);
        }
        else if( setting == "gating" ) {
            unsigned int temp;
            config >> temp;
            ui->gateMode->setCurrentIndex(temp);
            config >> temp;
            ui->rolling->setChecked(temp != 0);
            config >> temp;
            if( ui->gateMode->currentIndex() == CLASSIC_MODE ) {
                ui->gateLength->setValue(temp);
            }
            else {
                ui->numPeriods->setValue(temp);
            }
            config >> temp;
            ui->repetitions->setValue(temp);
            if( ui->gateMode->currentIndex() == CLASSIC_MODE ) {
                classicRepetitions = temp;
            }
            else {
                patternRepetitions = temp;
            }
        }
        else if( setting == "clocking" ) {
            int32_t temp;
            config >> temp;
            if( temp != ui->intFreq->currentIndex() )
                ui->intFreq->setCurrentIndex(temp);
            else
                on_intFreq_currentIndexChanged(temp);
            config >> temp;
            ui->useExt->setChecked(temp != 0);
            config >> classicRepetitions;
            config >> patternRepetitions;
        }
        else if( setting == "holdoff" ) {
            unsigned temp;
            config >> temp;
            ui->holdoff->setValue(temp);
            config >> temp;
            ui->autoHoldoff->setChecked(temp != 0);
        }
        else if( setting == "spadoff" ) {
            uint64_t temp;
            config >> temp;
            ui->spadoffIdle->setChecked(temp != 0);
            config >> temp;
            ui->patternInput->setSpadoff(temp);
            config >> temp;
            ui->spadoffOn->setValue(temp);
            config >> temp;
            ui->spadoffOff->setValue(temp);
        }
        else if( setting == "recharge" ) {
            uint64_t temp;
            config >> temp;
            ui->rechargeIdle->setChecked(temp != 0);
            config >> temp;
            ui->patternInput->setRecharge(temp);
            config >> temp;
            ui->rechargeOn->setValue(temp);
            config >> temp;
            ui->rechargeOff->setValue(temp);
        }
        else if( setting == "gate" ) {
            uint64_t temp;
            config >> temp;
            ui->gateIdle->setChecked(temp != 0);
            config >> temp;
            ui->patternInput->setGate(temp);
            config >> temp;
            ui->gateOn->setValue(temp);
            config >> temp;
            ui->gateOff->setValue(temp);
        }
        else if( setting == "imaging" ) {
            unsigned temp;
            config >> temp;
            ui->bitsPerPixel->setCurrentIndex(temp);
            config >> temp;
            ui->uFrameCount->setValue(temp);
            config >> temp;
            ui->frameCount->setValue(temp);
            config >> temp;
            ui->uFrameDelay->setValue(temp);
            config >> temp;
            ui->frameDelay->setValue(temp);
            config >> temp;
            ui->resetBeforeFrame->setChecked(temp != 0);
        }
        else if( setting == "readout" ) {
            unsigned temp;
            config >> temp;
            ui->softRate->setChecked(temp != 0);
            config >> temp;
            ui->shiftMode->setChecked(temp != 0);
            config >> temp;
            ui->writeSequence->setChecked(temp != 0);
            config >> temp;
            ui->stripHeader->setChecked(temp != 0);
            config >> temp;
            ui->padBytes->setChecked(temp != 0);
            config >> temp;
            ui->writeHeader->setChecked(temp != 0);
            config >> temp;
            ui->writeSettings->setChecked(temp != 0);
        }
        else if( setting == "flim" ) {
            unsigned temp;
            config >> temp;
            ui->coarseStart->setValue(temp);
            config >> temp;
            ui->finePerCoarse->setValue(temp);
            config >> temp;
            ui->stepSize->setValue(temp);
            config >> temp;
            ui->totalSteps->setValue(temp);
        }
        else if( setting == "basepath" ) {
            string temp;
            getline( config, temp );
            while(isspace(temp[0])) { temp = temp.substr(1); }
            ui->folderDisp->setText(QString::fromStdString(temp));
        }
    }
}

void MainWindow::fx3Connected()
{
    ui->previewButton->setEnabled(true);
}

void MainWindow::fx3Disconnected()
{
    ui->previewButton->setDisabled(true);
}

void MainWindow::fx3Open()
{
    fx3.init(0x4b4,0xf1);
    if(fx3)
    {
        fx3ReadTimestamp();
        fx3ReadStatus();
        fx3Connected();
    }
}

void MainWindow::fx3Reset()
{
    fx3.reset();
    if(fx3)
    {
        fx3ReadTimestamp();
        fx3ReadStatus();
        fx3Connected();
    }
}

uint32_t MainWindow::fx3GetLinkQuality()
{
    uint32_t counts[3];
    fx3.controlTransfer( 0xC0, FX3_GET_LINK_QUALITY_COUNTS, 0, 0, (unsigned char*)counts, 12 );
    fx3.controlTransfer( 0xC0, FX3_RESET_LINK_QUALITY_COUNTS, 0, 0, 0, 0 );
    return counts[0]|counts[1]|counts[2];
}

void MainWindow::fx3ReadTimestamp()
{
    uint32_t id;
    fx3.controlTransfer( 0xC0, FX3_GET_FIRMWARE_TIMESTAMP, 0, 0, (unsigned char*)&id, 4 );
    ui->fx3FwTimestamp->setText(QString::number(id,16));
    fx3.sendWord( 15, 0x00000000 );
    if( 1 != fx3.receive(1, &id) ) {
        cerr << "Read timestamp error." << endl;
        return;
    }
    ui->fpgaFwTimestamp->setText(QString::number(id,16));
}

void MainWindow::fx3ReadStatus(bool print)
{
    uint32_t cmd[5] = {
        FX3_FPGA_HEADER(1,2),
        0x10000000,
        0x00000000,
        FX3_FPGA_HEADER(5,1),
        0xe0000000,
    };
    fx3.send(5,cmd);
    uint32_t recvdata[3];
    if( 3 != fx3.receive( 3, recvdata ) ) {
        cerr << "Read status error." << endl;
        return;
    }

    if(print)
    {
        cout << "External frequency: " << recvdata[0]*1000.0 << " Hz" << endl;
        cout << "Status: 0x" << setw(8) << setfill('0') << hex << recvdata[1] << dec << endl;
        cout << "External: " << (recvdata[1]&0x1 ? "yes" : "no") << endl;
        cout << "Locked: " << (recvdata[1]&0x8 ? "yes" : "no") << endl;
    }

    if( !(recvdata[1]&0x80) ) {
        ui->statusBar->showMessage("Clock not programmed.");
    }
    else if( (recvdata[1]&0x20000000) ) {
        ui->statusBar->showMessage("Phase shift limit reached.", 3000);
    }
    else if( (recvdata[1]&0x9)==0x9 && !ui->useExt->isChecked() ) {
        ui->statusBar->showMessage("Clock locked to external reference.");
    }
    else if( (recvdata[1]&0x9)==0x8 && ui->useExt->isChecked() ) {
        ui->statusBar->showMessage("Clock locked to internal reference.");
    }
    ui->extFreq->setText(QString::number(recvdata[0]/1000.0,'f',3));
    ui->useExt->setChecked((recvdata[1]&0xf)==0xf);

    ui->capState->setText(QString::number(recvdata[2],16));

    fx3GetPhase();
}

double MainWindow::fx3GetPhase()
{
    uint32_t cmd[9] = {
        FX3_FPGA_HEADER(6,8),
        0x10000002,
        0x10000002,
        0x10000002,
        0x10000002,
        0x10000002,
        0x10000002,
        0x10000002,
        0x10000002
    };
    fx3.send(9,cmd);
    uint32_t recvdata[8];
    uint32_t count = fx3.receive( 8, recvdata );
    if( count != 8 ) {
        cerr << "Read phase error." << endl;
        return 200.0;
    }
    uint32_t avg = 0;
    for( uint32_t i = 0; i < 8; ++i ) {
        avg += recvdata[i];
    }
    ui->tdcAvg->setText(QString::number(avg/(8.0*256.0)));
    return avg/(8.0*256.0);
}

void MainWindow::fx3SyncClock(bool external)
{
    fx3.sendWord(1, 0x20000000|(external?1:0));
    fx3ReadStatus(true);
}

void MainWindow::fx3GetPreview()
{
    static uint32_t nextframenumber = 0;

    unsigned maxcount = ui->uFrameCount->value();
    if( maxcount > 255  ) maxcount = 255;

    //Read new frame
    applySettings();

    fx3.setMultiFrameMode( false ); //single frame mode
    fx3.setSoftRate( false );
    fx3.setImageLength((512*128+4)/4);
    fx3.clrMem();
    fx3.incMem( maxcount );
    fx3.readMem( 8 );
    fx3.sendWord(3,6);

    unsigned char img[128*512+8];
    int size = fx3.receive( 128*512+8, img, 200+frameTimeout );
    if( size != 128*512+8 ) {
        cerr << "Read preview error." << endl;
        return;
    }
    unsigned framenumber = ((img[3]<<24)|(img[2]<<16)|(img[1]<<8)|(img[0]<<0));
    if( nextframenumber && framenumber-nextframenumber != 0 ) {
        cerr << "Unexpected frame number." << endl;
        cerr << "Got: " << framenumber << ", expected: " << nextframenumber << endl;
    }
    nextframenumber = framenumber+1;

    double cmin = ui->contrastMin->value();
    double cmax = ui->contrastMax->value();
    double diff = cmax-cmin;
    if(diff < 1.0) diff = 1.0;

    unsigned mult = 256/(maxcount+1);
    unsigned int avg = 0;
    for( int pix = 0; pix < 512*128; ++pix ) {
        double cval = img[4+pix]*mult;
        cval = (cval-cmin)*(255.0/diff);
        if(cval < 0 ) cval = 0;
        unsigned val = cval+.5;
        if( val > 255 ) val = 255;
        avg += val;
        img[4+pix] = val;
    }

    ui->liveView->setData(img+4);
    ui->avgDisp->setText(QString::number(avg/(512.0*128.0)));
}
