// HiddenValleyFragmentation.cc is a part of the PYTHIA event generator.
// Copyright (C) 2017 Torbjorn Sjostrand.
// PYTHIA is licenced under the GNU GPL version 2, see COPYING for details.
// Please respect the MCnet Guidelines, see GUIDELINES for details.

// Function definitions (not found in the header) for the
// HiddenValleyFragmentation class and its helper classes.

#include "Pythia8/HiddenValleyFragmentation.h"

namespace Pythia8 {

//==========================================================================

// The HVStringFlav class is used to select HV-quark and HV-hadron flavours.

//--------------------------------------------------------------------------

// Initialize data members of the flavour generation.

void HVStringFlav::init(Settings& settings, ParticleData* particleDataPtrIn,
  Rndm* rndmPtrIn, Info* infoPtrIn) {

  // Save pointers.
  particleDataPtr = particleDataPtrIn;
  rndmPtr    = rndmPtrIn;
  infoPtr         = infoPtrIn;

  // Read in data from Settings.
  nFlav      = settings.mode("HiddenValley:nFlav");
  probVector = settings.parm("HiddenValley:probVector");

}

//--------------------------------------------------------------------------

// Pick a new HV-flavour given an incoming one.

FlavContainer HVStringFlav::pick(FlavContainer& flavOld, double, double) {

  // Initial values for new flavour.
  FlavContainer flavNew;
  flavNew.rank = flavOld.rank + 1;

  // Pick new HV-flavour at random; keep track of sign.
  flavNew.id = 4900100 + min( 1 + int(nFlav * rndmPtr->flat()), nFlav);
  if (flavOld.id > 0) flavNew.id = -flavNew.id;

  // Done.
  return flavNew;

}

//--------------------------------------------------------------------------

// Combine two HV-flavours to produce an HV-hadron.
// This is simplified procedure, assuming only two HV mesons defined.

int HVStringFlav::combine(FlavContainer& flav1, FlavContainer& flav2) {

  // Positive and negative flavour. Note that with kinetic mixing
  // the Fv are really intended to represent qv, so remap.
  int idMeson = 0;
  int idPos =  max( flav1.id, flav2.id) - 4900000;
  int idNeg = -min( flav1.id, flav2.id) - 4900000;
  if (idPos < 20) idPos = 101;
  if (idNeg < 20) idNeg = 101;

  // Pick HV-meson code, spin either 0 or 1.
  if (idNeg == idPos)     idMeson =  4900111;
  else if (idPos > idNeg) idMeson =  4900211;
  else                    idMeson = -4900211;
  if (rndmPtr->flat() < probVector) idMeson += ((idMeson > 0) ? 2 : -2);

  // Done.
  return idMeson;

}

//==========================================================================

// The HVStringPT class is used to select pT in HV fragmentation.

//--------------------------------------------------------------------------

// Initialize data members of the string pT selection.

void HVStringPT::init(Settings& settings, ParticleData* particleDataPtrIn,
  Rndm* rndmPtrIn, Info* infoPtrIn) {

  // Save pointer.
  particleDataPtr = particleDataPtrIn;
  rndmPtr         = rndmPtrIn;
  infoPtr         = infoPtrIn;

  // Parameter of the pT width. No enhancement, since this is finetuning.
  double sigmamqv  = settings.parm("HiddenValley:sigmamqv");
  double sigma     = sigmamqv * particleDataPtrIn->m0( 4900101);
  sigmaQ           = sigma / sqrt(2.);
  enhancedFraction = 0.;
  enhancedWidth    = 0.;

  // Parameter for pT suppression in MiniStringFragmentation.
  sigma2Had        = 2. * pow2( max( SIGMAMIN, sigma) );

}

//==========================================================================

// The HVStringZ class is used to select z in HV fragmentation.

//--------------------------------------------------------------------------

// Initialize data members of the string z selection.

void HVStringZ::init(Settings& settings, ParticleData& particleData,
  Rndm* rndmPtrIn) {

  // Save pointer.
  rndmPtr  = rndmPtrIn;

  // Paramaters of Lund/Bowler symmetric fragmentation function.
  aLund    = settings.parm("HiddenValley:aLund");
  bmqv2    = settings.parm("HiddenValley:bmqv2");
  rFactqv  = settings.parm("HiddenValley:rFactqv");

  // Use qv mass to set scale of bEff = b * m^2;
  mqv2     = pow2( particleData.m0( 4900101) );
  bLund    = bmqv2 / mqv2;

  // Mass of qv meson used to set stop scale for fragmentation iteration.
  mhvMeson = particleData.m0( 4900111);

}

//--------------------------------------------------------------------------

// Generate the fraction z that the next hadron will take using Lund/Bowler.

double HVStringZ::zFrag( int , int , double mT2) {

  // Shape parameters of Lund symmetric fragmentation function.
  double bShape = bLund * mT2;
  double cShape = 1. + rFactqv * bmqv2;
  return zLund( aLund, bShape, cShape);

}

//==========================================================================

// The HiddenValleyFragmentation class.

//--------------------------------------------------------------------------

// Initialize and save pointers.

bool HiddenValleyFragmentation::init(Info* infoPtrIn, Settings& settings,
  ParticleData* particleDataPtrIn, Rndm* rndmPtrIn) {

  // Save pointers.
  infoPtr         = infoPtrIn;
  particleDataPtr = particleDataPtrIn;
  rndmPtr         = rndmPtrIn;

  // Check whether Hidden Valley fragmentation switched on, and SU(N).
  doHVfrag = settings.flag("HiddenValley:fragment");
  if (settings.mode("HiddenValley:Ngauge") < 2) doHVfrag = false;
  if (!doHVfrag) return false;

  // Several copies of qv may be needed. Taken to have same mass.
  nFlav = settings.mode("HiddenValley:nFlav");
  if (nFlav > 1) {
    int spinType = particleDataPtr->spinType(4900101);
    double m0    = particleDataPtr->m0(4900101);
    for (int iFlav = 2; iFlav <= nFlav; ++iFlav)
      particleDataPtr->addParticle( 4900100 + iFlav, "qv", "qvbar",
      spinType, 0, 0, m0);
  }

  // Hidden Valley meson mass used to choose hadronization mode.
  mhvMeson = particleDataPtr->m0(4900111);

  // Initialize the hvEvent instance of an event record.
  hvEvent.init( "(Hidden Valley fragmentation)", particleDataPtr);

  // Create HVStringFlav instance for HV-flavour selection.
  hvFlavSelPtr = new HVStringFlav();
  hvFlavSelPtr->init( settings, particleDataPtr, rndmPtr, infoPtr);

  // Create HVStringPT instance for pT selection in HV fragmentation.
  hvPTSelPtr = new HVStringPT();
  hvPTSelPtr->init( settings, particleDataPtr, rndmPtr, infoPtr);

  // Create HVStringZ instance for z selection in HV fragmentation.
  hvZSelPtr = new HVStringZ();
  hvZSelPtr->init( settings, *particleDataPtr, rndmPtr);

  // Initialize auxiliary administrative class.
  hvColConfig.init(infoPtr, settings, hvFlavSelPtr);

  // Initialize HV-string and HV-ministring fragmentation.
  hvStringFrag.init(infoPtr, settings, particleDataPtr, rndmPtr,
    hvFlavSelPtr, hvPTSelPtr, hvZSelPtr);
  hvMinistringFrag.init(infoPtr, settings, particleDataPtr, rndmPtr,
    hvFlavSelPtr, hvPTSelPtr, hvZSelPtr);

  // Done.
  return true;

}

//--------------------------------------------------------------------------

// Perform the fragmentation.

bool HiddenValleyFragmentation::fragment(Event& event) {

  // Reset containers for next event.
  hvEvent.reset();
  hvColConfig.clear();
  ihvParton.resize(0);

  // Extract HV-particles from event to hvEvent. Assign HV-colours.
  // Done if no HV-particles found.
  if (!extractHVevent(event)) return true;

  // Store found string system. Analyze its properties.
  if (!hvColConfig.insert(ihvParton, hvEvent)) return false;

  // Collect sequentially all partons in the HV subsystem.
  // Copy also if already in order, or else history tracing may fail.
  hvColConfig.collect(0, hvEvent, false);

  // Mass used to decide how to fragment system.
  mSys = hvColConfig[0].mass;

  // HV-string fragmentation when enough mass to produce >= 3 HV-mesons.
  if (mSys > 3.5 * mhvMeson) {
    if (!hvStringFrag.fragment( 0, hvColConfig, hvEvent)) return false;

  // HV-ministring fragmentation when enough mass to produce 2 HV-mesons.
  } else if (mSys > 2.1 * mhvMeson) {
    if (!hvMinistringFrag.fragment( 0, hvColConfig, hvEvent, true))
    return false;

  // If only enough mass for one HV-meson assume HV-glueballs emitted.
  } else if (!collapseToMeson()) return false;

  // Insert HV particles from hvEvent to event.
  insertHVevent(event);

  // Done.
  return true;

}

//--------------------------------------------------------------------------

// Extract HV-particles from event to hvEvent. Assign HV-colours.

bool HiddenValleyFragmentation::extractHVevent(Event& event) {

  // Copy Hidden-Valley particles to special event record.
  for (int i = 0; i < event.size(); ++i) {
    int idAbs = event[i].idAbs();
    bool isHV = (idAbs > 4900000 && idAbs < 4900007)
             || (idAbs > 4900010 && idAbs < 4900017)
             || idAbs == 4900021 || idAbs == 4900101;
    if (isHV) {
      int iHV = hvEvent.append( event[i]);
      // Convert HV-gluons into normal ones so as to use normal machinery.
      if (event[i].id() ==  4900021) hvEvent[iHV].id(21);
      // Second mother points back to position in complete event;
      // otherwise construct the HV history inside hvEvent.
      hvEvent[iHV].mothers( 0, i);
      hvEvent[iHV].daughters( 0, 0);
      int iMother = event[i].mother1();
      for (int iHVM = 1; iHVM < hvEvent.size(); ++iHVM)
      if (hvEvent[iHVM].mother2() == iMother) {
        hvEvent[iHV].mother1( iHVM);
        if (hvEvent[iHVM].daughter1() == 0) hvEvent[iHVM].daughter1(iHV);
        else                                hvEvent[iHVM].daughter2(iHV);
      }
    }
  }

  // Done if no HV particles found.
  hvOldSize = hvEvent.size();
  if (hvOldSize == 1) return false;

  // Initial colour - anticolour parton pair.
  int colBeg = hvEvent.nextColTag();
  for (int iHV = 1; iHV < hvOldSize; ++iHV)
  if (hvEvent[iHV].mother1() == 0) {
    if (hvEvent[iHV].id() > 0) hvEvent[iHV].col( colBeg);
    else                       hvEvent[iHV].acol( colBeg);
  }

  // Then trace colours down to daughters; new colour if two daughters.
  for (int iHV = 1; iHV < hvOldSize; ++iHV) {
    int dau1 = hvEvent[iHV].daughter1();
    int dau2 = hvEvent[iHV].daughter2();
    if (dau1 > 0 && dau2 == 0)
      hvEvent[dau1].cols( hvEvent[iHV].col(), hvEvent[iHV].acol());
    else if (dau2 > 0) {
      int colHV  = hvEvent[iHV].col();
      int acolHV = hvEvent[iHV].acol();
      int colNew = hvEvent.nextColTag();
      if (acolHV == 0) {
        hvEvent[dau1].cols( colNew, 0);
        hvEvent[dau2].cols( colHV, colNew);
      } else if (colHV == 0) {
        hvEvent[dau1].cols( 0, colNew);
        hvEvent[dau2].cols( colNew, acolHV);
      // Temporary: should seek recoiling dipole end!??
      } else if (rndmPtr->flat() > 0.5) {
        hvEvent[dau1].cols( colHV, colNew);
        hvEvent[dau2].cols( colNew, acolHV);
      } else {
        hvEvent[dau1].cols( colNew, acolHV);
        hvEvent[dau2].cols( colHV, colNew);
      }
    }
  }

  // Pick up the colour end.
  int colNow = 0;
  for (int iHV = 1; iHV < hvOldSize; ++iHV)
  if (hvEvent[iHV].isFinal() && hvEvent[iHV].acol() == 0) {
    ihvParton.push_back( iHV);
    colNow = hvEvent[iHV].col();
  }

  // Trace colour by colour until reached anticolour end.
  while (colNow > 0) {
    for (int iHV = 1; iHV < hvOldSize; ++iHV)
    if (hvEvent[iHV].isFinal() && hvEvent[iHV].acol() == colNow) {
      ihvParton.push_back( iHV);
      colNow = hvEvent[iHV].col();
      break;
    }
  }

  // Done.
  return true;

}

//--------------------------------------------------------------------------

// Collapse of light system to one HV-meson, by the emission of HV-glueballs.

bool HiddenValleyFragmentation::collapseToMeson() {

  // If too low mass then cannot do anything. Should not happen.
  if (mSys < 1.001 * mhvMeson) {
    infoPtr->errorMsg("Error in HiddenValleyFragmentation::collapseToMeson:"
      " too low mass to do anything");
    return false;
  }

  // Choose mass of collective HV-glueball states flat between limits.
  double mhvGlue = (0.001 + 0.998 * rndmPtr->flat()) * (mSys - mhvMeson);

  // Find momentum in rest frame, with isotropic "decay" angles.
  double pAbs = 0.5 * sqrtpos( pow2(mSys*mSys - mhvMeson*mhvMeson
    - mhvGlue*mhvGlue) - pow2(2. * mhvMeson * mhvGlue) ) / mSys;
  double pz   = (2 * rndmPtr->flat() - 1.) * pAbs;
  double pT   = sqrtpos( pAbs*pAbs - pz*pz);
  double phi  = 2. * M_PI * rndmPtr->flat();
  double px   = pT * cos(phi);
  double py   = pT * sin(phi);

  // Construct four-vectors and boost them to event frame.
  Vec4 phvMeson( px, py, pz, sqrt(mhvMeson*mhvMeson + pAbs*pAbs) );
  Vec4 phvGlue( -px, -py, -pz, sqrt(mhvGlue*mhvGlue + pAbs*pAbs) );
  phvMeson.bst( hvColConfig[0].pSum );
  phvGlue.bst(  hvColConfig[0].pSum );

  // Add produced particles to the event record.
  vector<int> iParton = hvColConfig[0].iParton;
  int iFirst = hvEvent.append( 4900111, 82,  iParton.front(),
    iParton.back(), 0, 0, 0, 0, phvMeson, mhvMeson);
  int iLast  = hvEvent.append( 4900991, 82,  iParton.front(),
    iParton.back(), 0, 0, 0, 0, phvGlue, mhvGlue);

  // Mark original partons as hadronized and set their daughter range.
  for (int i = 0; i < int(iParton.size()); ++i) {
    hvEvent[ iParton[i] ].statusNeg();
    hvEvent[ iParton[i] ].daughters(iFirst, iLast);
  }

  // Done.
  return true;

}

//--------------------------------------------------------------------------

// Insert HV-particles from hvEvent to event.

bool HiddenValleyFragmentation::insertHVevent(Event& event) {

  // Offset for mother/daughter indices.
  hvNewSize = hvEvent.size();
  int nOffset = event.size() - hvOldSize;

  // Copy back HV-particles.
  int iNew, iMot1, iMot2, iDau1, iDau2;
  for (int iHV = hvOldSize; iHV < hvNewSize; ++iHV) {
    iNew = event.append( hvEvent[iHV]);

    // Restore HV-gluon codes. Do not keep HV-colours, to avoid confusion.
    if (hvEvent[iHV].id() == 21) event[iNew].id(4900021);
    event[iNew].cols( 0, 0);

    // Begin history construction.
    iMot1 = hvEvent[iHV].mother1();
    iMot2 = hvEvent[iHV].mother2();
    iDau1 = hvEvent[iHV].daughter1();
    iDau2 = hvEvent[iHV].daughter2();
    // Special mother for partons copied from event, else simple offset.
    // Also set daughters of mothers in original record.
    if (iMot1 > 0 && iMot1 < hvOldSize) {
      iMot1 = hvEvent[iMot1].mother2();
      event[iMot1].statusNeg();
      event[iMot1].daughter1(iNew);
    } else if (iMot1 > 0) iMot1 += nOffset;
    if (iMot2 > 0 && iMot2 < hvOldSize) {
      iMot2 = hvEvent[iMot2].mother2();
      event[iMot2].statusNeg();
      if (event[iMot2].daughter1() == 0) event[iMot2].daughter1(iNew);
      else                               event[iMot2].daughter2(iNew);
    } else if (iMot2 > 0) iMot2 += nOffset;
    if (iDau1 > 0) iDau1 += nOffset;
    if (iDau2 > 0) iDau2 += nOffset;
    event[iNew].mothers( iMot1, iMot2);
    event[iNew].daughters( iDau1, iDau2);
  }

  // Done.
  return true;

}

//==========================================================================

} // end namespace Pythia8
