MODULE moduleCollisions
  USE moduleSpecies
  USE moduleTable

  !Integer for when collisions are computed
  INTEGER:: everyColl

  !Abstract type for collision between two particles
  TYPE, ABSTRACT:: collisionBinary
    TYPE(table1D):: crossSec !cross section of collision
    CONTAINS
      PROCEDURE(collideBinary_interface), PASS, DEFERRED:: collide

  END TYPE collisionBinary

  ABSTRACT INTERFACE
    SUBROUTINE collideBinary_interface(self, part_i, part_j, vRel)
      USE moduleSpecies
      IMPORT:: collisionBinary

      CLASS(collisionBinary), INTENT(in):: self
      TYPE(particle), INTENT(inout), TARGET:: part_i, part_j
      REAL(8), INTENT(in):: vRel

    END SUBROUTINE

  END INTERFACE

  !Container for binary collisions
  TYPE:: collisionCont
    CLASS(collisionBinary), ALLOCATABLE:: obj

  END TYPE collisionCont

  !Binary elastic interaction
  TYPE, EXTENDS(collisionBinary):: collisionBinaryElastic
    CONTAINS
      PROCEDURE, PASS:: collide => collideBinaryElastic

  END TYPE collisionBinaryElastic

  !Ionization binary interaction
  TYPE, EXTENDS(collisionBinary):: collisionBinaryIonization
    REAL(8):: eThreshold !Minimum energy (non-dimensional units) required for ionization
    REAL(8):: deltaV !Change in velocity due to exchange of eThreshold
    CLASS(speciesCharged), POINTER:: electron          !Pointer to species considerer as electrons
    CLASS(speciesCharged), POINTER:: electronSecondary !Pointer to species considerer as secondary electron
    CONTAINS
      PROCEDURE, PASS:: collide => collideBinaryIonization

  END TYPE collisionBinaryIonization

  TYPE, EXTENDS(collisionBinary):: collisionBinaryRecombination
    REAL(8):: eBinding !binding energy of free electron in recombining ion
    REAL(8):: deltaV !Change in velocity due to energy exchange
    CLASS(speciesCharged), POINTER:: electron !Pointer to species considerer as electrons
    CONTAINS
      PROCEDURE, PASS:: collide => collideBinaryRecombination

  END TYPE collisionBinaryRecombination

  !Resonant charge-exchange
  TYPE, EXTENDS(collisionBinary):: collisionBinaryChargeExchange
    CONTAINS
      PROCEDURE, PASS:: collide => collideBinaryChargeExchange

  END TYPE collisionBinaryChargeExchange

  !Type for interaction matrix
  TYPE:: interactionsBinary
    CLASS(speciesGeneric), POINTER:: sp_i
    CLASS(speciesGeneric), POINTER:: sp_j
    INTEGER:: amount
    TYPE(collisionCont), ALLOCATABLE:: collisions(:)
    CONTAINS
      PROCEDURE, PASS:: init => initInteractionBinary
      PROCEDURE, PASS:: getSigmaVrel => getSigmaVrelBinary

  END TYPE interactionsBinary

  !Type to count number of collisions
  TYPE:: tallyCollisions
    INTEGER, ALLOCATABLE:: tally(:)

  END TYPE

  !Number of collision pairs (nSpecies*(nSpecies+1)/2)
  INTEGER:: nCollPairs = 0
  !Collision 'Matrix'. A symmetric 2D matrix put into a 1D array to save memory
  TYPE(interactionsBinary), ALLOCATABLE, TARGET:: interactionMatrix(:)
  !Folder for collision cross section tables
  CHARACTER(:), ALLOCATABLE:: pathCollisions

  CONTAINS
    !Velocity of center of mass of two particles
    PURE FUNCTION velocityCM(m_i, v_i, m_j, v_j) RESULT(vCM)

      IMPLICIT NONE

      REAL(8), INTENT(in):: m_i, m_j
      REAL(8), INTENT(in), DIMENSION(1:3):: v_i, v_j
      REAL(8):: vCM(1:3)

      vCM = (m_i*v_i + m_j*v_j) / (m_i + m_j)

    END FUNCTION velocityCM

    !Random direction for hard sphere collisions
    FUNCTION randomDirectionVHS() RESULT(n)
      USE moduleConstParam
      USE moduleRandom
      IMPLICIT NONE

      REAL(8):: n(1:3)
      REAL(8):: cosXi, sinXi, eps

      cosXi = random(-1.D0, 1.D0)
      sinXi = DSQRT(1.D0 - cosXi**2)
      eps    = random(0.D0, PI2)

      n = (/ cosXi, sinXi*DCOS(eps), sinXi*DSIN(eps) /)

    END FUNCTION randomDirectionVHS

    !Inits the interaction matrix
    SUBROUTINE initInteractionMatrix(matrix)
      USE moduleSpecies
      IMPLICIT NONE

      TYPE(interactionsBinary), INTENT(inout), ALLOCATABLE:: matrix(:)

      nCollPairs = (nSpecies*(nSpecies+1))/2
      ALLOCATE(matrix(1:nCollPairs))

      matrix(:)%amount = 0

    END SUBROUTINE initInteractionMatrix

    !Gets the interaction index from the collision matrix from index i,j
    FUNCTION interactionIndex(i,j) RESULT(k)

      INTEGER:: i, j
      INTEGER:: p
      INTEGER:: k

      k = i + j
      p = (k + ABS(i - j))/2
      k = k + (p*(p-3))/2

    END FUNCTION interactionIndex

    !Inits the binary interaction
    SUBROUTINE initInteractionBinary(self, amount,  i, j)
      USE moduleMath
      USE moduleSpecies
      IMPLICIT NONE

      CLASS(interactionsBinary), INTENT(inout):: self
      INTEGER, INTENT(in):: amount
      INTEGER, INTENT(in):: i, j
      REAL(8):: mass_i, mass_j

      self%sp_i => species(i)%obj
      self%sp_j => species(j)%obj

      self%amount = amount

      mass_i = species(i)%obj%m
      mass_j = species(j)%obj%m

      ALLOCATE(self%collisions(1:self%amount))

    END SUBROUTINE initInteractionBinary

    SUBROUTINE getSigmaVrelBinary (self, vRel, eRel, sigmaVrelTotal, sigmaVrel)
      IMPLICIT NONE

      CLASS(interactionsBinary), INTENT(in):: self
      REAL(8), INTENT(in):: vRel, eRel
      REAL(8), INTENT(out):: sigmaVrelTotal
      REAL(8), INTENT(out), ALLOCATABLE:: sigmaVrel(:)
      INTEGER:: c

      sigmaVrelTotal = 0.D0

      ALLOCATE(sigmaVrel(1:self%amount))

      DO c = 1, self%amount
        sigmaVrel(c) = self%collisions(c)%obj%crossSec%get(eRel)*vRel

      END DO
      sigmaVrelTotal = SUM(sigmaVrel)

    END SUBROUTINE getSigmaVrelBinary

    !ELASTIC COLLISIONS
    !Inits binary elastic collision
    SUBROUTINE initBinaryElastic(collision, crossSectionFilename)
      USE moduleTable
      USE moduleRefParam
      USE moduleConstParam
      IMPLICIT NONE

      CLASS(collisionBinary), INTENT(out), ALLOCATABLE:: collision
      CHARACTER(:), ALLOCATABLE, INTENT(in):: crossSectionFilename

      ALLOCATE(collisionBinaryElastic:: collision)

      !Reads data from file
      CALL collision%crossSec%init(crossSectionFilename)

      !Convert to no-dimensional units
      CALL collision%crossSec%convert(eV2J/(m_ref*v_ref**2), 1.D0/L_ref**2)

    END SUBROUTINE initBinaryElastic

    !Binary elastic process
    SUBROUTINE collideBinaryElastic(self, part_i, part_j, vRel)
      USE moduleSpecies
      USE moduleConstParam
      USE moduleRandom
      USE moduleMath
      IMPLICIT NONE

      CLASS(collisionBinaryElastic), INTENT(in):: self
      TYPE(particle), INTENT(inout), TARGET:: part_i, part_j
      REAL(8), INTENT(in):: vRel
      REAL(8):: m_i, m_j
      REAL(8), DIMENSION(1:3):: vCM, vp

      m_i = part_i%species%m
      m_j = part_j%species%m
      !Applies the collision
      vCM = velocityCM(part_i%weight*m_i, part_i%v, part_j%weight*m_j, part_j%v)
      vp  = vRel*randomDirectionVHS()

      !Assign velocities to particles
      part_i%v = vCM + m_j*vp / (m_i + m_j)
      part_j%v = vCM - m_i*vp / (m_i + m_j)

    END SUBROUTINE collideBinaryElastic

    !ELECTRON IMPACT IONIZATION
    !Inits electron impact ionization
    SUBROUTINE initBinaryIonization(collision, crossSectionFilename, energyThreshold, electron, electronSecondary)
      USE moduleTable
      USE moduleRefParam
      USE moduleConstParam
      USE moduleSpecies
      USE moduleErrors
      IMPLICIT NONE

      CLASS(collisionBinary), INTENT(out), ALLOCATABLE:: collision
      CHARACTER(:), ALLOCATABLE, INTENT(in):: crossSectionFilename
      REAL(8), INTENT(in):: energyThreshold
      CHARACTER(:), ALLOCATABLE, INTENT(in):: electron
      CHARACTER(:), ALLOCATABLE, OPTIONAL, INTENT(in):: electronSecondary
      INTEGER:: electronIndex, electronSecondaryIndex

      ALLOCATE(collisionBinaryIonization:: collision)

      !Reads data from file
      CALL collision%crossSec%init(crossSectionFilename)

      !Convert to no-dimensional units
      CALL collision%crossSec%convert(eV2J/(m_ref*v_ref**2), 1.D0/L_ref**2)

      !Specific parameters for ionization collision
      SELECT TYPE(collision)
      TYPE IS(collisionBinaryIonization)
        !Assign the energy threshold
        !Input energy is in eV. Convert to J with ev2J and then to
        !non-dimensional units.
        collision%eThreshold = energyThreshold*eV2J/(m_ref*v_ref**2)
        !species for impacting electron
        electronIndex = speciesName2Index(electron)
        SELECT TYPE(sp => species(electronIndex)%obj)
        TYPE IS(speciesCharged)
          collision%electron => sp

        CLASS DEFAULT
          CALL criticalError("Species " // sp%name // " chosen for " // &
                             "impacting electron is not a charged species", 'initBinaryIonization')

        END SELECT

        IF (PRESENT(electronSecondary)) THEN
          electronSecondaryIndex = speciesName2Index(electronSecondary)
          SELECT TYPE(sp => species(electronSecondaryIndex)%obj)
          TYPE IS(speciesCharged)
            collision%electronSecondary => sp

          CLASS DEFAULT
            CALL criticalError("Species " // sp%name // " chosen for " // &
                               "secondary electron is not a charged species", 'initBinaryIonization')

          END SELECT

        ELSE
          collision%electronSecondary => NULL()

        END IF

        !momentum change per ionization process
        collision%deltaV = sqrt(collision%eThreshold / collision%electron%m)

      END SELECT

    END SUBROUTINE initBinaryIonization

    !Binary electron impact ionization process
    SUBROUTINE collideBinaryIonization(self, part_i, part_j, vRel)
      USE moduleSpecies
      USE moduleErrors
      USE moduleList
      USE moduleRandom
      USE moduleMath
      USE OMP_LIB
      IMPLICIT NONE

      CLASS(collisionBinaryIonization), INTENT(in):: self
      TYPE(particle), INTENT(inout), TARGET:: part_i, part_j
      REAL(8), INTENT(in):: vRel
      REAL(8):: rMass, eRel
      TYPE(particle), POINTER:: electron => NULL(), neutral => NULL()
      REAL(8), DIMENSION(1:3):: vChange
      TYPE(particle), POINTER:: newElectron => NULL(), remainingNeutral => NULL()

      rMass = reducedMass(part_i%weight*part_i%species%m, part_j%weight*part_j%species%m)
      eRel = rMass*vRel**2
      !Relative energy must be higher than threshold
      IF (eRel > self%eThreshold) THEN
        IF    (ASSOCIATED(part_i%species, self%electron)) THEN
          electron => part_i
          neutral  => part_j

        ELSEIF(ASSOCIATED(part_j%species, self%electron)) THEN
          electron => part_j
          neutral  => part_i

        ELSE
          CALL criticalError("No matching between input particles and ionizing species", 'collideBinaryIonization')

        END IF

        !Exchange of velocity between particles
        vChange = self%deltaV*randomDirectionVHS()
        
        !Energy is loss by the primary electron
        electron%v = electron%v - vChange

        !Creates a new electron from ionization
        ALLOCATE(newElectron)

        !Copy basic information from primary electron
        newElectron = electron

        !If secondary electron species indicates, convert
        IF (ASSOCIATED(self%electronSecondary)) THEN
          newElectron%species => self%electronSecondary

        END IF

        !Secondary electorn gains energy from ionization
        newElectron%v       =  vChange

        !Correct the weight of the particles
        IF (electron%weight >= neutral%weight) THEN
          !If primary electron is hevier than neutral, reduce weight to secondary electron
          newElectron%weight = neutral%weight

        ELSEIF (electron%weight < neutral%weight) THEN
          !If primary electron is ligther than neutral, change weight of neutral and create new neutral
          ALLOCATE(remainingNeutral)

          remainingNeutral = neutral

          remainingNeutral%weight = neutral%weight - electron%weight

          neutral%weight = electron%weight

        END IF

        !Ionize neutral particle
        SELECT TYPE(sp => neutral%species)
        TYPE IS(speciesNeutral)
          CALL sp%ionize(neutral)

        CLASS DEFAULT
          CALL criticalError(sp%name // " is not a neutral", 'collideBinaryIonization')
          RETURN

        END SELECT

        !Adds new particles to the list
        CALL partCollisions%setLock()
        CALL partCollisions%add(newElectron)
        IF (ASSOCIATED(remainingNeutral)) THEN
          CALL partCollisions%add(remainingNeutral)

        END IF
        CALL partCollisions%unsetLock()

      END IF

    END SUBROUTINE collideBinaryIonization

    !ELECTRON ION RESONANT RECOMBINATION
    !Inits electron ion recombination
    SUBROUTINE initBinaryRecombination(collision, crossSectionFilename, energyBinding, electron)
      USE moduleTable
      USE moduleRefParam
      USE moduleConstParam
      USE moduleSpecies
      USE moduleErrors
      IMPLICIT NONE

      CLASS(collisionBinary), INTENT(out), ALLOCATABLE:: collision
      CHARACTER(:), ALLOCATABLE, INTENT(in):: crossSectionFilename
      REAL(8), INTENT(in):: energyBinding
      CHARACTER(:), ALLOCATABLE, INTENT(in):: electron
      INTEGER:: electronIndex

      ALLOCATE(collisionBinaryRecombination:: collision)

      !Reads data from file
      CALL collision%crossSec%init(crossSectionFilename)

      !Convert to no-dimensional units
      CALL collision%crossSec%convert(eV2J/(m_ref*v_ref**2), 1.D0/L_ref**2)

      !Specific parameters for ionization collision
      SELECT TYPE(collision)
      TYPE IS(collisionBinaryRecombination)
        !Assign the energy threshold
        !Input energy is in eV. Convert to J with ev2J and then to
        !non-dimensional units.
        collision%eBinding = energyBinding*eV2J/(m_ref*v_ref**2)
        electronIndex = speciesName2Index(electron)
        SELECT TYPE(sp => species(electronIndex)%obj)
        TYPE IS(speciesCharged)
          collision%electron => sp

        CLASS DEFAULT
          CALL criticalError("Species " // sp%name // " chosen for recombination is not a charged species", &
                             'initBinaryRecombination')

        END SELECT

      END SELECT

    END SUBROUTINE initBinaryRecombination

    !Binary recombination
    SUBROUTINE collideBinaryRecombination(self, part_i, part_j, vRel)
      USE moduleSpecies
      USE moduleErrors
      USE moduleList
      USE moduleRandom
      USE moduleMath
      IMPLICIT NONE

      CLASS(collisionBinaryRecombination), INTENT(in):: self
      REAL(8), INTENT(in):: vRel
      TYPE(particle), INTENT(inout), TARGET:: part_i, part_j
      TYPE(particle), POINTER:: electron => NULL(), ion => NULL()
      REAL(8), DIMENSION(1:3):: vp_i
      TYPE(particle), POINTER:: remainingIon => NULL()

      IF    (ASSOCIATED(part_i%species, self%electron)) THEN
        electron => part_i
        ion      => part_j

      ELSEIF(ASSOCIATED(part_j%species, self%electron)) THEN
        electron => part_j
        ion      => part_i

      ELSE
        CALL criticalError("No matching between input particles and ionizing species", 'collideBinaryIonization')

      END IF

      !Excess energy
      !TODO: This energy should be transformed into photons
      vp_i = ion%v*     (1.D0 -  (vRel + self%deltaV)/NORM2(ion%v))

      IF (electron%weight > ion%weight) THEN
        !Reduce weight of primary electron but particle continues
        electron%weight = electron%weight - ion%weight

      ELSE
        !Remove electron from simulation
        electron%n_in = .FALSE.

        !There is some ion remaining
        IF (electron%weight < ion%weight) THEN
          ALLOCATE(remainingIon)

          remainingIon = ion

          remainingIon%weight = ion%weight - electron%weight

          ion%weight = electron%weight

        END IF

      END IF

      !Neutralize ion particle
      SELECT TYPE(sp => ion%species)
      TYPE IS(speciesCharged)
        CALL sp%neutralize(ion)

      CLASS DEFAULT
        CALL criticalError(sp%name // " is not a charge", 'collideBinaryRecombination')

      END SELECT

      !Adds new particles to the list
      IF (ASSOCIATED(remainingIon)) THEN
        CALL partCollisions%setLock()
        CALL partCollisions%add(remainingIon)
        CALL partCollisions%unsetLock()
      END IF

    END SUBROUTINE collideBinaryRecombination

    !RESONANT CHARGE EXCHANGE
    !Inits resonant charge exchange
    SUBROUTINE initBinaryChargeExchange(collision, crossSectionFilename)
      USE moduleTable
      USE moduleRefParam
      USE moduleConstParam
      IMPLICIT NONE

      CLASS(collisionBinary), INTENT(out), ALLOCATABLE:: collision
      CHARACTER(:), ALLOCATABLE, INTENT(in):: crossSectionFilename

      ALLOCATE(collisionBinaryChargeExchange:: collision)

      !Reads data from file
      CALL collision%crossSec%init(crossSectionFilename)

      !Convert to no-dimensional units
      CALL collision%crossSec%convert(eV2J/(m_ref*v_ref**2), 1.D0/L_ref**2)

    END SUBROUTINE initBinaryChargeExchange

    SUBROUTINE collideBinaryChargeExchange(self, part_i, part_j, vRel)
      USE moduleSpecies
      USE moduleRandom
      USE moduleMath
      IMPLICIT NONE

      CLASS(collisionBinaryChargeExchange), INTENT(in):: self
      REAL(8), INTENT(in):: vRel
      TYPE(particle), INTENT(inout), TARGET:: part_i, part_j

      SELECT TYPE(sp => part_i%species)
      TYPE IS (speciesNeutral)
        !Species i is neutral, ionize particle i
        CALL sp%ionize(part_i)

      TYPE IS (speciesCharged)
        !Species i is charged, neutralize particle i
        CALL sp%neutralize(part_i)

      END SELECT

      SELECT TYPE(sp => part_j%species)
      TYPE IS (speciesNeutral)
        !Species j is neutral, ionize particle j
        CALL sp%ionize(part_j)

      TYPE IS (speciesCharged)
        !Species j is charged, neutralize particle j
        CALL sp%neutralize(part_j)

      END SELECT

    END SUBROUTINE collideBinaryChargeExchange

END MODULE moduleCollisions
