#include <iostream>
#include <fstream>

#include <TBenchmark.h>
#include <TMap.h>
#include <TList.h>
#include <TString.h>
#include <TObjString.h>
#include <TH1D.h>
#include <TH2D.h>
#include <TSystemDirectory.h>
#include <TSystemFile.h>
#include <TRegexp.h>

#include <PamCut/PamCutEnv.h>
#include <VKmask.h>
#include <TimeVec.h>


//Declaration of all functions
Int_t HandleInputPar(Int_t argc, char **argv, TMap* optmap); 
Bool_t IsOption(TList* optlist, TString word);
// command line parser
void PrintOptions(TMap* optmap);
//Making TH2D with LT profiles
TH2D* MakeLTmaskHisto(TObject* path_to_rig, Int_t nmasks, TimeVec* tm_vec, Int_t bun);
//Make TimeVector from textfile
TimeVec * MakeTimeVec( TObject* path_to_textfile);
Int_t FindMaskIDinList(TList *lmsk, VKmask* msk);


//general loop. Returns List of lists to store in root-files
TList * DoMasksProfile( TObject* inp_path_o, TObject* inp_pattern, TObject* path_to_rig, TimeVec* tm_vec, Double_t cut_f, Double_t b_min, Double_t b_max );

void SaveResult( TObject* out_path_o, TList* out_list);

Int_t main(Int_t argc, char **argv){
  // Arguments are following:
  //
  // --inp_dir /// input directory with root files
  // --rigbin_file /// file with left edges of rigidity bins
  // --cutof_factor /// factor to cut in SVL. Typycal value is 1.3
  // --babs_min /// minimum value of magnetic field. 0.26 to exclude SAA
  // --babs_max /// maximum value of m.f. usually 1000000 to include everything
  // --bunch_file /// textfile with bunches with start-stop values

  //Default values
  Double_t cut_f = 1.3;
  Double_t babs_min = 0.26;
  Double_t babs_max = 10000000.;
  

  TMap* optmap = new TMap(); //map with options
  HandleInputPar(argc,argv, optmap);
  PrintOptions(optmap);


  TBenchmark* bench = new TBenchmark();
  bench->Start("client");


  //Making TimeVec

  TimeVec * tm_vec = MakeTimeVec( optmap->GetValue("--bunch_file") ); 
  tm_vec->Print();


  TList *lout =  DoMasksProfile( optmap->GetValue("--inp_dir"), 
				 optmap->GetValue("--inp_pattern"), 
				 optmap->GetValue("--rigbin_file"),
				 tm_vec, cut_f, babs_min, babs_max );


  SaveResult(  optmap->GetValue("--out_dir"), lout);

  // report a benchmark message to the server
  bench->Stop("client");
  TString benchresult = " job";
  benchresult.Remove(0, 1);
  benchresult += " finished (";
  TDatime d;
  benchresult += d.AsSQLString();
  benchresult += " | cpu time = ";
  benchresult += floor((Double_t) bench->GetCpuTime("client"));
  benchresult += " s = ";
  benchresult += floor(((Double_t)bench->GetCpuTime("client"))/((Double_t) bench->GetRealTime("client"))*100.);
  benchresult += " % real time)";
  cout<<benchresult.Data()<<endl;
  delete bench;


  return 0;
}


// Command line parser

Int_t HandleInputPar(Int_t argc, char **argv, TMap* optmap){


  TList* allowed_opt = new TList();
  TString  optname = "--inp_dir"; // path with pattern to input files to make chain
  allowed_opt->Add(new TObjString(optname));
  optname = "--inp_pattern"; // pattern of files to make chain
  allowed_opt->Add(new TObjString(optname));
  optname="--out_dir"; //output path to store root-files 
  allowed_opt->Add(new TObjString(optname));
  optname = "--rigbin_file"; // full path to textfile with rigidity bins
  allowed_opt->Add(new TObjString(optname));
  optname = "--cutoff_factor"; // factor for SVL. Typ. is 1.3
  allowed_opt->Add(new TObjString(optname));
  optname = "--babs_min"; // cut to remove SAA and polar regions. Typ 0.26
  allowed_opt->Add(new TObjString(optname));
  optname = "--babs_max"; // 
  allowed_opt->Add(new TObjString(optname));
  optname = "--bunch_file"; // file with start-stop abstime
  allowed_opt->Add(new TObjString(optname));


  TString arg=""; 


 
  if(argc>1){
    if(!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help") ){
      return(1);
    };
    
    for (Int_t i = 1; i < argc; i++){
      if(IsOption(allowed_opt,argv[i])){
	TObjString* option =(TObjString*)allowed_opt->FindObject(argv[i]);
	arg="";
	i++;
	while((i<argc) && (!IsOption(allowed_opt,argv[i]))){
	  arg+=argv[i];
	  arg+=" ";
	  i++;
	}
	//Adding to map
	//Checking if unique
	if(!optmap->FindObject(option->GetName())){
	  arg.ReplaceAll(" ","");
	  optmap->Add(new TObjString(*option), new TObjString(arg));
	} else{
	  cout<<"! Error, you specified one option "
	      <<option->GetName()<<" 2 times"<<endl;
	  abort();
	}
	if(i<argc) i--;
      }
    }
  }
  delete allowed_opt;
  return 0;
}

