/*AA
 * seqIDataMgr.cpp
 *
 *  Created on: Mar 18, 2022
 *      Author: Fulvio Laudisio
 */
#include <pthread.h>
#include <cmath>

#include <ctime>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "seqIDataMgr.hpp"

#include "base64.h" // from64tobits

SeqDataMgr * SeqDataMgr::m_pSeqDataMgr = NULL;


double gDrotRef = 0.;
int gIndiPort = 0;
std::string gIndiAddress;

SeqDataMgr::SeqDataMgr(Ltcs::IifServiceWorkerInterfacePtr &_iif, Ice::PropertiesPtr props):
    m_props(props),
    sashaSave(IProperty::Switch, "sasha_save", "enable_save"),
    sashaObjName(IProperty::Text, "sasha", "OBJNAME"),
    sashaNumSeqs(IProperty::Number, "sasha", "num_seqs"),
    sashaSeqNum(IProperty::Number, "sasha", "seq_num"),
    sashaAcquire(IProperty::Number, "sasha", "acquire"),
    sashaNumCoadds(IProperty::Number, "sasha", "num_coadds"),
    sashaNumDrops(IProperty::Number, "sasha", "num_drops"),
    sashaNumReads(IProperty::Number, "sasha", "num_reads"),
    sashaNumGroups(IProperty::Number, "sasha", "num_groups"),
    sashaNumResets(IProperty::Number, "sasha", "num_resets"),
    sashaReadout(IProperty::Text, "sasha", "def_readout_region_name"),
    sashaReadoutRegion(IProperty::Text, "sasha", "readout_region"),
    sashaBlob(IProperty::Unknown, "sasha_save", "save_image"),
    sashaLargestFileNumber(IProperty::Number, "sasha_save", "largest_filenum"),
    sashaExposeProp(IProperty::Switch, "sasha", "start_acquire"),
    sharknirTemperatureDetector(IProperty::Unknown, "sharknir_temp", "sensor"),
    sharknirTemperatureColdFinger(IProperty::Unknown, "sharknir_temp", "sensor"),
    sharknirTemperatureInnerVessel(IProperty::Unknown, "sharknir_temp", "sensor"),
    sharknirTemperatureOuterVessel(IProperty::Unknown, "sharknir_temp", "sensor"),
    sashaPressure(IProperty::Unknown, "sharknir_pres1", "sensor"),
    sashaStop(IProperty::Switch, "sasha", "stop"),
    sashaFrequencyMode(IProperty::Text, "sasha", "def_mode_name"),
    sashaIntegrationTime(IProperty::Text, "sasha", "int_time"),
    sashaExposureTime(IProperty::Text, "sasha", "exp_time"),
    sashaEnableCont(IProperty::Switch, "sasha" , "enable_cont" ),
    lbtLeftInstrument(IProperty::Text, "lbto_iif", "status")
{
    U6_LLOG(__FUNCTION__);

    m_iif = _iif;
    m_ExpectedBlobs = 0;
    SeqDataMgr::m_pSeqDataMgr = this;
    m_pGetMotorStatus = NULL;
    m_pGetTcsPreset = NULL;
    m_pGetRtcStatus = NULL;
    m_timoeout_multiplier = 1;

    su::getValue<double>(m_props->getProperty("Shins.Tracking.Adc.DrotRefAngle"), gDrotRef);
    gIndiAddress = m_props->getProperty("Shins.SashaWs.IP");
    su::getValue<int>(m_props->getProperty("Shins.SashaWs.IndiPort"), gIndiPort);

    su::SetDataSaveDirectory(m_props->getProperty("Shins.DataMgr.DataSaveDirectory"));
    U9_LLOG(m_props->getProperty("Shins.DataMgr.DataSaveDirectory"));

//    sashaBlob.add(IElement("file"));
//    sashaBlob.setBLOBEnable(IProperty::Never);
//    sashaNumCoadds.add(IElement("value"));
//    sashaNumCoadds["value"].setValue<int>(1);


    m_sashaProps.insert(std::pair<std::string, IProperty & >("NDIT", sashaNumSeqs));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("NDITINDEX", sashaSeqNum));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("READOUT", sashaReadout));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("READOUTREGION", sashaReadoutRegion));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("SAVE", sashaSave));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("OBJECTNAME", sashaObjName));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("ACQUIRE", sashaAcquire));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("NCOADDS", sashaNumCoadds));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("NDROPS", sashaNumDrops));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("NGROUPS", sashaNumGroups));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("NREADS", sashaNumReads));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("NRESETS", sashaNumResets));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("EXPOSE", sashaExposeProp));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("STOP", sashaStop));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("FREQUENCY", sashaFrequencyMode));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("CONTINUOUS", sashaEnableCont));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("EXPTIME", sashaExposureTime));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("INTTIME", sashaIntegrationTime));

    m_sashaProps.insert(std::pair<std::string, IProperty & >("LARGESTFILENUM", sashaLargestFileNumber));

    m_sashaProps.insert(std::pair<std::string, IProperty & >("SENSORTEMPDET", sharknirTemperatureDetector));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("SENSORTEMPCF", sharknirTemperatureColdFinger));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("SENSORTEMPIV", sharknirTemperatureInnerVessel));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("SENSORTEMPOV", sharknirTemperatureOuterVessel));
    m_sashaProps.insert(std::pair<std::string, IProperty & >("SENSORPRES1", sashaPressure));

    m_sashaProps.insert(std::pair<std::string, IProperty & >("LBTLEFTINS", lbtLeftInstrument));

    m_sashaPropsSet["NDIT"] = true;
    m_sashaPropsSet["NDITINDEX"] = true;
    m_sashaPropsSet["READOUT"] = true;
    m_sashaPropsSet["READOUTREGION"] = true;
    m_sashaPropsSet["SAVE"] = true;
    m_sashaPropsSet["OBJECTNAME"] = true;
    m_sashaPropsSet["ACQUIRE"] = true;
    m_sashaPropsSet["NCOADDS"] = true;
    m_sashaPropsSet["NDROPS"] = true;
    m_sashaPropsSet["NGROUPS"] = true;
    m_sashaPropsSet["NREADS"] = true;
    m_sashaPropsSet["NRESETS"] = true;
    m_sashaPropsSet["EXPOSE"] = true;
    m_sashaPropsSet["FREQUENCY"] = true;
    m_sashaPropsSet["STOP"] = true;
    m_sashaPropsSet["CONTINUOUS"] = true;
    m_sashaPropsSet["INTTIME"] = true;
    m_sashaPropsSet["EXPTIME"] = true;


    m_sashaTiming["Full_Image"] = 4.2783f; // 4.595f;
    m_sashaTiming["Center"] = 2.6751f;
    m_sashaTiming["Bottom"] = 1.0719f;
    m_sashaTiming["Top"] = 1.0719f;
    m_sashaTiming["256x256"] = 0.2745f;
    m_sashaTiming["128x128"] = 0.0718f;
    m_sashaTiming["64x64"] = 0.0190;
    m_sashaTiming["32x32"] = 0.0054f;
    m_sashaTiming["1000x1000"] = 4.0481;
    m_sashaTiming["coro_stripe"] = 0.1283;
    m_sashaTiming["PD_wollaston_stripe_small"] = 0.1701;
    m_sashaTiming["PD_wollaston_stripe_large"] = 0.2953;
    m_sashaTiming["run2"] = 0.0718;
    m_sashaTiming["obs_stripe_200px"] = 0.4206f;
    m_sashaTiming["obs_stripe_512px"] = 1.0719f;

    m_sashaSizes["Full_Image"] = 8388608;
    m_sashaSizes["Center"] = 5242880;
    m_sashaSizes["Bottom"] = 2097152;
    m_sashaSizes["Top"] = 2097152;
    m_sashaSizes["256x256"] = 131072;
    m_sashaSizes["128x128"] = 32768;
    m_sashaSizes["64x64"] = 8192;
    m_sashaSizes["32x32"] = 2048;
    m_sashaSizes["1000x1000"] = 2000000;
    m_sashaSizes["coro_stripe"] = 245760;
    m_sashaSizes["PD_wollaston_stripe_small"] = 327680;
    m_sashaSizes["PD_wollaston_stripe_large"] = 573440;
    m_sashaSizes["run2"] = 33282;
    m_sashaSizes["obs_stripe_200px"] = 819200;
    m_sashaSizes["obs_stripe_512px"] = 2097152;

    m_getFitsAbortFlag = 0;
    m_bReceivingFitsFile = false;

    StartIndiClientThread();

    //Flags
    m_bFlagChanged = true;
    m_bSaveOnlyExposedFiles = true;
    m_bSaveIncomingBlob = false;
    m_bTest01 = false;
    m_bForcePropertySet = false;
    m_bFastSavingMode = false;

    m_instrumentHeader.init = true;

    m_sashaSetup[suc::InstrumentMode] = "GEN";
    m_sashaSetup["OBJECTNAME"] = "";
    m_sashaSetup["NGROUPS"] = "";
    m_sashaSetup["NREADS"] = "";
    m_sashaSetup["NDROPS"] = "";
    m_sashaSetup["NCOADDS"] = "";
    m_sashaSetup["NRESETS"] = "";
    m_sashaSetup["NDIT"] = "";
    m_sashaSetup["SAVE"] = "";
    m_sashaSetup["READOUT"] = "";
    m_sashaSetup["DIT"] = "";

    m_timeoutSetup.push_back("NGROUPS");
    m_timeoutSetup.push_back("NREADS");
    m_timeoutSetup.push_back("NDROPS");
    m_timeoutSetup.push_back("NCOADDS");
    m_timeoutSetup.push_back("NRESETS");

    m_rtcKeywords["TTBIASFILE"] = "";
    m_rtcKeywords["TTDMFLATFILE"] = "";
    m_rtcKeywords["TTSENSORTEMP"] = "-1000.";
    m_rtcKeywords["TTLOOPENABLED"] = "";
    m_rtcKeywords["TTWINCOORDX"] = "";
    m_rtcKeywords["TTWINCOORDY"] = "";
    m_rtcKeywords["TTWINROWS"] = "";
    m_rtcKeywords["TTWINCOLS"] = "";
    m_rtcKeywords["TTFRAMERATE"] = "";
    m_rtcKeywords["TTCAMTINT"] = "";
    m_rtcKeywords["TTDMMAXPOWER"] = "";
    m_rtcKeywords["TTCENTROIDGAINX"] = "";
    m_rtcKeywords["TTCENTROIDGAINY"] = "";
    m_rtcKeywords["TTCENTROIDORIGX"] = "";
    m_rtcKeywords["TTCENTROIDORIGY"] = "";
    m_rtcKeywords["TTDMMODESNUM"] = "";
    m_rtcKeywords["TTPIXELGAINFILE"] = "";
    m_rtcKeywords["TTPIXELENABLED"] = "";
    m_rtcKeywords["TTPIXELDECIMATION"] = "";
    m_rtcKeywords["TTDIAGENABLED"] = "";
    m_rtcKeywords["TTDIAGDECIMATION"] = "";

    // m_obContext.defocus_lamp = "OFF";
    // m_obContext.focus_lamp = "OFF";
    // m_obContext.flat_field_lamp = "OFF";

    pcf::TimeStamp timestamp = pcf::TimeStamp::now();
    su::SetTimeWorkingDirectory(timestamp.getFormattedIso8601Str());

    U8_LLOG(__FUNCTION__);
}


void SeqDataMgr::StartIndiClientThread()
{
    pthread_create(&m_indi_tid, NULL, indi_client_thread, this);
}


SeqDataMgr::~SeqDataMgr()
{
}


#define OCS_FRAME_TIME_FULLIMAGE    4.2783f
#define OCS_FRAME_TIME_CENTER       2.6751f
#define OCS_FRAME_TIME_BOTTOM       1.0719f
#define OCS_FRAME_TIME_TOP          1.0719f
#define OCS_FRAME_TIME_256X256      0.2745f
#define OCS_FRAME_TIME_128X128      0.0718f
#define OCS_FRAME_TIME_1000X1000    4.0481f
#define OCS_FRAME_TIME_COROSTRIPE   0.1283f
#define OCS_FRAME_TIME_WOLLASTONE   0.2953f
#define OCS_NUM_MAX_FRAMES 25

void set_ramp(float exp_time, float frame_time, int num_max_frames, int ncoadds, int &groups, int &reads, int &drops)
{
    float r = exp_time / (frame_time * ncoadds);
    float eff_frame_time = frame_time * ncoadds;
    if(r < num_max_frames)
    {
        groups = 1;
        reads = (int) r;
        if(r < 1.f)
            reads = 1;
        drops = 0;
    }
    else
    {
        groups = num_max_frames;
        reads = 1;
        float fpdrops = (exp_time - groups * eff_frame_time) / ((groups - 1) * eff_frame_time);
        int floor_drops = (int) fpdrops;
        float eff_exp_time = eff_frame_time * (groups * reads + (groups - 1) * floor_drops);
        float extra_time = eff_frame_time * (groups - 1);
        if(abs(exp_time - eff_exp_time) < abs(exp_time - (eff_exp_time + extra_time)))
        {
            drops = floor_drops;
        }
        else
        {
            drops = floor_drops + 1;
        }
    }
}


std::string get_current_wdir()
{
    char buff[FILENAME_MAX];
    getcwd(buff, FILENAME_MAX);
    std::string curr_work_dir(buff);
    return curr_work_dir;
}


float * get_modified_julian_day()
{
    time_t rawtime;
    time(&rawtime);
//    "rawtime = Numero di secondi dal 1 Gen 1970 00:00"
//    "MJD = Numero di giorni dalla mezzanotte del 17 Nov 1858"
    const int mjd_of_01011970 = 40587;
    double seconds = rawtime;
    static float result = mjd_of_01011970 + seconds/86400.;
    return &result;
}


int SeqDataMgr::setLampsStatus(const std::map<std::string, std::string> & _osLampsStatus)
{
    U6_LLOG(__FUNCTION__);
    std::map<std::string, std::string>::const_iterator itContext;
    itContext = _osLampsStatus.find("CAL_FIBER_DEFOCUS_LAMP");
    if(itContext != _osLampsStatus.end())
    {
        if(itContext->second == "ON" || itContext->second == "OFF")
        {
            m_obContext.defocus_lamp = itContext->second;
        }
        else
        {
            std::stringstream log_msg;
            log_msg << "Error : admitted values for " << itContext->first << " are \"ON\" or \"OFF\"";
            E_LLOG(log_msg.str());
            U8_LLOG("");
            throw(std::runtime_error(log_msg.str()));
        }
    }
    itContext = _osLampsStatus.find("CAL_FIBER_FOCUS_LAMP");
    if(itContext != _osLampsStatus.end())
    {
        if(itContext->second == "ON" || itContext->second == "OFF")
        {
            m_obContext.focus_lamp = itContext->second;
        }
        else
        {
            std::stringstream log_msg;
            log_msg << "Error : admitted values for " << itContext->first << " are \"ON\" or \"OFF\"";
            E_LLOG(log_msg.str());
            U8_LLOG("");
            throw(std::runtime_error(log_msg.str()));
        }
    }
    itContext = _osLampsStatus.find("CAL_FF_LAMP");
    if(itContext != _osLampsStatus.end())
    {
        if(itContext->second == "ON" || itContext->second == "OFF")
        {
            m_obContext.flat_field_lamp = itContext->second;
        }
        else
        {
            std::stringstream log_msg;
            log_msg << "Error : admitted values for " << itContext->first << " are \"ON\" or \"OFF\"";
            E_LLOG(log_msg.str());
            U8_LLOG("");
            throw(std::runtime_error(log_msg.str()));
        }
    }
    U8_LLOG("");
    return EXIT_SUCCESS;
}


int SeqDataMgr::setContext(const std::map<std::string, std::string> & _osContext)
{
    U6_LLOG(__FUNCTION__);
    std::map<std::string, std::string>::const_iterator itContext;
    bool bOB = false, bTPLID = false, bTPLSCRIPT = false, bTPL = false, bTPLTIME = false, bDPR = false;
    bool bInfoObj = false, bInfoComm = false, bInfoObserver = false, bInfoPartner = false, bInfoPiCoi = false, bInfoPropId = false;
    itContext = _osContext.find("OB");
    if(itContext != _osContext.end())
    {
        m_obContext.observation_block = itContext->second;
        bOB = true;
    }
    itContext = _osContext.find("TPLID");
    if(itContext != _osContext.end())
    {
        m_obContext.template_id = itContext->second;
        bTPLID = true;
    }
    itContext = _osContext.find("TPLSCRIPT");
    if(itContext != _osContext.end())
    {
        m_obContext.template_script = itContext->second;
        bTPLSCRIPT = true;
    }
    itContext = _osContext.find("TPL");
    if(itContext != _osContext.end())
    {
        m_obContext.template_name = itContext->second;
        bTPL = true;
    }
    itContext = _osContext.find("TPLTIME");
    if(itContext != _osContext.end())
    {
        m_obContext.template_time = itContext->second;
        bTPLTIME = true;
    }
    itContext = _osContext.find("DPR_TYPE");
    if(itContext != _osContext.end())
    {
        m_obContext.dpr_type = itContext->second;
        bDPR = true;
    }

    itContext = _osContext.find("OBJECT");
    if(itContext != _osContext.end())
    {
        m_obContext.info_object = itContext->second;
        bInfoObj = true;
    }
    
    itContext = _osContext.find("COMMENT");
    if(itContext != _osContext.end())
    {
        m_obContext.info_obs_comm = itContext->second;
        bInfoComm = true;
    }

    itContext = _osContext.find("OBSERVER");
    if(itContext != _osContext.end())
    {
        m_obContext.info_observer = itContext->second;
        bInfoObserver = true;
    }

    itContext = _osContext.find("PARTNER");
    if(itContext != _osContext.end())
    {
        m_obContext.info_partner = itContext->second;
        bInfoPartner = true;
    }

    itContext = _osContext.find("PI_COI");
    if(itContext != _osContext.end())
    {
        m_obContext.info_pi_coi = itContext->second;
        bInfoPiCoi = true;
    }

    itContext = _osContext.find("PROP_ID");
    if(itContext != _osContext.end())
    {
        m_obContext.info_propid = itContext->second;
        bInfoPropId = true;
    }

    //bool bNcpaFileLoaded = false, bNcpaRotation = false;
    itContext = _osContext.find("NCPA_FILE");
    if(itContext != _osContext.end())
    {
        m_obContext.ncpa_file = itContext->second;
        // bNcpaFileLoaded = true;
    }
    
    itContext = _osContext.find("NCPA_ROT_STATUS");
    if(itContext != _osContext.end())
    {
        m_obContext.ncpa_rotation_status = itContext->second;
        // bNcpaRotation = true;
    }

    // LBT Left Instrument Authorized
    itContext = _osContext.find("LBT_L_AUTH_INS");
    if(itContext != _osContext.end())
    {
        m_obContext.l_auth_ins_from_DD = itContext->second;
    }
    // LBT Right Instrument Authorized
    itContext = _osContext.find("LBT_R_AUTH_INS");
    if(itContext != _osContext.end())
    {
        m_obContext.r_auth_ins_from_DD = itContext->second;
    }

    if(false && !(bTPL && bTPLID && bTPLSCRIPT && bOB))
    {
        std::stringstream log_msg;
        log_msg << "Error : Missing at least one of the mandatory parameters {OB TPL TPLID TPLSCRIPT}";
        E_LLOG(log_msg.str());
        U8_LLOG("");
        throw(std::runtime_error(log_msg.str()));
    }
    U8_LLOG("");
    return EXIT_SUCCESS;
}


