#include "BMSpillFiller.h"

#include <BeamDataUtil/BeamMonSpill.h>
#include <BeamDataUtil/BDEarliest.h>
#include <BeamDataUtil/BDHornCurrent.h>
#include <BeamDataUtil/BDScalar.h>
#include <BeamDataUtil/BDHadMuMon.h>
#include <BeamDataUtil/BDDevices.h>
#include <BeamDataUtil/BDTarget.h>
#include <BeamDataUtil/BDSwicPeds.h>

#include <RawData/RawBeamMonBlock.h>

#include <MessageService/MsgService.h>

CVSID("$Id: BMSpillFiller.cxx,v 1.24 2006/05/27 07:26:38 rhatcher Exp $");

#include <DatabaseInterface/DbiWriter.h>
//  Instantiate associated Result Pointer class.
#include <DatabaseInterface/DbiWriter.tpl>
template class  DbiWriter<BeamMonSpill>;

#include <string>
#include <vector>
using namespace std;
#include <cmath>

const double min_horn_current = 150;


BMSpillFiller::BMSpillFiller(BDEarliest& bde,
			     BDScalar* bdpi[4],
			     BDHornCurrent& bdhc,
			     BDTarget& target,
			     BDHadMuMon* hadmu[4])
    : fEarliest(bde)
    , fHorn(bdhc)
    , fTarget(target)
    , fSpillsPerWrite(3000)
{
    this->SetSpillsPerWrite();
    for (int ind=0; ind<4; ++ind) fToroids[ind] = bdpi[ind];
    for (int ind=0; ind<4; ++ind) fHadMu[ind]   = hadmu[ind];
}

BMSpillFiller::~BMSpillFiller()
{
    this->DBU(true);
    while (fSpills.size()) {
	delete fSpills.back();
	fSpills.pop_back();
    }
}

void BMSpillFiller::SetSpillsPerWrite(size_t n)
{
    fSpillsPerWrite = n;
}

static const RawBeamData* get_dev(const RawBeamMonBlock& rbmb,
				  const char* devname)
{
    const RawBeamData* rbd = rbmb[devname];
    if (!rbd) return 0;
    if (!rbd->GetDataLength()) return 0;
    return rbd;
}


static bool is_pm_there(const RawBeamMonBlock& rbmb, const char* dev)
{
    // If the PM data isn't there, don't care if it's inserted or not,
    // it is still dead to us.
    const RawBeamData* pmswic = get_dev(rbmb,Form("E:M%sDS",dev));
    if (!pmswic) {
	MSG("BDD",Msg::kVerbose)
	    << "PM" << dev << " not in data\n";
	return false;
    }

    // PM data is there, see if LVDT can tell us if it's inserted or
    // not
    const double lvdt_inness_cut = 6.0;
    const RawBeamData* lvdt = get_dev(rbmb,Form("E_M%sLV",dev));
    if (lvdt && lvdt->GetDataLength()) {
	double val = lvdt->GetData()[0];
	if (val < lvdt_inness_cut) return true;
	MSG("BDD",Msg::kVerbose)
	    << "PM" << dev << " fails LVDT cut, val = " << val << endl;
	return false;
    }
    
#if 0
    // No LVDT, (initial running had typo in dev list).  Check if the
    // "I:PMxxx" device is there - not given clear understanding of
    // what this actually is.
    const RawBeamData* ipm = get_dev(rbmb,Form("I:PM%s",dev));
    if (ipm) {
	MSG("BDD",Msg::kDebug)
	    << "PM" << dev << " no I:PM" << dev << endl;
	return false;
    }
#endif

    return true;
}

