#include "DataCalib.h"

#include "FAD.h"
#include "FitsFile.h"
#include "DimDescriptionService.h"

#include "externals/fits.h"

using namespace std;

int  DataCalib::fStep = 0;
bool DataCalib::fProcessing = false;

vector<int32_t> DataCalib::fOffset(1440*1024, 0);
vector<int64_t> DataCalib::fGain  (1440*1024, 4096);
vector<int64_t> DataCalib::fTrgOff(1440*1024, 0);
vector<float>   DataCalib::fStats (1440*1024*6+3);

uint64_t DataCalib::fNumOffset = 1;
uint64_t DataCalib::fNumGain   = 2000;
uint64_t DataCalib::fNumTrgOff = 1;

void DataCalib::Restart()
{
    // Default gain:
    // 0.575*[45590]*2.5V / 2^16 = 0.99999 V
    fOffset.assign(1440*1024, 0);
    fGain.assign  (1440*1024, 4096);
    fTrgOff.assign(1440*1024, 0);

    fNumOffset = 1;
    fNumGain   = 2000;
    fNumTrgOff = 1;

    reinterpret_cast<uint32_t*>(fStats.data())[0] = 0;
    reinterpret_cast<uint32_t*>(fStats.data())[1] = 0;
    reinterpret_cast<uint32_t*>(fStats.data())[2] = 0;

    int i=0;
    while (i<1024*1440*2+3)  // Set mean and RMS to 0
        fStats[i++] = 0;
    while (i<1024*1440*3+3)
        fStats[i++] = 2000./4096; // Set mean to 0.5
    while (i<1024*1440*6+3)
        fStats[i++] = 0;   // Set everything else to 0

    fStep = 0;
    fProcessing = false;
}

void DataCalib::Update(DimDescribedService &dim)
{
    dim.Update(fStats);
}

bool DataCalib::Open(RUN_HEAD* h)
{
    if (h->NPix != 1440)
    {
        fMsg.Error("Number of pixels in header not 1440.");
        return false;
    }

    if (h->Nroi != 1024)
    {
        fMsg.Error("Region of interest not 1024.");
        return false;
    }

    if (fProcessing)
    {
        fMsg.Warn("Previous run not yet finished!");
        return false;
    }

    if (fStep==4)
    {
        fMsg.Warn("DRS Calibration already finished... please restart!");
        return false;
    }

    fProcessing = true;

    ostringstream name;
    name << "drs-calib-" << fStep;
    fFileName = name.str();

    Reset();
    InitSize(1440, 1024);

    return true;
}

bool DataCalib::WriteEvt(EVENT *e)
{
    // FIXME: SET StartPix to 0 if StartPix is -1

    if (fStep==0)
    {
        AddRel(e->Adc_Data, e->StartPix);
    }
    if (fStep==1)
    {
        AddRel(e->Adc_Data, e->StartPix, fOffset.data(), fNumOffset);
    }
    if (fStep==2)
    {
        AddAbs(e->Adc_Data, e->StartPix, fOffset.data(), fNumOffset);
    }

    return true;
}

