!moduleMeshBoundary: Boundary functions for the mesh edges
submodule(moduleMesh) boundaryParticle
  contains
    module function boundaryParticleName_to_Index(boundaryName) result(bp)
      use moduleErrors
      implicit none

      character(:), allocatable:: boundaryName
      integer:: bp
      integer:: b

      bp = 0
      do b = 1, nBoundariesParticle
        if (boundaryName == boundariesParticle(b)%obj%name) then
          bp = boundariesParticle(b)%obj%n

        end if

      end do

      if (bp == 0) then
        call criticalError('Boundary ' // boundaryName // ' not found', 'boundaryParticleName_to_Index')
      
      end if
    
    end function boundaryParticleName_to_Index

    module SUBROUTINE initWallTemperature(boundary, T, c)
      USE moduleRefParam
      IMPLICIT NONE

      CLASS(boundaryParticleGeneric), ALLOCATABLE, INTENT(inout):: boundary
      REAL(8), INTENT(in):: T, c !Wall temperature and specific heat
      REAL(8):: vTh

      vTh = DSQRT(c * T) / v_ref
      allocate(boundaryWallTemperature:: boundary)

      select type(boundary)
      type is(boundaryWallTemperature)
        boundary%vTh = vTh

      end select

    END SUBROUTINE initWallTemperature

    module SUBROUTINE initIonization(boundary, mImpact, m0, n0, v0, T0, ion, effTime, crossSection, eThreshold, electronSecondary)
      use moduleRefParam
      use moduleSpecies
      use moduleCaseParam
      use moduleConstParam
      use moduleErrors, only: criticalError
      implicit none

      class(boundaryParticleGeneric), allocatable, intent(inout):: boundary
      real(8), intent(in):: mImpact
      real(8), intent(in):: m0, n0, v0(1:3), T0 !Neutral properties
      integer, intent(in):: ion
      real(8), intent(in):: effTime
      character(:), allocatable, intent(in):: crossSection
      real(8), intent(in):: eThreshold
      integer, optional, intent(in):: electronSecondary

      ALLOCATE(boundaryIonization:: boundary)

      SELECT TYPE(boundary)
      TYPE IS(boundaryIonization)
        boundary%m0 = m0 / m_ref
        boundary%n0 = n0 * Vol_ref
        boundary%v0 = v0 / v_ref
        boundary%vTh = DSQRT(kb*T0/m0)/v_ref
        boundary%species => species(ion)%obj
        IF (PRESENT(electronSecondary)) THEN
          SELECT TYPE(sp => species(electronSecondary)%obj)
          TYPE IS(speciesCharged)
            boundary%electronSecondary => sp

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

          END SELECT

        ELSE
          boundary%electronSecondary => NULL()

        END IF
        boundary%effectiveTime = effTime / ti_ref
        CALL boundary%crossSection%init(crossSection)
        CALL boundary%crossSection%convert(eV2J/(m_ref*v_ref**2), 1.D0/L_ref**2)
        boundary%eThreshold = eThreshold*eV2J/(m_ref*v_ref**2)
        boundary%deltaV = DSQRT(boundary%eThreshold/mImpact)

      END SELECT

    END SUBROUTINE initIonization

    module subroutine initQuasiNeutrality(boundary)
      implicit none

      class(boundaryParticleGeneric), allocatable, intent(inout):: boundary
      integer:: e, et

      allocate(boundaryQuasiNeutrality:: boundary)

      select type(boundary)
      type is(boundaryQuasiNeutrality)
        boundary%alpha = 0.d0
        allocate(boundary%edges(0))

      end select

    end subroutine initQuasiNeutrality

    module SUBROUTINE reflection(self, edge, part)
      USE moduleCaseParam
      USE moduleSpecies
      IMPLICIT NONE

      class(boundaryReflection), intent(in):: self
      CLASS(meshEdge), INTENT(inout)::        edge
      CLASS(particle), INTENT(inout)::        part
      !rp  = intersection between particle and edge
      !rpp = final position of particle
      !vpp = final velocity of particle
      REAL(8), DIMENSION(1:3):: rp, vpp

      !Reflect particle velocity
      vpp = part%v - 2.D0*DOT_PRODUCT(part%v, edge%normal)*edge%normal
      part%v = vpp

      rp = edge%intersection(part%r)

      part%r = 2.D0*(rp - part%r) + part%r

      !particle is assumed to be inside
      part%n_in = .TRUE.

    END SUBROUTINE reflection

    !Absoption in a surface
    module SUBROUTINE absorption(self, edge, part)
      USE moduleCaseParam
      USE moduleSpecies
      IMPLICIT NONE

      class(boundaryAbsorption), intent(in):: self
      CLASS(meshEdge), INTENT(inout)::        edge
      CLASS(particle), INTENT(inout)::        part
      REAL(8):: rpp(1:3) !Position of particle projected to the edge
      REAL(8):: d !Distance from particle to edge

      rpp = edge%intersection(part%r)

      d = NORM2(rpp - part%r)

      IF (d >= 0.D0) THEN
        part%weight = part%weight/d

      END IF

      !Assign new position to particle
      part%r = rpp
      !Remove particle from the domain
      part%n_in = .FALSE.

      !Scatter particle in associated volume
      IF (ASSOCIATED(edge%e1)) THEN
        CALL edge%e1%scatter(edge%e1%nNodes, part)

      ELSE
        CALL edge%e2%scatter(edge%e2%nNodes, part)

      END IF

    END SUBROUTINE absorption

    !Transparent boundary condition
    module SUBROUTINE transparent(self, edge, part)
      USE moduleSpecies
      IMPLICIT NONE

      class(boundaryTransparent), intent(in):: self
      CLASS(meshEdge), INTENT(inout)::         edge
      CLASS(particle), INTENT(inout)::         part

      !Removes particle from domain
      part%n_in = .FALSE.

    END SUBROUTINE transparent

    !Symmetry axis. Reflects particles.
    !Although this function should never be called, it is set as a reflective boundary
    !to properly deal with possible particles reaching a corner and selecting this boundary.
    module SUBROUTINE symmetryAxis(self, edge, part)
      USE moduleSpecies
      IMPLICIT NONE

      class(boundaryAxis), intent(in):: self
      CLASS(meshEdge), INTENT(inout)::  edge
      CLASS(particle), INTENT(inout)::  part

      CALL genericReflection(edge, part)

    END SUBROUTINE symmetryAxis

    !Wall with temperature
    module SUBROUTINE wallTemperature(self, edge, part)
      USE moduleSpecies
      USE moduleRandom
      IMPLICIT NONE

      class(boundaryWallTemperature), intent(in):: self
      CLASS(meshEdge), INTENT(inout)::               edge
      CLASS(particle), INTENT(inout)::               part
      INTEGER:: i

      !Modifies particle velocity according to wall temperature
      DO i = 1, 3
        part%v(i) = part%v(i) + self%vTh*randomMaxwellian()

      END DO
          
      CALL genericReflection(edge, part)

    END SUBROUTINE wallTemperature

    !Ionization surface: an electron will pass through the surface
    ! and create an ion-electron pair based on a neutral background
    module SUBROUTINE ionization(self, edge, part)
      USE moduleList
      USE moduleSpecies
      USE moduleMesh
      USE moduleRefParam
      USE moduleRandom
      USE moduleMath
      IMPLICIT NONE

      class(boundaryIonization), intent(in):: self
      CLASS(meshEdge), INTENT(inout)::        edge
      CLASS(particle), INTENT(inout)::        part
      REAL(8):: vRel, eRel, mRel !relative velocity, energy and mass
      INTEGER:: nIonizations !Number of ionizations based on eRel
      REAL(8):: pIonization !Probability of ionization of each event
      INTEGER:: p
      REAL(8):: v0(1:3) !random velocity of neutral
      TYPE(particle), POINTER:: newElectron
      TYPE(particle), POINTER:: newIon

      mRel = reducedMass(self%m0, part%species%m)
      vRel = SUM(DABS(part%v-self%v0))
      eRel = mRel*vRel**2*5.D-1

      !Maximum number of possible ionizations based on relative energy
      nIonizations = FLOOR(eRel/self%eThreshold)

      DO p = 1, nIonizations
        !Get probability of ionization
        pIonization = 1.D0 - DEXP(-self%n0*self%crossSection%get(eRel)*vRel*self%effectiveTime/REAL(nIonizations))

        !If a random number is below the probability of ionization, create new pair of ion-electron
        IF (random() < pIonization) THEN
          !Assign random velocity to the neutral
          v0(1) = self%v0(1) + self%vTh*randomMaxwellian()
          v0(2) = self%v0(2) + self%vTh*randomMaxwellian()
          v0(3) = self%v0(3) + self%vTh*randomMaxwellian()

          !Allocates the new particles
          ALLOCATE(newElectron)
          ALLOCATE(newIon)

          IF (ASSOCIATED(self%electronSecondary)) THEN
            newElectron%species => self%electronSecondary

          ELSE
            newElectron%species => part%species

          END IF
          newIon%species      => self%species

          newElectron%v = v0 + (1.D0 + self%deltaV*v0/NORM2(v0))
          newIon%v      = v0

          newElectron%r = edge%randPos()
          newIon%r      = newElectron%r

          IF (ASSOCIATED(edge%e1)) THEN
            newElectron%cell = edge%e1%n

          ELSEIF (ASSOCIATED(edge%e2)) THEN
            newElectron%cell = edge%e2%n

          END IF
          newIon%cell      = newElectron%cell

          newElectron%Xi = mesh%cells(part%cell)%obj%phy2log(newElectron%r)
          newIon%Xi      = newElectron%Xi

          newElectron%weight = part%weight
          newIon%weight      = newElectron%weight

          newElectron%n_in = .TRUE.
          newIon%n_in      = .TRUE.

          !Add particles to list
          CALL partSurfaces%setLock()
          CALL partSurfaces%add(newElectron)
          CALL partSurfaces%add(newIon)
          CALL partSurfaces%unsetLock()

          !Electron loses energy due to ionization
          eRel = eRel - self%eThreshold
          vRel = 2.D0*DSQRT(eRel)/mRel

          !Reduce number of possible ionizations
          nIonizations = nIonizations - 1

        END IF

      END DO

      !Removes ionizing electron regardless the number of pair created
      part%n_in = .FALSE.

    END SUBROUTINE ionization

    module subroutine quasiNeutrality(self, edge, part)
      use moduleRandom
      implicit none

      class(boundaryQuasiNeutrality), intent(in):: self
      class(meshEdge), intent(inout)::             edge
      class(particle), intent(inout)::             part
      real(8), allocatable:: density(:)
      class(meshCell), pointer:: cell
      real(8):: EF_dir
      real(8):: alpha


      if (associated(edge%e1)) then
        cell => edge%e1

      else
        cell => edge%e2

      end if
      
      if (random() <= alpha) then
        call genericReflection(edge, part)

      else
        call genericTransparent(edge, part)

      end if

    end subroutine quasiNeutrality

    ! Generic boundary conditions for internal use
    module subroutine genericReflection(edge, part)
      use moduleCaseParam
      use moduleSpecies
      implicit none

      class(meshEdge), intent(inout)::        edge
      class(particle), intent(inout)::        part
      !rp  = intersection between particle and edge
      !rpp = final position of particle
      !vpp = final velocity of particle
      real(8), dimension(1:3):: rp, vpp

      !Reflect particle velocity
      vpp = part%v - 2.D0*dot_product(part%v, edge%normal)*edge%normal
      part%v = vpp

      rp = edge%intersection(part%r)

      part%r = 2.D0*(rp - part%r) + part%r

      !particle is assumed to be inside
      part%n_in = .TRUE.

    end subroutine genericReflection

    module subroutine genericTransparent(edge, part)
      use moduleSpecies
      implicit none

      class(meshEdge), intent(inout)::         edge
      class(particle), intent(inout)::         part

      !Removes particle from domain
      part%n_in = .FALSE.

    end subroutine genericTransparent

end submodule boundaryParticle
