using namespace std;
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include "TClonesArray.h"
#include "PData.h"
#include "PUtils.h"
#include "PReaction.h"
#include "PDecayChannel.h"
#include "PDecayManager.h"
#include "TStopwatch.h"
ClassImp(PDecayManager)
static char ret[200];
Int_t PReactionList::maxID = 0;
PDecayManager::PDecayManager() {
    makeStaticData();
    makeDistributionManager()->ExecAll("init"); 
    Int_t key = makeDataBase()->GetEntry("std_set");
    if (key<0) 
	Warning("PDecayManager()", "std_set not found");
    Int_t listkey = -1;
    while (makeDataBase()->MakeListIterator(key, "snpart", "slink" ,&listkey)) {
	PDecayChannel *ch = new PDecayChannel();
	
	makeDataBase()->SetParamTObj(listkey, "decaychannel", ch);   
    }
    decaychannel_param = makeDataBase()->GetParamTObj("decaychannel");
    UsedParticles 	= new PNextList<PParticle>;
    UsedParticleArrays 	= new PNextList<PParticle*>;
    UsedChannels 	= new PNextList<PChannel>;
    ReactionList	= new PNextList<PReactionList>;
    CurrentReactionListPointer = NULL;
    verbose             = 0;
    userSelection       = NULL;
    nTrigCond           = 0;
    fHGeant             = kFALSE;
    fWriteIndex         = kFALSE;
    fPythia             = NULL;
    maxFileSize         = 0;
    tauMax              = -1.;
    pdist = makeDistributionManager(); 
    Info("PDecayManager()", "(%s)", PRINT_CTOR);
    fileoutput_pos = 0;
    bulkdecay_pos  = pro_bulkdecay_pos = 0;
}
PDecayManager::~PDecayManager() {
    printf("decay manager destructor ok\n");
    UsedParticles->Delete(); 	delete UsedParticles;
    UsedParticleArrays->Delete(); delete UsedParticleArrays;
    UsedChannels->Delete(); 	delete UsedChannels;
    ReactionList->Delete();	delete ReactionList;
}
void PDecayManager::SetVerbose(Int_t v) {
    
    
    verbose = v;
}
void PDecayManager::AddChannel(Int_t id, PDecayChannel *n) {
    
    if (!makeStaticData()->IsParticleValid(id)) { 
	Warning("AddChannel", "id %i s unvalid", id);
	return;
    } else {
	Int_t key = makeDataBase()->GetEntryInt("pid", id);
	PDecayChannel *ch;
	TObject       *o;
	if (key < 0) return;
	if (!makeDataBase()->GetParamTObj(key, "decaychannel", &o)) return;
	ch = (PDecayChannel*)o;
	if (ch == NULL) {
	    makeDataBase()->SetParamTObj(key, "decaychannel", ch);   
	} else
	    ch->AddChannel(n);
    }
}
void PDecayManager::AddChannel(PParticle *p, PDecayChannel *n) {
    
    
    if (p) AddChannel(p->ID(), n);  
}
void PDecayManager::AddChannel(const char *p, PDecayChannel *n) {
    
    
    AddChannel(makeStaticData()->GetParticleID(p), n);  
}
PDecayChannel *PDecayManager::GetChannel(Int_t id) const {
    
    Int_t key = makeStaticData()->GetParticleKey(id); 
    if (key < 0) {
	Warning("GetChannel", "id %i not found in data base", id);
	return NULL;
    }
    TObject *ch;
    if (!makeDataBase()->GetParamTObj(key, decaychannel_param, &ch)) {
	Warning("GetChannel", "no decaychannel found for ID: %i", id);
	return NULL;
    }
    return (PDecayChannel*) ch;
}
PDecayChannel *PDecayManager::GetChannel(PParticle *p) const {
    
    
    if (p) return GetChannel(p->ID()); 
    return NULL;
}
PDecayChannel *PDecayManager::GetChannel(char *p) const {
    
    
    if (p) return GetChannel(makeStaticData()->GetParticleID(p));
    return NULL;
}
void PDecayManager::SetDefault(Int_t id, Int_t recursive) {
    
    if (!makeStaticData()->IsParticleValid(id)) return;
    
    Clear(id);
    PDecayChannel *c = GetChannel(id);
    if (!c) {
	Warning("SetDefault", "Channel not present");
    }
    const char *name = GetName(id);
    if (verbose) cout << endl << "Setting defaults for particle #" << id << ", " 
		      << name << endl;
    
    if (verbose) {
	cout << "Information found:" << endl;
	makeStaticData()->PrintParticle(id); 
    }
  
    
    Int_t key = makeDataBase()->GetEntryInt("pid", id);
    Int_t listkey = -1;
    Int_t tid[11];
    while (makeDataBase()->MakeListIterator(key, "pnmodes", "link", &listkey)) {
	tid[0] = 10; 
	makeStaticData()->GetDecayModeByKey(listkey, tid); 
	
	int *pos;
	makeDataBase()->GetParamInt(listkey, "didx", &pos); 
	Double_t weight = makeStaticData()->GetDecayBR(*pos);        
	c->AddChannel(weight, tid[0], &tid[1]);    
	if (recursive) {
	    for (int i=0; i<tid[0]; i++)
		SetDefault(tid[i+1]);
	}
    }
}
void PDecayManager::SetDefault(PParticle *p,Int_t recursive) {
    
    
    if (p) SetDefault(p->ID(), recursive);  
}
void PDecayManager::SetDefault(const char *p,Int_t recursive) {
    
    
    SetDefault(makeStaticData()->GetParticleID(p), recursive);  
}
void PDecayManager::Clear(Int_t id) {
    
    
    
    
    
    Int_t key = makeDataBase()->GetEntryInt("pid", id); 
    
    TObject *ch;
    if (!makeDataBase()->GetParamTObj(key, "decaychannel", &ch)) return;
    if (ch == NULL) return;
    PDecayChannel *ch2 = new PDecayChannel();
    if (!makeDataBase()->SetParamTObj(key, "decaychannel", ch2)) return;
    delete (PDecayChannel*) ch;
}
void PDecayManager::Clear(PParticle *p) {
    
    
    Clear(p->ID());  
}
void PDecayManager::MyClear(char *p) {
    
    
    Clear(makeStaticData()->GetParticleID(p));  
}
void PDecayManager::InitReaction(PParticle *start, PDecayChannel *CurrentChannel) {
    
    
    
    
    
    
    if (UsedParticles) {  
	UsedParticles->Delete();
	delete UsedParticles;
	UsedParticles = new PNextList<PParticle>;
    }
    if (UsedParticleArrays) { 
	UsedParticleArrays->Delete();  
	delete UsedParticleArrays;
	UsedParticleArrays = new PNextList<PParticle*>;
    }
    if (UsedChannels) {   
	UsedChannels->Delete();
	delete UsedChannels;
	UsedChannels = new PNextList<PChannel>;
    }
    if (ReactionList) {   
	ReactionList->Delete();
	delete ReactionList;
	ReactionList = new PNextList<PReactionList>;
    }
    CurrentReactionNumber = 0;
    CurrentReactionListPointer = NULL;
  
    
  
    if (verbose) cout << endl << "processing decay:" << endl; 
  
    
    if (!CurrentChannel) CurrentChannel = GetChannel(start);
    
    Int_t copyFlag = ( start->IsFireball() || start->IsFileInput() )
	? 0 : 1; 
    Int_t nReac1 = 0, nReac2 = 0;
    while (CurrentChannel) {
	
	
	PReactionList *tempRL = new PReactionList; 
	ConstructPChannel(start, CurrentChannel, tempRL, copyFlag);
	ReactionList->Add(tempRL);
	nReac1++;
	CurrentChannel = CurrentChannel->GetNext();
    }
    
    
   
    Int_t MoreToDo = 1;
    while (MoreToDo) {
	MoreToDo = 0;
	PNextList<PReactionList> *x = ReactionList;
	
	while (x) {	 
	    PReactionList* tempRL = x->Curr;
	    PChannel* tempPC = tempRL->ToDo->Pop();
	    
	    while (tempPC) {
		MoreToDo = 1;
		tempRL->Finished->Push(tempPC);
 
		Int_t           i, loop;
		Int_t           NOP = tempPC->GetNumPar();	
		PParticle     **LOP = tempPC->GetParticles();   
		PDecayChannel **pdc = new PDecayChannel*[NOP];  
		
      
		for(i=0; i<NOP; i++) 
		    pdc[i] = GetChannel(LOP[i+1]);
      
		
		
		
		
		
		
		loop = 0;
		while (pdc[NOP-1]) {
		    
		    PReactionList *newRL = tempRL->Clone();
		    
		    for (i=0; i<NOP; i++) {
			ConstructPChannel(LOP[i+1], pdc[i], tempRL);
			
			if ( (!i) || (loop) ) {
			    loop = 0;
			    pdc[i] = pdc[i]->GetNext();
			    
			    
			    if ( (i<(NOP-1)) && !pdc[i] ) {
				pdc[i] =  GetChannel(LOP[i+1]);
				loop = 1;
			    }
			}
		    } 
		    
		    
		    if (pdc[NOP-1]) { 
			ReactionList->Add(newRL);
			nReac2++;
			tempRL = newRL; 
		    } else 
			delete newRL;
		} 
     
		
		tempPC = tempRL->ToDo->Pop();
	    } 
      
	    
	    x = x->Next;
	} 
    } 
    NumberOfReactions = nReac1 + nReac2;  
}
void PDecayManager::ConstructPChannel(PParticle *p, PDecayChannel *c1,
				      PReactionList *RL, Int_t CopyFlag) {
    
    
    
    
    
    Double_t CurrentWeight = c1->GetWeight();
    if (CurrentWeight<1.0e-20) return;  
    RL->ReactionWeight *= CurrentWeight;
  
    
    
    Int_t  NumOfProducts;
    Int_t* ListOfProducts = c1->GetDaughters(NumOfProducts);
    PParticle** array = new PParticle*[NumOfProducts+1];
    UsedParticleArrays->Add(array);
    if (CopyFlag)     
	array[0] = p->Clone();
    else 		    
	array[0] = p;
  
    
    for (Int_t i=0; i<NumOfProducts; i++) {
	Int_t id = ListOfProducts[i];
	
	array[i+1] = new PParticle(id);
	UsedParticles->Add(array[i+1]);
	
    } 
    
    PChannel *tempPC = new PChannel(array, NumOfProducts);
    UsedChannels->Add(tempPC);
    RL->ToDo->Push(tempPC);
    
    if (verbose) {
	cout << "Next channel for " << p->Name() << ", weight: " << setw(8) << setprecision(2);
	cout.setf(ios::scientific);
	cout << RL->ReactionWeight << setprecision(0) << endl;
	
	
	tempPC->Print();
    }
    pdist->from_pdecaymanager = 1;
    pdist->Attach(tempPC); 
}
PReaction *PDecayManager::GetNextReaction(int wf, const char *name, int f0,
					  int f1, int f2, int f3,
					  TTree *tt) {
    
    
    
    
    
    
    
  
    char *filename;
  
    if (verbose) cout << "Selecting next reaction:" << endl;
    if (ListForReaction) delete [] ListForReaction;
  
    if (CurrentReaction) delete CurrentReaction;
  
    if (!ReactionList) {
	if (verbose) cout << " No initialization done." << endl;
	return NULL;
    }
    if (!CurrentReactionListPointer) CurrentReactionListPointer = ReactionList;
    else CurrentReactionListPointer = CurrentReactionListPointer->Next;
  
    if (!CurrentReactionListPointer) {
	if (verbose) cout << " No reaction left." << endl;
	return NULL;
    }
    
    CurrentReactionNumber++;
    if (verbose) cout << endl << "This is reaction #" << CurrentReactionNumber << endl;
    filename = new char[1024];
    if (f3 == 2) sprintf(filename,"%s",name);  
    else sprintf(filename, "%s_%03d", name, CurrentReactionNumber);
  
    PReactionList *tempRL = CurrentReactionListPointer->Curr->Clone();
    Int_t NumOfChannels = tempRL->Finished->Count;
    ListForReaction = new PChannel*[NumOfChannels];
    for (Int_t i=NumOfChannels-1; i>=0; i--) {
	ListForReaction[i] = tempRL->Finished->Pop();
    }
    CurrentWeight = tempRL->ReactionWeight;
    if (verbose) cout << " Weight of reaction is " << CurrentWeight << endl;
    if (wf)
	(ListForReaction[0]->GetParticles())[0]->SetW(CurrentWeight);
    CurrentReaction = new PReaction(ListForReaction, filename, NumOfChannels,
				    f0, f1, f2, f3, tt);
    CurrentReaction->SetHGeant(fHGeant);
    CurrentReaction->SetWriteIndex(fWriteIndex);
    if (maxFileSize > 0) CurrentReaction->SetMaxFileSize(maxFileSize);
    if (fPythia) CurrentReaction->SetPythia(fPythia);
    if (userSelection) {
	CurrentReaction->SetUserSelection(userSelection);
	CurrentReaction->SetTrigCond(nTrigCond);
    }
    if (tauMax >= 0.) {
	CurrentReaction->SetDecayAll(tauMax);
    }
    
    for (int i=0; i<bulkdecay_pos; i++)
	CurrentReaction->AddBulk(bulk[i]);
    for (int i=0; i<pro_bulkdecay_pos; i++)
	CurrentReaction->AddPrologueBulk(pro_bulk[i]);
    delete tempRL;
    return CurrentReaction;
}
PReaction *PDecayManager::GetNextReaction(const char *name, int f0,
					  int f1, int f2, int f3,
					  TTree *tt) {
    
    return GetNextReaction(0, name, f0, f1, f2, f3, tt);
}
Int_t PDecayManager::Loop(int num, int wf, const char *name, int f0,
			  int f1, int f2, int f3, int rf) {
    
    
    
    
    
    
    
    
    
  
    if (rf==1 && f3==1) f3 = 2;   
    Int_t rStart, rStop, rEntries;
    char* rDescription = new char[1024];
    Double_t rWeight = 0.;
    Int_t  SumOfEvents = 0;
    Double_t WeightSum = 0;
    char* filename = new char[1024];
    sprintf(filename, "%s.root", name);
    TFile *f  = NULL;
    TTree *td = NULL, *ti = NULL;  
    CurrentReaction = NULL;
    ListForReaction = NULL;
    if(!fHGeant) { 
	f  = new TFile(filename,"RECREATE");
	f->SetCompressionLevel(1);
    }
    td = new TTree("data", "Event data");
    sprintf(filename, "%s.evt", name);
    if (f3 == 2) PReaction::asciif = fopen(filename, "w");   
    PReaction::globalEventCounter = 0;
    if (rf == 1) { 
	PReaction *pReactionArray[NumberOfReactions];
	double     rWeights[NumberOfReactions];  
	Double_t wsave[NumberOfReactions];
	for (int i=0; i<NumberOfReactions; i++) {
	    CurrentReaction = NULL;
	    ListForReaction = NULL;
	    pReactionArray[i] = GetNextReaction(wf, name, f0, f1, f2, f3, td);
	    wsave[i] = GetCurrentWeight();
	    WeightSum += wsave[i];
	    rWeights[i] = WeightSum;    
	}
	for (int i=0; i<NumberOfReactions; i++) {
	    cout << endl << "Reaction " << i+1 << " -------------- with weight: "
		 << wsave[i] << endl;
	    pReactionArray[i]->Print();
	}
	for (int i=0; i<NumberOfReactions; i++) rWeights[i]/=WeightSum; 
	cout << endl << "Do " << num << " events now..." << endl;
	TStopwatch timer;                        
	timer.Start();
	int percentevents = (num/100) * (*makeStaticData()->GetBatchValue("_system_printout_percent")), 
	    cpc = 1, ipc = cpc*percentevents;
	double t0 = timer.RealTime();
	timer.Continue();
	double *events = makeStaticData()->GetBatchValue("_system_total_event_number");
	(*makeStaticData()->GetBatchValue("_system_total_events_to_sample")) = num;
	int system_printout_percent = int(*makeStaticData()->GetBatchValue("_system_printout_percent"));
	for (int i=0; i<num; i++) { 
	    double rand = PUtils::sampleFlat();
	    int    ind  = PUtils::FindIndex(NumberOfReactions, rWeights, rand);
	    pReactionArray[ind]->InitChannels();
	    pReactionArray[ind]->DisableWeightReset();
	    pReactionArray[ind]->loop(1, 0, 0); 
	    
	    if ((*events) == ipc) {
		printf(" %i%c done in %f sec\n", cpc*system_printout_percent,
		       '%', timer.RealTime()-t0);
		cpc++;
		ipc = cpc*percentevents;
		timer.Continue();
	    }
	}
	SumOfEvents = num;
    } else {  
	ti = new TTree("info", "Run information");
	ti->Branch("rStart",   &rStart,   "rStart/I");
	ti->Branch("rStop",    &rStop,    "rStop/I");
	ti->Branch("rEntries", &rEntries, "rEntries/I");
	ti->Branch("rWeight",  &rWeight,  "rWeight/D");
	ti->Branch("rDescription", (void*)rDescription, "rDescription/C");
	PReaction *r = GetNextReaction(wf, name, f0, f1, f2, f3, td);
	while (r) {
	    rStart = SumOfEvents + 1;  
	    if (verbose) r->Print();
	    rWeight = GetCurrentWeight();
	    WeightSum += rWeight;
	    if (verbose) cout << " Weight of this channel is " << rWeight << endl;
	    if (f3 == 1) PReaction::globalEventCounter = 0;
	    if (wf) rEntries = r->loop(num,1);
	    else rEntries = r->loop((Int_t)(num*rWeight + 0.5), 0);
	    SumOfEvents += rEntries;
	    rStop = SumOfEvents;
	    CurrentReaction = NULL; 
            
	    r = GetNextReaction(wf, name, f0, f1, f2, f3, td);
	    if (ti) ti->Fill();
	}
    }
    if (f) {
	f = td->GetCurrentFile();
	f->Write();
	f->Close();
	delete f;
    }
    if (f3 == 2) fclose(PReaction::asciif);
    delete [] filename;
    delete [] rDescription;
    if (verbose) cout << "Sum of weights is " << WeightSum << endl;
    return SumOfEvents;
}
Double_t PDecayManager::GetCurrentWeight() {
    
    return CurrentWeight;
}
void PDecayManager::MyPrint() const {
    
    cout << "Particle Decay List:" << endl;
    cout << "--------------------" << endl;
  
    Int_t key = makeDataBase()->GetEntry("std_set");
    if (key < 0)
	Warning("MyPrint", "std_set not found");
    Int_t listkey = -1;
    while (makeDataBase()->MakeListIterator(key, "snpart", "slink", &listkey)) {
	
	int id = makeStaticData()->GetParticleIDByKey(listkey);
	if (id < 1000) Print(id); 
    }
    PrintReactionList();
} 
void PDecayManager::Print(Int_t id) const  {
    
    if (!makeStaticData()->IsParticleValid(id)) return;
    PDecayChannel *DC = GetChannel(id);
    if (!DC) return;
    
    if (DC->GetWeight()) {
	cout.setf(ios::left);
	cout.width(20);
	cout << GetName(id);
	DC->Print();
	cout << endl;
    }
    return;
}
void PDecayManager::Print(PParticle *p) const  {
    
    
    if (p) Print(p->ID());
    return;
}
void PDecayManager::MyPrint(char *p) const  {
    
    if (p) Print(makeStaticData()->GetParticleID(p));
    return;
}
void PDecayManager::PrintReactionList() const {
    
    PNextList<PReactionList> *x = ReactionList;
    Int_t Entry = 0;
    while(x) {
	if ( x->Curr ) {
	    PReactionList *RL = x->Curr->Clone();
	    Entry++;
	    cout << "-----" << endl;
	    cout << "Decay chain #" << Entry << ", probability " 
		 << RL->ReactionWeight << endl;
	    PrintReactionListEntry(RL,cout);
	    delete RL;
	}
	x = x->Next;
    }
}
void PDecayManager::PrintReactionListEntry(PReactionList *RL, 
					   ostream &os) const{
    
    Int_t count = RL->Finished->Count;
    PChannel **temp = new PChannel*[count];
    for (Int_t i=count-1; i>=0; i--) {
	temp[i] = RL->Finished->Pop();
    }
    PrintChain((temp[0]->GetParticles())[0], temp, count, os);  
    delete [] temp; 
}
void PDecayManager::PrintChain(PParticle *p, PChannel **l, Int_t c, 
			       ostream &os) const {
    
    static Int_t indent = 0;
    indent += 2;
    for (Int_t i=0; i<c; i++) {
	Int_t       pcount = l[i]->GetNumPar();
	PParticle **plist  = l[i]->GetParticles();
	if (p == plist[0]) {
	    if (i == 0) indent = 2;
	    os.width(indent) ; 
	    os << " ";
	    os.setf(ios::left);
	    os << setw(12) << GetName(p->ID()) 
	       << setw(0) << " --> ";
	    for (Int_t k=1; k<=pcount; k++) {
		os << GetName((plist[k])->ID()) << " ";
	    }
	    os << endl;
	    for (Int_t k=1; k<=pcount; k++) {
		PrintChain(plist[k], l, c, os);
	    }
	}
    }
    indent -= 2;
}
const char *PDecayManager::GetName(Int_t id) const {
    
    if (id <=0 ) 	
	return "???";
    if (id < 1000)
	return (char*)makeStaticData()->GetParticleName(id);
    char  temp[200];
    Int_t Upper = id/1000;
    Int_t Lower = id - 1000*Upper;
    sprintf(ret, "%s ", makeStaticData()->GetParticleName(Lower));
    id = Upper;
    while (id) {
	Upper = id/1000;
	Lower = id - 1000*Upper;
	sprintf(temp, "%s + %s", makeStaticData()->GetParticleName(Lower),ret);
	strcpy(ret, temp);
	id = Upper;
    }
    return ret;
}
Bool_t PDecayManager::AddBulk(PBulkInterface *mybulk) {
    
    
    
  
    if (bulkdecay_pos == MAX_BULKDECAY ) {
	Error("AddBulk", "MAX_BULKDECAY reached");
	return kFALSE;
    }
    if (bulkdecay_pos && (bulk[bulkdecay_pos-1]->GetPriority() > mybulk->GetPriority())) {
	bulk[bulkdecay_pos]   = bulk[bulkdecay_pos-1];
	bulk[bulkdecay_pos-1] = mybulk;
	bulkdecay_pos++;
    } else
	bulk[bulkdecay_pos++] = mybulk;
    return kTRUE;
}
Bool_t PDecayManager::AddPrologueBulk(PBulkInterface *mybulk) {
    
    
    
    if (pro_bulkdecay_pos == MAX_BULKDECAY ) {
	Error("AddPrologueBulk", "MAX_BULKDECAY reached");
	return kFALSE;
    }
    if (pro_bulkdecay_pos && (pro_bulk[pro_bulkdecay_pos-1]->GetPriority() > mybulk->GetPriority())) {
	pro_bulk[pro_bulkdecay_pos]   = pro_bulk[pro_bulkdecay_pos-1];
	pro_bulk[pro_bulkdecay_pos-1] = mybulk;
	pro_bulkdecay_pos++;
    } else
	pro_bulk[pro_bulkdecay_pos++] = mybulk;
    return kTRUE;
}