bool DataCalib::ReadFits(const string &str, MessageImp &msg)
{
    if (fProcessing)
    {
        msg.Error("Reading "+str+" failed: DRS calibration in process.");
        return false;
    }

    try
    {
        fits file(str);
        if (!file)
        {
            msg.Error("Opening '"+str+"' failed: "+string(strerror(errno)));
            return false;
        }

        if (file.GetStr("TELESCOP")!="FACT")
        {
            msg.Error("Reading "+str+" failed: Not a valid FACT file (TELESCOP not FACT in header)");
            return false;
        }

        if (!file.HasKey("STEP"))
        {
            msg.Error("Reading "+str+" failed: Is not a DRS calib file (STEP not found in header)");
            return false;
        }

        if (file.GetNumRows()!=1)
        {
            msg.Error("Reading "+str+" failed: Number of rows in table is not 1.");
            return false;
        }

        vector<float> data(1440*1024*6+3);

        float *base = data.data();

        file.SetPtrAddress("RunNumberBaseline",      base,   1);
        file.SetPtrAddress("RunNumberGain",          base+1, 1);
        file.SetPtrAddress("RunNumberTriggerOffset", base+2, 1);
        file.SetPtrAddress("BaselineMean",           base+3 + 0*1024*1440, 1024*1440);
        file.SetPtrAddress("BaselineRms",            base+3 + 1*1024*1440, 1024*1440);
        file.SetPtrAddress("GainMean",               base+3 + 2*1024*1440, 1024*1440);
        file.SetPtrAddress("GainRms",                base+3 + 3*1024*1440, 1024*1440);
        file.SetPtrAddress("TriggerOffsetMean",      base+3 + 4*1024*1440, 1024*1440);
        file.SetPtrAddress("TriggerOffsetRms",       base+3 + 5*1024*1440, 1024*1440);

        if (!file.GetNextRow())
        {
            msg.Error("Reading data from "+str+" failed.");
            return false;
        }

        fStep      = file.GetUInt("STEP");
        fNumOffset = file.GetUInt("NBOFFSET");
        fNumGain   = file.GetUInt("NBGAIN");
        fNumTrgOff = file.GetUInt("NBTRGOFF");

        memcpy(fStats.data(), base, fStats.size()*sizeof(float));

        // Convert back to ADC counts: 256/125 = 4096/2000
        // Convert back to sum (mean * num_entries)
        for (int i=0; i<1024*1440; i++)
        {
            fOffset[i] = fNumOffset           *256*fStats[i+1024*1440*0+3]/125;
            fGain[i]   = fNumOffset*fNumGain  *256*fStats[i+1024*1440*2+3]/125;
            fTrgOff[i] = fNumOffset*fNumTrgOff*256*fStats[i+1024*1440*4+3]/125;
        }

        // DAC:  0..2.5V == 0..65535
        // V-mV: 1000
        //fNumGain *= 2500*50000;
        //for (int i=0; i<1024*1440; i++)
        //    fGain[i] *= 65536;
        if (fStep==0)
        {
            for (int i=0; i<1024*1440; i++)
                fGain[i] = fNumOffset*4096;
        }
        else
        {
            fNumGain *= 1953125;
            for (int i=0; i<1024*1440; i++)
                fGain[i] *= 1024;
        }

        // Now mark the stored DRS data as "officially valid"
        // However, this is not thread safe. It only ensures that
        // this data is not used before it is completely and correctly
        // read.
        fStep++;

        return true;
    }
    catch (const runtime_error &e)
    {
        msg.Error("Exception reading "+str+": "+e.what());
        return false;
    }
}

