////////////////////////////////////////////////////////////////////////
// CalScheme
//
// Abstract base class for mid-level calibrator class. 
//
// n.tagg1@physics.ox.ac.uk  N Tagg 2004, 
//
//
//
// $Id: CalScheme.cxx,v 1.9 2005/03/22 12:22:14 tagg Exp $
////////////////////////////////////////////////////////////////////////

#include "CalScheme.h"
#include "MessageService/MsgService.h"
#include <cassert>
#include <iostream>

CVSID( " $Id: CalScheme.cxx,v 1.9 2005/03/22 12:22:14 tagg Exp $ ");
ClassImp(CalScheme)

Int_t CalScheme::fsCalls[kNumberOfSchemeTypes]= {0};
Int_t CalScheme::fsErrors[kNumberOfSchemeTypes][kNumberOfErrorTypes] = {{0},{0}};
std::map<GenericThingId,int> CalScheme::fsChannelErrors;

std::ostream& operator<<(std::ostream& os, const CalScheme& s)
{ 
  s.PrintConfig(os);
  return os;
}

CalScheme::CalScheme() : fContext()
{
}

CalScheme::~CalScheme()
{
}

/////////////////////////////////////////////  
// Calibration methods
/////////////////////////////////////////////

DoubleErr CalScheme::GetCalibratedTime(DoubleErr ,   FloatErr,    const PlexStripEndId& ) const
{
  /// 
  /// Purpose: Apply time calibration
  ///
  /// In: raw time in seconds
  ///     raw charge on stripend
  ///     strip end
  ///
  /// Out: calibrated time.
  ///
  /// Must be implimented by: TimeCalibrator
  ///
 Unimplimented(); 
 return 0; 
}

FloatErr  CalScheme::GetPhotoElectrons(FloatErr, const PlexStripEndId& ) const
{
  /// 
  /// Purpose: Apply PE calibration
  ///
  /// In: raw adc
  ///     strip end
  ///
  /// Out: number of pes.
  ///
  /// Must be implimented by: PeCalibrator
  ///
 
 Unimplimented(); return 0; 
}

FloatErr  CalScheme::GetLinearizedVA(FloatErr, const RawChannelId& ) const
{
  /// 
  /// Purpose: Linearize a VA channel to charge injection data.
  ///          Usually applies only to pin diodes
  ///
  /// In: raw adc
  ///     raw channel ID
  ///
  /// Out: number of pes.
  ///
  /// Must be implimented by: VALinCalibrator
  ///
 
 Unimplimented(); return 0; 
}


FloatErr  CalScheme::GetDriftCorrected(FloatErr, const PlexStripEndId& ) const
{
  /// 
  /// Purpose: Apply drift correction
  ///
  /// In: raw adc
  ///     strip end
  ///
  /// Out: drift-corrected adcs
  ///
  /// Must be implimented by: DriftCalibrator
  ///
 Unimplimented(); return 0; 
}

FloatErr  CalScheme::GetLinearized(FloatErr, const PlexStripEndId& ) const
{
  /// 
  /// Purpose: Apply linearity correction
  ///
  /// In: drifted-corrected or raw ADC,
  ///     strip end
  ///
  /// Out: linearized adc (siglin)
  ///
  /// Must be implimented by: LinCalibrator
  ///
 Unimplimented(); return 0; 
}

FloatErr  CalScheme::GetStripToStripCorrected(FloatErr, const PlexStripEndId& ) const
{
  /// 
  /// Purpose: Apply strip-to-strip correction 
  /// i.e. scintillator light output, PMT gain, and light loss
  /// By default, assumes hit is in center of strip.
  ///
  ///
  /// In: linearized or raw adc (siglin)
  ///     strip end
  ///
  /// Out: corrected adc (sigcorr)
  ///
  /// Must be implimented by: StripCalibrator
  ///
 Unimplimented(); return 0; 
}

