#include "DataWriteFits.h"

#include "FAD.h"
#include "Converter.h"

using namespace std;

DataWriteFits::~DataWriteFits()
{
    Close();
    delete fConv;
}

template <typename T>
    void DataWriteFits::WriteKey(const string &name, const int idx, const T &value, const string &comment)
{
    ostringstream str;
    str << name << idx;

    ostringstream com;
    com << "Board " << setw(2) << idx << ": " << comment;

    fFile.WriteKey(str.str(), value, com.str());
}

// --------------------------------------------------------------------------
//
//! DataWriteFits constructor. This is the one that should be used, not the default one (parameter-less)
//! @param runid This parameter should probably be removed. I first thought it was the run number, but apparently it is not
//! @param h a pointer to the RUN_HEAD structure that contains the informations relative to this run
//
bool DataWriteFits::Open(RUN_HEAD* h)
{
    if (fConv)
    {
        Error("DataWriteFits::Open called twice.");
        return false;
    }

    fFile.AddColumn('I', "EventNum");
    fFile.AddColumn('I', "TriggerNum");
    fFile.AddColumn('S', "TriggerType");
    fFile.AddColumn('I', "NumBoards");
    fFile.AddColumn('C', "Errors",              4);
    fFile.AddColumn('I', "SoftTrig");
    fFile.AddColumn('I', "UnixTimeUTC",         2);
    fFile.AddColumn('I', "BoardTime",           NBOARDS);
    fFile.AddColumn('S', "StartCellData",       NPIX);
    fFile.AddColumn('S', "StartCellTimeMarker", NTMARK);
    fFile.AddColumn('S', "Data",                h->NPix*h->Nroi);
    fFile.AddColumn('S', "TimeMarker",          h->NTm*h->NroiTM);

    // Write length of physical pipeline (1024)

    fConv = new Converter(Converter::ToFormat(fFile.GetColumnTypes()));

    const size_t sz = (h->NPix*h->Nroi + h->NTm*h->NroiTM)*2;
    if (fConv->GetSize()-sz+4!=sizeof(EVENT))
    {
        ostringstream str;
        str << "The EVENT structure size (" << sizeof(EVENT) << ") doesn't match the described FITS row (";
        str << fConv->GetSize()-sz+4 << ")";
        Error(str);
        return false;
    }

    //Form filename, based on runid and run-type
    fFileName = FormFileName("fits");

    if (!fFile.OpenFile(fFileName))
        return false;

    if (!fFile.OpenTable("Events"))
        return false;

    if (!fFile.WriteDefaultKeys("fadctrl"))
        return false;

    Info("==> TODO: Write sampling frequency...");

    //write header data
    //first the "standard" keys
    try
    {
        fFile.WriteKey("BLDVER",   h->Version,  "Builder version");
        fFile.WriteKey("RUNID",    GetRunId(),  "Run number");
        fFile.WriteKey("RUNTYPE",  h->RunType,  "Type of run");
        fFile.WriteKey("NBOARD",   h->NBoard,   "Number of acquisition boards");
        fFile.WriteKey("NPIX",     h->NPix,     "Number of pixels");
        fFile.WriteKey("NTMARK",   h->NTm,      "Number of Time marks");
        fFile.WriteKey("NROI",     h->Nroi,     "Number of slices per pixels");
        fFile.WriteKey("NROITM",   h->NroiTM,   "Number of slices per time-marker");

        //FIXME should we also put the start and stop time of the received data ?
        //now the events header related variables
        fFile.WriteKey("CAMERA",   "MGeomCamFACT", "");
        fFile.WriteKey("DAQ",      "DRS4", "");
        fFile.WriteKey("ADCRANGE", 2000,        "Dynamic range in mV");
        fFile.WriteKey("ADC",      12,          "Resolution in bits");


        // Write a single key for:
        // -----------------------
        // Start package flag
        // package length
        // version number
        // status
        // Prescaler

        // Write 40 kays for (?)
        // Phaseshift
        // DAC

        for (int i=0; i<h->NBoard; i++)
        {
            const PEVNT_HEADER &hh = h->FADhead[i];

            // Header values whihc won't change during the run
            WriteKey("ID",    i, hh.board_id,   "Board ID");
            WriteKey("DNA",   i, hh.DNA,        "DNA");
            WriteKey("FWVER", i, hh.version_no, "Firmware Version");
        }

        // FIXME: Calculate average ref clock frequency
        for (int i=0; i<h->NBoard; i++)
        {
            const PEVNT_HEADER &hh = h->FADhead[i];

            if (hh.start_package_flag==0)
                continue;

            fFile.WriteKey("RUN",   hh.runnumber, "Run number");
            fFile.WriteKey("PRESC", hh.trigger_generator_prescaler, "Trigger generator prescaler");
            fFile.WriteKey("PHASE", hh.adc_clock_phase_shift, "ADC clock phase shift");

            for (int j=0; j<8; j++)
            {
                ostringstream dac;
                dac << "DAC" << j;
                fFile.WriteKey(dac.str(), hh.dac[j], "DAC command value");
            }

            break;
        }

        double avg = 0;
        int    cnt = 0;
        for (int i=0; i<h->NBoard; i++)
        {
            const PEVNT_HEADER &hh = h->FADhead[i];

            if (hh.start_package_flag==0)
                continue;

            avg += hh.REFCLK_frequency;
            cnt ++;
        }

        fFile.WriteKey("REFCLK", avg/cnt, "Average reference clock frequency in Hz");
    }
    catch (const CCfits::FitsException &e)
    {
        Error("CCfits::Table::addKey failed in "+fFileName+"': "+e.message());
        return false;
    }

    //Last but not least, add header keys that will be updated when closing the file
    return WriteFooter(NULL);
}