int SeqDataMgr::SetFitsValues(fitsfile *fptr)
{
    U6_LLOG(__FUNCTION__);

    /* here keywords should be added to SHINS FITS file header.
       Copy from ICD document and dtype from
       https://heasarc.gsfc.nasa.gov/docs/software/fitsio/c/c_user/node127.html */

    int FitStatus = 0;

    char TBD[] = "TBD";
    char * strValue = TBD;
    int intValue = 0;
    float floatValue = 0.f;
    float floatValue2 = 1.f;
    int boolValue = true;

    double lat_d = +32.701309;
    double lon_d = -109.889064;
    int elevatio = 3221;

    char time_string[24];
    time_t rawtime;
    time(&rawtime);
    strftime(time_string, 24, "%Y-%m-%dT%H:%M:%S.000", localtime(&rawtime));
    float bzero = 32768.f;
    float bscale = 1.f;

    STelescopeEnv tenv;
    getTelescopeEnv(tenv);

    int waitUpdateCounter = 0;
    while(m_bInstrumentSetupUpdated == false)
    {
        usleep(20000);
        waitUpdateCounter++;
    }
//    U9_LLOG("Waited for instrument update for " << waitUpdateCounter/50.f << " seconds");
    intValue = 0;
    fits_write_key(fptr, TINT,    "NEXTED   ", &intValue, "No extension are required", &FitStatus);
    fits_write_comment(fptr,      "Original MACIE file keys ---------------------------------------------", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "BZERO    ", &bzero, "offset data range to that of unsigned short", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "BSCALE   ", &bscale, "default scaling factor", &FitStatus);
    fits_write_key(fptr, TSTRING, "CAMERA   ", TBD, "Instrument name", &FitStatus);
    // fits_write_key(fptr, TSTRING, "LST-OBS  ", TBD, "'hh:mm:ss.sss'  Local sidereal time at start of observation", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "CAPCOMP  ", &floatValue, "PreAmp Compensation capacitor (0-63 counts)", &FitStatus);
    fits_write_key(fptr, TSTRING, "DTEOBSM",   time_string, "Date at end of observation CCYY-MM-DD (UTC)", &FitStatus);
    fits_write_key(fptr, TSTRING, "DETECTOM",  TBD, "H1RG / H2RG / H4RG", &FitStatus);
    fits_write_key(fptr, TSTRING, "EXPMODE  ", TBD, "SLOW / FAST", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "EXPTIME ",  &floatValue, "Total Exposure Time, millisec", &FitStatus);
    fits_write_key(fptr, TSTRING, "FILENAME ", TBD, "File name", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "FILTPOLE ", &floatValue, "PreAmp Low Pass filter (0-15 counts)", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "FRAME    ", &floatValue, "One frame time, millisec", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "GAINM    ", &floatValue, "ASIC Preamplifier Gain in db from -3 to 27 in s", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "ITIME   ",  &floatValue, "Integration tim, millisecs", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "NCOADDS  ", &floatValue, "Num Coadds", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "NDROPS   ", &floatValue, "Num of Drops per Ramp", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "NGROUPS  ", &floatValue, "Num of Groups per Ramp", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "NOUTPUTS ", &floatValue, "1,2,4,16,32 for H2G, Number of outputs used for", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "NRAMPS   ", &floatValue, "Num of Ramps to cycle through", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "NREADS  ",  &floatValue, "Num of Read frames in each Group of ramp", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "NRESETS  ", &floatValue, "Num of Reset Frames at the beginning of Ramp", &FitStatus);
    fits_write_key(fptr, TSTRING, "OBJNAME ",  TBD, "Target name", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "RAMPTIM  ", &floatValue, "Ramp time, millisecs", &FitStatus);
    fits_write_key(fptr, TSTRING, "SUBSECNM ", TBD, "Readout region name", &FitStatus);
    fits_write_key(fptr, TSTRING, "SUBSECH  ", TBD, "Readout region height, FITS", &FitStatus);
    fits_write_key(fptr, TSTRING, "SUBSECW  ", TBD, "Readout region width, FITS", &FitStatus);
    fits_write_key(fptr, TSTRING, "SUBSECX1 ", TBD, "Readout region X1, FITS", &FitStatus);
    fits_write_key(fptr, TSTRING, "SUBSECX2 ", TBD, "Readout region X2, FITS", &FitStatus);
    fits_write_key(fptr, TSTRING, "SUBSECY1 ", TBD, "Readout region Y1, FITS", &FitStatus);
    fits_write_key(fptr, TSTRING, "SUBSECY2 ", TBD, "Readout region Y2, FITS", &FitStatus);
    fits_write_key(fptr, TSTRING, "SYSSWVER ", TBD, "Software version number", &FitStatus);
    fits_write_key(fptr, TSTRING, "TIME-END ", TBD, "Time at end of observation HH:MM:SS:UUU (UTC)", &FitStatus);
    fits_write_key(fptr, TSTRING, "TIME-OBS ", TBD, "Time at start of observation HH:MM:SS:UUU (UTC)", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "USEREXP  ", &floatValue, "Requested Exposure Time, millisec", &FitStatus);
    fits_write_comment(fptr,      "End MACIE file keys --------------------------------------------------", &FitStatus);

//    fits_write_history(fptr,      "History and comments will be continued over multiple keyword if longer than 70 characters", &FitStatus);
//    fits_write_key(fptr, TSTRING, "COMMENT  ", strValue, "-----------------", &FitStatus);
    fits_write_key(fptr, TSTRING, "TPLNAME  ", su::get_char_star(m_obContext.template_name), "Template Name", &FitStatus);
    fits_write_key(fptr, TSTRING, "TPLID    ", su::get_char_star(m_obContext.template_id), "Template ID", &FitStatus);
    fits_write_key(fptr, TSTRING, "TPLSCRIPT", su::get_char_star(m_obContext.template_script), "Template Script", &FitStatus);
    fits_write_key(fptr, TSTRING, "OBID     ", su::get_char_star(m_obContext.observation_block), "Observation Block Identifier", &FitStatus);
    fits_write_key(fptr, TSTRING, "TPLTIME  ", su::get_char_star(m_obContext.template_time), "Date and time at start of Template", &FitStatus);
    fits_write_key(fptr, TSTRING, "BUNIT    ", (char *)"ADU", "Physical Unit of array values, should be ADU", &FitStatus);
    int blank = 0;
//  fits_write_key(fptr, TINT,    "BLANK    ", &blank, "Value used for NULL pixels", &FitStatus);

    fits_write_key(fptr, TSTRING, "ORIGIN   ", (char *)"MGIO-LBT", "Observatory", &FitStatus);
    fits_write_key(fptr, TDOUBLE, "LATITUDE ", &lat_d, "North latitude of LBT (deg; +=North)", &FitStatus);
    fits_write_key(fptr, TDOUBLE, "LONGITUD ", &lon_d, "Longitude of LBT (deg; +=East)", &FitStatus);
    fits_write_key(fptr, TINT,    "ELEVATIO ", &elevatio, "Elevation of LBT above sea level (m)", &FitStatus);
    fits_write_key(fptr, TSTRING, "TELESCOP ", (char *)"LBT-SX", "Telescope used", &FitStatus);
    boolValue = false; // TODO
    fits_write_key(fptr, TLOGICAL,"MASTER   ", &boolValue, "Is telescope acting as master", &FitStatus);
    fits_write_key(fptr, TSTRING, "INSTRUME ", (char *)"SHARK-NIR", "Instrument used", &FitStatus);

    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    fits_write_key(fptr, TSTRING, "OBJECT   ", su::get_char_star(m_obContext.info_object), "Observation title given by observer", &FitStatus);
//  fits_write_key(fptr, TSTRING, "OBJNAME  ", strValue, "Standard (IAU) object name", &FitStatus);
    fits_write_key(fptr, TSTRING, "OBSCOMM  ", su::get_char_star(m_obContext.info_obs_comm), "Observer's comments", &FitStatus);
//  fits_write_key(fptr, TSTRING, "IMAGETYP ", su::get_char_star(m_obContext.dpr_type), "See Table 17 in RD6", &FitStatus);
//  fits_write_key(fptr, TFLOAT,  "EXPTIME  ", &floatValue, "Total integration time (seconds)", &FitStatus);

    floatValue = 0.f;
//  su::getValue<float>(m_sashaSetup["DIT"], floatValue);
//  fits_write_key(fptr, TFLOAT,  "DIT      ", &floatValue, "Detector Integration Time (ms)", &FitStatus);
    floatValue = 0.f;
    su::getValue<float>(m_sashaSetup["NDIT"], floatValue);
    fits_write_key(fptr, TFLOAT,  "NDIT     ", &floatValue, "Number of integrations", &FitStatus);
    floatValue = 0.f;
    fits_write_key(fptr, TSTRING, "ORIGFILE ", su::get_char_star(m_last_generated_file), "<instrume>.<yyyymmdd>.<nnnnn>.[science | calib | monit].fits orig filename at the tel (nnnnn is a 5 digit running num)", &FitStatus);
    fits_write_key(fptr, TSTRING, "CREATOR  ", (char *)"SHINS", "Software task that created this file", &FitStatus);
//  fits_write_key(fptr, TINT,    "GRPNUM   ", &intValue, "Used to group related datafiles", &FitStatus);
    fits_write_key(fptr, TSTRING, "PARTNER  ", su::get_char_star(m_obContext.info_partner), "Name of institute observing", &FitStatus);
    fits_write_key(fptr, TSTRING, "DATE     ", time_string, "'YYYY-MM-DDThh:mm:ss.sss' UTC date and time the file was written", &FitStatus);
    fits_write_key(fptr, TSTRING, "DATE-OBS ", time_string, "'YYYY-MM-DDThh:mm:ss.sss' UTC date and time of observation start", &FitStatus);
    fits_write_key(fptr, TSTRING, "UTC-OBS  ", strValue, "'hh:mm:ss.sss' UTC of observation start", &FitStatus);
    fits_write_key(fptr, TSTRING, "LST-OBS  ", su::get_char_star(tenv.lst), "'hh:mm:ss.sss'  Local sidereal time at start of observation", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "MJD-OBS  ", get_modified_julian_day(), "'nnnnn.nnnnn' Modified Julian Date at the start of the observation. 5 decimals for 1 second accuracy", &FitStatus);
    fits_write_key(fptr, TSTRING, "OBSERVER ", su::get_char_star(m_obContext.info_observer), "Name of the observer", &FitStatus);
    fits_write_key(fptr, TSTRING, "PI-COI   ", su::get_char_star(m_obContext.info_pi_coi), "Name of PI", &FitStatus);
    fits_write_key(fptr, TSTRING, "PROPID   ", su::get_char_star(m_obContext.info_propid), "Proposal identification      ", &FitStatus);

    int equinox = 2000;
    char equinox_S[] = "2000";
    fits_write_key(fptr, TSTRING, "RADECSYS ", (char *) "FK5", " Reference system for the equatorial reference system", &FitStatus);
    fits_write_key(fptr, TINT,    "EQUINOX  ", &equinox, "Standard FK5 epoch for RA and DEC", &FitStatus);

    Nice::Date t0 = Nice::Date::now();
    //getTelescopeEnv(tenv);
    Nice::Date t1 = Nice::Date::now();
//    U9_LLOG("TCS RA = " << tenv.tcs_ra << " ; TCS DEC = " << tenv.tcs_dec << "; TCS_ALT = " << tenv.mcs_elevation << "; TCS_AZ = " << tenv.mcs_azimuth << "; MCS_EL = " << tenv.mcs_elevation << "; MCS_AZ = " << tenv.mcs_azimuth << "; DeltaT = " << t1 - t0);

    fits_write_key(fptr, TSTRING, "RA       ", su::get_char_star(tenv.tcs_ra), "'hh:mm:ss.ss' Right ascension (same as TELRA)", &FitStatus);
    fits_write_key(fptr, TSTRING, "DEC      ", su::get_char_star(tenv.tcs_dec), "'dd:mm:ss.s' Declination (same as DECRA)", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "AIRMASS  ", &tenv.air_mass, "Airmass at start of observation MJD-OBS", &FitStatus);
    fits_write_key(fptr, TSTRING, "DATASUM  ", strValue, "Checksum of data section only", &FitStatus);
    fits_write_key(fptr, TSTRING, "CHECKSUM ", strValue, "ASCII 1's complement checksum", &FitStatus);
    fits_write_key(fptr, TSTRING, "CHECKVER ", (char *) "COMPLEMENT", "Checksum algorithm", &FitStatus);
//    fits_write_key(fptr, TSTRING, "LBT_LOG  ", strValue, "Operations log entry", &FitStatus);

//    U9_LLOG("OBJ RA = " << &m_TcsPreset.objra);
    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    fits_write_key(fptr, TSTRING, "PMODEL      ", su::get_char_star(tenv.pointing_model), "Pointing model in use", &FitStatus);
    // fits_write_key(fptr, TSTRING, "OBJRADEC    ", strValue, "RADEC system for OBJRA, OBJDEC", &FitStatus);
    fits_write_key(fptr, TSTRING, "OBJEQUIN    ", &equinox_S, "EQUINOX for OBJRA, OBJDEC", &FitStatus);
    fits_write_key(fptr, TSTRING, "OBJRA       ", su::get_char_star(tenv.tcs_ra), "'hh:mm:ss' RA requested (from OB or catalog)", &FitStatus);
    fits_write_key(fptr, TSTRING, "OBJDEC      ", su::get_char_star(tenv.tcs_dec), "'dd:mm:ss' DEC requested (from OB or catalog)", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "OBJPMRA     ", &floatValue, "RA proper motion [mas]", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "OBJPMDEC    ", &floatValue, "DEC proper motion [mas]", &FitStatus);
    fits_write_key(fptr, TSTRING, "TELRA       ",  su::get_char_star(tenv.tcs_ra), "'hh:mm:ss.ss' RA at detector center at MJD-OBS", &FitStatus);
    fits_write_key(fptr, TSTRING, "TELDEC      ",  su::get_char_star(tenv.tcs_dec), "'dd:mm:ss.s' DEC at detector center at MJD-OBS", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "TELALT      ", &tenv.mcs_elevation, "'dd.dd' LBT mount altitude at detector center at MJD-OBS", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "TELAZ       ", &tenv.mcs_azimuth, "'ddd.dd. LBT mount azimuth at detector center at MJD-OBS", &FitStatus);
    fits_write_key(fptr, TSTRING, "AZTRKSTAT   ", su::get_char_star(tenv.az_tracking_state), "AZ Tracking status", &FitStatus);
    fits_write_key(fptr, TSTRING, "ELTRKSTAT   ", su::get_char_star(tenv.el_tracking_state), "EL Tracking status", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "TELTKRA     ", &tenv.tel_trk_RA, "'0.0' Tracking rate from sidereal in RA [arcsec]", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "TELTKDEC    ", &tenv.tel_trk_DEC, "'0.0' Tracking rate in DEC [arcsec]", &FitStatus);
    fits_write_key(fptr, TSTRING, "HA          ", su::get_char_star(tenv.hour_angle), "'+hh:mm:ss' hour angle at start of observation MJD-OBS", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "ZD          ", &tenv.zenith_dist, "'dd.dd' zenith distance of LBT mount at MJD-OBS [deg]. Complement of TELALT", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "MOONANGLEALT", &tenv.moon_alt, "'ddd.ddd' Moon Altitude angle at start [deg]", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "MOONANGLEAZ ", &tenv.moon_az, "'ddd.ddd' Moon Azimuth angle at start [deg]", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "SUNANGLEALT ", &tenv.sun_alt, "'ddd.ddd' Sun Altitude angle at start [deg]", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "SUNANGLEAZ  ", &tenv.sun_az, "'ddd.ddd' Sun Azimuth angle at start [deg]", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "PARANGLE    ", &tenv.parallactic_angle, "'ddd.d' Parallactic angle at start [deg]", &FitStatus);
    fits_write_key(fptr, TSTRING, "PARAANG  ", TBD, "Parallactic angle, degs", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "POSANGLE    ", &tenv.pos_angle, "'ddd.d' Telescope position angle [deg]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "ROTANGLE    ", &tenv.rot_angle, "'dd.d' Telescope rotator angle [deg]", &FitStatus);
    fits_write_key(fptr, TSTRING, "ROTASTAT    ", (char *)"OFF", "Rotator status. LBT rotator is always OFF when using SHARK-NIR", &FitStatus);

    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    fits_write_key(fptr, TSTRING, "HIERARCH TEL MODE",             su::get_char_star(tenv.telescope_mode), "Telescope Mode", &FitStatus);
    fits_write_key(fptr, TLOGICAL,"HIERARCH TEL BINOCULAR",        &m_TcsPreset.binocular, "Telescope Binocular Mode", &FitStatus);
    fits_write_key(fptr, TSTRING, "HIERARCH TEL AOMODE",           su::get_char_star(tenv.ao_mode), "Adaptive Optics Mode", &FitStatus);
    fits_write_key(fptr, TSTRING, "HIERARCH TEL OBJECTNAME",       su::get_char_star(m_TcsPreset.object_name), "Target Object's name", &FitStatus);
    fits_write_key(fptr, TSTRING, "HIERARCH TEL COORDINATE SYSTEM", (char *)"FK5", "Target Coordinate system", &FitStatus);
    fits_write_key(fptr, TSTRING, "HIERARCH TEL OBJEQUIN",         &equinox_S, "Target Coordinate System Equinox", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL EPOCH",            &tenv.epoch, "Epoch", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL OBJRA ",           &tenv.achieved_ra, "Target RA [rad]", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL OBJDEC",           &tenv.achieved_dec, "Target DEC [rad]", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL OBJMAG",           &m_TcsPreset.objmag, "Target Magnitude", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL PMRA",             &m_TcsPreset.pmra, "Target RA Proper Motion", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL PMDEC",            &m_TcsPreset.pmdec, "Target DEC Porper Motion", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL AORA",             &tenv.ao_ref_ra, "Adaptive Optics in RA", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL AODEC",            &tenv.ao_ref_dec, "Adaptive Optics in DEC", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL AOMAG",            &tenv.ao_ref_mag, "Ref star Magnitude", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL PMAORA",           &m_TcsPreset.pmaora, "Proper motion of AO in RA", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL PMAODEC",          &m_TcsPreset.pmaodec, "Proper motion of AO in DEC", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL GSRA",             &tenv.guide_star_ra, "Guide Star RA", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL GSDEC",            &tenv.guide_star_dec, "Guide Star in DEC", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL GSMAG",            &m_TcsPreset.gsmag, "Guide Star Magnitude", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL PMGSRA",           &m_TcsPreset.pmgsra, "Guide Star proper motion in RA", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL PMGSDEC",          &m_TcsPreset.pmgsdec, "Guide Star proper motion in DEC", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL DECOFFSET",        &tenv.target_decoffset, "Pointing Offset X", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH TEL RAOFFSET",         &tenv.target_raoffset, "Pointing Offset Y", &FitStatus);
    // TEL AOMode               Adaptive Optics Mode                        AOMODE
    // TEL OFFSET               Offset

// MISSING
//            "Epoch":"2022.5",
//            "Equinox": "j2000",
//            "BinocularFlag":"OFF",
//            "TelescopeMode": "ADAPTIVEACE_TRACK",
//            "TelescopeSide": "LEFT",

//    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M1-X     ", &floatValue, "X position of primary mirror [mm]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M1-Y     ", &floatValue, "Y position of primary mirror [mm]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M1-Z     ", &floatValue, "Focus of primary mirror [mm]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M1-RX    ", &floatValue, "Tilt of primary across X axis [arcsecs]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M1-RY    ", &floatValue, "Tilt of primary across X axis [arcsecs]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M1-RZ    ", &floatValue, "Rotation of primary about Z axis [arcsecs]", &FitStatus);

//    fits_write_key(fptr, TFLOAT,  "M2-X     ", &floatValue, "X position of secondary mirror [mm]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M2-Y     ", &floatValue, "Y position of secondary mirror [mm]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M2-Z     ", &floatValue, "Focus of secondary mirror [mm]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M2-RX    ", &floatValue, "Tilt of secondary across X axis [arcsecs]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M2-RY    ", &floatValue, "Tilt of secondary across X axis [arcsecs]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "M2-RZ    ", &floatValue, "Rotation of secondary about Z axis [arcsecs]", &FitStatus);

    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);

    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT DIMMSEEINGZ",  &tenv.dimm_seeing_zenith,    "DIMM seeing value correctedo to zenith [arcsecs]", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT DIMMSEEINGEL", &tenv.dimm_seeing_elevation, "DIMM seeing value corrected to LBT elevation [arcsec]", &FitStatus);
    fits_write_key(fptr, TLOGICAL, "HIERARCH LBT WEATHERALIVE", &tenv.lbt_weather_alive,     "Weather Station Link State", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT PRESSURE",     &tenv.lbt_pressure,          "Ambient Pressure [hPa]", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT TEMP",         &tenv.lbt_temp,              "Ambient Temperature [deg C]", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT HUMIDITY",     &tenv.lbt_humidity,          "LBT Relative Humidity [percent]", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT DEWPOINT",     &tenv.lbt_dewpoint,          "LBT Dew Point [deg C]", &FitStatus);    
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT WIND SPEED",   &tenv.lbt_wind_speed,        "LBT Wind Speed [m/s]", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT WIND DIR",     &tenv.lbt_wind_dir,          "LBT Wind Direction [deg]", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT WIND DIR RAW", &tenv.lbt_wind_dir_raw,      "LBT Wind Raw Direction [deg]", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT WIND SPEED FRONT",   &tenv.lbt_wind_speed_front,        "LBT Wind Speed Front [m/s]", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT WIND DIR FRONT",     &tenv.lbt_wind_dir_front,          "LBT Wind Direction Front [deg]", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT WIND DIR RAW FRONT", &tenv.lbt_wind_dir_raw_front,      "LBT Wind Raw Direction Front[deg]", &FitStatus);
    
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT SOULX",        &tenv.ao_offsetx,            "value for X axis of SOUL stage", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT SOULY",        &tenv.ao_offsety,            "value ofr Y axis of SOUL stage", &FitStatus);
    fits_write_key(fptr, TFLOAT,   "HIERARCH LBT SOULZ",        &tenv.ao_offsetz,            "value ofr Z axis of SOUL stage", &FitStatus);

    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
