#include "UtsusemiReductionCommon.hh"
//////////////////////////////////////////////////////////
UtsusemiReductionCommon::
UtsusemiReductionCommon()
{
    Initialize();
}
//////////////////////////////////////////////////////////
UtsusemiReductionCommon::
UtsusemiReductionCommon(ElementContainerMatrix *ecm)
{
    Initialize();
    SetTarget(ecm);
}
//////////////////////////////////////////////////////////
UtsusemiReductionCommon::
~UtsusemiReductionCommon()
{
    delete st;
}
const std::string UtsusemiReductionCommon::_MessageTag="UtsusemiReductionCommon::";
//////////////////////////////////////////////////////////
void UtsusemiReductionCommon::
Initialize(){
    SetPositionDeviationForCalcPhi(0.0,0.0,0.0);
    st = new StringTools();
}
//////////////////////////////////////////////////////////
void UtsusemiReductionCommon::
SetPositionDeviationForCalcPhi(double d_px, double d_py, double d_pz){
    CalcPhi_delta_px = fabs(d_px)/2.0;
    CalcPhi_delta_py = fabs(d_py)/2.0;
    CalcPhi_delta_pz = fabs(d_pz)/2.0;
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionCommon::
CalcPhi(ElementContainerMatrix *ecm, double d_px, double d_py, double d_pz){
    std::string process_key=_MessageTag+"CalcPhi";

    if (UCP.CheckProcess( ecm, process_key )) return true;

    SetPositionDeviationForCalcPhi(d_px, d_py, d_pz);
    if ((CalcPhi_delta_px==0.0)&&(CalcPhi_delta_py==0.0)&&(CalcPhi_delta_pz==0.0)){
        if (ecm->PutHeaderPointer()->CheckKey( UTSUSEMI_KEY_HEAD_TYPICALPIXELSIZE )==0){
            std::string msg = _MessageTag+"There is no information of pixel size in given data.";
            UtsusemiError(msg);
            return false;
        }else{
            std::vector<Double> ps = ecm->PutHeaderPointer()->PutDoubleVector(UTSUSEMI_KEY_HEAD_TYPICALPIXELSIZE);
            CalcPhi_delta_px = ps[0]/2.0;
            CalcPhi_delta_py = ps[1]/2.0;
            CalcPhi_delta_pz = ps[2]/2.0;
        }
    }

    for (UInt4 psd=0;psd<(ecm->PutTableSize());psd++){
        ElementContainerArray* eca = ecm->PutPointer(psd);
        for (UInt4 pixel=0;pixel<(eca->PutTableSize());pixel++){
            std::vector<Double> phi_pol(2,0.0);
            std::vector<Double> phi_azim(2,0.0);
            ElementContainer* ec = eca->PutPointer(pixel);
            if (ec->PutHeaderPointer()->CheckKey(UTSUSEMI_KEY_HEAD_PIXELPOSITION)==0){
                std::string msg = _MessageTag+"There is no PixelPosition in ElementContainer of Pixel-ID= ";
                msg+=st->Int4ToString(ec->PutHeaderPointer()->PutInt4(UTSUSEMI_KEY_HEAD_PIXELID))+".";
                UtsusemiError( msg );
                continue;
            }
            std::vector<Double> pposi = ec->PutHeaderPointer()->PutDoubleVector(UTSUSEMI_KEY_HEAD_PIXELPOSITION);
            Double L2 = sqrt( pposi[0]*pposi[0] + pposi[1]*pposi[1] + pposi[2]*pposi[2] );
            // Bugfix 100427
            phi_pol[0] = 180.0/M_PI*acos( pposi[2]/L2 );
            if ((d_px>=0.0)&&(d_py>=0.0)&&(d_pz>=0.0)){
                Double err = sqrt( (CalcPhi_delta_pz*CalcPhi_delta_pz + (pposi[2]*pposi[2]/(L2*L2))*(CalcPhi_delta_px*CalcPhi_delta_px+CalcPhi_delta_py*CalcPhi_delta_py+CalcPhi_delta_pz*CalcPhi_delta_pz) ))/L2/sqrt( pow( sin( phi_pol[0]/180.0*M_PI ),2.0 ) );
                phi_pol[1] = err/M_PI*180.0;
            }
            Double LL = pposi[0]*pposi[0]+pposi[1]*pposi[1];
            if (pposi[1]==0.0){
                if (pposi[0]>=0) phi_azim[0] = 0.0;
                else phi_azim[0] = 180.0;
            }else{
                phi_azim[0] = 180.0/M_PI*acos( pposi[0]/sqrt(LL) )*pposi[1]/sqrt( pposi[1]*pposi[1] );
            }
            //err = ( pow((1.0-pposi[0]*pposi[0]/LL),2.0) * pow(delta_px,2.0) + pow((2.0*pposi[0]*pposi[1]/LL),2.0)*pow(delta_py,2.0))/LL/sqrt( pow( sin( phi_azim[0] ),2.0 ) );
            if ((d_px>=0.0)&&(d_py>=0.0)&&(d_pz>=0.0)){
                Double err = sqrt( ( pow( (pposi[1]*pposi[1]*CalcPhi_delta_px),2.0 ) + pow( (2*pposi[0]*pposi[1]*CalcPhi_delta_py),2.0 ) ) / pow( sin( phi_azim[0]*M_PI/180.0 ),2.0 ) / pow( (pposi[0]*pposi[0] + pposi[1]*pposi[1]),3.0 ));
                phi_azim[1] = err*180.0/M_PI;
            }

            HeaderBase* h = ec->PutHeaderPointer();
            if (h->CheckKey(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES)==0)
                h->Add(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES,phi_pol);
            else
                h->OverWrite(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES,phi_pol);

            if (h->CheckKey(UTSUSEMI_KEY_HEAD_PIXELAZIMANGLES)==0)
                h->Add(UTSUSEMI_KEY_HEAD_PIXELAZIMANGLES,phi_azim);
            else
                h->OverWrite(UTSUSEMI_KEY_HEAD_PIXELAZIMANGLES,phi_azim);

            if (h->CheckKey(UTSUSEMI_KEY_HEAD_POLARANGLE)==0)
                h->Add(UTSUSEMI_KEY_HEAD_POLARANGLE,phi_pol[0]);
            else
                h->OverWrite(UTSUSEMI_KEY_HEAD_POLARANGLE,phi_pol[0]);

            if (h->CheckKey(UTSUSEMI_KEY_HEAD_AZIMANGLE)==0)
                h->Add(UTSUSEMI_KEY_HEAD_AZIMANGLE,phi_azim[0]);
            else
                h->OverWrite(UTSUSEMI_KEY_HEAD_AZIMANGLE,phi_azim[0]);
        }
    }

    UCP.AddProcess( ecm, process_key );
    return true;
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionCommon::
CalcPhi(double d_px, double d_py, double d_pz){
    ElementContainerMatrix *ecm = Put();
    if (ecm==NULL){
        UtsusemiError(_MessageTag+"CalcPhi >> No data is set.");
        return false;
    }
    return CalcPhi( ecm, d_px, d_py, d_pz );
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionCommon::
NormByBinWidth(ElementContainerMatrix* ecm, std::string key){
    std::string process_key=_MessageTag+"NormByBinWidth";
    HeaderBase* h = ecm->PutHeaderPointer();
    if (UCP.CheckProcess( ecm, process_key )) return true;

    if ((h->CheckKey(UTSUSEMI_KEY_HEAD_ISHISTOGRAM)==1)&&(h->PutInt4(UTSUSEMI_KEY_HEAD_ISHISTOGRAM)==0)) return true;

    // Find enabled ElementContainer
    ElementContainer* ec_enabled=NULL;
    for (UInt4 i=0; i<(ecm->PutSize()); i++){
        if (ecm->PutPointer(i)->PutHeaderPointer()->CheckKey(UTSUSEMI_KEY_HEAD_MASKED)==1){
            if (ecm->PutPointer(i)->PutHeaderPointer()->PutInt4(UTSUSEMI_KEY_HEAD_MASKED)==0){
                for (UInt4 j=0; j<(ecm->PutPointer(i)->PutSize()); j++){
                    if ((ecm->PutPointer(i)->PutPointer(j)->PutHeaderPointer()->CheckKey(UTSUSEMI_KEY_HEAD_MASKED)==1)){
                        if ((ecm->PutPointer(i)->PutPointer(j)->PutHeaderPointer()->PutInt4(UTSUSEMI_KEY_HEAD_MASKED)==0)){
                            ec_enabled=ecm->PutPointer(i)->PutPointer(j);
                            break;
                        }
                    }
                    if (ec_enabled!=NULL) break;
                }
            }
        }
        if (ec_enabled!=NULL) break;
    }
    if (ec_enabled==NULL){
        UtsusemiError( process_key+" >> given data is invalid " );
        return false;
    }

    if (key=="") key=ec_enabled->PutXKey();
    if (key==(ec_enabled->PutXKey())){
        for (UInt4 i=0; i<(ecm->PutSize()); i++){
            for (UInt4 j=0; j<(ecm->PutPointer(i)->PutSize()); j++){
                //ecm->PutPointer(i)->PutPointer(j)->HistToScat();
                // In near future, below commands will be replaced with HistToScat()
                ElementContainer* ec = ecm->PutPointer(i)->PutPointer(j);
                std::vector<Double>* X = ec->PutP( key );
                std::vector<Double>* Y = ec->PutP( ec->PutYKey() );
                std::vector<Double>* E = ec->PutP( ec->PutEKey() );
                if ( (X->size())==(Y->size()+1) ){
                    for (UInt4 k=0; k<(Y->size()); k++){
                        Double bin = fabs( (X->at(k+1)) - (X->at(k)) );
                        Y->at(k) = (Y->at(k))/bin;
                        E->at(k) = (E->at(k))/bin;
                    }
                    ec->PutHeaderPointer()->Add("NormByBinWidth",1); //[inamura 181126]
                }else{
                    UtsusemiError( process_key+" >> given key is invalid (key="+key+")" );
                    return false;
                }
            }
        }
    }else{
        if (ecm->PutPointer(0)->PutPointer(0)->CheckKey( key )==1){
            for (UInt4 i=0; i<(ecm->PutSize()); i++){
                for (UInt4 j=0; j<(ecm->PutPointer(i)->PutSize()); j++){
                    ElementContainer* ec = ecm->PutPointer(i)->PutPointer(j);
                    std::string yKey= ec->PutYKey();
                    std::string eKey= ec->PutEKey();
                    ec->SetKeys( key, yKey, eKey );
                    if (key!=(ec->PutXKey())){
                        UtsusemiError( process_key+" >> given key is invalid (key="+key+")" );
                        return false;
                    }
                    //ec->HistToScat();
                    // In near future, below commands will be replaced with HistToScat()
                    std::vector<Double>* X = ec->PutP( key );
                    std::vector<Double>* Y = ec->PutP( yKey );
                    std::vector<Double>* E = ec->PutP( eKey );
                    if ( (X->size())==(Y->size()+1) ){
                        for (UInt4 k=0; k<(Y->size()); k++){
                            Double bin = fabs( (X->at(k+1)) - (X->at(k)) );
                            Y->at(k) = (Y->at(k))/bin;
                            E->at(k) = (E->at(k))/bin;
                        }
                    }else{
                        UtsusemiError( process_key+" >> given key is invalid (key="+key+")" );
                        return false;
                    }
                }
            }
        }else{
            UtsusemiError( process_key+" >> given key is invalid (key="+key+")" );
            return false;
        }
    }

    process_key += "_with_"+key;

    if (h->CheckKey(UTSUSEMI_KEY_HEAD_ISHISTOGRAM)==0)
        h->Add(UTSUSEMI_KEY_HEAD_ISHISTOGRAM,0);
    else
        h->OverWrite(UTSUSEMI_KEY_HEAD_ISHISTOGRAM,0);

    UCP.AddProcess( ecm, process_key );
    return true;
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionCommon::
NormByBinWidth(std::string key){
    ElementContainerMatrix *ecm = Put();
    if (ecm==NULL){
        UtsusemiError(_MessageTag+"NormByBinWidth >> No data is set.");
        return false;
    }
    return NormByBinWidth( ecm, key );
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionCommon::
NormBySolidAngle( ElementContainerMatrix* ecm, Double L2, Double dS ){
    std::string process_key=_MessageTag+"NormBySolidAngle";
    if (UCP.CheckProcess( ecm, process_key )) return true;

    std::string process_key_required=_MessageTag+"CalcPhi";
    if (UCP.CheckProcess( ecm, process_key_required )){
    }else{
        UtsusemiError( process_key+" >> CalcPhi must be done before NormBySolidAngle." );
        return false;
    }

    HeaderBase* hh = ecm->PutHeaderPointer();
    if (L2==0.0){
        if (hh->CheckKey(UTSUSEMI_KEY_HEAD_TYPICALL2)==1)
            L2 = hh->PutDouble(UTSUSEMI_KEY_HEAD_TYPICALL2);
        else{
            UtsusemiError( process_key+" >> Typical L2 is not defined." );
            return false;
        }
    }

    if (dS==0.0){
        if (hh->CheckKey(UTSUSEMI_KEY_HEAD_TYPICALDS)==1)
            dS = hh->PutDouble(UTSUSEMI_KEY_HEAD_TYPICALDS);
        else{
            UtsusemiError( process_key+" >> Typical dS is not defined." );
            return false;
        }
    }

    Double dOmega = dS/(L2*L2);

    for (UInt4 i=0; i<(ecm->PutTableSize()); i++){
        HeaderBase* hh_eca = ecm->PutPointer(i)->PutHeaderPointer();
        if (hh_eca->CheckKey(UTSUSEMI_KEY_HEAD_DETTYPE)==1){
            if (hh_eca->PutString(UTSUSEMI_KEY_HEAD_DETTYPE)==UTSUSEMI_KEY_HEAD_DETTYPE_PSD){
                for (UInt4 j=0; j<( ecm->PutPointer(i)->PutSize() ); j++){
                    ElementContainer* ec = ecm->PutPointer(i)->PutPointer(j);
                    Double solid_angle = ec->PutHeaderPointer()->PutDouble(UTSUSEMI_KEY_HEAD_PIXELSOLIDANGLE);
                    solid_angle = solid_angle/dOmega;
                    if (solid_angle==0.0){
                        UtsusemiWarning(process_key+" >> Solid angle = 0 at "+st->UInt4ToString(i)+","+st->UInt4ToString(j) );
                    }else{
                        ec->MulMySelf(1.0/solid_angle);
                    }
                }
            }
        }
    }

    UCP.AddProcess( ecm, process_key );
    return true;
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionCommon::
NormBySolidAngle( Double L2, Double dS ){
    ElementContainerMatrix *ecm = Put();
    if (ecm==NULL){
        UtsusemiError(_MessageTag+"NormBySolidAngle >> No data is set.");
        return false;
    }
    return NormBySolidAngle( ecm, L2, dS );
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionCommon::
SolidAngleCorrect( ElementContainerMatrix *ecm ){
    if (ecm==NULL) return NormBySolidAngle();
    else return NormBySolidAngle(ecm);
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionCommon::
NormByKickers( ElementContainerMatrix *ecm, Double normFactor ){
    std::string process_key=_MessageTag+"NormByKickers";

    if (ecm==NULL) ecm = Put();
    if (ecm==NULL){
        UtsusemiError(process_key+" >> No data is set.");
        return false;
    }
    if (UCP.CheckProcess( ecm, process_key )) return true;

    HeaderBase* h=ecm->PutHeaderPointer();
    if (h->CheckKey(UTSUSEMI_KEY_HEAD_KICKERCOUNT)==0){
        UtsusemiError(process_key+" >> No KICKERCOUNT is set in the header of data.");
        return false;
    }

    Double cnts = (Double)( h->PutInt4(UTSUSEMI_KEY_HEAD_KICKERCOUNT) );
    if (cnts!=0.){
      if (normFactor<0.0) ecm->MulMySelf( 1.0 / fabs( normFactor ) );
      else ecm->MulMySelf( 1.0/ cnts * normFactor );
    }
    return true;
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionCommon::
NormByKickers( Double normFactor ){
  return NormByKickers( NULL, normFactor );
}
