#include "UtsusemiReductionInEla.hh"
//////////////////////////////////////////////////////////
UtsusemiReductionInEla::
UtsusemiReductionInEla()
{
}

//////////////////////////////////////////////////////////
UtsusemiReductionInEla::
UtsusemiReductionInEla(ElementContainerMatrix *ecm)
{
    SetTarget(ecm);
}

//////////////////////////////////////////////////////////
UtsusemiReductionInEla::
~UtsusemiReductionInEla()
{
}
const std::string UtsusemiReductionInEla::_MessageTag="UtsusemiReductionInEla::";
//////////////////////////////////////////////////////////
bool UtsusemiReductionInEla::
KiKfCorrect(ElementContainerMatrix* ecm){
    if (ecm==NULL){
        ecm = Put();
        if (ecm==NULL){
            UtsusemiError(_MessageTag+"KiKfCorrect >> No data is set.");
            return false;
        }
    }
    std::string process_key=_MessageTag+"::KiKfCorrection";
    if (UCP.CheckProcess( ecm, process_key )) return true;

    HeaderBase* h = ecm->PutHeaderPointer();
    HeaderBase* ech = NULL;
    bool isDirectGeometry = true;
    Double Ei = 0.0;
    if ((ecm->PutSize()!=0)&&(ecm->PutPointer(0)->PutSize()!=0)){
        ech = ecm->PutPointer(0)->PutPointer(0)->PutHeaderPointer();
        if (ech->CheckKey(UTSUSEMI_KEY_HEAD_EF)==1)
            isDirectGeometry = false;
    }
    if (isDirectGeometry){
        if (h->CheckKey(UTSUSEMI_KEY_HEAD_EI)==0){
            UtsusemiError("KiKfCorrection >> Ei is not set in ElementContainerMatrix.");
            return false;
        }
        Ei = h->PutDouble(UTSUSEMI_KEY_HEAD_EI);
    }

    UInt4 num_of_psd = ecm->PutTableSize();
    for (UInt4 psd=0;psd<num_of_psd;psd++){
        ElementContainerArray* eca = ecm->PutPointer(psd);
        UInt4 num_of_pixel = eca->PutTableSize();
        if (isDirectGeometry){
            for (UInt4 pixel=0;pixel<num_of_pixel;pixel++){
                ElementContainer* ec = eca->PutPointer(pixel);
                std::string xkey = ec->PutXKey();
                std::string ykey = ec->PutYKey();
                std::string ekey = ec->PutEKey();
                std::vector<Double> hw = ec->PutX();
                std::vector<Double> intensity = ec->PutY();
                std::vector<Double> error = ec->PutE();
                for (UInt4 i=0;i<(hw.size()-1);i++){
                    Double ki_over_kf = sqrt( Ei/(Ei - (hw[i+1]+hw[i])/2.0) );
                    intensity[i]=intensity[i]*ki_over_kf;
                    error[i]=error[i]*ki_over_kf;
                }
                ec->Remove(ykey);
                ec->Remove(ekey);
                ec->Add(ykey,intensity);
                ec->Add(ekey,error);
                ec->SetKeys(xkey,ykey,ekey);
            }
        }else{
            for (UInt4 pixel=0;pixel<num_of_pixel;pixel++){
                ElementContainer* ec = eca->PutPointer(pixel);
                ech = ec->PutHeaderPointer();
                Double Ef = ech->PutDouble(UTSUSEMI_KEY_HEAD_EF);
                std::string xkey = ec->PutXKey();
                std::string ykey = ec->PutYKey();
                std::string ekey = ec->PutEKey();
                std::vector<Double> hw = ec->PutX();
                std::vector<Double> intensity = ec->PutY();
                std::vector<Double> error = ec->PutE();
                for (UInt4 i=0;i<(hw.size()-1);i++){
                    //ki_over_kf = sqrt( Ei/(Ei - (hw[i+1]+hw[i])/2.0) );
                    Double ki_over_kf = sqrt( (Ef + (hw[i+1]+hw[i])/2.0)/Ef );
                    intensity[i]=intensity[i]*ki_over_kf;
                    error[i]=error[i]*ki_over_kf;
                }
                ec->Remove(ykey);
                ec->Remove(ekey);
                ec->Add(ykey,intensity);
                ec->Add(ekey,error);
                ec->SetKeys(xkey,ykey,ekey);
            }
        }
    }

    UCP.AddProcess( ecm, process_key );
    return true;

}
//////////////////////////////////////////////////////////
bool UtsusemiReductionInEla::
dHWCorrect(ElementContainerMatrix* ecm){
    if (ecm==NULL) return NormByBinWidth( UTSUSEMI_KEY_ENERGY );
    else return NormByBinWidth(ecm,UTSUSEMI_KEY_ENERGY);
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionInEla::
ToPowder( ElementContainerMatrix* ret_ecm, Double deltaQ, Double startQ, Double endQ ){
    return ToPowder( NULL, ret_ecm, deltaQ, startQ, endQ );
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionInEla::
ToPowder( ElementContainerMatrix* ecm, ElementContainerMatrix* ret_ecm, Double deltaQ, Double startQ, Double endQ ){
    std::string process_key = _MessageTag+"ToPowder";
    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* hh = ecm->PutHeaderPointer();
    Double _Ei = 0.0;
    if (hh->CheckKey(UTSUSEMI_KEY_HEAD_EI)==1){
        _Ei = hh->PutDouble(UTSUSEMI_KEY_HEAD_EI);
    }else{
        UtsusemiError(process_key+" >> No Ei value is set in the header of data.");
        return false;
    }

    if (deltaQ<=0.0){
        UtsusemiError(process_key+" >> Invalid parameter of deltaQ.");
        return false;
    }

    UtsusemiUnitConverter ucc;

    UInt4 numQ = 0;
    std::vector<Double> hw_vec;
    hw_vec.clear();

    if ((startQ>=0.0)&&(endQ>=0.0)&&(endQ>startQ)){
        numQ = (UInt4)( floor( (endQ - startQ)/deltaQ ) );
    }else{
        SearchInHeader sih( ecm );

        std::vector<Double> lim_theta = sih.FindLimitElementDoubleVector(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES,0);
        Double min_theta = lim_theta[0]*M_PI/180.0;
        Double max_theta = lim_theta[1]*M_PI/180.0;
        for (UInt4 i=0;i<ecm->PutTableSize();i++){
            HeaderBase* eca_h = ecm->PutPointer(i)->PutHeaderPointer();
            if ((eca_h->CheckKey(UTSUSEMI_KEY_HEAD_DETTYPE)==0)
                ||((eca_h->CheckKey(UTSUSEMI_KEY_HEAD_DETTYPE)==1)&&(eca_h->PutString(UTSUSEMI_KEY_HEAD_DETTYPE)!=UTSUSEMI_KEY_HEAD_DETTYPE_MONITOR))){
              hw_vec = ecm->PutPointer(i)->PutPointer(0)->PutX();
              break;
            }
        }
        if (hw_vec.size()!=0) {
            Double ki2 = ucc.EtoK2( _Ei );
            Double minQ = sqrt( 2.0*ki2*( 1.0 - cos( min_theta ) ) );
            Double maxQ = sqrt( 2.0*ki2*( 1.0 - cos( max_theta ) ) );
            /*
            for (UInt4 i=0;i<hw_vec.size();i++){
                Double kf2 = ucc.EtoK2( _Ei - hw_vec[i] );
                Double tmin = sqrt( ki2 + kf2 - 2.0*sqrt(ki2*kf2)*cos( min_theta ) );
                Double tmax = sqrt( ki2 + kf2 - 2.0*sqrt(ki2*kf2)*cos( max_theta ) );
                if (tmin<minQ){
                    minQ = tmin;
                }
                if (tmax>maxQ){
                    maxQ = tmax;
                }
            }
            */

            startQ = deltaQ/2.0;
            while( (startQ+deltaQ)<minQ ){
                startQ += deltaQ;
            }
            numQ = 0;
            while( (startQ+deltaQ*(Double)(numQ))<maxQ ){
                numQ += 1;
            }
        }else{
            UtsusemiError(process_key+" >> No hw vector is found.");
            return false;
        }
    }

    Double theta = 0.0;
    std::vector<Double> thetaBinQConst;

    for (UInt4 i=0;i<numQ;i++){
        Double tQ = startQ + (Double)(i)*deltaQ;
        Double alpha = 1.0 - tQ*tQ/ucc.EtoK2( _Ei )/2.0;
        if ( (alpha>=-1.0) && (alpha<=1.0) ){
            theta = acos( alpha )/M_PI*180.0;  //[rad]->[degree]
            thetaBinQConst.push_back( theta );
        }else{
        }
    }

    #ifdef _OPENMP
    #pragma omp parallel for

    #if (_OPENMP >= 200805)  // OpenMP 3.0 and later
    for (UInt4 i=0;i<ecm->PutTableSize();i++){
    #else
    for (Int4 i=0;i<(Int4)(ecm->PutTableSize());i++){
    #endif

    #else
    for (UInt4 i=0;i<ecm->PutTableSize();i++){
    #endif  // #ifdef _OPENMP
        ElementContainerArray* eca = ecm->PutPointer(i);
        for (UInt4 j=0;j<eca->PutTableSize();j++){
            ElementContainer* ec = eca->PutPointer(j);
            HeaderBase* hh = ec->PutHeaderPointer();
            std::vector<Double> p_vec = hh->PutDoubleVector(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES);
            if (hh->CheckKey(UTSUSEMI_KEY_HEAD_POLARANGLE)==1){
                hh->OverWrite(UTSUSEMI_KEY_HEAD_POLARANGLE, p_vec[0] );
            }else{
                hh->Add(UTSUSEMI_KEY_HEAD_POLARANGLE,p_vec[0]);
            }
        }
    }


    UInt4 num_of_binQ = (UInt4)(thetaBinQConst.size())-1;
    //std::vector< ElementContainer > eca_res_vec( num_of_binQ );
    //std::vector<Double> thetaBinCenter_vec( num_of_binQ, -1.0 );
    //std::vector<Double> thetaBinWidth_vec( num_of_binQ, -1.0 );
    std::vector<Double> thetaBinCenter;
    std::vector<Double> thetaBinWidth;
    ElementContainerArray ret_eca;

    for (UInt4 i=0;i<num_of_binQ;i++){
        SearchInHeader* sih2 = new SearchInHeader( ecm );
        sih2->Search(UTSUSEMI_KEY_HEAD_POLARANGLE,thetaBinQConst[i],thetaBinQConst[i+1]);
        std::vector<UInt4> psd_vec = sih2->PutResultIndex(0);
        std::vector<UInt4> pix_vec = sih2->PutResultIndex(1);
        delete sih2;

        if (psd_vec.empty()){
        }else{
            AverageElementContainerMatrix* aec = new AverageElementContainerMatrix( ecm, psd_vec, pix_vec );
            ElementContainer ec_res = aec->GetAverage();
            delete aec;

            HeaderBase* hh = ec_res.PutHeaderPointer();
            if ((hh->CheckKey(UTSUSEMI_KEY_HEAD_MASKED)==0)||((hh->CheckKey(UTSUSEMI_KEY_HEAD_MASKED)==1)&&(hh->PutInt4(UTSUSEMI_KEY_HEAD_MASKED)==0))){
                std::vector<Double> pa_vec;
                pa_vec.clear();
                pa_vec.push_back( (thetaBinQConst[i+1]+thetaBinQConst[i])/2.0 );
                pa_vec.push_back( (thetaBinQConst[i+1]-thetaBinQConst[i])/2.0 );

                if (hh->CheckKey(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES)==1){
                    hh->OverWrite(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES,pa_vec);
                }else{
                    hh->Add(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES,pa_vec);
                }

                if (hh->CheckKey(UTSUSEMI_KEY_HEAD_POLARANGLE)==1){
                    hh->OverWrite(UTSUSEMI_KEY_HEAD_POLARANGLE,pa_vec[0]);
                }else{
                    hh->Add(UTSUSEMI_KEY_HEAD_POLARANGLE,pa_vec[0]);
                }
                std::vector<Double> aa_vec(2,-1.0);
                if (hh->CheckKey(UTSUSEMI_KEY_HEAD_PIXELAZIMANGLES)==1)
                    hh->OverWrite(UTSUSEMI_KEY_HEAD_PIXELAZIMANGLES,aa_vec);
                else
                    hh->Add(UTSUSEMI_KEY_HEAD_PIXELAZIMANGLES,aa_vec);

                if (hh->CheckKey(UTSUSEMI_KEY_HEAD_AZIMANGLE)==1){
                    hh->OverWrite(UTSUSEMI_KEY_HEAD_AZIMANGLE,aa_vec[0]);
                }else{
                    hh->Add(UTSUSEMI_KEY_HEAD_AZIMANGLE,aa_vec[0]);
                }
                //eca_res_vec[i] = ec_res;
                //thetaBinCenter_vec[i] = pa_vec[0];
                //thetaBinWidth_vec[i] = pa_vec[1]*2.0;
                ret_eca.Add(ec_res);
                thetaBinCenter.push_back( pa_vec[0] );
                thetaBinWidth.push_back( pa_vec[1]*2.0 );

            }
        }
    }

    HeaderBase* hh_eca_res = ret_eca.PutHeaderPointer();
    hh_eca_res->Add("Bin",thetaBinQConst);
    hh_eca_res->Add("BinCenter",thetaBinCenter);
    hh_eca_res->Add("BinWidth",thetaBinWidth);
    hh_eca_res->Add(UTSUSEMI_KEY_HEAD_SAMPLETYPE,UTSUSEMI_KEY_HEAD_SAMPLETYPE_POWDER);

    ret_ecm->InputHeader( ecm->PutHeader() );
    ret_ecm->Add(ret_eca);
    HeaderBase* hh_ecm_ret = ret_ecm->PutHeaderPointer();
    if (hh_ecm_ret->CheckKey(UTSUSEMI_KEY_HEAD_SAMPLETYPE)==1){
        hh_ecm_ret->OverWrite(UTSUSEMI_KEY_HEAD_SAMPLETYPE,UTSUSEMI_KEY_HEAD_SAMPLETYPE_POWDER);
    }else{
        hh_ecm_ret->Add(UTSUSEMI_KEY_HEAD_SAMPLETYPE,UTSUSEMI_KEY_HEAD_SAMPLETYPE_POWDER);
    }

    return true;
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionInEla::
BoseFactorCorrect( ElementContainerMatrix* ecm, Double T ){
    std::string process_key = _MessageTag+"BoseFactorCorrect";
    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;

    for (UInt4 i=0; i<ecm->PutTableSize(); i++){
        ElementContainerArray* eca = ecm->PutPointer(i);
        for (UInt4 j=0; j<eca->PutTableSize(); j++){
            if (BoseFactorCorrectEC( eca->PutPointer(j), T )){
            }else{
                UtsusemiError(process_key+" >> Failed to do Bose factor correction.");
                return false;
            }
        }
    }

    UCP.AddProcess( ecm, process_key );
    return true;
}
//////////////////////////////////////////////////////////
bool UtsusemiReductionInEla::
BoseFactorCorrectEC( ElementContainer* ec, Double T ){
    std::string hw_title = ec->PutXKey();
    std::string int_title = ec->PutYKey();
    std::string err_title = ec->PutEKey();
    std::vector<Double>* hw_vect  = ec->PutP( hw_title );
    std::vector<Double>* int_vect = ec->PutP( int_title );
    std::vector<Double>* err_vect = ec->PutP( err_title );
    for (UInt4 k=0; k<(int_vect->size()); k++){
        Double ene = ((hw_vect->at(k))+(hw_vect->at(k+1)))/2.0;
        if (ene!=0.0){
            Double ene_J = ene*MLF_MEV2J; // [meV]->[J]
            Double bf = fabs(1.0/(1.0-exp(-1.0*ene_J/T/MLF_kB))); // MLF_kB [J/K]
            int_vect->at(k) = (int_vect->at(k))/bf;
            err_vect->at(k) = (err_vect->at(k))/bf;
        }else{
            int_vect->at(k) = 0.0;
            err_vect->at(k) = 0.0;
        }
    }

    return true;
}