//    fits_write_key(fptr, TSTRING, "TELCONF  ", &tenv.tel_conf,    "Telescope configuration", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "FOCSCALE ", &floatValue, "Scale at focal plane [arcsec]", &FitStatus);
    //U9_LLOG("INSMODE ---------------------------1---------(The one not working-------- :  " << m_sashaSetup[suc::InstrumentMode]);
    fits_write_key(fptr, TSTRING, "INSMODE  ", su::get_char_star(m_sashaSetup[suc::InstrumentMode]),  "Mode of use of instrument", &FitStatus);
    fits_write_key(fptr, TSTRING, "INSTHWV  ", (char *) "v1.0",    "Instrument hardware version", &FitStatus);
    fits_write_key(fptr, TSTRING, "INSTSWV  ", strValue,    "Instrument software version", &FitStatus);
//  fits_write_key(fptr, TFLOAT,  "INSFOCUS ", &floatValue, "Instrument focus [mm]", &FitStatus);

    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    // Table 11 Detector specific keywords
//    fits_write_key(fptr, TSTRING, "DETECTOR ", strValue, "Detector designation", &FitStatus);
//    fits_write_key(fptr, TSTRING, "DETNAME  ", (char *)"[1:2048, 1:2048]", "Same as DETECTOR DETSIZE '[1:2048,1:2048]' Unbinned size of detector full array", &FitStatus);

    int nccds = 1;
    int namps = 4;
    float pixscale = 14.5f;

    fits_write_key(fptr, TINT,   "NCCDS    ", &nccds, "Number of CCDs in the detector", &FitStatus);
    fits_write_key(fptr, TINT,   "NAMPS    ", &namps, "Number of amplifiers in the detector", &FitStatus);
    fits_write_key(fptr, TFLOAT, "PIXSCAL1 ", &pixscale, "Projected unbinned pixel scale along axis 1", &FitStatus);
    fits_write_key(fptr, TFLOAT, "PIXSCAL2 ", &pixscale, "Projected unbinned pixel scale along axis 2", &FitStatus);

    float pixsize = 18.f;
    fits_write_key(fptr, TFLOAT, "PIXSIZE1 ", &pixsize,    "Unbinned det pix size [microns] along Axis 1", &FitStatus);
    fits_write_key(fptr, TFLOAT, "PIXSIZE2 ", &pixsize,    "Unbinned det pix size [microns] along Axis 2", &FitStatus);
//  fits_write_key(fptr, TSTRING, "DET HWV  ", (char *)"0.0.0", "Detector Hardware Version", &FitStatus);
//  fits_write_key(fptr, TSTRING, "DET SWV  ", (char *)"0.0.0", "Detector Software Version", &FitStatus);
//  fits_write_key(fptr, TSTRING, "DET STAT ", strValue,    "Detector status", &FitStatus);
//  fits_write_key(fptr, TSTRING, "DEWAR    ", strValue,    "Dewar identification", &FitStatus);
//  fits_write_key(fptr, TSTRING, "DEWHWV   ", strValue,    "Dewar hardware version", &FitStatus);
//  fits_write_key(fptr, TSTRING, "DEWSWV   ", strValue,    "Dewar software version", &FitStatus);
//  fits_write_key(fptr, TSTRING, "DEWSTAT  ", strValue,    "Dewar status", &FitStatus);
//  fits_write_key(fptr, TFLOAT,  "DEWTEM1  ", &floatValue, "Dewar temperature", &FitStatus);

    fits_write_key(fptr, TSTRING, "DETSIZE", (char *)"[1:2048, 1:2048]", "Unbinned size of detector full array", &FitStatus);
//  fits_write_key(fptr, TSTRING, "PIXCAL1", (char *)"", "Projected unbinnned pixel scale along axis 1", &FitStatus);
//  fits_write_key(fptr, TSTRING, "PIXCAL2", (char *)"", "Projected unbinned pixel scale along axis 2", &FitStatus);

    SInstrumentHeader & ih = m_instrumentHeader;
    
    int xmin, xmax, ymin, ymax;
    std::string detActiveRegion = GetReadoutRegion(xmin, xmax, ymin, ymax);

    fits_write_key(fptr, TFLOAT,  "CCDTEM   ", &ih.temperature_start, "Detector Temperature", &FitStatus);
//  fits_write_key(fptr, TSTRING, "UDEWTEM  ", strValue,    "Units for Dewar temperatures", &FitStatus);
    std::string u_tem = "K";
    fits_write_key(fptr, TSTRING, "UCCDTEM  ", su::get_char_star(u_tem),    "Units for CCD temperature", &FitStatus);
//  fits_write_key(fptr, TSTRING, "CCDNAME  ", strValue,    "Name of individual CCD (serial #)", &FitStatus);
    fits_write_key(fptr, TSTRING, "CCDID    ", (char *)"part 17028",    "Detector serial number (=ccdname)", &FitStatus);
//  fits_write_key(fptr, TSTRING, "AMPNAME  ", strValue,    "Amplifier name", &FitStatus);
//  fits_write_key(fptr, TSTRING, "CCDHW    ", strValue,    "Same as DETHWV if NCCDS = 1", &FitStatus);
//  fits_write_key(fptr, TSTRING, "CCDSW    ", strValue,    "Same as DETSWV if NCCDS = 1", &FitStatus);
//  fits_write_key(fptr, TSTRING, "CCDGAIN  ", strValue,    "CCD gain state", &FitStatus);
    float gain = 1.97;// 2.9 [e-/DN]; //1.97 [e-/ADU];
    fits_write_key(fptr, TFLOAT,  "GAIN     ", &gain, "Detector gain in [e-/ADU], error = +- 0.02", &FitStatus);
    float rdnoise = 181.2;
    fits_write_key(fptr, TFLOAT,  "RDNOISE  ", &rdnoise, "Detector readout noise in e-", &FitStatus);
    int saturate = 65535;
    fits_write_key(fptr, TINT,    "SATURATE ", &saturate,   "Saturation value in ADU", &FitStatus);
//  fits_write_key(fptr, TSTRING, "BPM      ", strValue,    "Name of bad pixel mask", &FitStatus);
    fits_write_key(fptr, TSTRING, "CCDSIZE  ", (char *)"[1:2048, 1:2048]",    "'[x1:x2,y1:y2]' Same as DETSIZE", &FitStatus);
    fits_write_key(fptr, TSTRING, "CCDPSIZE ", (char *)"[1:2048, 1:2048]",    "'[x1:x2,y1:y2]' Same as CCDSIZE except for drift scanning", &FitStatus);
    fits_write_key(fptr, TINT,    "CCDSUM   ", &intValue,   "CCD on-chip summing (binning)", &FitStatus);
//    fits_write_key(fptr, TSTRING, "AMPSIZE  ", strValue,    "'[x1:x2,y1:y2]' Unbinned amplifier readout size", &FitStatus);
    fits_write_key(fptr, TSTRING, "DATASEC  ", su::get_char_star(detActiveRegion),    "'[x1:x2,y1:y2]' Image data section", &FitStatus);
    fits_write_key(fptr, TSTRING, "CCDSEC   ", su::get_char_star(detActiveRegion),    "'[x1:x2,y1:y2]' Region of the CCD read", &FitStatus);
//    fits_write_key(fptr, TSTRING, "DETSEC   ", strValue,    "'[x1:x2,y1:y2]' Detector section", &FitStatus);
//    fits_write_key(fptr, TSTRING, "BIASSEC  ", strValue,    "'[x1:x2,y1:y2]' Bias section", &FitStatus);
    fits_write_key(fptr, TSTRING, "TRIMSEC  ", su::get_char_star(detActiveRegion),    "'[x1:x2,y1:y2]' Section of useful data", &FitStatus);
//    fits_write_key(fptr, TSTRING, "AMPSEC   ", strValue,    "'[x1:x2,y1:y2]' Mapping of CCD section to amplifier coordinates", &FitStatus);
//    fits_write_key(fptr, TINT,    "CCDNAMPS ", &intValue,   "Number of amplifiers to readout the CCD", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "PREFLASH ", &floatValue, "CCD preflash time [ms]", &FitStatus);
//  fits_write_key(fptr, TFLOAT,  "ITIME    ", &floatValue, "Exposure time[s] per coadd", &FitStatus);
//  fits_write_key(fptr, TINT,    "NCOADDS  ", &intValue,   "Number of coadds (result is sum)", &FitStatus);
//    fits_write_key(fptr, TINT,    "ACOAVGS  ", &intValue,   "Number of co-avgs (if individual exps are averaged and not summed –sum & NCOADDS preferred)", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "DETTIME  ", &floatValue, "Total exposure time in seconds: NCOADDS x ITIME", &FitStatus);
//  fits_write_key(fptr, TSTRING, "READMODE ", strValue,    "Readout mode", &FitStatus);
//  fits_write_key(fptr, TINT,    "NREADS   ", &intValue,   "# reads at beg, end or during ITIME", &FitStatus);

    // Possible values are
    //  - FLUX when the image is produced by SHARKNIR-CAL-SCI-02
    //  - WAFFLE when the image is produced by SHARKNIR-CAL-SCI-08
    //  - OBJECT in all other cases
    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    fits_write_key(fptr, TSTRING, "HIERARCH LBT DPR TYPE", su::get_char_star(m_obContext.dpr_type), "Data Product Type", &FitStatus);

//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS MECH1 NAME", su::get_char_star(ih.inbeam_dep.nameid), "", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS MECH1 ID",   &ih.inbeam_dep.mechanical_id, "General mechanical device unique ID", &FitStatus);
//   fits_write_key(fptr, TLOGICAL,"HIERARCH LBT INS MECH1 ST",   &ih.inbeam_dep.status,    "T Deployed F Non Deployed", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS MECH1 POS",  &ih.inbeam_dep.position1, "Position axis [um]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS MECH1 ENC",  &ih.inbeam_dep.encoder1,  "Position axis [enc]", &FitStatus);

//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS MIRR1 POS1", &ih.inbeam_tt.position1, "Position of axis 1 [mm]", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS MIRR1 POS2", &ih.inbeam_tt.position2, "Position of axis 2 [mm]", &FitStatus);

//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS SHUT1 NAME", su::get_char_star(ih.shutter.nameid), "", &FitStatus);
//   fits_write_key(fptr, TLOGICAL,"HIERARCH LBT INS SHUT1 ST",   &ih.shutter.status,    "T Shutter open", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS SHUT1 POS",  &ih.shutter.position1, "Position of axis [um]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS SHUT1 ENC",  &ih.shutter.encoder1,  "Position of axis [enc]", &FitStatus);

//   fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);

//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS MIRR3 NAME", su::get_char_star(ih.cal_mirror_dep.nameid), "Name of optical element", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS MIRR3 ID",   &ih.cal_mirror_dep.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TLOGICAL,"HIERARCH LBT INS MIRR3 ST",   &ih.cal_mirror_dep.status,    "Status of deployable mirror", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS MIRR3 POS",  &ih.cal_mirror_dep.position1, "Position of axis [mm]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS MIRR3 ENC",  &ih.cal_mirror_dep.encoder1,  "Position of axis [enc]", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI4 NAME", su::get_char_star(ih.cal_filter_dep.nameid), "Name of optical element ", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI4 TYPE", su::get_char_star(ih.cal_filter_dep.type), "Named Position if any", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI4 NP",   su::get_char_star(ih.cal_filter_dep.type), "Named Position if any", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI4 ID",   &ih.cal_filter_dep.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS OPTI4 POS",  &ih.cal_filter_dep.position1, "Position of axis [mm]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI4 ENC",  &ih.cal_filter_dep.encoder1,  "Position of axis [enc]", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI5 NAME", su::get_char_star(ih.cal_fiber_dep.nameid), "Name of optical element ", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI5 TYPE", su::get_char_star(ih.cal_fiber_dep.type),   "Named Position if any", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI5 NP",   su::get_char_star(ih.cal_fiber_dep.type),   "Named Position if any", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI5 ID",   &ih.cal_fiber_dep.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS OPTI5 POS",  &ih.cal_fiber_dep.position1, "Position of axis [mm]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI5 ENC",  &ih.cal_fiber_dep.encoder1,  "Position of axis [enc]", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI6 NAME", su::get_char_star(ih.cal_flat_field_lamp.nameid), "common name for flat field lamp", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI6 ID",   &ih.cal_flat_field_lamp.mechanical_id, "Sensor ID for OPTI6 ", &FitStatus);
//   fits_write_key(fptr, TLOGICAL,"HIERARCH LBT INS OPTI6 VAL",  &ih.cal_flat_field_lamp.status, "T for ON, F for OFF ", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI7 NAME", su::get_char_star(ih.cal_fiber_focus_lamp.nameid), "common name for focus lamp", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI7 ID",   &ih.cal_fiber_focus_lamp.mechanical_id, "Sensor ID for OPTI7 ", &FitStatus);
//   fits_write_key(fptr, TLOGICAL,"HIERARCH LBT INS OPTI7 VAL",  &ih.cal_fiber_focus_lamp.status, "T for ON, F for OFF ", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI8 NAME", su::get_char_star(ih.cal_fiber_defocus_lamp.nameid), "common name for defocus lamp", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI8 ID",   &ih.cal_fiber_defocus_lamp.mechanical_id, "Sensor ID for OPTI8 ", &FitStatus);
//   fits_write_key(fptr, TLOGICAL,"HIERARCH LBT INS OPTI8 VAL",  &ih.cal_fiber_defocus_lamp.status, "T for ON, F for OFF ", &FitStatus);
//
//   fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS FILT1 NAME", su::get_char_star(ih.nd_filt_w.nameid), "Name of optical element", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS FILT1 NP",   su::get_char_star(ih.nd_filt_w.type),   "Named position if any", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT1 NO",   &ih.nd_filt_w.intValue, "Position of wheel used", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT1 ID",   &ih.nd_filt_w.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS FILT1 POS",  &ih.nd_filt_w.position1, "Position of axis [deg]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT1 ENC",  &ih.nd_filt_w.encoder1, "Position of axis [enc]", &FitStatus);

//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI1 NAME", su::get_char_star(ih.apodizer_w.nameid), "Name of optical element", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI1 NP",   su::get_char_star(ih.apodizer_w.type),   "Named position if any", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI1 NO",   &ih.apodizer_w.intValue, "Position of wheel used", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI1 ID",   &ih.apodizer_w.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS OPTI1 POS",  &ih.apodizer_w.position1, "Position of axis [deg]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI1 ENC",  &ih.apodizer_w.encoder1, "Position of axis [enc]", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI2 NAME", su::get_char_star(ih.coro_slit_w.nameid), "Name of optical element", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI2 NP",   su::get_char_star(ih.coro_slit_w.type),   "Named position if any", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI2 NO",   &ih.coro_slit_w.intValue, "Position of wheel used", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI2 ID",   &ih.coro_slit_w.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS OPTI2 POS",  &ih.coro_slit_w.position1, "Position of axis [deg]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI2 ENC",  &ih.coro_slit_w.encoder1, "Position of axis [enc]", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI3 NAME", su::get_char_star(ih.lyot_grism_w.nameid), "Name of optical element", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS OPTI3 NP",   su::get_char_star(ih.lyot_grism_w.type),   "Named position if any", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI3 NO",   &ih.lyot_grism_w.intValue, "Position of wheel used", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI3 ID",   &ih.lyot_grism_w.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS OPTI3 POS",  &ih.lyot_grism_w.position1, "Position of axis [deg]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS OPTI3 ENC",  &ih.lyot_grism_w.encoder1, "Position of axis [enc]", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS FILT2 NAME", su::get_char_star(ih.sci_filt_w1.nameid), "Name of optical element", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS FILT2 NP",   su::get_char_star(ih.sci_filt_w1.type),   "Named Position if any", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT2 NO",   &ih.sci_filt_w1.status, "Position of wheel used", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT2 ID",   &ih.sci_filt_w1.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS FILT2 POS",  &ih.sci_filt_w1.position1, "Position of axis [deg]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT2 ENC",  &ih.sci_filt_w1.encoder1, "Position of axis [enc]", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS FILT3 NAME", su::get_char_star(ih.sci_filt_w2.nameid), "Name of optical element", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS FILT3 NP",   su::get_char_star(ih.sci_filt_w2.type),   "Named position if any", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT3 NO",   &ih.sci_filt_w2.intValue, "Position of wheel used", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT3 ID",   &ih.sci_filt_w2.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS FILT3 POS",  &ih.sci_filt_w2.position1, "Position of axis [deg]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT3 ENC",  &ih.sci_filt_w2.encoder1, "Position of axis [enc]", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS MECH2 NAME", su::get_char_star(ih.pupil_lens_dep.nameid), "Name of the optical element", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS MECH2 ID",   &ih.pupil_lens_dep.mechanical_id, "General mechanical device unique ID", &FitStatus);
//   fits_write_key(fptr, TLOGICAL,"HIERARCH LBT INS MECH2 ST",   &ih.pupil_lens_dep.status, "T deployed; F not deployed", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS MECH2 POS",  &ih.pupil_lens_dep.position1, "Position axis [um]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS MECH2 ENC",  &ih.pupil_lens_dep.encoder1, "Position axis [enc]", &FitStatus);
//
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS FILT4 NAME", su::get_char_star(ih.db_filt_w.nameid), "Name of optical element", &FitStatus);
//   fits_write_key(fptr, TSTRING, "HIERARCH LBT INS FILT4 NP",   su::get_char_star(ih.db_filt_w.type),   "Named position if any", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT4 NO",   &ih.db_filt_w.intValue, "Position of wheel used", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT4 ID",   &ih.db_filt_w.mechanical_id, "ID of the element", &FitStatus);
//   fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS FILT4 POS",  &ih.db_filt_w.position1, "Position of axis [deg]", &FitStatus);
//   fits_write_key(fptr, TINT,    "HIERARCH LBT INS FILT4 ENC",  &ih.db_filt_w.encoder1, "Position of axis [enc]", &FitStatus);

   fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);

    fits_write_key(fptr, TSTRING, "HIERARCH INS DROT NAME",  su::get_char_star(ih.derotator.nameid), "", &FitStatus);
    fits_write_key(fptr, TINT,    "HIERARCH INS DROT ID",    &ih.derotator.mechanical_id, "General mechanical device unique ID", &FitStatus);
    fits_write_key(fptr, TFLOAT,  "HIERARCH INS DROT POS",   &ih.derotator.position1, "Position axis [deg]", &FitStatus);
    fits_write_key(fptr, TINT,    "HIERARCH INS DROT ENC",   &ih.derotator.encoder1, "Position axis [enc]", &FitStatus);
    fits_write_key(fptr, TINT,    "HIERARCH INS DROT ABSENC",&ih.derotator.encoder2, "Position axis [absenc]", &FitStatus);
    fits_write_key(fptr, TSTRING, "HIERARCH INS DROT_MODE",  su::get_char_star(ih.drot_mode.type), "Derotator Mode", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS DROT BEGIN", &ih.derotator.position2, "Physical position at start [deg]", &FitStatus);

   fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    fits_write_key(fptr, TSTRING, "HIERARCH INS ADC NAME",   su::get_char_star(su::left(ih.adc.nameid, 3)), "", &FitStatus);
    fits_write_key(fptr, TINT,    "HIERARCH INS ADC1 ENC",   &ih.adc.encoder1, "Position of axis 1 [enc]", &FitStatus);
    fits_write_key(fptr, TINT,    "HIERARCH INS ADC2 ENC",   &ih.adc.encoder2, "Position of axis 2 [enc]", &FitStatus);
    fits_write_key(fptr, TSTRING, "HIERARCH INS ADC_MODE",   su::get_char_star(ih.adc_mode.type), "Atmospheric Dispersion Corrector Mode", &FitStatus);

//    fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS TEMP1 VAL BEGIN", &ih.temperature_start, "Cryo-vacuum T [K] at observation start ", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "HIERARCH LBT INS TEMP1 VAL END ",  &ih.temperature_end, "Cryo-vacuum T [K] at end of observation", &FitStatus);


    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);

    getMaskCombo(ih);