Bool_t IsOption(TList *optlist, TString word){
  if(!optlist->FindObject(word)) return kFALSE;
  return kTRUE;
}

//Printing user options
void PrintOptions(TMap* optmap){
  cout<<"Application will be executed with next options: "<<endl;
  TMapIter *n= (TMapIter *)optmap->MakeIterator();
  TObject *o; while( (o=(TObject *) n->Next())) {
    cout<<" option: "<<setw(16)
	<<((TObjString*)o)->GetName()
	<<"    value: "
	<<((TObjString*)optmap->GetValue(o))->GetName()<<endl;      
  } 
}


TH2D* MakeLTmaskHisto(TObject* path_to_rig, Int_t nmasks, TimeVec* tm_vec, Int_t bun){



  //rig bins
  TH1D *h_templ_r = Pa::GetBinning( ((TObjString*)path_to_rig)->GetName() );
  const Int_t nrb = h_templ_r->GetNbinsX();
  Double_t *r_bins = new Double_t[nrb+1];
  for(Int_t k = 0; k<nrb+1; k++) r_bins[k]=h_templ_r->GetBinLowEdge(k+1);
  delete h_templ_r;

  //mask bins
  Double_t *m_bins = new Double_t[nmasks+1];
    for(Int_t k = 0; k<nmasks+1; k++) m_bins[k]=0.5+k;
   
  TString name = "LT_hist_bunch_";
  name += bun;
  name.ReplaceAll(" ", "");
  
  TString title = "Bunch_";
  title += bun;
  title.ReplaceAll(" ","");
  title += ", From ";
  UInt_t start, stop;
  tm_vec->GetStartStop( bun, start, stop );
  title += Pa::PrintTime( start );
  title += " To ";
  title += Pa::PrintTime( stop );

  return new TH2D(name, title, nrb, r_bins, nmasks, m_bins);

}

TimeVec * MakeTimeVec( TObject* path_to_textfile){

  if( !path_to_textfile ){
    cout<<" No path to TimeVec textfile specified. Check --bunch_file option "<<endl;
    abort();
  }

  TString path = ((TObjString*)path_to_textfile)->GetName();

  ifstream bun_list;
  bun_list.open(path.Data());
  if(!bun_list){
    cout<<"! Error, Text file with start-stop abstime: "<<path
        <<"  can't be read. Check --bunch_file option"<<endl;
    abort();
  }
  TString start_s,stop_s;


  TimeVec *v_out = new TimeVec();

  while (!bun_list.eof()) {
    bun_list>>start_s;
    bun_list>>stop_s;
    if(start_s != ""){
      v_out->Add( start_s.Atoll(), stop_s.Atoll());
    }
  }

  return v_out;
}