// --------------------------------------------------------------------------
//
//! This writes one event to the file
//! @param e the pointer to the EVENT
//
bool DataWriteFits::WriteEvt(EVENT *e)
{
    if (!fFile.AddRow())
        return false;

    const size_t sz = sizeof(EVENT) + sizeof(e->StartPix)*e->Roi+sizeof(e->StartTM)*e->RoiTM;

    const vector<char> data = fConv->ToFits(reinterpret_cast<char*>(e)+4, sz-4);

    return fFile.WriteData(data.data(), data.size());
}

bool DataWriteFits::WriteFooter(RUN_TAIL *rt)
{
    try
    {
        /*
        fFile.WriteKey("NBEVTOK",  rt ? rt->nEventsOk  : uint32_t(0),
                       "How many events were written");

        fFile.WriteKey("NBEVTREJ", rt ? rt->nEventsRej : uint32_t(0),
                       "How many events were rejected by SW-trig");

        fFile.WriteKey("NBEVTBAD", rt ? rt->nEventsBad : uint32_t(0),
                       "How many events were rejected by Error");
        */

        //FIXME shouldn't we convert start and stop time to MjD first ?
        //FIXME shouldn't we also add an MjD reference ?

        fFile.WriteKey("TSTART",   rt ? rt->PCtime0    : uint32_t(0),
                       "Time when first event received");

        fFile.WriteKey("TSTOP",    rt ? rt->PCtimeX    : uint32_t(0),
                       "Time when last event received");
    }
    catch (const CCfits::FitsException &e)
    {
        Error("CCfits::Table::addKey failed in '"+fFile.GetName()+"': "+e.message());
        return false;
    }
    return true;
}

// --------------------------------------------------------------------------
//
//! Closes the file, and before this it write the TAIL data
//! @param rt the pointer to the RUN_TAIL data structure
//
bool DataWriteFits::Close(RUN_TAIL *rt)
{
    if (!fFile.IsOpen())
        return false;

    WriteFooter(rt);

    fFile.Close();

    return true;
}