// Table 10 Instrument setup keyword
    fits_write_key(fptr, TSTRING, "INS INSTRUMENT MODE",        su::get_char_star(m_sashaSetup[suc::InstrumentMode]), "Instrument Mode", &FitStatus);

    //U9_LLOG("------------CORO_SLIT_W  :  " << ih.coro_slit_w.type);
    fits_write_key(fptr, TSTRING, "INS MASKCOMBO",              su::get_char_star(ih.mask_comb.type), "Mask Combination", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS SHUTTER",                su::get_char_star(ih.shutter.type), "Shutter Deployer", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS INBEAM_DEP",             su::get_char_star(ih.inbeam_dep.type), "Input Beam Deployer", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS INBEAM_TT",              su::get_char_star(ih.inbeam_tt.type), "Input Beam TipTilt Mirror", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS APODIZER_W",             su::get_char_star(ih.apodizer_w.type), "Apodizer Wheel", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS ND_FILT_W",              su::get_char_star(ih.nd_filt_w.type), "Neutral Density Filter Wheel", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS CORO_SLIT_W",            su::get_char_star(ih.coro_slit_w.type), "Coronagraphic Filter Wheel", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS LYOT_GRISM_W",           su::get_char_star(ih.lyot_grism_w.type), "Lyot Grism Wheel", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS DB_FILT_W",              su::get_char_star(ih.db_filt_w.type), "Dual Band Filter Wheel", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS SCI_FILT_W1",            su::get_char_star(ih.sci_filt_w1.type), "Scientific Filter Wheel 1", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS SCI_FILT_W2",            su::get_char_star(ih.sci_filt_w2.type), "Scientific Filter Wheel 2", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS PUPIL_LENS_DEP",         su::get_char_star(ih.pupil_lens_dep.type), "Pupil Lens Deployer", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS CAL_FIBER_FOCUS_LAMP",   su::get_char_star(ih.cal_fiber_focus_lamp.type), "Calibration Fiber Focus Lamp", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS CAL_FIBER_DEFOCUS_LAMP", su::get_char_star(ih.cal_fiber_defocus_lamp.type), "Calibration Fiber Defocus Lamp", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS CAL_FF_LAMP",            su::get_char_star(ih.cal_flat_field_lamp.type), "Calibration Flat Field Lamp", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS CAL_FIBER_DEP",          su::get_char_star(ih.cal_fiber_dep.type), "Calibration Fiber Deployer", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS CAL_FILTER_DEP",         su::get_char_star(ih.cal_filter_dep.type), "Calibration Filter Deployer", &FitStatus);
    fits_write_key(fptr, TSTRING, "INS CAL_MIRROR_DEP",         su::get_char_star(ih.cal_mirror_dep.type), "Calibratio Mirror Deployer", &FitStatus);


    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    // Table 11 Detector Setup keywords
    floatValue = 0.f;
//    su::getValue<float>(m_sashaSetup["DIT"], floatValue);
//    fits_write_key(fptr, TFLOAT,  "DET DIT",   &floatValue, "Detector Integration Time", &FitStatus);
//    intValue = su::convertStringToType<int>(sashaNumReads["value"].getValue());
//    fits_write_key(fptr, TINT,    "DET NREADS", &intValue, "Number of Reads", &FitStatus);
//    intValue = su::convertStringToType<int>(sashaNumDrops["value"].getValue());
//    fits_write_key(fptr, TINT,    "DET NDROPS", &intValue, "Number of Drops", &FitStatus);
//    intValue = su::convertStringToType<int>(sashaNumGroups["value"].getValue());
//    fits_write_key(fptr, TINT,    "DET NGROUPS", &intValue, "Number of Groups", &FitStatus);
//    intValue = su::convertStringToType<int>(sashaNumCoadds["value"].getValue());
//    fits_write_key(fptr, TINT,    "DET NCOADDS", &intValue, "Number of Coadditions", &FitStatus);
//    intValue = su::convertStringToType<int>(sashaNumSeqs["value"].getValue());
//    fits_write_key(fptr, TINT,    "DET NDIT", &intValue, "Number of DITs", &FitStatus);
    fits_write_key(fptr, TSTRING, "DET READOUT", su::get_char_star(sashaReadout["value"].getValue()), "Readout Mode", &FitStatus);
    intValue = su::convertStringToType<int>(sashaSeqNum["value"].getValue());
    fits_write_key(fptr, TINT,    "DET CEXP", &intValue, "Current exposure", &FitStatus);

    fits_write_key(fptr, TINT,    "DET XMIN", &xmin, "Minimum of X-axis", &FitStatus);
    fits_write_key(fptr, TINT,    "DET XMAX", &xmax, "Maximum of X-axis", &FitStatus);
    fits_write_key(fptr, TINT,    "DET YMIN", &ymin, "Minimum of Y-axis", &FitStatus);
    fits_write_key(fptr, TINT,    "DET YMAX", &ymax, "Maximum of Y-axis", &FitStatus);
//    fits_write_key(fptr, TLOGICAL,"DET NEXTASBG", &intValue, "Next", &FitStatus);
//    fits_write_key(fptr, TLOGICAL,"DET PREVASBG", &intValue, "Previous", &FitStatus);
    intValue = sashaSave["value"].getSwitchState() == IElement::On;
    fits_write_key(fptr, TLOGICAL,"DET SAVE", &intValue, "Save Flag on SASHA", &FitStatus);

    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    // Table 12 RTC Setup Keywords
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTWINCOORDX"]);
    fits_write_key(fptr, TINT,    "TTWINCOORDX", &intValue, "Distance in rows from px in full frame", &FitStatus);
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTWINCOORDY"]);
    fits_write_key(fptr, TINT,    "TTWINCOORDY", &intValue, "Distance in cols from px in full frame", &FitStatus);
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTWINROWS"]);
    fits_write_key(fptr, TINT,    "TTWINROWS", &intValue, "Tiptilt Window Rows", &FitStatus);
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTWINCOLS"]);
    fits_write_key(fptr, TINT,    "TTWINCOLS", &intValue, "Tiptilt Window Columns", &FitStatus);
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTFRAMERATE"]);
    fits_write_key(fptr, TINT,    "TTFRAMERATE", &intValue, "TipTilt frame rate", &FitStatus);
    floatValue = su::convertStringToType<float>(m_rtcKeywords["TTCAMTINT"]);
    fits_write_key(fptr, TFLOAT,  "TTCAMTINT", &floatValue, "TipTilt Camera integration time", &FitStatus);
//    if(m_pGetRtcStatus){ intValue = su::convertStringToType<int>(m_pGetRtcStatus("TTLOOPENABLED"));}
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTLOOPENABLED"]);
    fits_write_key(fptr, TLOGICAL,"TTLOOPENABLED", &intValue, "Flag indicating fast tip/tilt loop status", &FitStatus);
//    fits_write_key(fptr, TLOGICAL,"TTHISTORYENABLED", &intValue, "Number of images save", &FitStatus);
//    fits_write_key(fptr, TSTRING, "TTTIMEHISTORYFILE", (char *)"", "Time History Filename", &FitStatus);
//    fits_write_key(fptr, TINT,    "TTTIMEHISTLEN", &intValue, "Number of history lines if history is enabled", &FitStatus);
    floatValue = su::convertStringToType<float>(m_rtcKeywords["TTDMMAXPOWER"]);
    fits_write_key(fptr, TFLOAT,  "TTDMMAXPOWER", &floatValue, "Max Power allowed to DM", &FitStatus);
    fits_write_key(fptr, TSTRING, "TTBIASFILE", su::get_char_star(m_rtcKeywords["TTBIASFILE"]), "Bias File", &FitStatus);
    fits_write_key(fptr, TSTRING, "TTDMFLATFILE", su::get_char_star(m_rtcKeywords["TTDMFLATFILE"]), "Flat File", &FitStatus);
    if(m_pGetRtcStatus && !m_bFastSavingMode){m_rtcKeywords["TTSENSORTEMP"] = m_pGetRtcStatus("TTSENSORTEMP");}
    floatValue = su::convertStringToType<float>(m_rtcKeywords["TTSENSORTEMP"]);
    fits_write_key(fptr, TFLOAT,  "TTSENSORTEMP", &floatValue, "sensor temperature in degree C", &FitStatus);
    floatValue = su::convertStringToType<float>(m_rtcKeywords["TTCENTROIDGAINX"]);
    fits_write_key(fptr, TFLOAT,  "TTCENTROIDGAINX", &floatValue, "TipTilt gain on centroid X", &FitStatus);
    floatValue = su::convertStringToType<float>(m_rtcKeywords["TTCENTROIDGAINY"]);
    fits_write_key(fptr, TFLOAT,  "TTCENTROIDGAINY", &floatValue, "TipTilt gain on centroid Y", &FitStatus);
    floatValue = su::convertStringToType<float>(m_rtcKeywords["TTCENTROIDORIGX"]);
    fits_write_key(fptr, TFLOAT,  "TTCENTROIDORIGX", &floatValue, "X Origin position in [-1,1] coordinates", &FitStatus);
    floatValue = su::convertStringToType<float>(m_rtcKeywords["TTCENTROIDORIGY"]);
    fits_write_key(fptr, TFLOAT,  "TTCENTROIDORIGY", &floatValue, "Y Origin position in [-1,1] coordinates", &FitStatus);
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTDMMODESNUM"]);
    fits_write_key(fptr, TINT,    "TTDMMODESNUM", &intValue, "number of modes in zonal influence matrix", &FitStatus);
    fits_write_key(fptr, TSTRING, "TTPIXELGAINFILE", su::get_char_star(m_rtcKeywords["TTPIXELGAINFILE"]), "Gain factor for each pixel in which the centroid is calculated", &FitStatus);
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTPIXELENABLED"]);
    fits_write_key(fptr, TLOGICAL,"TTPIXELENABLED", &intValue, "Enable saving frames on memory", &FitStatus);
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTPIXELDECIMATION"]);
    fits_write_key(fptr, TINT,    "TTPIXELDECIMATION", &intValue,   "Frame saving decimation factor", &FitStatus);
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTDIAGENABLED"]);
    fits_write_key(fptr, TLOGICAL,"TTDIAGENABLED", &intValue, "Diagnostic enabled", &FitStatus);
    intValue = su::convertStringToType<int>(m_rtcKeywords["TTDIAGDECIMATION"]);
    fits_write_key(fptr, TINT,    "TTDIAGDECIMATION", &intValue,   "TipTilt diagnostic decimation factor", &FitStatus);

    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    int WCSAXES = 2;
    if(m_sashaSetup[suc::InstrumentMode] == "LSS") { WCSAXES = 3;}
    fits_write_key(fptr, TINT,    "WCSAXES   ", &WCSAXES,     "Dimensionality of WCS: 2 for image, 3 for spectrum", &FitStatus);
    if(WCSAXES == 2)
    {
        fits_write_key(fptr, TSTRING, "CTYPE1   ", (char *)"RA---TAN",    "Pixel coordinate system", &FitStatus);
        fits_write_key(fptr, TSTRING, "CTYPE2   ", (char *)"DEC--TAN",    "Pixel coordinate system", &FitStatus);
        fits_write_key(fptr, TSTRING, "RADESYS  ", (char *)"ICRS",        "reference system", &FitStatus);
        fits_write_key(fptr, TSTRING, "CUNIT1   ", (char *)"deg",         "Unit of coordinate transformation", &FitStatus);
        fits_write_key(fptr, TSTRING, "CUNIT2   ", (char *)"deg",         "Unit of coordinate transformation", &FitStatus);

        floatValue2 = 0.0000040277777777777;
        fits_write_key(fptr, TFLOAT,  "CDELT1   ", &floatValue2,"Increment along axis 1 as [CUNIT1]/pxl", &FitStatus);
        fits_write_key(fptr, TFLOAT,  "CDELT2   ", &floatValue2,"Increment along axis 2 as [CUNIT2]/pxl", &FitStatus);

        float ra_tel_deg = tenv.achieved_ra/suc::DEGREE, ra_point_offset = 0.f, ra_guide_offset = 0.f, ra_shark = 0.f;
        float dec_tel_deg = tenv.achieved_dec/suc::DEGREE, dec_point_offset = 0.f, dec_guide_offset = 0.f, dec_shark = 0.f;
        floatValue = ra_tel_deg + ra_point_offset + ra_guide_offset + ra_shark;
        fits_write_key(fptr, TFLOAT,  "CRVAL1   ", &floatValue, "RA at CRPIX1 in units CUNIT1", &FitStatus);
        floatValue = dec_tel_deg + dec_point_offset + dec_guide_offset + dec_shark;
        fits_write_key(fptr, TFLOAT,  "CRVAL2   ", &floatValue, "DEC at CRPIX2 in units CUNIT2", &FitStatus);

        floatValue = 58.1f + 885.f - xmin;
        fits_write_key(fptr, TFLOAT,  "CRPIX1   ", &floatValue, "Value of X Ref. pixel", &FitStatus);
        floatValue = 51.5f + 1020.f - ymin;
        fits_write_key(fptr, TFLOAT,  "CRPIX2   ", &floatValue, "Value of Y Ref. pixel", &FitStatus);

        double sign = (m_bTest01 ? 1. : -1.);
        floatValue = std::cos((ih.derotator.position1 - gDrotRef + sign*tenv.parallactic_angle)*suc::DEGREE);
        fits_write_key(fptr, TFLOAT,  "PC1_1    ", &floatValue, "Scale in [CUNIT1]/pixel", &FitStatus);
        int flip = -1;
        floatValue *= flip;
        fits_write_key(fptr, TFLOAT,  "PC2_2    ", &floatValue, "Scale in [CUNIT2]/pixel", &FitStatus);
        floatValue = std::sin((ih.derotator.position1 - gDrotRef + sign*tenv.parallactic_angle)*suc::DEGREE);
        fits_write_key(fptr, TFLOAT,  "PC1_2    ", &floatValue, "Rotation and skew", &FitStatus);
        fits_write_key(fptr, TFLOAT,  "PC2_1    ", &floatValue, "Rotation and skew", &FitStatus);
    }
    else if(WCSAXES == 3)
    {
        fits_write_key(fptr, TSTRING, "CTYPE1   ", (char *)"WAVE",    "Pixel coordinate system", &FitStatus);
        fits_write_key(fptr, TSTRING, "CTYPE2   ", (char *)"DIST",    "Pixel coordinate system", &FitStatus);
        // fits_write_key(fptr, TSTRING, "CTYPE3   ", strValue,    "Pixel coordinate system", &FitStatus);
        fits_write_key(fptr, TSTRING, "CUNIT1   ", (char *)"mas",    "Unit of coordinate transformation", &FitStatus);
        fits_write_key(fptr, TSTRING, "CUNIT2   ", (char *)"nm",    "Unit of coordinate transformation", &FitStatus);
        // fits_write_key(fptr, TSTRING, "CUNIT3   ", strValue,    "Unit of coordinate transformation", &FitStatus);
        float crval_1 = 0.0;
        float crval_2 = 800;
        fits_write_key(fptr, TFLOAT,  "CRVAL1   ", &crval_1, "Wavelength at CRPIX1", &FitStatus);
        fits_write_key(fptr, TFLOAT,  "CRVAL2   ", &crval_2, "RA at CRPIX1 in units CUNIT1", &FitStatus);
        float cdelt_1 = pixscale;
        float cdelt_2 = (2000-800)/2048;
        fits_write_key(fptr, TFLOAT,  "CDELT1   ", &cdelt_1,"Increment along axis 1 as [CUNIT1]/pxl", &FitStatus);
        fits_write_key(fptr, TFLOAT,  "CDELT2   ", &cdelt_2,"Increment along axis 2 as [CUNIT2]/pxl", &FitStatus);
        float crpix_1 = 0;
        float crpix_2 = 0;
        fits_write_key(fptr, TFLOAT,  "CRPIX1   ", &crpix_1, "Value of X Ref. pixel", &FitStatus);
        fits_write_key(fptr, TFLOAT,  "CRPIX2   ", &crpix_2, "Value of Y Ref. pixel", &FitStatus);
        // fits_write_key(fptr, TFLOAT,  "CRVAL3   ", &floatValue, "DEC at CRPIX2 in units CUNIT2", &FitStatus);
        // fits_write_key(fptr, TFLOAT,  "CD1_1    ", &floatValue, "Increment for CTYPE1", &FitStatus);
        // fits_write_key(fptr, TFLOAT,  "CD2_2    ", &floatValue, "Increment for CTYPE2", &FitStatus);
        // fits_write_key(fptr, TFLOAT,  "CD3_3    ", &floatValue, "Increment for CTYPE3", &FitStatus);
        int dispAxis = 1;
        fits_write_key(fptr, TINT,    "DISPAXIS ", &dispAxis,       "Dispersion axis (1=rows; 2=columns)", &FitStatus);
        fits_write_key(fptr, TSTRING, "DISPUNIT ", (char *)"Pixel", "Units for coordinate along DISPAXIS", &FitStatus);
        fits_write_key(fptr, TSTRING, "DISPWC   ", strValue,        "Approximate central dispersion coordinate on detector [DISPAXIS]", &FitStatus);
        fits_write_key(fptr, TSTRING, "DISPDW   ", strValue,        "Approximate central dispersion per pixel on the detector", &FitStatus);
    }

   // TODO : Table 15 Enclosure, environment, and weather specific keywords
    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
    floatValue = 0.f;
//    fits_write_key(fptr, TFLOAT,  "DOMEPAN  ", &floatValue, "Dome position angle [degrees E of N]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "DOMTEM   ", &floatValue, "Dome temperature [degrees C]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "DOMHUM   ", &floatValue, "Dome relative humidity [%]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "DOMDEWPT ", &floatValue, "Dome dew point [degrees C]", &FitStatus);

    fits_write_record(fptr, "        ------------------------------------------------------------------------", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "ENVTEM   ", &tenv.lbt_temp, "Outside temperature, C, at MJD-OBS", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "ENVPRE   ", &tenv.lbt_pressure, "Atmospheric pressure [mbar]", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "ENVWIN   ", &tenv.smt_wind_speed, "Outside wind speed (m/s) at MJD-OBS", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "ENVDIR   ", &tenv.smt_wind_dir, "(degrees) Direction of wind: E from N", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "ENVHUM   ", &tenv.lbt_humidity, "Outside relative humidity % at MJD-OBS", &FitStatus);
    //   fits_write_key(fptr, TFLOAT,  "ENVDEWPT ", &tenv.lbt_dewpoint, "Outside dew point (degrees C) at MJD-OBS", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "ENVTAU   ", &tenv.smt_tau, "Opacity at 225 GHz, from SMT", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "SEEING   ", &tenv.dimm_seeing, "Seeing estimate from S-H WFS", &FitStatus);
//    std::string operator_comment = " no comment by operator ";
//    fits_write_key(fptr, TSTRING, "WEATHER  ", su::get_char_star(operator_comment), "Comment by telescope operator or observer", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "SEEWAV   ", &floatValue, "Wavelength for seeing estimate", &FitStatus);
//    fits_write_key(fptr, TFLOAT,  "SEECAL   ", &floatValue, "Seeing: S-H to seeing calibration curve", &FitStatus);

    U8_LLOG("");
    return FitStatus;
}


