#include "NxspeFileIO.hh"
//////////////////////////////////////////////////////////
NxspeFileIO::NxspeFileIO(bool isReduce){
    _Initialize();
    _isReduce = isReduce;
}
//////////////////////////////////////////////////////////
NxspeFileIO::~NxspeFileIO(){
}
//////////////////////////////////////////////////////////
void NxspeFileIO::_Initialize(){
    _MessageTag = "NxspeFileIO::";
    _fixed_energy = 0.0;
    _kikf = true;
    _orientationAngle = 0.0;
    _instCode = "SIK";
    _sampleTemperature = 0.0;
    _sampleType = "unknown";

    _param_AzimuthAng_list.clear();
    _param_AzimuthAngWidth_list.clear();
    _param_PolarAng_list.clear();
    _param_PolarAngWidth_list.clear();
    _param_L2_list.clear();
    _param_Energy_list.clear();
    _intensity.clear();
    _error.clear();
    _isReduce = false;

    _ecm = NULL;
    _pixelNo_per_det = 100;
    _Status = true;
}
//////////////////////////////////////////////////////////
bool NxspeFileIO::SetData( ElementContainerMatrix* ECM, Double angle, Double temperature ){
    _Status = false;
    _ecm = ECM;
    if (!_ImportData(ECM)){
        _Initialize();
        return false;
    }
    SetSampleInfo( angle, temperature );
    _Status = true;
    return true;
}
//////////////////////////////////////////////////////////
bool NxspeFileIO::_ImportData(ElementContainerMatrix* ecm){
    HeaderBase *hh = ecm->PutHeaderPointer();
    if (hh->CheckKey(UTSUSEMI_KEY_HEAD_INSTRUMENT)==1)
        _instCode = hh->PutString(UTSUSEMI_KEY_HEAD_INSTRUMENT);
    else{
        UtsusemiError(_MessageTag+"SetData >> Inst code is not found.");
        return false;
    }

    if (hh->CheckKey(UTSUSEMI_KEY_HEAD_EI)==1)
        _fixed_energy = hh->PutDouble(UTSUSEMI_KEY_HEAD_EI);
    else if (hh->CheckKey(UTSUSEMI_KEY_HEAD_EF)==1)
        _fixed_energy = hh->PutDouble(UTSUSEMI_KEY_HEAD_EF);
    else{
        UtsusemiError(_MessageTag+"SetData >> fixed energy is not found.");
        return false;
    }

    if (_isReduce)
        _param_Energy_list = _reducePrec(ecm->PutPointer(0)->PutPointer(0)->PutX());
    else
        _param_Energy_list = ecm->PutPointer(0)->PutPointer(0)->PutX();

    bool isBad = false;
    for (UInt4 i=0; i<ecm->PutSize(); i++){
        hh = ecm->PutPointer(i)->PutHeaderPointer();
        if (( hh->CheckKey(UTSUSEMI_KEY_HEAD_MASKED)==1)&&(hh->PutInt4(UTSUSEMI_KEY_HEAD_MASKED)==1)){
        }else{
            for (UInt4 j=0; j<ecm->PutPointer(i)->PutSize(); j++){
                ElementContainer *ec = ecm->PutPointer(i)->PutPointer(j);
                HeaderBase* hh_ec = ec->PutHeaderPointer();
                if ((hh_ec->CheckKey(UTSUSEMI_KEY_HEAD_MASKED)==1)&&(hh_ec->PutInt4(UTSUSEMI_KEY_HEAD_MASKED)==1)){
                }else{
                    if (hh_ec->CheckKey(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES)==1){
                        std::vector<Double> pols = hh_ec->PutDoubleVector(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES);
                        std::vector<Double> azms = hh_ec->PutDoubleVector(UTSUSEMI_KEY_HEAD_PIXELAZIMANGLES);
                        _param_PolarAng_list.push_back( pols[0] );
                        _param_PolarAngWidth_list.push_back( pols[1] );
                        _param_AzimuthAng_list.push_back( azms[0] );
                        _param_AzimuthAngWidth_list.push_back( azms[1] );
                    }else{
                        UtsusemiError(_MessageTag+"SetData >> Pixel Angle Info is not found.");
                        return false;
                    }

                    if (hh_ec->CheckKey(UTSUSEMI_KEY_HEAD_PIXELPOSITION)==1){
                        std::vector<Double> p = hh_ec->PutDoubleVector(UTSUSEMI_KEY_HEAD_PIXELPOSITION);
                        _param_L2_list.push_back( sqrt( p[0]*p[0] + p[1]*p[1] + p[2]*p[2] )/1000.0 ); // [m]
                    }else{
                        UtsusemiError(_MessageTag+"SetData >> Pixel position Info is not found.");
                        return false;
                    }
                    if (_isReduce){
                        _intensity.push_back( _reducePrec(ec->PutY()) );
                        _error.push_back( _reducePrec(ec->PutE()) );
                    }else{
                        _intensity.push_back( ec->PutY() );
                        _error.push_back( ec->PutE() );
                    }
                }
            }
        }
    }
    return true;
}
//////////////////////////////////////////////////////////
std::vector<Double> NxspeFileIO::_reducePrec(std::vector<Double> vi){
    std::vector<float>* conv = new std::vector<float>(vi.size());
    for (UInt4 i=0; i<(vi.size()); i++) conv->at(i) = (float)(vi[i]);

    std::vector<Double> ret;
    ret.resize(vi.size(), 0.0);
    copy(conv->begin(), conv->end(), ret.begin());
    delete conv;
    return ret;
}
//////////////////////////////////////////////////////////
bool NxspeFileIO::_ExportData(ElementContainerMatrix* ecm){
    if (_intensity.empty()){
        UtsusemiError(" No data");
        return false;
    }
    HeaderBase *hh = ecm->PutHeaderPointer();
    if (hh->CheckKey(UTSUSEMI_KEY_HEAD_INSTRUMENT)==1)
        hh->OverWrite(UTSUSEMI_KEY_HEAD_INSTRUMENT,_instCode);
    else
        hh->Add(UTSUSEMI_KEY_HEAD_INSTRUMENT,_instCode);

    if (hh->CheckKey(UTSUSEMI_KEY_HEAD_EI)==1)
        hh->OverWrite(UTSUSEMI_KEY_HEAD_EI,_fixed_energy);
    else if (hh->CheckKey(UTSUSEMI_KEY_HEAD_EF)==1)
        hh->OverWrite(UTSUSEMI_KEY_HEAD_EF,_fixed_energy);
    else
        hh->Add(UTSUSEMI_KEY_HEAD_EI,_fixed_energy);

    //if (hh->CheckKey(UTSUSEMI_KEY_HEAD_MASKED)==0)
    //    hh->Add(UTSUSEMI_KEY_HEAD_MASKED,0);

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

    if (hh->CheckKey(UTSUSEMI_KEY_HEAD_SAMPLETYPE) == 1)
        hh->OverWrite(UTSUSEMI_KEY_HEAD_SAMPLETYPE, _sampleType);
    else
        hh->Add(UTSUSEMI_KEY_HEAD_SAMPLETYPE, _sampleType);

    std::vector<std::string> proc;
    proc.push_back("E2H");
    proc.push_back("TOF TO ENERGY TRANSFER");
    if (_kikf)
        proc.push_back("KiKfCorrection");
    if (hh->CheckKey(UTSUSEMI_KEY_HEAD_DATAPROCESSED)==1)
        hh->OverWrite(UTSUSEMI_KEY_HEAD_DATAPROCESSED, proc);
    else
        hh->Add(UTSUSEMI_KEY_HEAD_DATAPROCESSED, proc);

    UInt4 size1 = (UInt4)(_intensity.size());
    UInt4 size2 = (UInt4)(_intensity[0].size());
    UInt4 num_of_det = (UInt4)(size1/_pixelNo_per_det);
    UInt4 amari = size1%_pixelNo_per_det;
    if (amari!=0) num_of_det++;
    ecm->Allocate( num_of_det );
    for (UInt4 i=0; i<num_of_det; i++){
        ElementContainerArray* eca = new ElementContainerArray();
        Int4 num_of_maskedec=0;
        UInt4 numPix = _pixelNo_per_det;
        if (i==(num_of_det-1)) numPix = amari;
        for (UInt4 j=0; j<numPix; j++){
            UInt4 ind = i*_pixelNo_per_det + j;
            if (ind>=size1) break;
            std::vector<Double> pixelAA(2,0);
            pixelAA[0] = _param_AzimuthAng_list[ind];
            pixelAA[1] = _param_AzimuthAngWidth_list[ind];
            std::vector<Double> pixelPA(2,0);
            pixelPA[0] = _param_PolarAng_list[ind];
            pixelPA[1] = _param_PolarAngWidth_list[ind];
            Double L2 = _param_L2_list[ind]*1000.0; // [mm]

            std::vector<Double> pv(3,0.0);
            pv[2] = L2*( cos( pixelPA[0]/180.0*PI ) );
            pv[0] = L2*(sin( pixelPA[0]/180.0*PI ))*( cos( pixelAA[0]/180.0*PI ) );
            pv[1] = sqrt( L2*L2 - pv[0]*pv[0] - pv[2]*pv[2] );
            if (pixelAA[0]>180.0) pv[1] = -1.0*pv[1];

            UInt4 num_of_maskbins = 0;
            for (UInt4 k=0; k<_intensity[ind].size(); k++){
                if (isfinite(_intensity[ind][k])){
                }else{
                    _intensity[ind][k] = UTSUSEMIMASKVALUE64;
                    if (_error[ind][k]==0.0)
                        _error[ind][k] = -1.0;
                    if (_error[ind][k]>0.0)
                        _error[ind][k] = -1.0*_error[ind][k];
                }
                if (_error[ind][k]<0.0)
                    num_of_maskbins++;
            }
            Int4 ec_masked = 0;
            if (num_of_maskbins==_intensity[ind].size()){
                ec_masked = 1;
                num_of_maskedec++;
            }
            HeaderBase hh_ec;
            hh_ec.Add(UTSUSEMI_KEY_HEAD_PIXELPOSITION,pv);
            hh_ec.Add(UTSUSEMI_KEY_HEAD_PIXELPOLARANGLES,pixelPA);
            hh_ec.Add(UTSUSEMI_KEY_HEAD_PIXELAZIMANGLES,pixelAA);
            hh_ec.Add(UTSUSEMI_KEY_HEAD_MASKED,ec_masked);
            ElementContainer ec(hh_ec);
            ec.Add( UTSUSEMI_KEY_ENERGY, _param_Energy_list, UTSUSEMI_KEY_ENERGY_UNIT );
            ec.Add( UTSUSEMI_KEY_INTENSITY, _intensity[ind], UTSUSEMI_KEY_COUNTS_UNIT );
            ec.Add( UTSUSEMI_KEY_ERROR, _error[ind], UTSUSEMI_KEY_COUNTS_UNIT );
            ec.SetKeys( UTSUSEMI_KEY_ENERGY, UTSUSEMI_KEY_INTENSITY, UTSUSEMI_KEY_ERROR);

            eca->Add(ec);
        }

        HeaderBase* hh_eca = eca->PutHeaderPointer();
        if (num_of_maskedec==eca->PutSize())
            hh_eca->Add(UTSUSEMI_KEY_HEAD_MASKED,1);
        else
            hh_eca->Add(UTSUSEMI_KEY_HEAD_MASKED,0);

        ecm->Set(i,eca);
    }

    return true;
}
//////////////////////////////////////////////////////////
void NxspeFileIO::SetSampleInfo( Double angle, Double temperature ){
    _orientationAngle = angle;
    _sampleTemperature = temperature;
    _Status = true;
}
//////////////////////////////////////////////////////////
bool NxspeFileIO::Save( std::string _filepath, std::string _username, UInt4 compMode ){
    if (_ecm==NULL){
        UtsusemiError(_MessageTag+"Save >> Data is not set.");
        return false;
    }

    if (compMode > 1){
        UtsusemiError(_MessageTag + "Save >> compMode argument is wrong (0 or 1)");
        return false;
    }
    WriteNeXusFile *W = new WriteNeXusFile( _filepath, _username, compMode );
    W->MakeOpenGroup( "Entry1", "NXentry");
    //W->WriteData("program_name",std::string("utsusemi"));
    W->WriteDataWithAttribute("program_name",std::string("utsusemi"), std::string("version"),std::string("4.0"));
    //W->WriteData("definition",std::string("NXSPE"));
    W->WriteDataWithAttribute("definition", std::string("NXSPE"), std::string("version"), std::string("1.2")); // by R. Murasaki

    // NXSPE_info
    W->MakeOpenGroup( "NXSPE_info", "NXcollection" );
    W->WriteData("fixed_energy", _fixed_energy);
    W->WriteData("ki_over_kf_scaling", bool(_kikf));
    W->WriteData("psi", _orientationAngle);
    W->CloseGroup();

    // data
    W->MakeOpenGroup( "data", "NXdata" );
    W->WriteData("azimuthal", _param_AzimuthAng_list);
    W->WriteData("azimuthal_width", _param_AzimuthAngWidth_list);
    W->WriteData("polar", _param_PolarAng_list);
    W->WriteData("polar_width", _param_PolarAngWidth_list);
    W->WriteData("distance", _param_L2_list);
    W->WriteData("energy", _param_Energy_list);
    W->WriteData("data", _intensity);
    W->WriteData("error", _error);
    W->CloseGroup();

    // Instrument
    W->MakeOpenGroup( "instrument", "NXinstrument" );
    W->WriteData( "name", _instCode );
    W->MakeOpenGroup( "fermi_chopper", std::string("NXfermi_chopper") );
    W->WriteData( "energy", _fixed_energy );
    W->CloseGroup();
    W->MakeOpenGroup( "fermi", std::string("NXfermi_chopper") );
    W->WriteData( "energy", _fixed_energy );
    W->CloseGroup();
    W->CloseGroup();

    // sample
    W->MakeOpenGroup( "sample", "NXsample" );
    W->WriteData( "rotation_angle", _orientationAngle );
    W->WriteData( "seblock", std::string("unkown") );
    W->WriteData( "temperature", _sampleTemperature );
    W->CloseGroup();

    delete W;
    return true;
}
//////////////////////////////////////////////////////////
bool NxspeFileIO::Read( ElementContainerMatrix* ECM, std::string filepath, UInt4 pixelNo ){
    _Initialize();
    _pixelNo_per_det = pixelNo;

    ReadNeXusFile *R = new ReadNeXusFile( filepath );
    std::vector<std::string> vs1 = R->GetNextEntry();
    R->OpenGroup( vs1[0], vs1[1] );

    while(true){
        std::vector<std::string> vs2 = R->GetNextEntry();
        std::string name_l2 = vs2[0];
        std::string class_l2 = vs2[1];
        if (class_l2=="") break;
        else{
            // Check NXSPE
            if (name_l2=="definition"){
                std::string data_type;
                R->ReadData(name_l2,data_type);
                if (data_type!="NXSPE"){
                    UtsusemiError( _MessageTag+"Read >>> This file is not Nxspe ("+filepath+")" );
                    R->CloseGroup();
                    delete R;
                    _Initialize();
                    return false;
                }
            }
            // NXSPE_info
            if (name_l2=="NXSPE_info"){
                R->OpenGroup( name_l2, "NXcollection" );
                while(true){
                    std::vector<std::string> vs3 = R->GetNextEntry();
                    std::string name_l3 = vs3[0];
                    std::string class_l3 = vs3[1];
                    if (class_l3=="") break;
                    if (name_l3=="fixed_energy") R->ReadData(name_l3, _fixed_energy);
                    if (name_l3=="ki_over_kf_scaling") R->ReadData(name_l3, _kikf);
                    if (name_l3=="psi") R->ReadData(name_l3, _orientationAngle);
                }
                R->CloseGroup(); // NXSPE_info close
            }
            // data
            if (name_l2=="data"){
                R->OpenGroup( name_l2, "NXdata" );
                while(true){
                    std::vector<std::string> vs3 = R->GetNextEntry();
                    std::string name_l3 = vs3[0];
                    std::string class_l3 = vs3[1];
                    if (class_l3=="") break;
                    if (name_l3=="azimuthal") R->ReadData(name_l3, _param_AzimuthAng_list);
                    if (name_l3=="azimuthal_width") R->ReadData(name_l3, _param_AzimuthAngWidth_list);
                    if (name_l3=="polar") R->ReadData(name_l3, _param_PolarAng_list);
                    if (name_l3=="polar_width") R->ReadData(name_l3, _param_PolarAngWidth_list);
                    if (name_l3=="distance") R->ReadData(name_l3, _param_L2_list);
                    if (name_l3=="energy") R->ReadData(name_l3, _param_Energy_list);
                    if (name_l3=="data") R->ReadData(name_l3, _intensity);
                    if (name_l3=="error") R->ReadData(name_l3, _error);
                }
                R->CloseGroup(); // data close
            }
            // Instrument
            if (name_l2=="instrument"){
                R->OpenGroup( name_l2, "NXinstrument" );
                while(true){
                    std::vector<std::string> vs3 = R->GetNextEntry();
                    std::string name_l3 = vs3[0];
                    std::string class_l3 = vs3[1];
                    if (class_l3=="") break;
                    if (name_l3=="name") R->ReadData( "name", _instCode );
                    if ((name_l3=="felmi")||(name_l3=="fermi_chopper")){
                        R->OpenGroup( name_l3, std::string("NXfermi_chopper") );
                        while(true){
                            std::vector<std::string> vs4 = R->GetNextEntry();
                            std::string name_l4 = vs4[0];
                            std::string class_l4 = vs4[1];
                            if (class_l4=="") break;
                            if (name_l4=="energy") R->ReadData( name_l4, _fixed_energy );
                        }
                        R->CloseGroup(); // fermi_chopper close
                    }
                }
                R->CloseGroup(); // instrument close
            }
        }
    }
    R->CloseGroup(); // vs1[0] close

    delete R;

    if (_ExportData(ECM)) return true;
    else return false;
}
//////////////////////////////////////////////////////////
void NxspeFileIO::Test(){
    std::vector< std::vector<Double> > v(7);
    for (UInt4 i=0; i<7; i++){
        v[i].clear();
        for (UInt4 j=0; j<3; j++){
            v[i].push_back( (Double)j );
        }
    }
    std::vector<Double> v2(10);
    for (UInt4 i=0; i<10; i++)
        v2[i] = (Double)i;


    WriteNeXusFile W("Test.nxspe","Inamura",1);
    W.MakeOpenGroup( "Entry1", "NXentry" );
    //W.MakeOpenGroup( "Data1", "NXdata" );
    W.WriteData( "TEST_INT4", 111 );
    W.WriteData( "TEST", v );
    W.WriteData( "TESTv2", v2 );
    //W.CloseGroup();
    W.CloseGroup();

}
