#include "METOOLS/Explicit/Vertex.H"

#include "MODEL/Main/Single_Vertex.H"
#include "MODEL/Main/Model_Base.H"
#include "METOOLS/Explicit/Dipole_Kinematics.H"
#include "ATOOLS/Org/Message.H"
#include "ATOOLS/Org/Exception.H"
#include "ATOOLS/Org/STL_Tools.H"
#include "ATOOLS/Org/MyStrStream.H"
#include "ATOOLS/Org/Shell_Tools.H"

#include <algorithm>
#include <typeinfo>

using namespace METOOLS;
using namespace ATOOLS;

namespace METOOLS {

  template <class Type> std::string
  GetName(const Type &o,const int mode=0)
  {
    std::string id=Demangle(typeid(o).name());
    size_t pos=id.find("METOOLS::");
    if (pos<std::string::npos) id.erase(pos,9);
    pos=id.find("_Calculator");
    if (pos<std::string::npos) id.erase(pos,11);
    if (mode&1) {
      pos=id.find('<');
      if (pos<std::string::npos) {
	size_t epos=id.rfind('>');
	if (epos<std::string::npos) id.erase(pos,epos-pos+1);
      }
    }
    return id;
  }

}

size_t Vertex::s_vlmode(0);

std::map<std::string,Int_Vector> Vertex::s_h;

Vertex::Vertex(const Vertex_Key &key): 
  p_v(key.p_mv), p_c(NULL),
  p_info(key.p_dinfo), p_kin(NULL), p_h(NULL),
  m_sign(false), m_fperm(0), m_stype(key.m_stype),
  m_icplfac(1.0)
{
  if (key.p_mv==NULL) return;
  if (p_info)
    p_kin = new Dipole_Kinematics
      (p_info,key.m_j[0],key.m_j[1],key.p_k,key.p_c,key.p_kt);
  key.p_v=this;
  Vertex_Key ckey(key);
  for (ckey.m_n=0;ckey.m_n<key.p_mv->Lorentz.size();++ckey.m_n) {
    std::string ctag(ToString(ckey.p_mv->Color[ckey.m_n].PID()));
    if (key.p_dinfo) {
      if (key.m_stype==2) ctag="S-D";
      else if (abs(ckey.p_c->Flav().StrongCharge())==3) ctag="S-T";
      else if (key.p_c->Flav().StrongCharge()==8) ctag="S-F";
      else {
	for (size_t i(0);i<m_lc.size();++i) {
	  delete m_lc[i];
	  delete m_cc[i];
	}
	m_lc.clear();
	m_cc.clear();
	return;
      }
    }
    m_cc.push_back(CC_Getter::GetObject(ctag,ckey));
    if (m_cc.back()==NULL) {
      msg_Info()<<*ckey.p_mv<<std::endl;
      THROW(fatal_error,"Color calculator not implemented '"+
	    ctag+"'");
    }
    ckey.p_cc=m_cc.back();
    std::string lname=ckey.m_p;
    if(key.p_dinfo)
      lname+="X"+MODEL::s_model->MappedLorentzName(ckey.p_mv->Lorentz[ckey.m_n]);
    else lname+=ckey.p_mv->Lorentz[ckey.m_n];
    m_lc.push_back(LC_Getter::GetObject(lname,ckey));
    if (m_lc.back()==NULL) {
      msg_Out()<<*ckey.p_mv<<std::endl;
      THROW(fatal_error,"Lorentz calculator not implemented '"+lname+"'");
    }
  }
}

Vertex::~Vertex()
{
  for (size_t i(0);i<m_lc.size();++i) {
    delete m_lc[i];
    delete m_cc[i];
  }
  for (size_t i(0);i<m_j.size();++i)
    if (m_j[i]!=NULL) m_j[i]->DetachOut(this);
  if (p_kin) delete p_kin;
}