void SeqDataMgr::getTelescopeEnv(STelescopeEnv & env)
{

    // DIMMFWHM                 DIMM seeing, [arcsecs]                      DIMMSeeing (DIMMSeeingZenith, DIMMSeeingElevation)
    // DIMMFLUX                 DIMM mean centroid flux                     DIMMMeanFlux
    // LBTWLINK                 Weather Station Link State                  LBTWeatherAlive (LBTWeatherAliveFront)
    // LBTPRES                  Ambient Pressure [hPa]                      LBTPressure
    // LBTTEMP                  Ambient Temperature [deg C]                 LBTTemp
    // LBTHUM                   LBT Relative Humidity [percent]             LBTHumidity
    // LBTDWPT                  LBT Dew Point [deg C]                       LBTDewPoint
    static Nice::Date lastCall;
    Nice::Date currentCall = Nice::Date::now();
    if(currentCall - lastCall < Nice::Time::seconds(5))return;

    lbto::result res;
    lbto::DDstruct ddt;
    lbto::SeqDD dd;
    ddt.DDname = "DIMMSeeingZenith";                  //float         //1
    dd.push_back(ddt); 
    ddt.DDname = "DIMMSeeingElevation";               //float         //2
    dd.push_back(ddt);
    ddt.DDname = "LBTWeatherAlive";                   //bool          //3
    dd.push_back(ddt);
    ddt.DDname = "LBTPressure";                       //float         //4
    dd.push_back(ddt);
    ddt.DDname = "LBTTemp";                           //float         //5
    dd.push_back(ddt);
    ddt.DDname = "LBTHumidity";                       //float         //6
    dd.push_back(ddt);
    ddt.DDname = "LBTDewPoint";                       //float         //7
    dd.push_back(ddt);
    ddt.DDname = "LBTWindSpeed";                      //float         //8
    dd.push_back(ddt);
    ddt.DDname = "LBTWindDir";                        //float         //9
    dd.push_back(ddt);
    ddt.DDname = "LBTWindDirRaw";                     //float         //10
    dd.push_back(ddt);
    ddt.DDname = "LBTWindSpeedFront";                 //float         //11
    dd.push_back(ddt);
    ddt.DDname = "LBTWindDirFront";                   //float         //12
    dd.push_back(ddt);
    ddt.DDname = "LBTWindDirFrontRaw";                //float         //13
    dd.push_back(ddt);

    ddt.DDname = "AirMass";                           //float         //14
    dd.push_back(ddt);

    ddt.DDname = "ELPosition";                        //float         //15
    dd.push_back(ddt);
    ddt.DDname = "AZPosition";                        //float         //16
    dd.push_back(ddt);
    ddt.DDname = "L_AchievedALT";                     //float         //17
    dd.push_back(ddt);
    ddt.DDname = "L_AchievedAZ";                      //float         //18
    dd.push_back(ddt);
    ddt.DDname = "AZTrackingState";                   //String        //19
    dd.push_back(ddt);
    ddt.DDname = "ELTrackingState";                   //String        //20
    dd.push_back(ddt);

    ddt.DDname = "L_PresetRotMode";                   //String        //21
    dd.push_back(ddt);
    ddt.DDname = "L_AOmode";                          //String        //22
    dd.push_back(ddt);
    ddt.DDname = "Epoch";                             //float         //23
    dd.push_back(ddt);
    ddt.DDname = "L_TargetRA";                        //float         //24
    dd.push_back(ddt);
    ddt.DDname = "L_TargetDEC";                       //float         //25
    dd.push_back(ddt);
    ddt.DDname = "L_AchievedRA";                      //float         //26
    dd.push_back(ddt);
    ddt.DDname = "L_AchievedDEC";                     //float         //27
    dd.push_back(ddt);
    ddt.DDname = "L_AchievedRAHMS";                   //String        //28
    dd.push_back(ddt);
    ddt.DDname = "L_AchievedDECDMS";                  //String        //29
    dd.push_back(ddt);
    ddt.DDname = "L_AOReferenceRA";                   //float         //30
    dd.push_back(ddt);
    ddt.DDname = "L_AOReferenceDEC";                  //float         //31
    dd.push_back(ddt);
    ddt.DDname = "L_AORefMag";                        //float         //32
    dd.push_back(ddt);
    ddt.DDname = "L_GuideRA";                         //float         //33
    dd.push_back(ddt);
    ddt.DDname = "L_GuideDEC";                        //float         //34
    dd.push_back(ddt);
    ddt.DDname = "L_DECOffset";                       //float         //35
    dd.push_back(ddt);
    ddt.DDname = "L_RAOffset";                        //float         //36
    dd.push_back(ddt);
    ddt.DDname = "L_POXOffset";                       //float         //37
    dd.push_back(ddt);
    ddt.DDname = "L_POYOffset";                       //float         //38
    dd.push_back(ddt);

    ddt.DDname = "L_AOOffsetX";                       //float         //39  // WFS_LBTI_SX_baysidex_L_PSO_CUR 1; WFS_LBTI_SX_auxloops_L_modalplotTask_MAG
    dd.push_back(ddt);
    ddt.DDname = "L_AOOffsetY";                       //float         //40
    dd.push_back(ddt);
    ddt.DDname = "L_AOOffsetZ";                       //float         //41
    dd.push_back(ddt);

    ddt.DDname = "L_NSRATRKRate";                     //float         //42
    dd.push_back(ddt);
    ddt.DDname = "L_NSDECTRKRate";                    //float         //43
    dd.push_back(ddt);

    ddt.DDname = "MoonAlt";                           //float         //44
    dd.push_back(ddt);
    ddt.DDname = "MoonAz";                            //float         //45
    dd.push_back(ddt);
    ddt.DDname = "SunAlt";                            //float         //46
    dd.push_back(ddt);
    ddt.DDname = "SunAz";                             //float         //47
    dd.push_back(ddt);

    ddt.DDname = "LST";                               //String        //48
    dd.push_back(ddt);
    ddt.DDname = "ParAngle";                          //float         //49
    dd.push_back(ddt);
    ddt.DDname = "L_PointingModel";                  //String         //50
    dd.push_back(ddt);
    //ddt.DDname = "UTC";
    //dd.push_back(ddt);
    


    bool get_parameters = true;
    int retry_counter = 0;
    if(!m_iif)
    {
        W_LLOG("Tcs Interface not connected. Skipping getParameters");
        return;
    }
    while(get_parameters)
    {
        get_parameters = false;
        try
        {
            res = m_iif->iifGetParameter(dd);
        }
        catch(Basda::ServiceIsBusyException)
        {
            if(retry_counter && retry_counter % 10 == 0)
            {
                W_LLOG("Busy : retrying " << retry_counter);
            }
            retry_counter++;
            get_parameters = true;
            usleep(50000);
            continue;
        }
        catch(const std::exception& e)
        {
            W_LLOG(e.what());
            return;
        }
        catch(...)
        {
            std::string what = boost::current_exception_diagnostic_information();
            std::string toErase = "std::exception::what :";
            size_t pos = what.find(toErase);
            if(pos != std::string::npos)what.erase(pos, toErase.length());
            what.erase(std::remove(what.begin(), what.end(), '\n'), what.end());
            std::stringstream log_msg;
            log_msg << "Error trying getting DD parmateres : " << what;
            W_LLOG(log_msg.str());
            return;
        }
    }
    if(res.rescode == EXIT_SUCCESS)
    {
        double temp;
        // std::vector<std::string>::const_iterator it;
        // int counter = 0;
        // for(it = res.resmsg.begin(); it < res.resmsg.end(); it++)
        // {
        //     counter++;
        //     U9_LLOG(counter << " ~ " << *it);
        // }
        env.dimm_seeing_zenith = atof(res.resmsg[1].c_str());          //f   //1
        env.dimm_seeing_elevation = atof(res.resmsg[2].c_str());       //f   //2
        env.lbt_weather_alive = (bool)atoi(res.resmsg[3].c_str());     //b   //3
        env.lbt_pressure = atof(res.resmsg[4].c_str());                //f   //4
        env.lbt_temp = atof(res.resmsg[5].c_str());                    //f   //5
//        U9_LLOG(env.lbt_temp);
        env.lbt_humidity = atof(res.resmsg[6].c_str());                //f   //6
        env.lbt_dewpoint = atof(res.resmsg[7].c_str());                //f   //7
        env.lbt_wind_speed = atof(res.resmsg[8].c_str());              //f   //8
        env.lbt_wind_dir = atof(res.resmsg[9].c_str());                //f   //9
        env.lbt_wind_dir_raw = atof(res.resmsg[10].c_str());           //f   //10
        env.lbt_wind_speed_front = atof(res.resmsg[11].c_str());       //f   //11
        env.lbt_wind_dir_front = atof(res.resmsg[12].c_str());         //f   //12
        env.lbt_wind_dir_raw_front = atof(res.resmsg[13].c_str());     //f   //13

        env.air_mass = atof(res.resmsg[14].c_str());                   //f   //14
        
        env.mcs_elevation = atof(res.resmsg[15].c_str())/3600;         //f   //15
        env.mcs_azimuth = atof(res.resmsg[16].c_str())/3600;           //f   //16
        env.achieved_alt = atof(res.resmsg[17].c_str());               //f   //17
        env.achieved_az = atof(res.resmsg[18].c_str());                //f   //18
        env.az_tracking_state = res.resmsg[19];                        //S   //19
        env.el_tracking_state = res.resmsg[20];                        //S   //20

        env.telescope_mode = res.resmsg[21];                           //S   //21
        env.ao_mode = res.resmsg[22];                                  //S   //22
        env.epoch = atof(res.resmsg[23].c_str());                      //f   //23
        env.target_ra = atof(res.resmsg[24].c_str());                  //f   //24
        env.target_dec = atof(res.resmsg[25].c_str());                 //f   //25
        env.achieved_ra = atof(res.resmsg[26].c_str());                //f   //26 ?
        env.achieved_dec = atof(res.resmsg[27].c_str());               //f   //27 ?
        env.tcs_ra = res.resmsg[28];                                   //S   //28
        env.tcs_dec = res.resmsg[29];                                  //S   //29
        env.ao_ref_ra = atof(res.resmsg[30].c_str());                  //f   //30
        env.ao_ref_dec = atof(res.resmsg[31].c_str());                 //f   //31

        if( res.resmsg[32] == "nan")
        {
            env.ao_ref_mag = -999.0f;                                  //f   //32
        }
        else
        {
            env.ao_ref_mag = atof(res.resmsg[32].c_str());             //f   //32
        }
        env.guide_star_ra = atof(res.resmsg[33].c_str());              //f   //33
        env.guide_star_dec = atof(res.resmsg[34].c_str());             //f   //34
        env.target_decoffset = atof(res.resmsg[37].c_str());           //f   //37  (or 35?)
        env.target_raoffset = atof(res.resmsg[38].c_str());            //f   //38  (or 36?)

        env.ao_offsetx = atof(res.resmsg[39].c_str());                 //f   //39
        env.ao_offsety = atof(res.resmsg[40].c_str());                 //f   //40
        env.ao_offsetz = atof(res.resmsg[41].c_str());                 //f   //41

        env.tel_trk_RA = atof(res.resmsg[42].c_str());                 //f   //42
        env.tel_trk_DEC = atof(res.resmsg[43].c_str());                //f   //43

        env.moon_alt = atof(res.resmsg[44].c_str());                   //f   //44
        env.moon_az = atof(res.resmsg[45].c_str());                    //f   //45
        env.sun_alt = atof(res.resmsg[45].c_str());                    //f   //46
        env.sun_az = atof(res.resmsg[47].c_str());                     //f   //47

        env.lst = res.resmsg[48];                                      //S   //48

        env.parallactic_angle = atof(res.resmsg[49].c_str());          //f   //49
        env.pointing_model = res.resmsg[50];                           //S   //50
//        env.utc = atof(res.resmsg[40].c_str());

        //Converting string to int arr
        float rad2deg = 180/M_PI;
        float sec2rad = M_PI/(12*3600);
        int lst_arr[4];
        lst_arr[0] = atoi(env.lst.substr(0,2).c_str());
        lst_arr[1] = atoi(env.lst.substr(3,2).c_str());
        lst_arr[2] = atoi(env.lst.substr(6,2).c_str());
        lst_arr[3] = atoi(env.lst.substr(9,5).c_str()); //5 decimal digits after ','
        float lst = lst_arr[0]*3600 + lst_arr[1]*60 + lst_arr[2] + (float)lst_arr[3]/100000; //division by 100000 due to the choicheq
        //U9_LLOG("____________ LST destruct: "<<lst_arr[0]<<" : "<<lst_arr[1]<<" : "<<lst_arr[2]<<" . "<<lst_arr[3]);

        //Converting string to int arr
        int ra_arr[4];
        ra_arr[0] = atoi(env.tcs_ra.substr(0,2).c_str());
        ra_arr[1] = atoi(env.tcs_ra.substr(3,2).c_str());
        ra_arr[2] = atoi(env.tcs_ra.substr(6,2).c_str());
        ra_arr[3] = atoi(env.tcs_ra.substr(9,5).c_str()); //5 decimal digits after ','
        float ra = ra_arr[0]*3600 + ra_arr[1]*60 + ra_arr[2] + (float)ra_arr[3]/100000; //division by 100000 due to the choicheq
        //U9_LLOG("____________ RA destruct: "<<ra_arr[0]<<" : "<<ra_arr[1]<<" : "<<ra_arr[2]<<" . "<<ra_arr[3]);

        //Converting float val to int arr in format hh:mm:ss.sss
        float ha = ra-lst;
        if(ha<0) {ha = ha+24*3600;}
        //U9_LLOG("____________ ha in seconds: "<<ha);
        int ha_arr[4];
        ha_arr[0] = ha/3600;
        ha_arr[1] = ((int)ha%3600)/60;
        ha_arr[2] = ((int)ha%3600)%60;
        ha_arr[3] = (ha-(int)ha)*1000;
        //U9_LLOG("____________ HA destruct: "<<ha_arr[0]<<" : "<<ha_arr[1]<<" : "<<ha_arr[2]<<" . "<<ha_arr[3]);

        std::stringstream hour_angle;
        if(ha_arr[0]<10) {hour_angle<<"0";}
        hour_angle << ha_arr[0] << ":";
        if(ha_arr[1]<10) {hour_angle<<"0";}
        hour_angle << ha_arr[1] << ":";
        if(ha_arr[2]<10) {hour_angle<<"0";}
        hour_angle << ha_arr[2] << "." <<ha_arr[4];
        //U9_LLOG("Hour Angle =  "<<hour_angle.str());
        env.hour_angle = hour_angle.str();

        //Converting string to int arr
        int dec_arr[4];
        dec_arr[0] = atoi(env.tcs_dec.substr(0,2).c_str());
        dec_arr[1] = atoi(env.tcs_dec.substr(3,2).c_str());
        dec_arr[2] = atoi(env.tcs_dec.substr(6,2).c_str());
        dec_arr[3] = atoi(env.tcs_dec.substr(9,2).c_str()); //2 decimal digits after ','
        float dec = dec_arr[0] + dec_arr[1]/60 + dec_arr[2]/3600 + (float)dec_arr[3]/(3600*100); //division by 100 due to the choiche
        //U9_LLOG("____________ DEC destruct: "<<dec_arr[0]<<" : "<<dec_arr[1]<<" : "<<dec_arr[2]<<" . "<<dec_arr[3]);

        //Converting all values in rad
        ha  = ha*sec2rad;
        dec = dec/rad2deg;
        float lat = 32.701309/rad2deg;
        float h = asin(cos(ha)*cos(dec)*cos(lat)+sin(dec)*sin(lat));
        //U9_LLOG("_______________________ h = "<<h<<" rad ,h = "<<h*rad2deg<<" deg. _______ Zen Dist = "<<90-h*rad2deg<<" deg.");
        env.zenith_dist = ceil((90-h*rad2deg)*100)/100;
    }
    else
    {
        for(unsigned int i = 0; i < res.resmsg.size(); i++)
        {
            U9_LLOG(res.resmsg[i]);
        }
    }
}

void SeqDataMgr::getMaskCombo(SInstrumentHeader & ih)
{
    int coro_delimiter = ih.coro_slit_w.type.find(":");
    int coro_word_len = ih.coro_slit_w.type.length()-coro_delimiter;
    std::string coro_named_pos= ih.coro_slit_w.type.substr(coro_delimiter+1,coro_word_len);

    int lyot_delimiter = ih.lyot_grism_w.type.find(":");
    int lyot_word_len = ih.lyot_grism_w.type.length()-lyot_delimiter;
    std::string lyot_named_pos= ih.lyot_grism_w.type.substr(lyot_delimiter+1,lyot_word_len);

    ih.mask_comb.type = coro_named_pos+"+"+lyot_named_pos;

    //U9_LLOG("Prova funzione, IH.Mask_Comb : " << ih.mask_comb.type);
}

std::string SeqDataMgr::GetReadoutRegion(int & xmin, int & xmax, int & ymin, int & ymax)
{
    xmin = xmax = ymin = ymax = 0;
    std::map<std::string, indi::IElement>::const_iterator itRegion;
    const std::map<std::string, indi::IElement> & elements = sashaReadoutRegion.getElements();
    itRegion = elements.find("x1");
    if(itRegion != elements.end())
    {
        if(!su::getValue<int>(itRegion->second.get(), xmin)){ W_LLOG("Could not get readout region XMIN value");}
    }
    else { W_LLOG("Could not find readout region XMIN value");}
    itRegion = elements.find("x2");
    if(itRegion != elements.end())
    {
        if(!su::getValue<int>(itRegion->second.get(), xmax)){ W_LLOG("Could not get readout region XMAX value");}
    }
    else { W_LLOG("Could not find readout region XMAX value");}
    itRegion = elements.find("y1");
    if(itRegion != elements.end())
    {
        if(!su::getValue<int>(itRegion->second.get(), ymin)){ W_LLOG("Could not get readout region YMIN value");}
    }
    else { W_LLOG("Could not find readout region YMIN value");}
    itRegion = elements.find("y2");
    if(itRegion != elements.end())
    {
        if(!su::getValue<int>(itRegion->second.get(), ymax)){ W_LLOG("Could not get readout region YMAX value");}
    }
    else { W_LLOG("Could not find readout region YMAX value");}
    
    std::stringstream activeRegion;
    activeRegion << "["<< xmin <<":"<< xmax <<", "<< ymin <<":"<< ymax << "]";
    return activeRegion.str();
}


int SeqDataMgr::sashaEnableSave(const std::map<std::string, std::string> & _dSashaSetup)
{
    // To be Implemented
    U6_LLOG(__FUNCTION__ << " ~ DEPRECATED");
    Ice::Int status = EXIT_SUCCESS;
#ifdef UNDEFINED
    IProperty sashaSave(IProperty::Switch), recvProp;
    std::map<std::string, std::string>::const_iterator itFind;
    itFind = _dSashaSetup.find("SAVE");

    // m_ptrIc->connect();
    // NOTE while alive, indiserver does not close the connection to the client.
    // U1_LLOG("Command sasha to save to file");
    U1_LLOG("Command sasha to save to file");

    sashaSave.setDevice("sasha_save");
    sashaSave.setName("enable_save");
    sashaSave.setPerm(IProperty::ReadWrite);
    sashaSave.setState(IProperty::Busy);
    sashaSave.add(IElement("value"));
    // Switch properties can have value On or Off
    sashaSave["value"].setSwitchState(IElement::Off);
    U1_LLOG(sashaSave.getType());
    if(!(itFind == _dSashaSetup.end()))
    {
        if((su::str_tolower(itFind->second) == "true") || (itFind->second == "1"))
        {
            sashaSave["value"].setSwitchState(IElement::On);
        }
    }

    //
    m_ptrIc2->send_new_property_wait2(sashaSave, recvProp);
#endif
    // status = m_ptrIc->close();
    U8_LLOG(__FUNCTION__);
    return status;
}


