#include "CalcAzimuthProfile.hh"
//////////////////////////////////////////////////////////
CalcAzimuthProfile::
CalcAzimuthProfile()
{
    Initialize();
}
//////////////////////////////////////////////////////////
CalcAzimuthProfile::
CalcAzimuthProfile( ElementContainerMatrix* ecm )
{
    Initialize();
    SetTarget(ecm);
}
//////////////////////////////////////////////////////////
CalcAzimuthProfile::
~CalcAzimuthProfile()
{

}
//////////////////////////////////////////////////////////
void CalcAzimuthProfile::
Initialize(){
    _AzimBin.clear();
    _NumOfMulth = UtsusemiGetNumOfMulTh();
    _MessageTag = "CalcAzimuthProfile::";
    _QzRange.first = 0.;
    _QzRange.second= 0.;
    _LambRange.first = 0.;
    _LambRange.second = 0.;
}
//////////////////////////////////////////////////////////
bool CalcAzimuthProfile::
SetAzimuthBinning( double azim_bin, double azim_min, double azim_max ){

    if (azim_min>azim_max){
        double tmp = azim_min;
        azim_min = azim_max;
        azim_max = tmp;
    }
    //if (azim_min<0.0) return false;

    _AzimRange = MakeBinsZeroCenter( azim_min, azim_max, azim_bin );
    _AzimBinWidth = azim_bin;

    _AzimBin.clear();
    for (double azim=_AzimRange.first; azim<(_AzimRange.second+_AzimBinWidth/2.0); azim+=_AzimBinWidth)
        _AzimBin.push_back( azim );

    _Iazim.clear();
    _Error.clear();
    _Count.clear();
    std::vector<double> tmp_x( (_AzimBin.size()-1), 0.0 );

    _Iazim.resize( _NumOfMulth, tmp_x );
    _Error.resize( _NumOfMulth, tmp_x );
    _Count.resize( _NumOfMulth, tmp_x );

    return true;
}
//////////////////////////////////////////////////////////
bool CalcAzimuthProfile::
SetQRadius( double q_center, double q_width ){
    if (q_center<=0.0) return false;
    if (q_center<(q_width/2.0)) return false;

    _QRange.first = q_center-(q_width/2.0);
    _QRange.second= q_center+(q_width/2.0);
    return true;
}
//////////////////////////////////////////////////////////
bool CalcAzimuthProfile::
SetQzRange( double qz_min, double qz_max ){
    if (qz_min>qz_max){
        double tmp = qz_min;
        qz_min = qz_max;
        qz_max = tmp;
    }
    _QzRange.first = qz_min;
    _QzRange.second = qz_max;
    return true;
}
//////////////////////////////////////////////////////////
bool CalcAzimuthProfile::
SetLambRange( double lamb_min, double lamb_max ){
    if (lamb_min>lamb_max){
        double tmp = lamb_min;
        lamb_min = lamb_max;
        lamb_max = tmp;
    }
    if (lamb_min<0.0) return false;
    _LambRange.first = lamb_min;
    _LambRange.second = lamb_max;
    return true;
}
//////////////////////////////////////////////////////////
std::pair<double,double> CalcAzimuthProfile::
MakeBinsZeroCenter(double s_val, double e_val, double step ){

    std::pair<double,double> ret;
    if (s_val>=0.0){
        double v = -step/2.0;
        while(true){
            if ((v+step)>=s_val){
                ret.first = v;
                break;
            }
            v += step;
        }

        while(true){
            if (v>e_val) {
                ret.second=v;
                break;
            }
            v += step;
        }
    }else{
        double v = -step/2.0;
        while(true){
            if (v<=s_val){
                ret.first = v;
                break;
            }
            v -= step;
        }
        v = -step/2.0;
        while(true){
            if (v>e_val){
                ret.second = v;
                break;
            }
            v += step;
        }
    }
    return ret;
}
//////////////////////////////////////////////////////////
ElementContainer CalcAzimuthProfile::
Execute( bool isIncludeQz, bool isAverage ){
    ElementContainerMatrix* ecm = Put();

    for (UInt4 i=0; i<_NumOfMulth; i++){
        for (UInt4 j=0; j<_Iazim[i].size(); j++){
            _Iazim[i][j] = 0.0;
            _Error[i][j] = 0.0;
            _Count[i][j] = 0.0;
        }
    }

    bool isSetLambRange = true;
    if ((_LambRange.first==0.0)&&(_LambRange.second==0.0)) isSetLambRange = false;
    bool isSetQzRange = true;
    if ((_QzRange.first==0.0)&&(_QzRange.second==0.0)) isSetQzRange = false;

    Double azmin = _AzimRange.first;
    Double azmax = _AzimRange.second;
    if ( azmin < 0.0 ) azmin = 360.0 + azmin;
    if ( azmax < 0.0 ) azmax = 360.0 + azmax;

#ifdef MULTH
    omp_set_num_threads( _NumOfMulth );
#endif
    UInt4 ThNum = 0;
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
    for (UInt4 i=0; i<ecm->PutSize(); i++){
#else
    for (Int4 i=0; i<ecm->PutSize(); i++){
#endif
        if (ecm->PutPointer(i)->PutHeaderPointer()->PutInt4("MASKED")==1) continue;
        for (UInt4 j=0; j<ecm->PutPointer(i)->PutSize(); j++){
            ElementContainer* ec = ecm->PutPointer(i)->PutPointer(j);
            if (ec->PutHeaderPointer()->PutInt4("MASKED")==1) continue;
            std::vector<double> iq_int = ec->PutY();
            std::vector<double> iq_err = ec->PutE();

            std::vector<double> qxh = ec->Put("Qx");
            std::vector<double> qyh = ec->Put("Qy");
            std::vector<double> qzh = ec->Put("Qz");
            std::vector<double> lmh = ec->Put("Lamb");

#ifdef MULTH
            ThNum = omp_get_thread_num();
#endif
            for (UInt4 k=0; k<iq_int.size(); k++){
                double qx = (qxh[k+1]+qxh[k])/2.0;
                double qy = (qyh[k+1]+qyh[k])/2.0;
                double qz = (qzh[k+1]+qzh[k])/2.0;
                double lmd = (lmh[k+1]+lmh[k])/2.0;

                double azim = 180.0/M_PI*acos( qx/sqrt(qx*qx+qy*qy) )*qy/sqrt( qy*qy );
                if (azim<0.0) azim = 360.0 + azim;

                double q = 0.0;
                if (isIncludeQz) q = sqrt( qx*qx + qy*qy + qz*qz );
                else q = sqrt( qx*qx + qy*qy );

                if (isSetLambRange)
                    if ( (_LambRange.first>lmd)||(_LambRange.second<lmd) ) continue;
                if (isSetQzRange)
                    if ((qz<_QzRange.first )&&( _QzRange.second<qz ) ) continue;
                //if ( ( _AzimRange.first<=azim )&&( azim<=_AzimRange.second ) ){
                bool flag = false;
                if (azmin > azmax ){ // _AzimRange.first < 0.0 < _AzimRange.second
                    if ( ((azmin <= azim)&&(azim<360.0))||((0.0<=azim)&&(azim<=azmax)) ) flag = true;
                }else if ( ( azmin <= azim )&&( azim<=azmax ) ) flag = true;

                if (flag){
                    if ( ( _QRange.first<=q )&&( q<=_QRange.second ) ){
                        double azim2 = azim - _AzimRange.first;
                        if (azim2>360.0) azim2 -= 360.0;
                        UInt4 ind = floor( azim2/ _AzimBinWidth );
                        if (ind>=(_Iazim[0].size())){
                            std::cout << "azmin, azmax = "<<azmin <<", "<< azmax<<std::endl;
                            std::cout << " azim2, ind = "<<azim2<<", "<<ind<<std::endl;
                        }
                        if (iq_err[k]<0.0){
                        }else{
                            _Iazim[ThNum][ind] += iq_int[k];
                            _Error[ThNum][ind] += iq_err[k]*iq_err[k];
                            _Count[ThNum][ind] += 1.0;
                        }
                    }
                }
            }
        }
    }

    std::vector<Double> Intensity( _Iazim[0].size(), 0.0 );
    std::vector<Double> Error( _Iazim[0].size(), 0.0 );
    std::vector<Double> Count( _Iazim[0].size(), 0.0 );
    for (UInt4 i=0; i<_Iazim[0].size(); i++){
        for (UInt4 j=0; j<_NumOfMulth; j++){
            Intensity[i] += _Iazim[j][i];
            Error[i] += _Error[j][i];
            Count[i] += _Count[j][i];
        }
        if (Count[i]!=0.0){
            if (isAverage){
                Intensity[i] /= Count[i];
                Error[i] = sqrt( Error[i] )/Count[i];
            }else{
                Error[i] = sqrt(Error[i]);
            }
        }
    }

    ElementContainer ec( ecm->PutHeader() );
    ec.Add("Azimuth",_AzimBin,"degree");
    ec.Add("Intensity",Intensity,"counts");
    ec.Add("Error",Error );
    ec.SetKeys("Azimuth","Intensity","Error");

    return ec;
}