FloatErr  CalScheme::GetAttenCorrected(FloatErr, FloatErr, const PlexStripEndId& ) const
{
  /// 
  /// Purpose: Correct for attenuation along strip.
  ///
  /// In: sigcorr, 
  ///     position along strip (in strip coordinates: 0 is center of strip.)
  ///     strip end
  ///
  /// Out: position-corrected charge (SigMap)
  ///  
  /// Must be implimented by: AttenCalibrator
  ///  

 Unimplimented(); return 0; 
}

FloatErr  CalScheme::GetMIP(FloatErr,                        const PlexStripEndId&) const
{
   /// 
  /// Purpose: Convert from corrected charge to energy units.
  /// MIPs are usually defined as the the light seen from a muon going in the Z-direction
  /// through the center of the strip (i.e. ~2 MeV of muon energy)
  ///
  /// In: sigcorr or sigmap
  ///     Strip end (May be ignored, depending upon implimentation)
  ///
  /// Out: energy in MIPs
  ///  
  /// Must be implimented by: MIPCalibrator
  /// 
 Unimplimented(); return 0; 
}

/////////////////////////////////////////////  
// Decalibration methods
/////////////////////////////////////////////


DoubleErr CalScheme::DecalTime(DoubleErr , FloatErr ,    const PlexStripEndId& ) const
{
  ///
  /// Inverse-calibration for use by Monte-Carlo 
  ///
  /// Purpose: Go from calibrated time to raw time
  ///
  /// Input: True time
  ///        Raw charge on strip end
  ///        Strip end
  ///
  /// Output: Raw, uncalibrated time.
  ///
  /// Must be implimented by: TimeCalibrator
  ///
  Unimplimented(); return 0; 
}

void CalScheme::DecalGainAndWidth(FloatErr& , FloatErr& , const PlexStripEndId& ) const
{
  ///
  /// Inverse-calibration for use by Monte-Carlo 
  ///
  /// Purpose: Get PMT gain and width of single PE response
  ///
  /// Input: Strip end
  ///
  /// Output: Gain and and width of 1-pe response in ADCs
  ///
  /// Must be implimented by: PeCalibrator
  ///
  Unimplimented(); return; 
}

void CalScheme::DecalGainAndWidth(FloatErr& , FloatErr& , const PlexPixelSpotId& ) const
{
  ///
  /// Inverse-calibration for use by Monte-Carlo 
  ///
  /// Purpose: Get PMT gain and width of single PE response
  ///
  /// Input: Pixel spot
  ///
  /// Output: Gain and and width of 1-pe response in ADCs
  ///         Should be slightly more clever than the strip-end based
  ///         one.. i.e. it should make guesses.
  ///
  /// Must be implimented by: PeCalibrator
  ///
  Unimplimented(); return; 
}

FloatErr CalScheme::DecalVALinearity(FloatErr , const RawChannelId& ) const
{
  ///
  /// Inverse-calibration for use by Monte-Carlo 
  ///
  /// Purpose: Apply VA nonlinearity 
  ///
  /// Input: linear charge
  ///        channel ID
  ///
  /// Output: nonlinear charge
  ///
  /// Must be implimented by: VALinCalibrator
  ///
  Unimplimented(); return 0; 
}

FloatErr CalScheme::DecalDrift(FloatErr ,                    const PlexStripEndId& ) const
{
  ///
  /// Inverse-calibration for use by Monte-Carlo 
  ///
  /// Purpose: Get gain drift
  ///
  /// Input: True gain of phototube in PEs,
  ///        Strip end
  ///
  /// Output: Drifted gain of phototube
  ///
  /// Must be implimented by: DriftCalibrator
  ///
 Unimplimented(); return 0; 
}

FloatErr CalScheme::DecalLinearity(FloatErr ,                      const PlexStripEndId& ) const
{
  ///
  /// Inverse-calibration for use by Monte-Carlo 
  ///
  /// Purpose: Apply nonlinearity function.
  ///
  /// Input: Linear charge expected from a perfect phototube,
  ///        Strip end
  ///
  /// Output: Realistic charge after PMT and electronics nonlinearity
  ///
  /// Must be implimented by: LinCalibrator
  ///
 
  Unimplimented(); return 0; 
}

