#include "BDSwicCalibrator.h"

#include "BDSwicDevice.h"

#include <Conventions/Munits.h>
#include <RawData/RawBeamMonHeaderBlock.h>
#include <RawData/RawBeamMonBlock.h>

#include <MessageService/MsgService.h>
CVSID("$Id: BDSwicCalibrator.cxx,v 1.12 2005/08/22 15:40:44 bishai Exp $");


static const float max_profile_pedestal_rms = 15.0; // ADC

BDSwicCalibrator* BDSwicCalibrator::fInstance = 0;

BDSwicCalibrator& BDSwicCalibrator::Get()
{
    if (!fInstance) fInstance = new BDSwicCalibrator;
    return *fInstance;
}

BDSwicCalibrator::BDSwicCalibrator()
    : fPeds(), fMask()
{
}

BDSwicCalibrator::~BDSwicCalibrator()
{
}

void BDSwicCalibrator::Calibrate(const RawBeamMonHeaderBlock& rbmhb,
				 const RawBeamMonBlock& rbmb)
{
    VldContext vc = rbmhb.GetVldContext();
    bool new_peds = fPeds.SetSpillTime(vc);
    bool new_mask = fMask.SetSpillTime(vc);

    if (! (new_peds || new_mask)) return;

    DevList::iterator it, done = fDevList.end();
    for (it=fDevList.begin(); it != done; ++it) {
	this->CalibrateOne(rbmhb,rbmb,**it);
    }
}

void BDSwicCalibrator::CalibrateOne(const RawBeamMonHeaderBlock& /*rbmhb*/,
				    const RawBeamMonBlock& rbmb,
				    BDSwicDevice& sd)
{
    if (!sd.IsValid()) {
	MSG("BD",Msg::kDebug)
	    << "BDSwicCalibrator: given invalid SWIC data\n";
	return;
    }

    string name = sd.GetData().GetName();
    MSG("BDU",Msg::kVerbose)
	<< "BDSwicCalibrator: calibrating " << name << endl;

    // Pedestals
    vector<double> mean(96,0), sigma(96,0);
    int ok = fPeds.GetPeds(name.c_str(),mean,sigma);
    if (!ok) {
	MSG("BD",Msg::kDebug) << "BDSwicCalibrator:: no peds for: " 
				<< name << endl;
    }
    
    // Channel masks
    vector<double> mask = fMask.GetMask(name.c_str());
    if (!mask.size()) {
	MSG("BD",Msg::kWarning) << "BDSwicCalibrator:: no mask for: "
				<< name << endl;
	mask = vector<double>(96,1);
    }
    // for now hard code some masking of high peds in addition to any
    // masking from the DB.
    BDSwicDevice::MonitorType monitor_type = sd.GetMonitorType();
    if (monitor_type == BDSwicDevice::kProfile) {
	for (int ind=0; ind<96; ++ind)
	    if (sigma[ind] > max_profile_pedestal_rms) mask[ind] = 0.0;
    }

    switch (monitor_type) {
    case BDSwicDevice::kMuon:
	sd.SetCapacitance(1.0e2*Munits::picofarad);
	break;
    case BDSwicDevice::kProfile: // fall through
    case BDSwicDevice::kHadron:  // fall through
    case BDSwicDevice::kUnknown: // fall through
    default:			 // fall through
 	sd.SetCapacitance(1.0e5*Munits::picofarad);
	break;
    }

    sd.SetPeds(mean);
    sd.SetNoise(sigma);
    sd.SetMask(mask);

    // if we don't have a PIC type device we are done here
    BDSwicDevice::MonitorType type = sd.GetMonitorType();
    if (! (type == BDSwicDevice::kHadron || type == BDSwicDevice::kMuon)) return;
    
    // temp/pressure corrections for PICs - this is a quasi static
    // calibration in that we hard code the gain formula.  This may
    // migrate to a DB table some day.

    // Get corresponding temperature/pressure device name
    string tname="", pname="";
    if (name == "E:HADMDS") {
        //tname = "E:TTHADM";
        tname = "E:HMRTD";
        //pname = "E:HMGPD";
	pname = "E:HMGPR";
    }
    else if (name == "E:MMA1DS") {
        //tname = "E:TTMA1";
        tname = "E:MM1RTD";
        //pname = "E:MM1GPD";
	pname = "E:MM1GPR";
    }
    else if (name == "E:MMA2DS") {
        //tname = "E:TTMA2";
        tname = "E:MM2RTD";
        //pname = "E:MM2GPD";
	pname = "E:MM2GPR";
    }
    else if (name == "E:MMA3DS") {
        //tname = "E:TTMA2";
        tname = "E:MM3RTD";
        //pname = "E:MM3GPD";
	pname = "E:MM3GPR";
    }
    else {
        MSG("BD",Msg::kWarning) 
	    << "Unknown had/mu monitor: " << name
	    << " no temp/pressure correction done\n";
        return;
    }

    const RawBeamData* rbd_temp = rbmb[tname.c_str()];
    const RawBeamData* rbd_pres = rbmb[pname.c_str()];

    if (!rbd_temp || !rbd_pres ||
        !rbd_temp->GetDataLength() || !rbd_pres->GetDataLength()) {
        MSG("BD",Msg::kWarning) 
	    << "Could not get temp/pres devices for "
	    << name << " no temp/pressure correction done\n";
        return;
    }
    
    const double temp = Munits::ToCelcius(Munits::FromFahrenheit(rbd_temp->GetData()[0]));
    const double pres = rbd_pres->GetData()[0];

    // temperature / pressure correction.  Currently the constants are
    // hard coded.  If they ever need to change, we need the DBI
    // machinery to hold them.

    // From Dharma:
    // Icorr = Iraw*(1+.0013*(gasPressure-756 torr))*(1+0.0034*(alcoveTemp-20 C))

    // From Mary:
    // (1+0.00078*(gasPressure-756 torr))*(1+0.00135*(alcoveTemp-20 C))

    const double pressure_nominal = 756; // tor
    const double temperature_nominal = 20; // degC

#if 0
    const double pressure_factor = 0.00078;
    const double temperature_factor = 0.00135;
#else  // Dharma:
    const double pressure_factor = 0.0013;
    const double temperature_factor = 0.0034;
#endif

    double tp_scale = 
        (1.0+pressure_factor   *(pres - pressure_nominal)) *
        (1.0+temperature_factor*(temp - temperature_nominal));
    
    sd.SetGainCorrection(tp_scale);


    // Note: we don't do any truly static calibrations at this stage
    // since they were done when the device as added, below.
}

bool BDSwicCalibrator::AddDevice(BDSwicDevice& dev)
{
    // Reject if no underlying data.
    if (!dev.IsValid()) {
        MSG("BD",Msg::kWarning)
	    << "Not adding invalid device\n";
	return false;
    }

    const char* device_name = dev.GetData().GetName().c_str();

    // Already gotchya, sucker.
    if (find(fDevList.begin(),fDevList.end(),&dev) != fDevList.end()) {
	return true;
    }

    fPeds.AddDevice(device_name);
    fMask.AddDevice(device_name);

    // add device to list
    fDevList.push_back(&dev);

    return true;
}

void BDSwicCalibrator::RemoveDevice(BDSwicDevice& dev)
{
    fDevList.remove(&dev);
}