void Vertex::Evaluate()
{
  SetZero();
  if (p_kin && !p_kin->Trig()) return;
  for (Current_Vector::const_iterator jit(m_j.begin());
       jit!=m_j.end();++jit) if ((*jit)->Zero()) return;
  if (p_kin) {
    for (LC_Vector::const_iterator lit(m_lc.begin());
	 lit!=m_lc.end();++lit) (*lit)->Evaluate();
    if (!p_c->Zero()) {
      const CObject *c(p_kin->JK()->J().front().front());
      p_kin->JKT()->ConstructJ(p_kin->JKT()->P(),0,(*c)(0),(*c)(1),0);
    }
    return;
  }
#ifdef DEBUG__BG
  msg_Debugging()<<METHOD<<"():\n";
  msg_Indent();
#endif
  if (m_j.size()==2) {
    size_t hid(0), sh0(m_j[0]->J().size()), sh1(m_j[1]->J().size());
    for (size_t h0(0);h0<sh0;++h0) {
      const CObject_Vector *hjj0(&m_j[0]->J()[h0]);
      if (hjj0->empty()) { hid+=sh1; continue; }
      for (size_t h1(0);h1<sh1;++h1) {
	const CObject_Vector *hjj1(&m_j[1]->J()[h1]);
	if (hjj1->empty()) { ++hid; continue; }
	for (size_t c0(0);c0<hjj0->size();++c0) {
	  m_cjj[0]=(*hjj0)[c0];
	  for (size_t c1(0);c1<hjj1->size();++c1) {
	    m_cjj[1]=(*hjj1)[c1];
	    for (size_t k(0);k<m_cc.size();++k)
	      if (m_cc[k]->Evaluate(m_cjj)) {
		CObject *j(m_lc[k]->Evaluate(m_cjj));
		if (j==NULL) continue;
		j->Multiply(p_v->Coupling(k)*m_cc[k]->Coupling());
		j->SetH(H(hid));
		m_cc[k]->AddJ(j);
		SetZero(false);
	      }
	  }
	}
	++hid;
      }
    }
    return;
  }
  size_t hid(0);
  Int_Vector m_cjc(m_j.size()), m_hjc(m_j.size(),0);
  std::vector<const CObject_Vector*> m_hjj(m_j.size());
  for (size_t j(0);j<m_hjj.size();++j) m_hjj[j]=&m_j[j]->J().front();
  for (size_t hc(m_hjc.size()-1);m_hjc[0]<m_j[0]->J().size();) {
    if(m_hjc[hc]==m_j[hc]->J().size()){m_hjc[hc--]=0;++m_hjc[hc];continue;}
    m_hjj[hc]=&m_j[hc]->J()[m_hjc[hc]];if(hc<m_hjc.size()-1){++hc;continue;}
    for (Int_Vector::iterator i(m_cjc.begin());i!=m_cjc.end();++i) *i=0;
    bool zero(false);
    for (size_t i(0);i<m_cjj.size();++i)
      if (m_hjj[i]->empty()) {zero=true;break;}
      else m_cjj[i]=m_hjj[i]->front();
    if (zero) {++m_hjc[hc];++hid;continue;}
    for (size_t cc(m_cjc.size()-1);m_cjc[0]<m_hjj[0]->size();) {
      if (m_cjc[cc]==m_hjj[cc]->size()){m_cjc[cc--]=0;++m_cjc[cc];continue;}
      m_cjj[cc]=(*m_hjj[cc])[m_cjc[cc]];if(cc<m_cjc.size()-1){++cc;continue;}
      for (size_t k(0);k<m_cc.size();++k)
	if (m_cc[k]->Evaluate(m_cjj)) {
	  CObject *j(m_lc[k]->Evaluate(m_cjj));
	  if (j==NULL) continue;
	  j->Multiply(p_v->Coupling(k)*m_cc[k]->Coupling());
	  j->SetH(H(hid));
	  m_cc[k]->AddJ(j);
	  SetZero(false);
	}
      ++m_cjc[cc];
    }
    ++m_hjc[hc];
    ++hid;
  }
}

void Vertex::FindPermutation()
{
  m_fperm=0;
#ifdef DEBUG__BG
  msg_Debugging()<<METHOD<<"(): {\n";
#endif
  Int_Vector id(p_c->Id()), fid(p_c->FId());
  Int_Vector pid(m_j[0]->Id()), pfid(m_j[0]->FId());
  for (size_t i(1);i<m_j.size();++i) {
    pid.insert(pid.end(),m_j[i]->Id().begin(),m_j[i]->Id().end());
    pfid.insert(pfid.end(),m_j[i]->FId().begin(),m_j[i]->FId().end());
  }
#ifdef DEBUG__BG
  msg_Debugging()<<"  pid = "<<pid<<", pfid = "<<pfid<<"\n";
  msg_Debugging()<<"  id  = "<<id<<", fid  = "<<fid<<"\n";
#endif
  for (size_t i(0);i<id.size();++i) {
    for (size_t j(0);j<pid.size();++j) 
      if (pid[j]==id[i] && i!=j) {
	for (size_t k(j);k!=i;) {
	  size_t l(j>i?k-1:k+1);
 	  m_fperm+=pfid[k]==1&&pfid[l]==1;
#ifdef DEBUG__BG
	  if (pfid[k]==1 && pfid[l]==1)
	    msg_Debugging()<<"  swap "<<pid[l]<<" & "<<pid[k]<<"\n";
#endif
	  std::swap<int>(pid[k],pid[l]);
	  std::swap<int>(pfid[k],pfid[l]);
	  k=l;
	}
	break;
      }
  }
  m_sign=m_fperm%2==1;
#ifdef DEBUG__BG
  msg_Debugging()<<"} => "<<*this<<"\n";
#endif
}

