#include "IFieldMapDescription.hxx"
//#include "IFieldMap.hxx"
#include <iostream>
#include <TSystem.h>
#include <TROOT.h>
#include <TString.h>
#include <set>
#include <cstdlib>
using std::string;

namespace {
    std::ostream& operator<<(std::ostream& stream,const TVector3& vect){
        stream<<"("<<vect.x()<<", "<<vect.y()<<", "<<vect.z() <<")";
        return stream;
    }
}

COMET::IFieldMapDescription::PathList COMET::IFieldMapDescription::fFieldmapPaths;

COMET::IFieldMapDescription::IFieldMapDescription():
    COMET::IElementFieldDescription(),
    fFilename(),
    fCheckSum(NULL),
    fScaling(0),
    fGradient(0),
    fTimeOffset(-999999999)
    {
        SetupFilePath();
    }

COMET::IFieldMapDescription::IFieldMapDescription(const std::string& fieldMapFile,
                                                  Double_t scale, 
                                                  const TVector3* translation, 
                                                  const TRotation* rotation):
    COMET::IElementFieldDescription(rotation, translation),
    fFilename(fieldMapFile),
    fCheckSum(NULL),
    fScaling(scale),
    fGradient(0),
    fTimeOffset(-999999999)
    {
        fCheckSum=TMD5::FileChecksum(fFilename.c_str());
        SetupFilePath();
    }

COMET::IFieldMapDescription::IFieldMapDescription(const std::string& fieldMapFile,
        Double_t scale, Double_t gradient,
        Double_t timeOffset, const TVector3* translation, 
        const TRotation* rotation):
    COMET::IElementFieldDescription(rotation, translation),
    fFilename(fieldMapFile),
    fScaling(scale),
    fGradient(gradient),
    fTimeOffset(timeOffset)
    {
        fCheckSum=TMD5::FileChecksum(fFilename.c_str());
        SetupFilePath();
    }

COMET::IFieldMapDescription::IFieldMapDescription(const IFieldMapDescription& rhs):
    COMET::IElementFieldDescription(rhs),
    fScaling(rhs.Scaling()),
    fGradient(rhs.Gradient()),
    fTimeOffset(rhs.TimeOffset()),
    fFilename(rhs.Filename()),
    fCheckSum(NULL)
    {
        if (rhs.CheckSum()) fCheckSum = new TMD5(*rhs.CheckSum());
    }

 
COMET::IFieldMapDescription& COMET::IFieldMapDescription::operator=(IFieldMapDescription rhs){
    // Note that rhs is copy constructed when passed.  Swap the copy with the 
    // current reference
    swap(*this, rhs);
    // Return the copy, swapped data
    return *this;
}

void COMET::IFieldMapDescription::SetupFilePath(){
    if(fFieldmapPaths.empty()){
        fFieldmapPaths.push_back("$PWD");
        fFieldmapPaths.push_back("$ICEDUST_FIELDMAPS");
        fFieldmapPaths.push_back("$OAEMFIELDROOT/fieldmaps");
        fFieldmapPaths.push_back("$ICEDUST_LOCAL_STORAGE/fieldmaps");
    }
}

COMET::IFieldMapDescription::~IFieldMapDescription(){
    if(fCheckSum) delete fCheckSum;
}

void COMET::IFieldMapDescription::ls(Option_t* ) const{
    std::cout<< fFilename<<"("<<fCheckSum->AsString()<<"): " << std::endl;
    TROOT::IncreaseDirLevel();
    std::string indent(TROOT::GetDirLevel(),' ');
    if(fScaling!=1.0) {
        std::cout<<indent<<"Scaled: "<<fScaling<<std::endl;
    }
    if(fTranslation){
        std::cout<<indent<<"Translation: ("<<fTranslation->x()
                                     <<", "<<fTranslation->y()
                                     <<", "<<fTranslation->z()<<")" << std::endl;
    }
    if(fRotation){
        double angle = 0;
        TVector3 axis;
        fRotation->AngleAxis(angle,axis);
        std::cout<<indent<<"Direction: angle="<<angle
                                   <<", axis=("<<axis.x()
                                         <<", "<<axis.y()
                                         <<", "<<axis.z()<<")" << std::endl;
    }
    TROOT::DecreaseDirLevel();
}

//COMET::IElementField* COMET::IFieldMapDescription::Recreate()const{
//    std::string filename=FindFullFilename();
//    if (filename.empty()) return NULL;
//    return new COMET::IFieldMap(filename,fScaling,fGradient,fTimeOffset,fTranslation,fRotation);
//}

std::string COMET::IFieldMapDescription::FindFullFilename()const{
    // Remove any prefixed directories
    size_t last_slash=fFilename.find_last_of('/');
    string basename;
    if(last_slash==fFilename.length()){
        COMETError("Seem to have a directory as a fieldmap file path.  Weird....");
        return "";
    }
    if(last_slash!=string::npos) {
        basename=fFilename.substr(last_slash+1);
    }else basename=fFilename;
    while (fFilename[last_slash]=='/'){
      last_slash--; // to avoid multiple '/'
    }
    string dirname=fFilename.substr(0,last_slash+1);
    last_slash=dirname.find_last_of('/');
    if(last_slash!=string::npos) {
        dirname=dirname.substr(last_slash+1);
    }

    std::vector<TString> bad_checksums;
    for(PathList::const_iterator i_path=fFieldmapPaths.begin();
            i_path!=fFieldmapPaths.end();++i_path){
        TString path = *i_path;
        TString newpath = path;
        gSystem->ExpandPathName(newpath);
        if (newpath==path) continue;
        for (int icheck = 0; icheck<=1; icheck++){
            TString fullfilename=newpath;
            if (icheck==0) fullfilename+="/"+basename;
            else fullfilename+="/"+dirname+"/"+basename;
            if(!gSystem->AccessPathName(fullfilename.Data())){
                TMD5* checksum=TMD5::FileChecksum(fullfilename.Data());
                if(string(checksum->AsString()) == fCheckSum->AsString()){
                    return string(fullfilename.Data());
                }else{
                    bad_checksums.push_back(fullfilename);
                }
            }
        }
    }

    COMETError("Unable to find fieldmap file, '"<<fFilename<<"' in any of the following places:");
    PrintFieldmapSearchPaths(true);

    if(!bad_checksums.empty()){
        COMETError("The following files matched the name but didn't match the MD5 check sum:");
            for(std::vector<TString>::const_iterator i_path=bad_checksums.begin();
                    i_path!=bad_checksums.end();++i_path){
                COMETError("    "<<*i_path);
            }
    }
    return "";
}

void COMET::IFieldMapDescription::PrintFieldmapSearchPaths(bool use_error_stream){
    SetupFilePath();
    for(PathList::const_iterator i_path=fFieldmapPaths.begin();
            i_path!=fFieldmapPaths.end();++i_path){
        if(use_error_stream) COMETError("    "<<*i_path);
        else                 COMETLog  ("    "<<*i_path);
    }
}