void BMSpillFiller::Spill(const RawBeamMonHeaderBlock& /*rbmhb*/,
			  const RawBeamMonBlock& rbmb)
{
    BeamMonSpill* spill = new BeamMonSpill;
    BeamMonSpill::StatusBits bits = spill->GetStatusBits(); // is zeroed

    double dae=0, vme=0;
    fEarliest.GetTimestamps(dae, vme);
    if (dae==0 && vme==0) {
	MSG("BDD",Msg::kWarning)
	    << "Both DAE and VME timestamps are zero.\n";
	return;
    }


    if (!vme) bits.time_source = 1;
    VldTimeStamp vts_dae(dae), vts_vme(vme);
    spill->SetTimestamps(vts_dae,vts_vme);

    spill->SetToroids(fToroids[0]->GetValue(),
		      fToroids[1]->GetValue(), 
		      fToroids[2]->GetValue(), 
		      fToroids[3]->GetValue());

    double horn_current = fHorn.GetValue();
    if (fabs(horn_current) > min_horn_current) bits.horn_on = 1;
    else {
	MSG("BDD",Msg::kDebug) << "Horn current: "
			      << horn_current << " not high enough\n";
    }
    spill->SetHornCurrent(horn_current);

    // BPMs
    vector<double> xp,yp,xi,yi,iave;
    fTarget.BpmProjection(xp,yp,xi,yi);
    for (size_t ind=0; ind<xi.size(); ++ind)
	iave.push_back(0.5*(xi[ind]+yi[ind]));
    spill->SetBPM(xp,yp,iave);
    bits.n_batches = xi.size();

    // profile monitors
    double x=0,y=0,w=0,h=0;
    fTarget.ProfileProjection(x,y,w,h);
    spill->SetProfile(x,y,w,h);

    // Determine which time to use to get the target position
    double tgttime=vme;
    if (tgttime==0) tgttime=dae;

    bool is_in = false;
    double location = -9999;
    BDTarget::BeamType beam_type = fTarget.TargetIn(is_in,location,tgttime);
    bits.target_in = is_in ? 1 : 0;
    if (is_in) {
	switch (beam_type) {
	case BDTarget::kLE: bits.beam_type = 1; break;
	case BDTarget::kME: bits.beam_type = 2; break;
	case BDTarget::kHE: bits.beam_type = 3; break;
	case BDTarget::kPsME: bits.beam_type = 4; break;
	case BDTarget::kPsHE: bits.beam_type = 5; break;
	case BDTarget::kUnknown: default:
	    MSG("BDD",Msg::kDebug)
		<< "Unknown beam type, distance = " << location << endl;
	    bits.beam_type = 0;
	    break;
	}
    }
    else {
	MSG("BDD",Msg::kWarning)
	    << "Target not in!\n";
    }
	

    bits.pedestal = BDSwicPeds::IsPedSpill(rbmb);
    if (bits.pedestal) MSG("BDD",Msg::kDebug) << "Got ped spill\n";

    bits.pm121_in = is_pm_there(rbmb,"121");
    bits.pmtgt_in = is_pm_there(rbmb,"TGT");

    if(!bits.pm121_in) MSG("BDD",Msg::kDebug) << "No pm121\n";
    if(!bits.pmtgt_in) MSG("BDD",Msg::kDebug) << "No pmtgt\n";

    spill->SetStatusBits(bits);

    spill->SetHadMuInt(fHadMu[0]->GetTotalCharge(),
		       fHadMu[1]->GetTotalCharge(),
		       fHadMu[2]->GetTotalCharge(),
		       fHadMu[3]->GetTotalCharge());


    fSpills.push_back(spill);

    BeamMonSpill::StatusBits sb = spill->GetStatusBits();

    MSG("BDD",Msg::kVerbose)
	<< " horn_on:" << sb.horn_on
	<< " target_in:" << sb.target_in
	<< " beam_type:" << sb.beam_type
	<< " pedestal:" << sb.pedestal
	<< " pm121_in:" << sb.pm121_in
	<< " pmtgt_in:" << sb.pmtgt_in
	<< endl;

    MSG("BDD",Msg::kVerbose) << fSpills.size() << " spills\n";

    this->DBU();
}

void BMSpillFiller::DBU(bool all)
{
    // 1. Decide if there are enough spills in a range, o.w. return
    // 2. Write them to DBI
    // 3. Remove from list
    // 4. Call recursively

    size_t nspills = fSpills.size();

    if (!nspills) {
	MSG("BDD",Msg::kDebug) << "no spills to send to DB\n";
	return;
    }

    // Not yet collected enough spills to even consider being full and
    // haven't been asked to unconditionally write everything.
    if (!all && nspills < fSpillsPerWrite) {
	MSG("BDD",Msg::kVerbose)
	    << "dump all not requested and not enough spills ("
	    << nspills << " < " << fSpillsPerWrite << endl;
	return;
    }

    if (nspills > fSpillsPerWrite) nspills = fSpillsPerWrite;

    // Find the spill that is just outside of one spill block length
    // away from the first spill.
    deque<BeamMonSpill*>::iterator it1=fSpills.begin();
    deque<BeamMonSpill*>::iterator done = it1 + nspills;
    deque<BeamMonSpill*>::iterator it2 = done;
    --it2;
    
    // Initial vld range includes a padding around the first/last spills
    VldTimeStamp beg = (*it1)->SpillTime();
    VldTimeStamp end = (*it2)->SpillTime();

    // truncate to integral seconds (DBI does it anyways)
    beg = VldTimeStamp(beg.GetSec());
    // add 1 second to catch last spill
    end = VldTimeStamp(end.GetSec()+1);

    VldRange range(Detector::kNear|Detector::kFar,SimFlag::kData,
		   beg,end,"Beam");
    VldTimeStamp now;
    DbiWriter<BeamMonSpill> writer(range,-1,0,now);
    
    size_t count = 0;
    for (deque<BeamMonSpill*>::iterator it=it1; it != done; ++it) {
	writer << **it;
	delete *it;
	++count;
    }

    MSG("BDD",Msg::kDebug) << " wrote " << count
			  <<" for range: " << range << endl;


    ++it2;
    fSpills.erase(it1,it2);

    writer.Close();

    // iterate if necessary.
    this->DBU(all);
}