//  Patch to get rid of the timeout problem of sasha driver. NREAD is sent whenever NDIT changes and no other parameters are sent
int SeqDataMgr::sashaEndSetup(std::map<std::string, std::string> & _dTimeoutSetup, bool ndit_changed)
{
    U6_LLOG(__FUNCTION__);
    int status = EXIT_SUCCESS;
    if(ndit_changed && _dTimeoutSetup.empty())
    {
        IProperty & prop = m_sashaProps.find("NREADS")->second;
        const std::map<std::string, indi::IElement> elements = prop.getElements();
        if(elements.find("value") != elements.end())
        {
            _dTimeoutSetup["NREADS"] = prop["value"].getValue();
        }
        else
        {
            std::stringstream log_msg;
            log_msg << "Error : IProperty \"" << prop.getDevice() << "." << prop.getName() << "\" from INDI Server has changed";
            E_LLOG(log_msg.str());
            throw(std::runtime_error(log_msg.str()));
        }
    }
    std::map<std::string, std::string>::iterator itSetup;
    int num_properties = _dTimeoutSetup.size();
    int counter = 0, sent_properties = 0;
    try
    {
        for(itSetup = _dTimeoutSetup.begin(); itSetup != _dTimeoutSetup.end(); itSetup++)
        {
            if(m_getFitsAbortFlag)break;
            counter++;
            IProperty & prop = m_sashaProps.find(itSetup->first)->second;
            const std::map<std::string, indi::IElement> elements = prop.getElements();
            bool bPropError = false, bSetProperty = false;
            if(elements.find("value") != elements.end())
            {
                if(m_bForcePropertySet || itSetup->second != prop["value"].getValue())
                {
                    prop["value"].setValue<int>(su::convertStringToType<int>(itSetup->second));
                    bSetProperty = true;
                }
            }
            else
                bPropError = true;
            if(bPropError)
            {
                std::stringstream log_msg;
                log_msg << "Error : IProperty \"" << prop.getDevice() << "." << prop.getName() << "\" from INDI Server has changed";
                E_LLOG(log_msg.str());
                throw(std::runtime_error(log_msg.str()));
            }
            else if(bSetProperty || (ndit_changed && sent_properties == 0 && counter == num_properties))
            {
                if(!bSetProperty && (ndit_changed && sent_properties == 0 && counter == num_properties))
                {
                    U9_LLOG("Sending property " << itSetup->first << " to make camera driver recalculate exposure duration");
                }
                m_sashaPropsSet[itSetup->first] = false;
                SendNewProperty(prop);
                sent_properties++;
            }
        }
        bool wait_result = false;
        wait_result = WaitSetMessages(60000000);
        if(!wait_result)status = EXIT_FAILURE;
    }
    catch(...)
    {
        E_LLOG(__FUNCTION__);
        U8_LLOG("");
        throw;
    }
    
    U8_LLOG("");
    return status;
}

int SeqDataMgr::sashaSetup(const std::map<std::string, std::string> & _dSashaExpose)
{
    U6_LLOG(__FUNCTION__);
    int status = EXIT_SUCCESS;
    m_exposeMutex.lock();
    m_getFitsAbortFlag = 0;
    bool bObjectNamesSet = false;
    try
    {
        std::map<std::string, std::string>::iterator itSashaSetup;
        for(itSashaSetup = m_sashaSetup.begin(); itSashaSetup != m_sashaSetup.end(); itSashaSetup++)
        {
            if(itSashaSetup->first == suc::InstrumentMode)
            {
                itSashaSetup->second = "GEN";
            }
            else
                itSashaSetup->second.clear();
        }
        std::map<std::string, std::string>::const_iterator itSetup;
        for(itSetup = _dSashaExpose.begin(); itSetup != _dSashaExpose.end(); itSetup++)
        {
            U9_LLOG("Keyword : " << itSetup->first << "; Value : " << itSetup->second);
            if(m_sashaSetup.find(itSetup->first) == m_sashaSetup.end())
            {
                std::stringstream log_msg;
                log_msg << "Keyword not allowed in sashaExpose : " << itSetup->first;
                E_LLOG(log_msg.str());
                throw(std::invalid_argument(log_msg.str()));
            }
            m_sashaSetup[itSetup->first] = itSetup->second;
        }
        bool bForceTimeoutRefresh = false;
        bool ndit_changed = false;

        checkContinuousFlag();

        std::map<std::string, std::string> incomingSetup = _dSashaExpose;
        std::map<std::string, std::string> timeoutSetup;
        timeoutSetup = su::extract_setup(m_timeoutSetup, incomingSetup);
        for(itSetup = incomingSetup.begin(); itSetup != incomingSetup.end(); itSetup++)
        {
            if(m_getFitsAbortFlag)break;
            if(itSetup->first == "DIT" || itSetup->first == "InstrumentMode")
            {
            }
            else
            {
    //            U9_LLOG("setup : " << itSetup->first << ":" << itSetup->second);
                IProperty & prop = m_sashaProps.find(itSetup->first)->second;
                const std::map<std::string, indi::IElement> elements = prop.getElements();
                bool bPropError = false, bSetProperty = false;
                if(itSetup->first == "SAVE")
                {
                    bool bSave = true;
                    su::get_bool_from_setup(_dSashaExpose,  "SAVE",  bSave);
                    std::map<std::string, indi::IElement>::const_iterator itEl;
                    if(elements.find("value") != elements.end())
                    {
                        indi::IElement::SwitchStateType switch_state = (bSave ? IElement::On : IElement::Off);
                        if(m_bForcePropertySet || prop["value"].getSwitchState() != switch_state)
                        {
                            prop["value"].setSwitchState(switch_state);
//                            U9_LLOG("Setting : " << "value" << " to " << bSave);
                            bSetProperty = true;
                        }
                    }
                    else
                        bPropError = true;
                }
                else if(itSetup->first == "OBJECTNAME")
                {
                    if(elements.find("value") != elements.end())
                    {
                        std::string current_name = prop["value"].getValue();
                        if(m_bForcePropertySet || current_name != itSetup->second)
                        {
                            prop["value"].setValue(itSetup->second);
                            bSetProperty = true;
                            bObjectNamesSet = true;
                        }
                    }
                    else
                        bPropError = true;
                }
    //            else if(itSetup->first == "NCOADDS")
    //            {
    //                if(elements.find("num_coadds") != elements.end())
    //                    prop["num_coadds"].setValue<int>(su::convertStringToType<int>(itSetup->second));
    //                else
    //                    bPropError = true;
    //            }
                else if(itSetup->first == "READOUT")
                {
                    if(elements.find("value") != elements.end())
                    {
                        if(m_bForcePropertySet || prop["value"].getValue() != itSetup->second)
                        {
                            prop["value"].setValue(itSetup->second);
                            bSetProperty = true;
                        }
                    }
                    else
                        bPropError = true;
                }
                else
                {
                    if(elements.find("value") != elements.end())
                    {
//                        int value = su::convertStringToType<int>(prop["value"].getValue());
                        if(m_bForcePropertySet || itSetup->second != prop["value"].getValue())
                        {
                            if(itSetup->first == "NDIT")ndit_changed = true;
                            prop["value"].setValue<int>(su::convertStringToType<int>(itSetup->second));
                            bSetProperty = true;
                        }
                    }
                    else
                        bPropError = true;
                }
                if(bPropError)
                {
                    std::stringstream log_msg;
                    log_msg << "Error : IProperty \"" << prop.getDevice() << "." << prop.getName() << "\" from INDI Server has changed";
                    E_LLOG(log_msg.str());
                    throw(std::runtime_error(log_msg.str()));
                }
                else if(bSetProperty)
                {
                    m_sashaPropsSet[itSetup->first] = false;
                    SendNewProperty(prop);
                }
            }
        }
        updateInstrumentHeader();
        if(false && !bObjectNamesSet)
        {
            const std::map<std::string, indi::IElement> elements = sashaObjName.getElements();
            if(elements.find("value") != elements.end())
            {
                m_sashaPropsSet["OBJECTNAME"] = false;
                sashaObjName["value"].setValue(m_TcsPreset.object_name);
                SendNewProperty(sashaObjName);
            }
        }
        bool wait_result = false;
        wait_result = WaitSetMessages(60000000);
        if(!wait_result)status = EXIT_FAILURE;
        int status2 = sashaEndSetup(timeoutSetup, ndit_changed);
        if(status == EXIT_FAILURE || status2 == EXIT_FAILURE)status = EXIT_FAILURE;
    }
    catch(...)
    {
        m_exposeMutex.unlock();
        U8_LLOG("");
        throw;
    }
    m_exposeMutex.unlock();
    U8_LLOG(__FUNCTION__ << " exiting");
    return status;
}


int SeqDataMgr::sashaSetFrequency(const std::map<std::string, std::string> & _dSashaExpose)
{
    U6_LLOG(__FUNCTION__);
    int status = EXIT_SUCCESS;
    m_exposeMutex.lock();
    try
    {
        std::map<std::string, std::string>::const_iterator itSetup;
        itSetup = _dSashaExpose.find("FREQUENCY");
        if(itSetup != _dSashaExpose.end())
        {
            IProperty & prop = m_sashaProps.find(itSetup->first)->second;
            const std::map<std::string, indi::IElement> elements = prop.getElements();
            if(elements.find("value") != elements.end())
            {
                if(m_bForcePropertySet || prop["value"].getValue() != itSetup->second)
                {
                    prop["value"].setValue(itSetup->second);
                    m_sashaPropsSet[itSetup->first] = false;
                    SendNewProperty(prop);
                    //camera reinitializes and readout region is reset
                    m_sashaPropsSet["READOUT"] = false;
                    WaitSetMessages(60000000);
                }
            }
            else
            {
                U9_LLOG("Error: frequency property from INDI server has changed");
            }
        }
    }
    catch(...)
    {
        m_exposeMutex.unlock();
        U8_LLOG("");
        throw;
    }
    m_exposeMutex.unlock();
    U8_LLOG(__FUNCTION__ << " exiting");
    return status;
}

int SeqDataMgr::checkContinuousFlag()
{
    int status = EXIT_SUCCESS;
    if(!m_bFastSavingMode)
    {
    if(!(sashaExposeProp["value"].getSwitchState() == IElement::Off))
    {
        m_sashaPropsSet["STOP"] = false;
        sashaStop["value"].setSwitchState(IElement::On);
        SendNewProperty(sashaStop);
        m_sashaPropsSet["EXPOSE"] = false;
        U6_LLOG(__FUNCTION__ << " ~ Clearing Expose Property");
        WaitSetMessages(10000000);
        m_sashaPropsSet["EXPOSE"] = false;
        WaitSetMessages(10000000);
        U8_LLOG("");
    }
//    if(!(sashaEnableCont["value"].getSwitchState() == IElement::Off))
//  enable_cont property is resetted every time because is sometimes found misaligned with the one on the indi server
    {
        m_sashaPropsSet["CONTINUOUS"] = false;
        sashaEnableCont["value"].setSwitchState(IElement::Off);
        SendNewProperty(sashaEnableCont);
        WaitSetMessages(10000000);
    }
    }

    return status;
}

int SeqDataMgr::sashaExpose(std::vector<std::string> & out_files)
{
    U6_LLOG(__FUNCTION__);
    int status = EXIT_SUCCESS;
    m_exposeMutex.lock();
    m_getFitsAbortFlag = 0;
    try
    {
        checkContinuousFlag();
        m_last_generated_files.clear();
        if(m_bSaveOnlyExposedFiles && !m_getFitsAbortFlag)
        {
            sashaBlob.setBLOBEnable(IProperty::Also);
            SendNewProperty(sashaBlob);
        }
        bool wait_result = false;
        wait_result = WaitSetMessages(60000000);
        if(!m_getFitsAbortFlag)
        {
            m_sashaPropsSet["EXPOSE"] = false;
            sashaExposeProp["value"].setSwitchState(IElement::On);
            SendNewProperty(sashaExposeProp);
            m_ExpectedBlobs = su::convertStringToType<int>(sashaNumSeqs["value"].getValue());
            m_bInstrumentSetupUpdated = false;
            updateInstrumentHeader();
            wait_result = WaitSetMessages(30000000);
        }
        if(!m_getFitsAbortFlag)
        {
            wait_result = WaitStartAcquireOff(estimateTiming());
            m_bBlobArrived = false;
        }
        if(m_bSaveOnlyExposedFiles)
        {
            unsigned int milliseconds = estimateBlobDelivery();
            if(m_ExpectedBlobs > 0)WaitBLOB(milliseconds);
            sashaBlob.setBLOBEnable(IProperty::Never);
            SendNewProperty(sashaBlob);
        }
        if(!wait_result)status = EXIT_FAILURE;
        if(m_getFitsAbortFlag)
        {
            throw(std::runtime_error("Error acquisition aborted by user"));
        }
    }
    catch(...)
    {
        m_exposeMutex.unlock();
        if(m_getFitsAbortFlag)m_getFitsAbortFlag--;
        U8_LLOG("");
        throw;
    }
    if(m_getFitsAbortFlag)m_getFitsAbortFlag--;
    m_exposeMutex.unlock();
    out_files = m_last_generated_files;
    m_last_generated_files.clear();
    U8_LLOG(__FUNCTION__ << " exiting");
    return status;
}


void SeqDataMgr::abortExposure()
{
    m_getFitsAbortFlag = 1;
    SendNewProperty(sashaStop);
}

bool SeqDataMgr::WaitSetMessages(int timeout)
{
    bool wait_sets = true;
    int waited_time = 0;
    int sleep_time = 1000;
    std::stringstream log_msg;
    while(wait_sets)
    {
        usleep(sleep_time);
        waited_time += sleep_time;
        sleep_time = 49000*(waited_time == 1000) + 50000*(waited_time > 1000);
        std::map<std::string, bool>::iterator itSet;
        for(itSet = m_sashaPropsSet.begin(); itSet != m_sashaPropsSet.end(); itSet++)
        {
            if(!itSet->second)break;
        }
        wait_sets = itSet != m_sashaPropsSet.end();
        if(waited_time >= timeout)
        {
            log_msg << __FUNCTION__ << " ~ Timed Out waiting for : ";
            for(itSet = m_sashaPropsSet.begin(); itSet != m_sashaPropsSet.end(); itSet++)
            {
                if(!itSet->second)
                    log_msg << itSet->first << " ~ ";
            }
            break;
        }
        if(m_bReceivedEof)
        {
            U6_LLOG(__FUNCTION__);
            E_LLOG("Received EOF");
            U8_LLOG("");
            throw(std::runtime_error("Indi Server signaled EoF"));
        }
        if(m_getFitsAbortFlag)break;
    }
    if(waited_time > 1000)
    {
        U6_LLOG(__FUNCTION__);
        if(!log_msg.str().empty()){E_LLOG(log_msg.str());};
        U8_LLOG(__FUNCTION__ << " ~ Exiting; waited " << waited_time/1000.f << "ms");
    }
    return !wait_sets;
}

bool SeqDataMgr::WaitStartAcquireOff(unsigned int timeout)
{
    U6_LLOG(__FUNCTION__);
    bool wait_result = true;
    unsigned int waited_time = 0;
    while(wait_result)
    {
        usleep(200000);
        waited_time += 200;
        wait_result = !(sashaExposeProp["value"].getSwitchState() == IElement::Off);
        if(waited_time >= timeout)
        {
            E_LLOG(__FUNCTION__ << " ~ Timed Out");
            break;
        }
        if((waited_time % 300000) == 0)
        {
            U9_LLOG(__FUNCTION__ << " ~ Waiting " << waited_time/1000.f << "/" << timeout/1000.f << "s");
        }
        if(m_getFitsAbortFlag)break;
        if(m_bReceivedEof)
        {
            E_LLOG("Received EOF");
            U8_LLOG("");
            throw(std::runtime_error("Indi Server signaled EoF"));
        }
    }
    U8_LLOG(__FUNCTION__ << " ~ Exiting; waited " << waited_time << "ms");
    return !wait_result;
}

bool SeqDataMgr::WaitBLOB(unsigned int timeout)
{
    U6_LLOG(__FUNCTION__);
    bool wait_result = true;
    unsigned int waited_time = 0;
    while(wait_result)
    {
        usleep(200000);
        waited_time += 200;
        wait_result = !(m_bBlobArrived == true);
        if(wait_result && waited_time >= timeout)
        {
            E_LLOG(__FUNCTION__ << " ~ Timed Out");
            break;
        }
        if((waited_time % 300000) == 0)
        {
            U9_LLOG(__FUNCTION__ << " ~ Waiting " << waited_time/1000.f << "/" << timeout/1000.f << "s");
        }
        if(m_getFitsAbortFlag)break;
        if(m_bReceivedEof)
        {
            E_LLOG("Received EOF");
            U8_LLOG("");
            throw(std::runtime_error("Indi Server signaled EoF"));
        }
    }
    U8_LLOG(__FUNCTION__ << " ~ Exiting; waited " << waited_time << "ms");
    return !wait_result;
}
  // I_LOG("Asking for temperature to sensor");
  // IProperty sharknirTemp(IProperty::Text), recvProp;
  // sharknirTemp.setDevice("sharknir_temp");
  // sharknirTemp.setName("sensor");
  // sharknirTemp.add(IElement("enable_alarm"));
  // sharknirTemp.add(IElement("enable_collection"));
  // sharknirTemp.add(IElement("identifie"));
  // sharknirTemp.add(IElement("is_connected"));
  // sharknirTemp.add(IElement("max_value"));
  // sharknirTemp.add(IElement("min_value"));
  // sharknirTemp.add(IElement("notify_email_file"));
  // sharknirTemp.add(IElement("notify_email_list"));
  // sharknirTemp.add(IElement("tag"));
  // sharknirTemp.add(IElement("units"));
  // sharknirTemp.add(IElement("value"));
  // heater
  /*
  IProperty sharknirTempHeater(IndiProperty::Text);
  sharknirTempHeater.setDevice("sharknir_sensors");
  sharknirTempHeater.setName("Heater");
  sharknirTempHeater.setPerm(IndiProperty::ReadOnly);
  sharknirTempHeater.setState(IndiProperty::Busy);
  sharknirTempHeater.add(IndiElement("command"));
  sharknirTempHeater.add(IndiElement("enable_collection"));
  sharknirTempHeater.add(IndiElement("hardware_num"));
  sharknirTempHeater.add(IndiElement("identifier"));
  sharknirTempHeater.add(IndiElement("max_power"));
  sharknirTempHeater.add(IndiElement("power"));
  sharknirTempHeater.add(IndiElement("last_alarm"));
  sharknirTempHeater.add(IndiElement("max_value"));
  sharknirTempHeater.add(IndiElement("min_value"));
  sharknirTempHeater.add(IndiElement("resp_prefix_size"));
  sharknirTempHeater.add(IndiElement("setpoint"));
  sharknirTempHeater.add(IndiElement("simulation_value"));
  sharknirTempHeater.add(IndiElement("time"));
  sharknirTempHeater.add(IndiElement("units"));
  sharknirTempHeater.add(IndiElement("value"));
  */