void DataCalib::WriteFits()
{
#ifdef HAVE_FITS
    FitsFile file(fMsg);

    file.AddColumn('I', "RunNumberBaseline");
    file.AddColumn('I', "RunNumberGain");
    file.AddColumn('I', "RunNumberTriggerOffset");
    file.AddColumn('F', "BaselineMean",      1024*1440, "mV");
    file.AddColumn('F', "BaselineRms",       1024*1440, "mV");
    file.AddColumn('F', "GainMean",          1024*1440, "mV");
    file.AddColumn('F', "GainRms",           1024*1440, "mV");
    file.AddColumn('F', "TriggerOffsetMean", 1024*1440, "mV");
    file.AddColumn('F', "TriggerOffsetRms",  1024*1440, "mV");

    fFileName = FormFileName("drs.fits");

    if (!file.OpenFile(fFileName))
        return;

    if (!file.OpenTable("DrsCalibration"))
        return;

    if (!file.WriteDefaultKeys("fadctrl"))
        return;

    if (!file.WriteKeyNT("STEP",     fStep, "") ||
        !file.WriteKeyNT("ADCRANGE", 2000,             "Dynamic range of the ADC in mV") ||
        !file.WriteKeyNT("DACRANGE", 2500,             "Dynamic range of the DAC in mV") ||
        !file.WriteKeyNT("ADC",      12,               "Resolution of ADC in bits")      ||
        !file.WriteKeyNT("DAC",      16,               "Resolution of DAC in bits")      ||
        !file.WriteKeyNT("DACLEVEL", 50000,            "Applied DAC level in counts")    ||
        !file.WriteKeyNT("NBOFFSET", fNumOffset,       "Number of entries for offset calibration") ||
        !file.WriteKeyNT("NBGAIN",   fNumGain/1953125, "Number of entries for gain calibration")   ||
        !file.WriteKeyNT("NBTRGOFF", fNumTrgOff,       "Number of entries for trigger offset calibration") ||
        !file.WriteKeyNT("NPIX",     1440,             "Number of channels in the camera") ||
        !file.WriteKeyNT("NROI",     1024,             "Region of interest")
       )
        return;

    vector<char> buf;
    buf.reserve(fStats.size()*sizeof(float));

    char *src  = reinterpret_cast<char*>(fStats.data());
    char *end  = reinterpret_cast<char*>(fStats.data()+1024*1440*6+3);
    char *dest = buf.data();

    while (src<end)
    {
        reverse_copy(src, src+sizeof(float), dest);
        src  += sizeof(float);
        dest += sizeof(float);
    }

    if (!file.AddRow())
        return;

    if (!file.WriteData(buf.data(), 1024*1440*sizeof(float)*6+3))
        return;

    ostringstream str;
    str << "Wrote DRS calibration data (step=" << fStep << ") to '" << fFileName << "'";
    Info(str.str());
#endif
}

bool DataCalib::Close(RUN_TAIL *)
{
    if (fStep==0)
    {
        fOffset.assign(fSum.begin(), fSum.end());
        fNumOffset = fNumEntries;

        for (int i=0; i<1024*1440; i++)
            fGain[i] = 4096*fNumOffset;

        // Scale ADC data from 12bit to 2000mV
        GetSampleStats(fStats.data()+3, 2000./4096);
        reinterpret_cast<uint32_t*>(fStats.data())[0] = GetRunId();;
    }
    if (fStep==1)
    {
        fGain.assign(fSum.begin(), fSum.end());
        fNumGain = fNumEntries;

        // DAC:  0..2.5V == 0..65535
        // V-mV: 1000
        //fNumGain *= 2500*50000;
        //for (int i=0; i<1024*1440; i++)
        //    fGain[i] *= 65536;
        fNumGain *= 1953125;
        for (int i=0; i<1024*1440; i++)
            fGain[i] *= 1024;

        // Scale ADC data from 12bit to 2000mV
        GetSampleStats(fStats.data()+1024*1440*2+3, 2000./4096/fNumOffset);//0.5);
        reinterpret_cast<uint32_t*>(fStats.data())[1] = GetRunId();;
    }
    if (fStep==2)
    {
        fTrgOff.assign(fSum.begin(), fSum.end());
        fNumTrgOff = fNumEntries;

        // Scale ADC data from 12bit to 2000mV
        GetSampleStats(fStats.data()+1024*1440*4+3, 2000./4096/fNumOffset);//0.5);
        reinterpret_cast<uint32_t*>(fStats.data())[2] = GetRunId();;
    }

    if (fStep>=0 && fStep<=2)
        WriteFits();

    fDim.Update(fStats);

    fStep++;

    fProcessing = false;

    return true;
}

void DataCalib::Apply(int16_t *val, const int16_t *start, uint32_t roi)
{
    CalibData::Apply(val, start, roi,
                     fOffset.data(), fNumOffset,
                     fGain.data(),   fNumGain,
                     fTrgOff.data(), fNumTrgOff);
}

void DataCalib::Apply(float *vec, int16_t *val, const int16_t *start, uint32_t roi)
{
    CalibData::Apply(vec, val, start, roi,
                     fOffset.data(), fNumOffset,
                     fGain.data(),   fNumGain,
                     fTrgOff.data(), fNumTrgOff);
}