void Vertex::InitPols()
{
#ifdef DEBUG__BG
  msg_Debugging()<<METHOD<<"() {\n";
#endif
  m_cjj.resize(m_j.size());
  int nmax(0);
  std::string id;
  for (size_t i(0);i<m_j.size();++i) {
    id+=m_j[i]->H().SpinID();
    nmax=Max(nmax,m_j[i]->Id().back());
  }
  static std::map<int,std::string> s_imap;
  for (size_t i(0);i<=nmax;++i)
    for (size_t j(0);j<m_j.size();++j)
      if (std::find(m_j[j]->Id().begin(),
		    m_j[j]->Id().end(),i)!=
	  m_j[j]->Id().end()) {
	std::map<int,std::string>::iterator iit(s_imap.find(j));
	if (iit==s_imap.end()) iit=s_imap.insert(make_pair(j,ToString(j))).first;
	id+="_"+iit->second;
	break;
      }
  std::map<std::string,Int_Vector>::iterator hit(s_h.find(id));
  if (hit!=s_h.end()) {
    p_h=&hit->second;
#ifdef DEBUG__BG
    msg_Debugging()<<"  "<<id<<" mapped to '"<<p_h<<"'\n}\n";
#endif
    return;
  }
  p_h=&s_h.insert(make_pair(id,Int_Vector())).first->second;
#ifdef DEBUG__BG
  msg_Debugging()<<"  "<<id<<" stored in '"<<p_h<<"'\n";
#endif
  Int_Vector m_hjc(m_j.size(),0);
  std::vector<Int_Vector> hjj(m_j.size());
  for (size_t i(0);i<hjj.size();++i) hjj[i]=m_j[i]->H()(0);
  for (size_t hc(m_hjc.size()-1);m_hjc[0]<m_j[0]->H().N();) {
    if(m_hjc[hc]==m_j[hc]->H().N()){m_hjc[hc--]=0;++m_hjc[hc];continue;}
    hjj[hc]=m_j[hc]->H()(m_hjc[hc]);if(hc<m_hjc.size()-1){++hc;continue;}
    Int_Vector ch(hjj.back()), id(m_j.back()->Id());
    id.reserve(p_c->Id().size());
    ch.reserve(p_c->Id().size());
    for (size_t i(0);i<hjj.size()-1;++i) {
      for (size_t m(0);m<hjj[i].size();++m) {
	Int_Vector::iterator cit(ch.begin()), iit(id.begin());
	for (;iit<id.end();++iit,++cit) if (m_j[i]->Id()[m]<*iit) break;
	id.insert(iit,m_j[i]->Id()[m]);
	ch.insert(cit,hjj[i][m]);
      }
    }
#ifdef DEBUG__BG
    msg_Debugging()<<"  ["<<p_h->size()<<"]: j = "<<m_hjc
		   <<", h = "<<ch<<" -> id = "<<p_c->H()(ch)<<"\n";
#endif
    p_h->push_back(p_c->H()(ch));
    ++m_hjc[hc];
  }
#ifdef DEBUG__BG
  msg_Debugging()<<"}\n";
#endif
}

bool Vertex::Map(const Vertex &v)
{
#ifdef DEBUG__BG
  msg_Debugging()<<"      "<<(m_cc.size()?GetName(*m_cc.front()):"")
		 <<"|"<<(m_lc.size()?GetName(*m_lc.front()):"")
		 <<" "<<VId()<<" "<<CVLabel()<<"\n"
		 <<"      "<<(v.m_cc.size()?GetName(*v.m_cc.front()):"")
		 <<"|"<<(v.m_lc.size()?GetName(*v.m_lc.front()):"")
		 <<" "<<v.VId()<<" "<<v.CVLabel()<<"\n";
#endif
  if(m_cc.size()!=v.m_cc.size()) return false;
  for (size_t i{ 0 }; i < m_cc.size(); ++i) {
    const Color_Calculator* cc{ m_cc[i] };
    const Color_Calculator* other_cc{ v.m_cc[i] };
    if (typeid(*cc) != typeid(*other_cc))
      return false;
    const Lorentz_Calculator* lc{ m_lc[i] };
    const Lorentz_Calculator* other_lc{ v.m_lc[i] };
    if (typeid(*lc) != typeid(*other_lc))
      return false;
    if (p_v->cpl[i].Value() != v.p_v->cpl[i].Value())
      return false;
  }
  return VId()==v.VId();
}