void * SeqDataMgr::indi_client_thread(void * pMe)
{
    //usleep(15000000);
    while(System::isTimeToQuit() == false)
    {
        SeqDataMgr & Me = *((SeqDataMgr *)pMe);
        Me.m_bReceivedEof = false;

        try
        {
            IBase::setupSocketFd(gIndiPort, gIndiAddress);
            U9_LLOG("Connected to INDI Server");
            std::map<std::string, IProperty &>::iterator itProp;
            for(itProp = Me.m_sashaProps.begin(); itProp != Me.m_sashaProps.end(); itProp++)
            {
                itProp->second.clear();
            }
            std::string id = "identifier";
            Me.sharknirTemperatureDetector.add(IElement(id));
            Me.sharknirTemperatureDetector[id].setValue<std::string>("A");
            Me.sharknirTemperatureColdFinger.add(IElement(id));
            Me.sharknirTemperatureColdFinger[id].setValue<std::string>("B");
            Me.sharknirTemperatureInnerVessel.add(IElement(id));
            Me.sharknirTemperatureInnerVessel[id].setValue<std::string>("C");
            Me.sharknirTemperatureOuterVessel.add(IElement(id));
            Me.sharknirTemperatureOuterVessel[id].setValue<std::string>("D");
            Me.sashaBlob.setBLOBEnable(IProperty::Never);
            Me.sashaNumCoadds.add(IElement("value"));
            Me.sashaNumCoadds["value"].setValue<int>(1);
            Me.m_bFlagChanged = true;
        }
        catch(...)
        {
            std::string what = boost::current_exception_diagnostic_information();
            std::string toErase = "std::exception::what :";
            size_t pos = what.find(toErase);
            if(pos != std::string::npos)what.erase(pos, toErase.length());
            what.erase(std::remove(what.begin(), what.end(), '\n'), what.end());
            std::stringstream log_msg;
            log_msg << "Error trying connection to INDI server : " << what;
            W_LLOG(log_msg.str());
            usleep(60000000);
            continue;
        }
        std::map<std::string, IProperty &>::iterator itProp;
        for(itProp = Me.m_sashaProps.begin(); itProp != Me.m_sashaProps.end(); itProp++)
        {
    //        U9_LLOG("Getting : " << it->second.getDevice() << "." << it->second.getName());
            IOut(IMessage::Get) << itProp->second << std::endl;
        }

        IIn indiIn;
        IMessage indiMsgReceive;
        IProperty ipRecv;
        std::vector<IProperty>::iterator itVec;
        while(Me.m_bReceivedEof == false)
        {
            if(Me.m_bFlagChanged)
            {
                Me.m_bFlagChanged = false;
                if(Me.m_bSaveOnlyExposedFiles)
                    Me.sashaBlob.setBLOBEnable(IProperty::Never);
                else
                    Me.sashaBlob.setBLOBEnable(IProperty::Also);
                IOut(IMessage::EnB) << Me.sashaBlob << std::endl;

            }
            if(!Me.m_newProperties.empty())
            {
                Me.m_newPropMutex.lock();
                while(!Me.m_newProperties.empty())
                {
                    itVec = Me.m_newProperties.begin();
                    if(itVec->getDevice() == "sasha_save" && itVec->getName() == "save_image")
                    {
                        U9_LLOG("save_image ~ BLOB  Enable set to \"" << IProperty::getBLOBEnableString(itVec->getBLOBEnable()) << "\"");
                        IOut(IMessage::EnB) << *itVec << std::endl;
                    }
                    else
                    {
                        IOut(IMessage::New) << *itVec << std::endl;
                    }
                    Me.m_newProperties.erase(itVec);
                }
                Me.m_newPropMutex.unlock();
            }
            if(indiIn.isReady(200) == true)
            {
                indiIn >> indiMsgReceive;
                IProperty propReceive = indiMsgReceive.getProperty();
                switch(indiMsgReceive.getType())
                {
                case IMessage::Unknown :
                    U9_LLOG(__FUNCTION__ << " ~ Received an Unknown for : \"" << Me.full_property_string(propReceive) << "\"");
                    break;
                case IMessage::Def :
//                    U9_LLOG(__FUNCTION__ << " ~ Received a DEF for : \"" << Me.full_property_string(propReceive) << "\"");
                    Me.CheckUpdateProperty(propReceive);
                    break;
                case IMessage::Del :
                    U9_LLOG(__FUNCTION__ << " ~ Received a DEL for : \"" << Me.full_property_string(propReceive) << "\"");
                    break;
                case IMessage::EnB :
                    U9_LLOG(__FUNCTION__ << " ~ Received a ENB for : \"" << Me.full_property_string(propReceive) << "\"");
                    break;
                case IMessage::Eof :
                    U9_LLOG(__FUNCTION__ << " ~ Received an EOF for : \"" << Me.full_property_string(propReceive) << "\"");
                    Me.m_bReceivedEof = true;
                    break;
                case IMessage::Get :
                    U9_LLOG(__FUNCTION__ << " ~ Received a Get for : \"" << Me.full_property_string(propReceive) << "\"");
                    break;
                case IMessage::Msg :
                    U9_LLOG(__FUNCTION__ << " ~ Received a Msg for : \"" << Me.full_property_string(propReceive) << "\"");
                    break;
                case IMessage::New :
//                    U9_LLOG(__FUNCTION__ << " ~ Received a NEW for : \"" << Me.full_property_string(propReceive) << "\"");
                    break;
                case IMessage::Set :
                    if((propReceive.getDevice() != "sharknir_temp" && propReceive.getName() != "sensor") &&
                        propReceive.getDevice() != "lbto_iif" && propReceive.getName() != "status")
                    {
                        // Enable to echo received properties
                        if(propReceive.getDevice() == "sasha" && propReceive.getName() == "seq_num")
                        {
                            U9_LLOG(__FUNCTION__ << " ~ Received a SET for : \"" << Me.full_property_string(propReceive) << "\"");
                        }
                        else
                        {
                            U9_LLOG(__FUNCTION__ << " ~ Received a SET for : \"" << Me.full_property_string(propReceive) << "\"");
                        }
                    }
                    Me.UpdateProperty(propReceive);
                    break;
                default:
                    E_LLOG(__FUNCTION__ << " ~ Unexpected INDI message (" << indiMsgReceive.getType() << ") : \"" << Me.full_property_string(propReceive) << "\"");
                    break;
                }
            }
        }
        usleep(60000000);
    }
    W_LLOG(__FUNCTION__ << " ~ Exiting");
    return NULL;
}

unsigned int SeqDataMgr::estimateTiming()
{
    int nreads, ngroups, ndrops, ncoadds, nresets, ndit, frame_size, frame_delay;
    std::string readout = sashaReadout["value"].getValue();
    double frame_time = 4.5f, size;
    if(m_sashaTiming.find(readout) != m_sashaTiming.end())
        frame_time = m_sashaTiming[readout];
    else
    {
        W_LLOG("Could not find readout reagion " << readout << ". Please Update");
    }
    nreads = su::convertStringToType<int>(sashaNumReads["value"].getValue());
    ngroups = su::convertStringToType<int>(sashaNumGroups["value"].getValue());
    ndrops = su::convertStringToType<int>(sashaNumDrops["value"].getValue());
    nresets = su::convertStringToType<int>(sashaNumResets["value"].getValue());
    ncoadds = su::convertStringToType<int>(sashaNumCoadds["value"].getValue());
    ndit = su::convertStringToType<int>(sashaNumSeqs["value"].getValue());
    int ncoadds2 = su::convertStringToType<int>(sashaAcquire["num_coadds"].getValue());
    if(ncoadds2 != ncoadds)
    {
        // ncoadds = ncoadds2;
        W_LLOG("sasha.acquire.num_coadds differs from sasha.num_coadds");
    }
    if(m_sashaSizes.find(readout) != m_sashaSizes.end())
    {
        frame_size = m_sashaSizes[readout];
    }
    else
    {
        frame_size = 2048*2048*2;
    }
    size = ngroups*nreads*frame_size;
    frame_delay = 30*((int)((size + 999999)/1000000)); // ~ 30 ms per MB
    unsigned int result1 = 1000*frame_time*ndit*(ngroups*(nreads + ndrops)*ncoadds - ndrops*ncoadds + nresets);
    unsigned int result2 = ndit*frame_delay;
    unsigned int result = 1000*frame_time*ndit*(ngroups*(nreads + ndrops)*ncoadds - ndrops*ncoadds + nresets) + ndit*frame_delay + 1000*ndit + 5000;
    U9_LLOG("ndit : " << ndit << "; nreads : " << nreads << "; ngroups : " << ngroups << "; ndrops : " << ndrops << "; ncoadds : " << ncoadds << "; nresets : " << nresets << "; frame time : " << frame_time << "; frame_size : " << frame_size << "; size : " << size << "; results = {" << result1 << ", " << result2 << ", " << result << "}");
    return m_timoeout_multiplier*result;
}

unsigned int SeqDataMgr::estimateBlobDelivery()
{
    int nreads, ngroups, ndrops, ncoadds, nresets, ndit, frame_size, frame_delay;
    std::string readout = sashaReadout["value"].getValue();
    double frame_time = 4.5f, size;
    if(m_sashaTiming.find(readout) != m_sashaTiming.end())
        frame_time = m_sashaTiming[readout];
    else
    {
        W_LLOG("Could not find readout reagion " << readout << ". Please Update");
    }
    nreads = su::convertStringToType<int>(sashaNumReads["value"].getValue());
    ngroups = su::convertStringToType<int>(sashaNumGroups["value"].getValue());
    ndrops = su::convertStringToType<int>(sashaNumDrops["value"].getValue());
    nresets = su::convertStringToType<int>(sashaNumResets["value"].getValue());
    ncoadds = su::convertStringToType<int>(sashaNumCoadds["value"].getValue());
    ndit = su::convertStringToType<int>(sashaNumSeqs["value"].getValue());
    int ncoadds2 = su::convertStringToType<int>(sashaAcquire["num_coadds"].getValue());
    if(ncoadds2 != ncoadds)
    {
        // ncoadds = ncoadds2;
        W_LLOG("sasha.acquire.num_coadds differs from sasha.num_coadds");
    }
    if(m_sashaSizes.find(readout) != m_sashaSizes.end())
    {
        frame_size = m_sashaSizes[readout];
    }
    else
    {
        frame_size = 2048*2048*2;
    }
    size = ngroups*nreads*frame_size;
    frame_delay = 5000 + 70*((int)((size + 999999)/1000000)); // ~ 70 ms per MB
    unsigned int result = 1000*frame_time*ndit*(ngroups*(nreads + ndrops)*ncoadds - ndrops*ncoadds + nresets) + ndit*frame_delay + 1000*ndit;
    U9_LLOG("ndit : " << ndit << "; nreads : " << nreads << "; ngroups : " << ngroups << "; ndrops : " << ndrops << "; ncoadds : " << ncoadds << "; nresets : " << nresets << "; frame time : " << frame_time << "; frame_size : " << frame_size << "; size : " << size << "; frame_delay : " << frame_delay);
    return (unsigned int)frame_delay;

}


void SeqDataMgr::SendNewProperty(IProperty & prop)
{
    U6_LLOG(__FUNCTION__ << " ~ " << full_property_string(prop));
    m_newPropMutex.lock();
//    prop.setState(IProperty::Ok);
    m_newProperties.push_back(prop);
    m_newPropMutex.unlock();
    U8_LLOG("");
}


void SeqDataMgr::UpdateProperty(const IProperty & prop)
{
    std::string device = prop.getDevice();
    std::string name = prop.getName();
    bool prop_updated = false;
    std::map<std::string, IProperty &>::iterator it;
    if(device == "sasha_save" && name == "save_image")
    {
        SaveIncomingFile(prop);
        m_bBlobArrived = true;
    }
    else
    {
        for(it = m_sashaProps.begin(); it != m_sashaProps.end(); it++)
        {
            if(device == it->second.getDevice())
            {
                if(name == it->second.getName())
                {
                    prop_updated = true;
                    if((prop.getDevice() == "sharknir_temp"  || prop.getDevice() == "sharknir_pres1") && prop.getName() == "sensor")
                    {
                        std::string id = "identifier";
                        if(prop.getElements().find(id) != prop.getElements().end())
                        {
                            if(prop[id].get() == it->second[id].get())
                            {
                                it->second = prop;
//                                U9_LLOG("Property SET : " << prop.getDevice() << "." << prop.getName() << "." << prop[id].get());
                            }
                            continue;
                        }
                    }
                    if(prop.getDevice() == "sharknir_pres1" && prop.getName() == "sensor")
                    {
                        std::string id = "value";
                        if(prop.getElements().find(id) != prop.getElements().end())
                        {
                            it->second = prop;
                            continue;
                        }
                    }
                    if(prop.getDevice() == "lbto_iif" && prop.getName() == "status")
                    {
                        std::string id = "l_inst_auth";
                        if(prop.getElements().find(id) != prop.getElements().end())
                        {
                            if(prop[id].get() != it->second[id].get())
                            {
                                it->second = prop;
                                U9_LLOG("Property SET : " << prop.getDevice() << "." << prop.getName() << "." << id << " = " << prop[id].get());
                            }
                            continue;
                        }
                    }
                    it->second = prop;
                    m_sashaPropsSet[it->first] = true;
                    // We do not log temperatures and pressure
//                    if(prop.getDevice() != "sharknir_temp" && prop.getName() != "sensor" && prop.getDevice() != "sharknir_pres1" && prop.getName() != "sensor")
                    {
//                        U9_LLOG("Property SET : " << prop.getDevice() << "." << prop.getName());
                    }
                    break;
                }
            }
        }
        if(!prop_updated)
        {
            U9_LLOG("Unrecognized property " << prop.getDevice() << "." << prop.getName() << " received");
        }
    }
}


void SeqDataMgr::CheckUpdateProperty(const IProperty & prop)// DEF
{
    bool prop_updated = false;
    std::map<std::string, IProperty &>::iterator it;
    for(it = m_sashaProps.begin(); it != m_sashaProps.end(); it++)
    {
        if(prop.getDevice() == it->second.getDevice())
        {
            if(prop.getName() == it->second.getName())
            {
                if(prop.getDevice() == "sharknir_temp" && prop.getName() == "sensor")
                {
                    if(it->second.getElements().size())
                    {
//                        U9_LLOG(full_property_string(it->second));
//                        U9_LLOG(full_property_string(prop));
                    }
                    std::string id = "identifier";
                    if(prop.getElements().find(id) != prop.getElements().end())
                    {
                        if(prop[id].get() == it->second[id].get())
                        {
                            prop_updated = true;
                            if(it->second.getElements().size() == 1)
                            {
                                it->second = prop;
                                U9_LLOG("Property Initialized : " << prop.getDevice() << "." << prop.getName() << "." << prop[id].get());
                                break;
                            }
                        }
                        else
                        {
//                            U9_LLOG(full_property_string(prop));
                            continue;
                        }
                    }
                    else
                    {
                        U9_LLOG("Property NOT Initialized : " << full_property_string(prop));
                        continue;
                    }
                }
                if(prop.getDevice() == "lbto_iif" && prop.getName() == "status")
                {
                    std::string id = "l_inst_auth";
                    prop_updated = true;
                    if(prop.getElements().find(id) != prop.getElements().end())
                    {
                        if( it->second.getElements().empty() )
                        {
                            it->second = prop;
                            U9_LLOG("Property Initialized : " << prop.getDevice() << "." << prop.getName() << "." << id << " = " << prop[id].get());
                        }
                        continue;
                    }
                }
                else
                {
                    prop_updated = true;
                    if(it->second.getElements().empty())
                    {
                        it->second = prop;
    //                    it->second.setState(IProperty::Ok);
                        U9_LLOG("Property Initialized : " << full_property_string(prop));
                    }
                    break;
                }
            }
        }
    }
    if(!prop_updated)
    {
        U9_LLOG("Unrecognized property \"" << full_property_string(prop) << "\" received");
    }
}

std::string SeqDataMgr::full_property_string(const IProperty &prop)
{
    std::stringstream log_message;
    log_message << prop.createUniqueKey();
    log_message << " : ";
    std::map<std::string, indi::IElement>::const_iterator it;
    const std::map<std::string, indi::IElement> & elements = prop.getElements();
    switch(prop.getType())
    {
        case IProperty::Text:
        case IProperty::Number:
            if(prop.getType() == IProperty::Text)
                log_message << "IProperty::Text -> ";
            else
                log_message << "IProperty::Number -> ";
            for(it = elements.begin(); it != elements.end(); it++)
            {
                if(it->first != "file")
                {
                    log_message << "Element: '" << it->first << "'; Value : " << it->second.get() << "; ";
                }
                else
                {
                    log_message << "Element: '" << it->first << "'";
                }
            }
            break;
        case IProperty::Switch:
            log_message << "IProperty::Switch -> ";
            for(it = elements.begin(); it != elements.end(); it++)
            {
                if(it->first == "value")
                {
                    bool isOn = it->second.getSwitchState() == IElement::On;
                    log_message << "Element: '" << it->first << "'; Value : IElement::" << (isOn ?  "On" : "Off") << "; ";
                }
                else
                {
                    log_message << "Element: '" << it->first << "'; Value : " << it->second.get() << "; ";
                }
            }
            break;
        case IProperty::Unknown:
            log_message << "IProperty::Unknown -> ";
            for(it = elements.begin(); it != elements.end(); it++)
            {
                log_message << "Element: '" << it->first << "'; Value : " << it->second.get() << "; ";
            }
            break;
        case IProperty::Light:
            log_message << "IProperty::Light -> ";
            for(it = elements.begin(); it != elements.end(); it++)
            {
                log_message << "Ligth Type - Element: \"" << it->first << "\"; Value : \"" << it->second.get() << "\"; ";
            }
            break;
        case IProperty::BLOB:
            log_message << "IProperty::BLOB -> ";
            for(it = elements.begin(); it != elements.end(); it++)
            {
                log_message << "BLOB Type size : " << prop["file"].getSize<unsigned int>() << "; ";
            }
            break;
    }
//    else
    {
//        log_message << "EOF Message";
    }
    return log_message.str();
}

void * SeqDataMgr::fit_mem_realloc(void * old_pointer, size_t new_size)
{
    void * pNew = NULL;
    pNew = realloc(old_pointer, new_size);
    U9_LLOG(__FUNCTION__ << "  ~ " << old_pointer << " -> " << pNew << " ~ " << new_size/2880.f);
    return pNew;
}