TList * DoMasksProfile( TObject* inp_path_o, TObject* inp_pattern, TObject* path_to_rig, TimeVec* tm_vec, Double_t cut_f, Double_t b_min, Double_t b_max ){

  if( !inp_path_o ){
    cout<<"No input directory defined, check --inp_dir option"<<endl;
    abort();
  }
  if( !inp_pattern ){
    cout<<"No input pattern defined, check --inp_pattern option"<<endl;
    abort();
  }

  if( !path_to_rig ){
    cout<<" No path to Rigidity bins textfile specified. Check --rigbin_file option "<<endl;
    abort();
  }
  
  //template for LT cutoff profile
  TH1D* lt_templ = Pa::GetBinning( ((TObjString*)path_to_rig)->GetName() );

  
  //Here I'm making chain and define some variables
  Double_t babs, svlcutoff, ltime;
  UInt_t abstime;
  UInt_t mask[12];
 
  TChain* ch = new TChain("mask_vk_tree");

  ch->SetBranchAddress("abstime",&abstime);
  ch->SetBranchAddress("babs",&babs);
  ch->SetBranchAddress("ltime",&ltime);
  ch->SetBranchAddress("svlcutoff",&svlcutoff);
  ch->SetBranchAddress("VKmask",&mask);


  //Adding files to chain
  TSystemDirectory* dir = new TSystemDirectory("dir", ((TObjString*)inp_path_o)->GetName());
  TList* ldir = dir->GetListOfFiles();
  TString pattern = ((TObjString*)inp_pattern)->GetName();

  TRegexp re ( pattern, pattern.MaybeWildcard() );
  for(int i=0; i<ldir->GetEntries(); i++) {
      TSystemFile* sysf = (TSystemFile*) ldir->At(i);
      if(sysf->IsDirectory()) continue;
      TString nm = sysf->GetName();
      TString path = sysf->GetTitle();
      TString s = nm;
      if ((nm!=pattern) && s.Index(re) == kNPOS) continue;
      path += sysf->GetName();
      cout<<path<<endl;
      ch->AddFile( path );
  }
  delete ldir;
  delete dir;



  ch->Add( ((TObjString*)inp_path_o)->GetName() );
  cout<<"entries: "<<ch->GetEntries()<<endl;

  //Making general lists
  TList *lout = new TList();
  for(Int_t bun = 0; bun<tm_vec->GetSize(); bun++){
    //fi
    TList *lbun = new TList();
    //first list is list to be saved: TH2D + List of masks
    lbun->Add(new TList() );
    //second list is aux list, not to be saved. Here I'll put TH1Ds for specific mask as function of rigidity cutoff
    lbun->Add(new TList() );
    lout->Add( lbun );
  }

  VKmask *probe_mask = new VKmask();

  for(UInt_t ev = 0; ev < ch->GetEntries(); ev++){
    ch->GetEntry(ev);
    if(ev%1000000==0) cout<<ev<<endl;

    Int_t nb = tm_vec->GetIntervalId( abstime );
    if (nb < 0 ) continue;
    if ( ( babs < b_min) || ( babs > b_max) ) continue; 

    //Filling up current mask
    for(Int_t j =0; j<12; j++) probe_mask->SetMaskRow(j, mask[j]);
    //Compare with already stored masks
    TList *lb = (TList*)lout->At( nb );
    TList *lb_msk =(TList*)lb->At(0);
    TList *lb_hst =(TList*)lb->At(1);
    Int_t id = FindMaskIDinList( lb_msk, probe_mask);
    //if nothing found...
    if(id < 0 ){
      cout<<"-->A new mask... for interval:"<<nb<<endl;
      VKmask *nm = new VKmask();
      for(Int_t j = 0; j<12; j++) nm->SetMaskRow( j, probe_mask->GetMaskRow(j) );
      lb_msk->Add( nm );

      Int_t msk_count = lb_msk->GetEntries();
      TString nm_prof = "bunch_";
      nm_prof += nb;
      nm_prof += "_mask_";
      nm_prof += msk_count;
      TH1D* lt_prof = (TH1D*)lt_templ->Clone( nm_prof );
      Int_t r_b = lt_templ->FindBin( svlcutoff * cut_f );
      for(Int_t i = r_b + 1; i<lt_templ->GetNbinsX()+1; i++)
	lt_prof->AddBinContent( i, ltime );
      lb_hst->Add( lt_prof );   
      cout<<"<--A new mask..."<<endl;
    } else { //if this mask was already there, increment LT
      TH1D* lt_prof = (TH1D*)lb_hst->At( id );
      Int_t r_b = lt_templ->FindBin( svlcutoff * cut_f );
      for(Int_t i = r_b + 1; i<lt_templ->GetNbinsX()+1; i++)
	lt_prof->AddBinContent( i, ltime );
    }

  }

  //After everything we will create TH2D profiles for each bunch
  //TH2D will be added to 1st list (where we have masks) as last object
  for(Int_t bun = 0; bun<tm_vec->GetSize(); bun++){
    TList *lb = (TList*)lout->At(bun);
    TList *lb_msk =(TList*)lb->At(0);
    TList *lb_hst =(TList*)lb->At(1);
    
    const Int_t nmasks = lb_hst->GetEntries(); 
    cout<<"Number of mask for bunch "<<bun<<" is:"<<nmasks<<endl;
    TH2D* h2_mask_prof = MakeLTmaskHisto(path_to_rig, nmasks, tm_vec,  bun);
    for(Int_t msk = 0; msk < nmasks; msk ++ ){
      TH1D* h_prof = (TH1D*)lb_hst->At(msk);
      for(Int_t r_b = 0; r_b<h_prof->GetNbinsX()+1; r_b ++ )
	h2_mask_prof->SetBinContent( r_b+1, msk+1, h_prof->GetBinContent(r_b+1));
    }

    lb_msk->Add( h2_mask_prof );
  }


  return lout;
}

Int_t FindMaskIDinList(TList *lmsk, VKmask* msk){
  
  for(Int_t j = 0; j<lmsk->GetEntries(); j++){
    VKmask *tms = (VKmask*)lmsk->At(j);
    if (*tms==*msk) return j;
  }
  return -1;
}

void SaveResult( TObject* out_path_o, TList* out_list){

 if( !out_path_o ){
    cout<<"No input directory defined, check --out_dir option"<<endl;
    abort();
  }

 TString path = ((TObjString*)out_path_o)->GetName();

 for(Int_t bun = 0; bun < out_list->GetEntries(); bun++){
   TString file = path;
   file += "/out_bun_";
   file += bun;
   file += ".root";
   TList *lb = (TList*)out_list->At(bun);
   TList *lb_msk =(TList*)lb->At(0);
   TFile *fout = new TFile (file, "recreate" );
  
   lb_msk->Write("masks", TObject::kSingleKey);
   fout->Close();
 }

}