void Vertex::AddJ(const Current_Vector &j)
{
  for (size_t i(0);i<j.size();++i) AddJ(j[i]);
}

std::string Vertex::VId() const
{
  std::string estr("v");
  for (size_t i(0);i<m_j.size();++i)
    estr+="_"+ToString(m_j[i]->CId());
  return estr+"_"+ToString(p_c->CId());
}

std::string Vertex::CVLabel() const
{
  std::string label(m_lc[0]->Label());
  for (size_t i(1);i<m_lc.size();++i) label+=";"+m_lc[i]->Label();
  return label;
}

std::string Vertex::VLabel() const
{
  std::string label;
  if (s_vlmode&1) label+="\\scriptstyle\\blue F="+ToString(m_fperm);
  if (s_vlmode&2) {
    if (m_cc.empty() || m_lc.empty()) THROW(fatal_error,"Invalid call");
    std::string id(GetName(*m_cc.front())+"_"+GetName(*m_lc.front(),1));
    for (size_t pos;(pos=id.find("_"))!=std::string::npos && 
	   id[pos-1]!='\\';id.replace(pos,1,"\\_"));
    if (s_vlmode&16) {
      label+=std::string(label.length()>0?"\\\\":"")+
	"\\scriptstyle\\green O="+id+"("+m_j[0]->Flav().TexName();
      for (size_t i(1);i<m_j.size();++i) label+=","+m_j[i]->Flav().TexName();
      label+=")";
    }
  }
  if (s_vlmode&4)
    label+=std::string(label.length()>0?"\\\\":"")+
      "\\scriptstyle\\red L="+CVLabel();
  if (s_vlmode&8) {
    label+=std::string(label.length()>0?"\\\\":"")+
      "\\scriptstyle\\green C={"+p_v->cpl.front().String();
    for (size_t i(1);i<p_v->cpl.size();++i) label+=","+p_v->cpl[i].String();
    label+="}";
  }
  for (size_t pos(label.find(','));
       pos!=std::string::npos;pos=label.find(',',pos+2))
    label.replace(pos,1,",,");
  return "decor.size=0ex,label=$\\begin{array}{c}"+label+"\\end{array}$";
}

void Vertex::CollectGraphs(Graph_Node *graph) const
{
  graph->push_back("    \\fmfv{"+VLabel()+"}{"+VId()+"}");
  graph->push_back("    %% "+VId());
  for (size_t i(0);i<m_j.size();++i) m_j[i]->CollectGraphs(graph);
}

const std::vector<int> &Vertex::Order() const
{
  return p_v->order;
}

int Vertex::Order(const size_t &id) const
{
  return p_v->order[id];
}

std::ostream &METOOLS::operator<<(std::ostream &str,const Vertex &v)
{
  for (size_t i(0);i<v.J().size();++i) {
    if (i) str<<"(+)";
    str<<'{'<<v.J(i)->Type()<<','<<v.J(i)->Flav()<<'}'<<v.J(i)->Id();
    if (v.J(i)->Sub()) str<<"S["<<v.J(i)->Sub()->Id()
			  <<v.J(i)->Sub()->Sub()->Id()<<"]";
  }
  if (v.JC()!=NULL) {
    str<<"-";
    if (v.Color().size() && v.Lorentz().size()) {
      str<<"'"<<GetName(*v.Color().front())
	 <<"*"<<GetName(*v.Lorentz().front());
      for (size_t i(1);i<v.Color().size();++i)
	str<<"+"<<GetName(*v.Color()[i])
	   <<"*"<<GetName(*v.Lorentz()[i]);
      str<<"'";
    }
    if (v.V()) str<<v.Order();
    str<<"->{"<<v.JC()->Type()
       <<','<<v.JC()->Flav()<<'}'<<v.JC()->Id();
  }
  if (v.Kin()) str<<" D["<<v.Kin()->JK()->Id()
		  <<","<<v.Kin()->Type()<<"]";
  return str<<" {"<<v.FPerm()<<","<<v.Sign()<<"}";
}