int SeqDataMgr::UpdateFitFile(fitsfile * pInFit, fitsfile * ptrFits)
{
     char simple_test[84];
    *(simple_test + 80) = 0;
    char string_value[32];
    int ref_fit_status = 0;
    int update_fit_status = 0;
    int int_value;
    float float_value;
    fits_read_key(pInFit, TFLOAT,    "BZERO", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == KEY_NO_EXIST || ref_fit_status)
    {
        if(ref_fit_status != KEY_NO_EXIST)
        {
            U9_LLOG("ref_fit_status = " << ref_fit_status);
        }
        ref_fit_status = 0;
    }
    else
    {
        fits_update_key(ptrFits, TFLOAT,    "BZERO    ", &float_value, simple_test, &update_fit_status);
    }
    fits_read_key(pInFit, TFLOAT,    "BSCALE", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == KEY_NO_EXIST)
    {
        ref_fit_status = 0;
    }
    else
    {
        fits_update_key(ptrFits, TFLOAT,    "BSCALE   ", &float_value, simple_test, &update_fit_status);
    }
    fits_read_key(pInFit, TSTRING, "CAMERA", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "CAMERA", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    // fits_read_key(pInFit, TSTRING, "LST-OBS", string_value, simple_test, &ref_fit_status);
    // if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "LST-OBS", string_value, simple_test, &update_fit_status);
    // else {W_LLOG("Could not find key " << "LST-OBS" ); ref_fit_status = 0; }
    fits_read_key(pInFit, TSTRING, "PARAANG", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "PARAANG", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "CAPCOMP", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "CAPCOMP", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "DATE-OBS", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "DTEOBSM", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "DETECTOR", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "DETECTOM", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "EXPMODE", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "EXPMODE", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "EXPTIME", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "EXPTIME", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "FILENAME", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "FILENAME", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "FILTPOLE", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "FILTPOLE", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "FRAME", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "FRAME", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "GAIN", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "GAINM", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "ITIME", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "ITIME", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "NCOADDS", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "NCOADDS", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "NDROPS", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "NDROPS", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "NGROUPS", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "NGROUPS", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "NOUTPUTS", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "NOUTPUTS", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "NRAMPS", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "NRAMPS", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "NREADS", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "NREADS", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "NREADS", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "NRESETS", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "NRESETS", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "OBJNAME", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "OBJNAME", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "RAMPTIM", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "RAMPTIM", &float_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "SUBSECNM", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "SUBSECNM", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "SUBSECH  ", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "SUBSECH  ", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "SUBSECW  ", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "SUBSECW  ", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "SUBSECX1 ", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "SUBSECX1 ", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "SUBSECX2 ", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "SUBSECX2 ", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "SUBSECY1 ", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "SUBSECY1 ", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "SUBSECY2 ", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "SUBSECY2 ", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "SYSSWVER", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "SYSSWVER", &string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "TIME-END", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TSTRING, "TIME-END", string_value, simple_test, &update_fit_status);
    else ref_fit_status = 0;
    fits_read_key(pInFit, TSTRING, "TIME-OBS", string_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0){
        fits_update_key(ptrFits, TSTRING, "TIME-OBS", string_value, simple_test, &update_fit_status);
        fits_update_key(ptrFits, TSTRING, "UTC-OBS", string_value, simple_test, &update_fit_status);
    }
    else ref_fit_status = 0;
    fits_read_key(pInFit, TFLOAT,  "USEREXP", &float_value, simple_test, &ref_fit_status);
    if(ref_fit_status == 0)fits_update_key(ptrFits, TFLOAT,  "USEREXP", &float_value, simple_test, &update_fit_status);

    if(update_fit_status)
    {
        U9_LLOG("Update Fit Status : " << update_fit_status);
        LogCfitsioStack(5);
    }
    return update_fit_status;
}

void SeqDataMgr::LogCfitsioStack(int n)
{
    char simple_test[84];
    *(simple_test + 80) = 0;
    for(int i = 0; i < 7; i++)
    {
        fits_read_errmsg(simple_test);
        U9_LLOG(simple_test);
    }

}

void SeqDataMgr::SaveIncomingFile(const IProperty & prop)
{
    U6_LLOG(__FUNCTION__);
    std::string filenameSASHA, filenameSHINS, dateISO8601;

    if(m_ExpectedBlobs > 0)m_ExpectedBlobs--;
    fitsfile * ptrFitsFileSASHA = NULL;
    fitsfile * ptrFitsFileSHINS = NULL;
    int FitsStatusShins = 0, FitsStatusSasha = 0;
//    std::vector<long> vecNaxes(4, 0);
    long naxes[4] = { 36, 40, 1, 1 };
    int bitpix = 16, naxis = 2;
    long naxesSasha[4] = { 36, 40, 1, 1 };
    int bitpixSasha = 16, naxisSasha = 4;
    char simple_test[84];
    *(simple_test + 80) = 0;
    if(prop.find("file"))
    {
        if(prop["file"].getSize<float>() > 0.0)
        {
            const char * fitsBytes = prop["file"].get().c_str();
            size_t blobSize = prop["file"].getSize<unsigned int>();
            void * pBlobIncoming = malloc(blobSize);
            if(pBlobIncoming)
            {
                int blobLength = from64tobits((char *)pBlobIncoming, fitsBytes);
                if(blobLength < 0)
                {
                    free(pBlobIncoming);
                    A_LLOG("Bad base 64 FITS file WILL NOT BE SAVED");
                }
                else
                {
                    dateISO8601 = prop.getTimeStamp().getFormattedIso8601Str();
                    su::SetTimeWorkingDirectory(dateISO8601);
                    filenameSHINS = "SHARKNIR." + dateISO8601 + "." + m_sashaSetup[suc::InstrumentMode] + ".fits";
                    m_last_generated_file = filenameSHINS;
                    filenameSASHA = "SHARKNIR." + dateISO8601 + "." + m_sashaSetup[suc::InstrumentMode] + ".SASHA.fits";
                    if(m_bSaveIncomingBlob)
                        su::debug_save_memory_block((char *)pBlobIncoming, blobSize, filenameSASHA.c_str());
                    fits_open_memfile(&ptrFitsFileSASHA, "", READONLY, &pBlobIncoming, &blobSize, 0, NULL, &FitsStatusSasha);
                    fits_get_img_param(ptrFitsFileSASHA, 4, &bitpixSasha, &naxisSasha, naxesSasha, &FitsStatusSasha);
                    if(FitsStatusSasha)LogCfitsioStack(5);

                    fits_create_file(&ptrFitsFileSHINS, filenameSHINS.c_str(), &FitsStatusShins);
                    fits_create_img(ptrFitsFileSHINS, bitpix, naxis, naxes, &FitsStatusShins);
                    FitsStatusShins = SetFitsValues(ptrFitsFileSHINS);
                    FitsStatusShins = UpdateFitFile(ptrFitsFileSASHA, ptrFitsFileSHINS);
                    fits_resize_img(ptrFitsFileSHINS, bitpixSasha, naxisSasha, naxesSasha, &FitsStatusShins);
                    fits_copy_data(ptrFitsFileSASHA, ptrFitsFileSHINS, &FitsStatusShins);
                    fits_write_chksum(ptrFitsFileSHINS, &FitsStatusShins);
                    fits_flush_file(ptrFitsFileSHINS, &FitsStatusShins);
                    if(FitsStatusShins)LogCfitsioStack(5);
                    FitsStatusShins = 0;
                    fits_close_file(ptrFitsFileSHINS, &FitsStatusShins);
                    if(FitsStatusShins)LogCfitsioStack(5);

                    fits_close_file(ptrFitsFileSASHA, &FitsStatusSasha);
                    U9_LLOG("Saved fits file " << filenameSHINS);
                    m_last_generated_files.push_back(filenameSHINS);

                    free(pBlobIncoming);
                }
            }
            else
            {
                std::stringstream log_msg;
                log_msg << "Error : could not allocate memory for incoming blob";
                E_LLOG(log_msg.str());
                U8_LLOG("");
                throw std::runtime_error(log_msg.str());
            }
        }
    }

    U8_LLOG("");
}

void SeqDataMgr::updateInstrumentHeader()
{
    U6_LLOG(__FUNCTION__);
    SInstrumentHeader & ih = m_instrumentHeader;
    static Nice::Date lastCall;
    Nice::Date currentCall = Nice::Date::now();
    if(currentCall - lastCall < Nice::Time::seconds(5))return;
    if(ih.init)
    {
        ih.init = false;
        
        ih.adc.nameid = suc::ADC1;
        ih.adc.mechanical_id = 0;

        ih.apodizer_w.nameid = suc::APODIZER_W;
        ih.apodizer_w.mechanical_id = 0;

        ih.cal_fiber_defocus_lamp.nameid = suc::CAL_FIBER_DEFOCUS_LAMP;
        ih.cal_fiber_defocus_lamp.mechanical_id = 0;

        ih.cal_fiber_dep.nameid = suc::CAL_FIBER_DEP;
        ih.cal_fiber_dep.mechanical_id = 0;
        
        ih.cal_fiber_focus_lamp.nameid = suc::CAL_FIBER_FOCUS_LAMP;
        ih.cal_fiber_focus_lamp.mechanical_id = 0;

        ih.cal_filter_dep.nameid = suc::CAL_FILTER_DEP;
        ih.cal_filter_dep.mechanical_id = 0;

        ih.cal_flat_field_lamp.nameid = suc::CAL_FF_LAMP;
        ih.cal_flat_field_lamp.mechanical_id = 0;

        ih.cal_mirror_dep.nameid = suc::CAL_MIRROR_DEP;
        ih.cal_mirror_dep.mechanical_id = 0;

        ih.coro_slit_w.nameid = suc::CORO_SLIT_W;
        ih.coro_slit_w.mechanical_id = 0;

        ih.db_filt_w.nameid = suc::DB_FILT_W;
        ih.db_filt_w.mechanical_id = 0;

        ih.derotator.nameid = suc::DROT;
        ih.derotator.mechanical_id = 0;
        
        ih.inbeam_dep.nameid = suc::INBEAM_DEP;
        ih.inbeam_dep.mechanical_id = 0;

        ih.inbeam_tt.nameid = suc::INBEAM_TT;
        ih.inbeam_tt.mechanical_id = 0;

        ih.lyot_grism_w.nameid = suc::LYOT_GRISM_W;
        ih.lyot_grism_w.mechanical_id = 0;

        ih.nd_filt_w.nameid = suc::ND_FILT_W;
        ih.nd_filt_w.mechanical_id = 0;

        ih.pupil_lens_dep.nameid = suc::PUPIL_LENS_DEP;
        ih.pupil_lens_dep.mechanical_id = 0;

        ih.sci_filt_w1.nameid = suc::SCI_FILT_W1;
        ih.sci_filt_w1.mechanical_id = 0;
        
        ih.sci_filt_w2.nameid = suc::SCI_FILT_W2;
        ih.sci_filt_w2.mechanical_id = 0;
        
        ih.shutter.nameid = suc::SHUTTER;
        ih.shutter.mechanical_id = 0;

        ih.adc_mode.nameid = suc::ADC_Mode;
        ih.drot_mode.nameid = suc::DROT_Mode;
        ih.mask_comb.nameid = suc::MaskComb;

        ih.temperature_start = ih.temperature_end = 0.0f;
    }
    ih.temperature_end = ih.temperature_start;
    ih.temperature_start = getMeanTemperature();
    const std::map<std::string, indi::IElement> & elementsA = sharknirTemperatureDetector.getElements();
    float valueA = 0.f;
    bool bValueA = false;
    if(elementsA.find("value") != elementsA.end())
    {
        valueA = elementsA.find("value")->second.get<float>();
        bValueA = true;
    }
    ih.temperature_start = valueA;

    if(m_pGetMotorStatus && !m_bFastSavingMode)
    {
        m_pGetMotorStatus(ih.apodizer_w);
        m_pGetMotorStatus(ih.coro_slit_w);
        m_pGetMotorStatus(ih.lyot_grism_w);
        m_pGetMotorStatus(ih.nd_filt_w);
        m_pGetMotorStatus(ih.db_filt_w);
        m_pGetMotorStatus(ih.sci_filt_w1);
        m_pGetMotorStatus(ih.sci_filt_w2);

        m_pGetMotorStatus(ih.inbeam_dep);
        m_pGetMotorStatus(ih.pupil_lens_dep);
        ih.pupil_lens_dep.status = ih.pupil_lens_dep.type == "IN";
        m_pGetMotorStatus(ih.shutter);
        ih.shutter.status = ih.shutter.type == "OUT";

        m_pGetMotorStatus(ih.inbeam_tt);

        m_pGetMotorStatus(ih.cal_fiber_dep);
        m_pGetMotorStatus(ih.cal_mirror_dep);
        ih.cal_mirror_dep.status = ih.cal_mirror_dep.type == "IN";
        m_pGetMotorStatus(ih.cal_filter_dep);

        m_pGetMotorStatus(ih.derotator);
        m_pGetMotorStatus(ih.adc);

        m_pGetMotorStatus(ih.drot_mode);
        m_pGetMotorStatus(ih.adc_mode);
        m_pGetMotorStatus(ih.mask_comb);
    }

    ih.cal_flat_field_lamp.status = m_obContext.flat_field_lamp == "ON";
    ih.cal_flat_field_lamp.type = m_obContext.flat_field_lamp;
    ih.cal_fiber_focus_lamp.status = m_obContext.focus_lamp == "ON";
    ih.cal_fiber_focus_lamp.type = m_obContext.focus_lamp;
    ih.cal_fiber_defocus_lamp.status = m_obContext.defocus_lamp == "ON";
    ih.cal_fiber_defocus_lamp.type = m_obContext.defocus_lamp;


    if(m_pGetRtcStatus)
    {
        for(std::map<std::string, std::string>::iterator itRtc = m_rtcKeywords.begin(); itRtc != m_rtcKeywords.end(); itRtc++)
        {
            if(!(m_bFastSavingMode && itRtc->first == "TTSENSORTEMP"))
                itRtc->second = m_pGetRtcStatus(itRtc->first);
        }
    }

    if(m_pGetTcsPreset && !m_bFastSavingMode)
    {
        m_TcsPreset = m_pGetTcsPreset();
    }

    m_bInstrumentSetupUpdated = true;
    U8_LLOG(__FUNCTION__);
}

float SeqDataMgr::getMeanTemperature()
{
    const std::map<std::string, indi::IElement> & elementsA = sharknirTemperatureDetector.getElements();
    float valueA = 0.f, valueB = 0.f;
    bool bValueA = false, bValueB = false;
    if(elementsA.find("value") != elementsA.end())
    {
        valueA = elementsA.find("value")->second.get<float>();
        bValueA = true;
    }
    const std::map<std::string, indi::IElement> & elementsB = sharknirTemperatureColdFinger.getElements();
    if(elementsB.find("value") != elementsB.end())
    {
        valueB = elementsB.find("value")->second.get<float>();
        bValueB = true;
    }

    float meanTemperature = 0.f;
    if(bValueA)meanTemperature += valueA;
    if(bValueB)meanTemperature += valueB;
    if(bValueA && bValueB)meanTemperature /= 2.f;

    return meanTemperature;
}



int SeqDataMgr::getJsonStatus(std::string & jsonStatus)
{
    int status = EXIT_SUCCESS;
    // GET TEMPERATURE STATUS 
    const std::map<std::string, indi::IElement> & temperatureA_elements = sharknirTemperatureDetector.getElements();
    float valueA = 0.f;
    if(temperatureA_elements.find("value") != temperatureA_elements.end())
    {
        valueA = temperatureA_elements.find("value")->second.get<float>();
    }

    {
        std::stringstream json_alias;
        json_alias << "\"sasha_temp\": { \"name\": \"sasha_temp\", \"value\": \"" << valueA << "\"}";
        jsonStatus += json_alias.str();
        jsonStatus += ", ";
    }
    // GET NUM READS 
    const std::map<std::string, indi::IElement> & nreads_elements = sashaNumReads.getElements();
    int NREADS = 0;
    if(nreads_elements.find("value") != nreads_elements.end())
    {
        NREADS = nreads_elements.find("value")->second.get<int>();
    }

    {
        std::stringstream json_alias;
        json_alias << "\"NREADS\": { \"name\": \"NREADS\", \"value\": \"" << NREADS << "\"}";
        jsonStatus += json_alias.str();
        jsonStatus += ", ";
    }
    // GET NUM COADDS
    const std::map<std::string, indi::IElement> & ncoadds_elements = sashaNumCoadds.getElements();
    int NCOADDS = 0;
    if(ncoadds_elements.find("value") != ncoadds_elements.end())
    {
        NCOADDS = ncoadds_elements.find("value")->second.get<int>();
    }

    {
        std::stringstream json_alias;
        json_alias << "\"NCOADDS\": { \"name\": \"NCOADDS\", \"value\": \"" << NCOADDS << "\"}";
        jsonStatus += json_alias.str();
        jsonStatus += ", ";
    }

    // GET NUM DROPS
    const std::map<std::string, indi::IElement> & ndrops_elements = sashaNumDrops.getElements();
    int NDROPS = 0;
    if(ndrops_elements.find("value") != ndrops_elements.end())
    {
        NDROPS = ndrops_elements.find("value")->second.get<int>();
    }

    {
        std::stringstream json_alias;
        json_alias << "\"NDROPS\": { \"name\": \"NDROPS\", \"value\": \"" << NDROPS << "\"}";
        jsonStatus += json_alias.str();
        jsonStatus += ", ";
    }

    // GET NUM GROUPS
    const std::map<std::string, indi::IElement> & ngroups_elements = sashaNumGroups.getElements();
    int NGROUPS = 0;
    if(ngroups_elements.find("value") != ngroups_elements.end())
    {
        NGROUPS = ngroups_elements.find("value")->second.get<int>();
    }

    {
        std::stringstream json_alias;
        json_alias << "\"NGROUPS\": { \"name\": \"NGROUPS\", \"value\": \"" << NGROUPS << "\"}";
        jsonStatus += json_alias.str();
        jsonStatus += ", ";
    }

    // GET NDIT
    const std::map<std::string, indi::IElement> & ndit_elements = sashaNumSeqs.getElements();
    int NDIT = 0;
    if(ndit_elements.find("value") != ndit_elements.end())
    {
        NDIT = ndit_elements.find("value")->second.get<int>();
    }

    {
        std::stringstream json_alias;
        json_alias << "\"NDIT\": { \"name\": \"NDIT\", \"value\": \"" << NDIT << "\"}";
        jsonStatus += json_alias.str();
        //jsonStatus += ", ";
    }
    // // GET INS AUTH
    // const std::map<std::string, indi::IElement> & lbt_inst_auth = lbtLeftInstrument.getElements();
    // if(lbt_inst_auth.find("l_inst_auth") != lbt_inst_auth.end())
    // {
    //     std::stringstream json_alias;
    //     json_alias << "\"LBT_L_AUTH\": { \"name\": \"LBT_L_AUTH\", \"value\": \"" << lbt_inst_auth.find("l_inst_auth")->second.get() << "\"}";
    //     jsonStatus += json_alias.str();
    //     //jsonStatus += ", ";
    // }
    // GET Authorized Instrument from DD
    if(m_obContext.l_auth_ins_from_DD != "")
    {
        std::stringstream json_alias;
        jsonStatus += ", ";
        json_alias << "\"lbt_l_auth\": { \"name\": \"lbt_l_auth\", \"value\": \"" << m_obContext.l_auth_ins_from_DD << "\"}";
        jsonStatus += json_alias.str();
    }
    if(m_obContext.r_auth_ins_from_DD != "")
    {
        std::stringstream json_alias;
        jsonStatus += ", ";
        json_alias << "\"lbt_r_auth\": { \"name\": \"lbt_r_auth\", \"value\": \"" << m_obContext.r_auth_ins_from_DD << "\"}";
        jsonStatus += json_alias.str();
    }
    // GET NCPA loaded and status
    if(m_obContext.ncpa_rotation_status != "")
    {
        std::stringstream json_alias;
        jsonStatus += ", ";
        json_alias << "\"ncpa_rot_status\": { \"name\": \"ncpa_rot_status\", \"value\": \"" << m_obContext.ncpa_rotation_status << "\"}";
        jsonStatus += json_alias.str();
    }

    if(m_obContext.ncpa_file != "")
    {
        std::stringstream json_alias;
        jsonStatus += ", ";
        json_alias << "\"ncpa_file_loaded\": { \"name\": \"ncpa_file_loaded\", \"value\": \"" << m_obContext.ncpa_file << "\"}";
        jsonStatus += json_alias.str();
    }

    
    return status;
}


void SeqDataMgr::setFlags(uint64_t flags, uint64_t more_flags)
{
    if(flags & shins::flags::SAVE_INCOMING_SASHA_FILES)
    {
        m_bSaveIncomingBlob = true;
        U9_LLOG("Save Incoming Sasha files : " << "ON");
    }
    if(flags & shins::flags::SEQDATA_TEST01)
    {
        m_bTest01 = true;
        U9_LLOG("SeqIDataMgr TEST01 : " << "ON");
    }
    if(flags & shins::flags::SAVE_ONLY_EXPOSED_SASHA_FILES)// 2
    {
        if(!m_bSaveOnlyExposedFiles)
        {
            m_bSaveOnlyExposedFiles = true;
            m_bFlagChanged = true;
        }
        U9_LLOG("Save only exposed files : " << "ON");
    }
    if(flags & shins::flags::SEQDATA_FORCE_PROPERTY_SET)
    {
        m_bForcePropertySet = true;
        U9_LLOG("SeqIDataMgr Force Property Set : " << "ON");
    }
    if(flags & shins::flags::FAST_SAVING_MODE)
    {
        updateInstrumentHeader();
        m_bFastSavingMode = true;
        U9_LLOG("SeqIDataMgr Fast Saving Mode Set : ON");
    }
    if(flags & shins::flags::DOUBLE_SASHA_TIMEOUTS)
    {
        m_timoeout_multiplier *= 2;
        U9_LLOG("Sasha timeout multiplier set to " << m_timoeout_multiplier);
    }
};
void SeqDataMgr::unsetFlags(uint64_t flags, uint64_t more_flags)
{
    if(flags & shins::flags::SAVE_INCOMING_SASHA_FILES)
    {
        m_bSaveIncomingBlob = false;
        U9_LLOG("Save Incoming Sasha files : " << "OFF");
    }
    if(flags & shins::flags::SEQDATA_TEST01)
    {
        m_bTest01 = false;
        U9_LLOG("SeqIDataMgr TEST01 : " << "OFF");
    }
    if(flags & shins::flags::SAVE_ONLY_EXPOSED_SASHA_FILES)// 2
    {
        if(m_bSaveOnlyExposedFiles)
        {
            m_bSaveOnlyExposedFiles = false;
            m_bFlagChanged = true;
        }
        U9_LLOG("Save only exposed files : " << "OFF");
    }
    if(flags & shins::flags::SEQDATA_FORCE_PROPERTY_SET)
    {
        m_bForcePropertySet = false;
        U9_LLOG("SeqIDataMgr Force Property Set : " << "OFF");
    }
    if(flags & shins::flags::FAST_SAVING_MODE)
    {
        m_bFastSavingMode = false;
        U9_LLOG("SeqIDataMgr Fast Saving Mode Set : OFF");
    }
};

void SeqDataMgr::toggleFlags(uint64_t flags, uint64_t more_flags)
{
    if(flags & shins::flags::SAVE_INCOMING_SASHA_FILES)
    {
        m_bSaveIncomingBlob = !m_bSaveIncomingBlob;
        U9_LLOG("Save incoming Sasha files : " << (m_bSaveIncomingBlob ? "ON" : "OFF"));
    }
    if(flags & shins::flags::SEQDATA_TEST01)
    {
        m_bTest01 = !m_bTest01;
        U9_LLOG("SeqIDataMgr TEST01 : " << (m_bTest01 ? "ON" : "OFF"));
    }
    if(flags & shins::flags::SAVE_ONLY_EXPOSED_SASHA_FILES) // 2
    {
        m_bSaveOnlyExposedFiles = !m_bSaveOnlyExposedFiles;
        m_bFlagChanged = true;
        U9_LLOG("Save only exposed files : " << (m_bSaveOnlyExposedFiles ? "ON" : "OFF"));
    }
    if(flags & shins::flags::SEQDATA_FORCE_PROPERTY_SET)
    {
        m_bForcePropertySet = !m_bForcePropertySet;
        U9_LLOG("SeqIDataMgr Force Property Set : " << (m_bForcePropertySet ? "ON" : "OFF"));
    }
    if(flags & shins::flags::FAST_SAVING_MODE)
    {
        if(!m_bFastSavingMode)
        {
            updateInstrumentHeader();
        }
        m_bFastSavingMode = !m_bFastSavingMode;
        U9_LLOG("SeqIDataMgr Fast Saving Mode Set : " << (m_bFastSavingMode ? "ON" : "OFF"));
    }
};


void dummy()
{

}