FloatErr CalScheme::DecalStripToStrip(FloatErr ,               const PlexStripEndId& ) const
{
  ///
  /// Inverse-calibration for use by Monte-Carlo 
  ///
  /// Purpose: Get strip-to-strip variation
  ///
  /// Input: Charge expected from a perfect strip with a perfect phototube
  ///        Strip end
  ///
  /// Output: Charge expected from this strip, including PMT gain
  ///
  /// Must be implimented by: StripCalibrator
  ///

 Unimplimented(); return 0; 
}

FloatErr CalScheme::DecalAttenCorrected(FloatErr , FloatErr,  const PlexStripEndId& ) const
{
  ///
  /// Inverse-calibration for use by Monte-Carlo 
  ///
  /// Purpose: Get atttenuation correction
  ///
  /// Input: Light expected if energy deposited in middle of strip
  ///        True position of hit along strip
  ///        Strip end
  ///
  /// Output: Light seen at end of WLS pigtail
  ///
  /// Must be implimented by: AttenCalibrator
  ///
  
 Unimplimented(); return 0; 
}

FloatErr CalScheme::DecalMIP(FloatErr ,                         const PlexStripEndId& ) const
{
  ///
  /// Inverse-calibration for use by Monte-Carlo 
  ///
  /// Purpose: Get atttenuation correction
  ///
  /// Input: True energy of hit in MIPs
  ///        Strip end (may be ignored, depending on implimentation)
  ///
  /// Output: SigMaps that this hit will result in.
  ///
  /// Must be implimented by: MIPCalibrator
  ///
  
 Unimplimented(); return 0; 
}

//////////////////////////////////////////////////////////////////////
// Other.
//////////////////////////////////////////////////////////////////////
Float_t CalScheme::GetTemperature(Int_t ) const
{
  ///
  /// Request temperature at current vld context
  ///
  /// Purpose: Get temperature
  ///
  /// Input: User-defined.
  ///
  /// Output: Temperature in degrees Celcius
  ///
  /// Must be implimented by: Thermometer
  ///
  Unimplimented(); return 0;
}



// Override this method to do context reshuffle.
void CalScheme::Reset( const VldContext& context, Bool_t force)
{
  ///
  /// Called by the user to indicate that the current event
  /// context has changed.
  /// 
  /// This routine calls this->DoReset(), 
  /// which performs the user tasks for the current implimentation.

  // Only perform actions if this context is a ligit one. Otherwise, don't bother
  // spamming with DB messages.
  if(context.IsValid()) {

    // Only perform actions if the context changes. Otherwise, don't
    // bother.
    if( (fContext!=context) || (force) ) {
      MSG("Calib",Msg::kVerbose) << "Resetting " << this->GetName() << "  " << context.AsString() << endl;
      fContext = context;
      this->DoReset(context);
    }
  }
}



void  CalScheme::Unimplimented() const
{
  ///
  /// A short routine that warns the user that an improper scheme has been implimented.
  /// Then crashes, just to show 'em.
  ///
  MSG("Calib",Msg::kFatal) << this->GetName() << " called with unimplimented function. " << endl
			   << "Calibrator is not set up correctly!" << endl;
  assert(0);
}


void CalScheme::ResetStatistics()
{
  ///
  /// Sets all error count statistics to zero.
  /// 
  /// Usually called by Calibrator c'tor at start of job.
  ///
  for(int i=0;i<kNumberOfSchemeTypes;i++) {
    fsCalls[i] = 0;
    for(int j=0;j<kNumberOfErrorTypes;j++)
      fsErrors[i][j] = 0;
  }
  fsChannelErrors.clear();
}

void CalScheme::IncrementErrors(SchemeType_t i, 
				ErrorType_t j)
{
  ///
  /// Increment an error count.
  ///

  // Mod the values by the max value to prevent buffer overflows.

  fsErrors[i % kNumberOfSchemeTypes][j % kNumberOfErrorTypes]++;
}

void CalScheme::IncrementErrors(SchemeType_t i, 
				ErrorType_t j,
				const GenericThingId& id)
{
  ///
  /// Increment an error count.
  ///

  // Mod the values by the max value to prevent buffer overflows.
  IncrementErrors(i,j);
  fsChannelErrors[id]++;
}


