#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', "PCTime",              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(GetRunId(), "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");
        }
    }
    catch (const CCfits::FitsException &e)
    {
        Error("CCfits::Table::addKey failed in "+fFileName+"': "+e.message());
        return false;
    }

    /*
     //now the boards related keywords
     for (int i=0; i<h->NBoard; i++)
     {
     const PEVNT_HEADER &hh = h->FADhead[i];

     WriteKey("STPKGFG", i, hh.start_package_flag, "Start package flag");
     WriteKey("PKGLEN",  i, hh.package_length, "Package length");
     WriteKey("STATUS",  i, hh.PLLLCK, "");

     //            WriteKey("TRIGCRC", i, hh.trigger_crc,      "Trigger CRC");
     //            WriteKey("TRIGTYP", i, hh.trigger_type,     "Trigger type");
     //            WriteKey("TRIGID",  i, hh.trigger_id,       "Trigger ID");
     //            WriteKey("EVTCNTR", i, hh.fad_evt_counter,  "FAD Event Counter");
     //            WriteKey("REFCLK",  i, hh.REFCLK_frequency, "Reference Clock Frequency");

     WriteKey("PHASESH", i, hh.adc_clock_phase_shift,          "ADC clock phase shift");
     WriteKey("TRGGEN",  i, hh.number_of_triggers_to_generate, "Number of triggers to generate");
     WriteKey("PRESC",   i, hh.trigger_generator_prescaler,    "Trigger generator prescaler");
     WriteKey("RUNNB",   i, hh.runnumber,                      "Run number");

     WriteKey("TIME",    i, hh.time,      "Time");

     //            for (int j=0;j<NTemp;j++)
     //            {
     //                str.str(""); str2.str("");
     //                str << "DRS_T" << i << j;
     //                str2 << "DRS temperature #" << i << " " << j;
     //                WriteKey(str.str(), h->FADhead[i].drs_temperature[j], str2.str());
     //            }
     for (int j=0;j<NDAC;j++)
     WriteKey("DAC", i*NDAC+j, hh.dac[j], "DAC");
     }
     */

    //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;
}