void CalScheme::IncrementErrors(SchemeType_t i, ErrorType_t j,
				Int_t c)
{
  IncrementErrors(i,j,GenericThingId(c));
}

void CalScheme::IncrementErrors(SchemeType_t i, ErrorType_t j,
				const PlexStripEndId& c)
{
  IncrementErrors(i,j,GenericThingId(c));
}

void CalScheme::IncrementErrors(SchemeType_t i, ErrorType_t j,
				const RawChannelId& c)
{
  IncrementErrors(i,j,GenericThingId(c));
}

void CalScheme::IncrementErrors(SchemeType_t i, ErrorType_t j,
				const PlexPixelSpotId& c)
{
  IncrementErrors(i,j,GenericThingId(c));
}

void CalScheme::IncrementErrors(SchemeType_t i, ErrorType_t j,
				const PlexLedId& c)
{
  IncrementErrors(i,j,GenericThingId(c));
}


void CalScheme::IncrementCalls(SchemeType_t i)				
{
  ///
  /// Increment count of number of calls.
  ///

  // Mod by the max value to prevent buffer overflows.
  fsCalls[i % kNumberOfSchemeTypes]++;
}



void CalScheme::PrintErrorStats( std::ostream& os ) const
{
  os << "Calibrator Errors Statistics:" << endl;
  os << Form("%20s %10s ||%10s|%10s|%10s|%10s|%10s|%10s\n",
	     "Calibrator","Calls","General","Miss Table","Miss Row","FP Error","Insuf.Data","Bad Input");
  for(int type = 0; type < kNumberOfSchemeTypes; type ++) {
    os << Form("%20s %10d || %8d | %8d | %8d | %8d | %8d | %8d\n",
	       SchemeTypeName((SchemeType_t)type),
	       fsCalls[type], 
	       fsErrors[type][kGeneralErr],
	       fsErrors[type][kMissingTable],
	       fsErrors[type][kMissingRow],
	       fsErrors[type][kFPE],
	       fsErrors[type][kDataInsufficient],
	       fsErrors[type][kBadInput]);
  }
  // Print out channel errors.
  // This bit of arcane code basically does an
  // insertion sort on the number of errors generated
  // in order to figure out which channels have generated
  // the most errors.

  os << "Channels with the most errors: " << endl;
  std::multimap<int,GenericThingId> sortedChannels;
  std::map<GenericThingId,int>::iterator it;
  for(it = fsChannelErrors.begin(); it!= fsChannelErrors.end(); it++) {
    sortedChannels.insert(std::pair<int,GenericThingId>(-(it->second),it->first));
  }

  std::multimap<int,GenericThingId>::iterator it2;
  it2 = sortedChannels.begin();
  int i=0;
  while((i<10) && (it2!=sortedChannels.end())) {
    os << "Errors: " << Form("%8d",-(it2->first)) << "  " << (it2->second).AsString() << endl;
    it2++; i++;
  }
}


// Override this to print out your configuration.
// Default does something sensible, but not very good.
void  CalScheme::PrintConfig(std::ostream &os) const
{
  ///
  /// Prints out the current configuration status
  /// Should be overridden by implimentation.
  ///
  os << "Generic CalScheme PrintConfig()" <<endl;
  const Registry& r = this->GetConfig();
  r.PrettyPrint(os);
}


// I/O wrappers.
void CalScheme::PrintConfig( MsgStream& ms ) const
{
  stringstream oss;
  PrintConfig(oss);
  ms << oss.str();
}

void CalScheme::PrintConfig() const
{
  PrintConfig(*(MsgService::Instance()->GetStream("Calib")));
}


void CalScheme::PrintErrorStats( MsgStream& ms ) const
{
  stringstream oss;
  PrintErrorStats(oss);
  ms << oss.str();
}

void CalScheme::PrintErrorStats() const
{
  PrintErrorStats(*(MsgService::Instance()->GetStream("Calib")));
}

