!moduleMesh: General module for Finite Element mesh
MODULE moduleMesh
  USE moduleList
  USE moduleOutput
  USE moduleCollisions
  use moduleSpecies, only: nSpecies
  IMPLICIT NONE

  ! MESH ELEMENTS
  !Generic mesh element
  TYPE, PUBLIC, ABSTRACT:: meshElement
    !Index
    INTEGER:: n = 0

  END TYPE meshElement

  !Generic Node element
  TYPE, PUBLIC, ABSTRACT, EXTENDS(meshElement):: meshNode
    !Node volume
    REAL(8):: v = 0.D0
    !Output values
    TYPE(outputNode), ALLOCATABLE:: output(:)
    TYPE(emNode):: emData
    !Lock indicator for scattering
    INTEGER(KIND=OMP_LOCK_KIND):: lock
    CONTAINS
      !DEFERED PROCEDURES
      PROCEDURE(initNode_interface),    DEFERRED, PASS:: init
      PROCEDURE(getCoord_interface),    DEFERRED, PASS:: getCoordinates
      !GENERIC PROCEDURES
      PROCEDURE,                                  PASS:: resetOutput

  END TYPE meshNode

  interface
    pure module subroutine resetOutput(self)
      class(meshNode), intent(inout):: self

    end subroutine resetOutput

  end interface

  ABSTRACT INTERFACE
    !Interface of init a node (3D generic coordinates)
    SUBROUTINE initNode_interface(self, n, r)
      IMPORT:: meshNode
      CLASS(meshNode), INTENT(out):: self
      INTEGER, INTENT(in):: n
      REAL(8), INTENT(in):: r(1:3)

    END SUBROUTINE initNode_interface

    !Interface to get coordinates from node
    PURE FUNCTION getCoord_interface(self) RESULT(r)
      IMPORT:: meshNode
      CLASS(meshNode), INTENT(in):: self
      REAL(8):: r(1:3)

    END FUNCTION getCoord_interface

  END INTERFACE

  !Containers for nodes in the mesh
  TYPE:: meshNodeCont
    CLASS(meshNode), ALLOCATABLE:: obj

  END TYPE meshNodeCont

  ! Array of pointers to nodes.
  TYPE:: meshNodePointer
    CLASS(meshNode), POINTER:: obj
    contains
      procedure, pass:: meshNodePointer_equal_type_type
      procedure, pass:: meshNodePointer_equal_type_int
      generic:: operator(==) => meshNodePointer_equal_type_type, meshNodePointer_equal_type_int

  END TYPE meshNodePointer

  interface
    module subroutine meshNodePointer_add(self, node)
      class(meshNodePointer), allocatable, intent(inout):: self(:)
      integer, intent(in):: node

    end subroutine meshNodePointer_add

    module function meshNodePointer_equal_type_type(self, other) result(isEqual)
      class(meshNodePointer), intent(in):: self, other
      logical:: isEqual

    end function meshNodePointer_equal_type_type

    module function meshNodePointer_equal_type_int(self, other) result(isEqual)
      class(meshNodePointer), intent(in):: self
      integer, intent(in):: other
      logical:: isEqual

    end function meshNodePointer_equal_type_int

  end interface

  !Parent of Edge element
  TYPE, PUBLIC, ABSTRACT, EXTENDS(meshElement):: meshEdge
    !Nomber of nodes in the edge
    INTEGER:: nNodes
    !Connectivity to cells
    CLASS(meshCell), POINTER:: e1 => NULL(), e2 => NULL()
    !Connectivity to cells in meshColl
    CLASS(meshCell), POINTER:: eColl => NULL()
    !Normal vector
    REAL(8):: normal(1:3)
    ! Surface of edge
    REAL(8):: surface = 0.D0
    !Pointer to boundary per species
    TYPE(boundaryParticlePointer), allocatable:: boundariesParticle(:)
    !Physical surface for the edge
    CONTAINS
      !DEFERED PROCEDURES
      PROCEDURE(initEdge_interface),         DEFERRED, PASS:: init
      PROCEDURE(getNodesEdge_interface),     DEFERRED, PASS:: getNodes
      PROCEDURE(intersectionEdge_interface), DEFERRED, PASS:: intersection
      PROCEDURE(randPosEdge_interface),      DEFERRED, PASS:: randPos
      procedure(centerEdge_interface),       deferred, pass:: center
      PROCEDURE(fPsi_interface),             DEFERRED, NOPASS:: fPsi
      !Gather value and spatial derivative on the nodes at position Xi
      PROCEDURE, PASS, PRIVATE:: gatherF_edge_scalar
      GENERIC:: gatherF  => gatherF_edge_scalar

  END TYPE meshEdge

  interface
    pure module function gatherF_edge_scalar(self, Xi, nNodes, valNodes) result(f)
      class(meshEdge), intent(in):: self
      real(8), intent(in):: Xi(1:3)
      integer, intent(in):: nNodes
      real(8), intent(in):: valNodes(1:nNodes)
      real(8):: f

    end function gatherF_edge_scalar

  end interface

  ABSTRACT INTERFACE
    !Inits the edge parameters
    subroutine initEdge_interface(self, n, p, ps)
      use moduleSpecies, only:nSpecies
      import:: meshEdge

      class(meshEdge), intent(out):: self
      integer, intent(in):: n
      integer, intent(in):: p(:)
      integer, intent(in):: ps

    end subroutine initEdge_interface

    !Get nodes index from node
    PURE FUNCTION getNodesEdge_interface(self, nNodes) RESULT(n)
      IMPORT:: meshEdge
      CLASS(meshEdge), INTENT(in):: self
      INTEGER, INTENT(in):: nNodes
      INTEGER:: n(1:nNodes)

    END FUNCTION getNodesEdge_interface

    !Returns the intersecction between an edge and a line defined by point r0
    PURE FUNCTION intersectionEdge_interface(self, r0) RESULT(r)
      IMPORT:: meshEdge
      CLASS(meshEdge), INTENT(in):: self
      REAL(8), INTENT(in), DIMENSION(1:3):: r0
      REAL(8):: r(1:3)

    END FUNCTION intersectionEdge_interface

    !Returns a random position in the edge
    FUNCTION randPosEdge_interface(self) RESULT(r)
      IMPORT:: meshEdge
      CLASS(meshEdge), INTENT(in):: self
      REAL(8):: r(1:3)

    END FUNCTION randPosEdge_interface

    ! Returns the center point of an edge
    FUNCTION centerEdge_interface(self) RESULT(r)
      IMPORT:: meshEdge
      CLASS(meshEdge), INTENT(in):: self
      REAL(8):: r(1:3)

    END FUNCTION centerEdge_interface

  END INTERFACE

  !Containers for edges in the mesh
  TYPE:: meshEdgeCont
    CLASS(meshEdge), ALLOCATABLE:: obj

  END TYPE meshEdgeCont

  ! Array of pointers to edges.
  type:: meshEdgePointer
    class(meshEdge), pointer:: obj
    contains
      procedure, pass:: meshEdgePointer_equal_type_type
      procedure, pass:: meshEdgePointer_equal_type_int
      generic:: operator(==) => meshEdgePointer_equal_type_type, meshEdgePointer_equal_type_int

  end type meshEdgePointer

  interface
    module subroutine meshEdgePointer_add(self, edge)
      class(meshEdgePointer), allocatable, intent(inout):: self(:)
      integer, intent(in):: edge

    end subroutine meshEdgePointer_add

    module function meshEdgePointer_equal_type_type(self, other) result(isEqual)
      class(meshEdgePointer), intent(in):: self, other
      logical:: isEqual

    end function meshEdgePointer_equal_type_type

    module function meshEdgePointer_equal_type_int(self, other) result(isEqual)
      class(meshEdgePointer), intent(in):: self
      integer, intent(in):: other
      logical:: isEqual

    end function meshEdgePointer_equal_type_int

  end interface

  !Parent of cell element
  TYPE, PUBLIC, ABSTRACT, EXTENDS(meshElement):: meshCell
    !Number of nodes in the cell
    INTEGER:: nNodes
    !Maximum collision rate
    REAL(8), ALLOCATABLE:: sigmaVrelMax(:)
    !Arrays for counting number of collisions
    TYPE(tallyCollisions), ALLOCATABLE:: tallyColl(:)
    !Volume
    REAL(8):: volume = 0.D0
    !List of particles inside the volume
    TYPE(listNode), ALLOCATABLE:: listPart_in(:)
    !Lock indicator for listPart_in
    INTEGER(KIND=OMP_LOCK_KIND):: lock
    !Total weight of particles inside cell
    REAL(8), ALLOCATABLE:: totalWeight(:)
    CONTAINS
      !DEFERRED PROCEDURES
      !Init the cell
      PROCEDURE(initCell_interface),         DEFERRED, PASS::   init
      !Get the index of the nodes
      PROCEDURE(getNodesCell_interface),     DEFERRED, PASS::   getNodes
      !Calculate random position on the cell
      PROCEDURE(randPosCell_interface),      DEFERRED, PASS::   randPos
      !Obtain functions and values of cell natural functions
      PROCEDURE(fPsi_interface),             DEFERRED, NOPASS:: fPsi
      PROCEDURE(dPsi_interface),             DEFERRED, NOPASS:: dPsi
      PROCEDURE(partialDer_interface),       DEFERRED, PASS::   partialDer
      PROCEDURE(detJac_interface),           DEFERRED, NOPASS:: detJac
      PROCEDURE(invJac_interface),           DEFERRED, NOPASS:: invJac
      !Procedures to get specific values in the node
      PROCEDURE(gatherCellArray_interface),  DEFERRED, PASS::   gatherElectricField
      PROCEDURE(gatherCellArray_interface),  DEFERRED, PASS::   gatherMagneticField
      !Compute K and F to solve PDE on the mesh
      PROCEDURE(elemK_interface),            DEFERRED, PASS::   elemK
      PROCEDURE(elemF_interface),            DEFERRED, PASS::   elemF
      !Check if particle is inside the cell
      PROCEDURE(inside_interface),           DEFERRED, NOPASS:: inside
      !Convert physical coordinates (r) into logical coordinates (Xi)
      PROCEDURE(phy2log_interface),          DEFERRED, PASS::   phy2log
      !Returns the neighbour element based on particle position outside the cell
      PROCEDURE(neighbourElement_interface), DEFERRED, PASS::   neighbourElement
      !Scatter properties of particles on cell nodes
      PROCEDURE, PASS:: scatter
      !Subroutine to find in which cell a particle is located
      PROCEDURE, PASS:: findCell
      !Gather value and spatial derivative on the nodes at position Xi
      procedure, pass, private:: gatherF_cell_scalar
      procedure, pass, private:: gatherF_cell_array
      procedure, pass, private:: gatherDF_cell_scalar
      generic:: gatherF  => gatherF_cell_scalar, gatherF_cell_array
      generic:: gatherDF => gatherDF_cell_scalar

  END TYPE meshCell

  interface
    recursive module subroutine findCell(self, part, oldCell)
      use moduleSpecies, only: particle

      class(meshCell), intent(inout):: self
      class(particle), intent(inout), target:: part
      class(meshCell), optional, intent(in):: oldCell

    end subroutine findCell

    module subroutine scatter(self, nNodes, part)
      use moduleSpecies, only: particle

      class(meshCell), intent(inout):: self
      integer, intent(in):: nNodes
      class(particle), intent(in):: part

    end subroutine scatter

    pure module function gatherF_cell_scalar(self, Xi, nNodes, valNodes) result(f)
      class(meshCell), intent(in):: self
      real(8), intent(in):: Xi(1:3)
      integer, intent(in):: nNodes
      real(8), intent(in):: valNodes(1:nNodes)
      real(8):: f

    end function gatherF_cell_scalar

    pure module function gatherF_cell_array(self, Xi, nNodes, valNodes) result(f)
      class(meshCell), intent(in):: self
      real(8), intent(in):: Xi(1:3)
      integer, intent(in):: nNodes
      real(8), intent(in):: valNodes(1:nNodes, 1:3)
      real(8):: f(1:3)

    end function gatherF_cell_array

    pure module function gatherDF_cell_scalar(self, Xi, nNodes, valNodes) RESULT(df)

      class(meshCell), intent(in):: self
      real(8), intent(in):: Xi(1:3)
      integer, intent(in):: nNodes
      real(8), intent(in):: valNodes(1:nNodes)
      real(8):: df(1:3)

    end function gatherDF_cell_scalar

  end interface

  ABSTRACT INTERFACE
    SUBROUTINE initCell_interface(self, n, p, nodes)
      IMPORT:: meshCell, meshNodeCont
      CLASS(meshCell), INTENT(out):: self
      INTEGER, INTENT(in):: n
      INTEGER, INTENT(in):: p(:)
      TYPE(meshNodeCont), INTENT(in), TARGET:: nodes(:)

    END SUBROUTINE initCell_interface

    PURE FUNCTION getNodesCell_interface(self, nNodes) RESULT(n)
      IMPORT:: meshCell
      CLASS(meshCell), INTENT(in):: self
      INTEGER, INTENT(in):: nNodes
      INTEGER:: n(1:nNodes)

    END FUNCTION getNodesCell_interface

    FUNCTION randPosCell_interface(self) RESULT(r)
      IMPORT:: meshCell
      CLASS(meshCell), INTENT(in):: self
      REAL(8):: r(1:3)

    END FUNCTION randPosCell_interface

    PURE FUNCTION fPsi_interface(Xi, nNodes) RESULT(fPsi)
      REAL(8), INTENT(in):: Xi(1:3)
      INTEGER, INTENT(in):: nNodes
      REAL(8):: fPsi(1:nNodes)

    END FUNCTION fPsi_interface

    PURE FUNCTION dPsi_interface(Xi, nNodes) RESULT(dPsi)
      REAL(8), INTENT(in):: Xi(1:3)
      INTEGER, INTENT(in):: nNodes
      REAL(8):: dPsi(1:3, 1:nNodes)

    END FUNCTION dPsi_interface

    PURE FUNCTION partialDer_interface(self, nNodes, dPsi) RESULT(pDer)
      IMPORT:: meshCell
      CLASS(meshCell), INTENT(in):: self
      INTEGER, INTENT(in):: nNodes
      REAL(8), INTENT(in):: dPsi(1:3, 1:nNodes)
      REAL(8):: pDer(1:3, 1:3)

    END FUNCTION partialDer_interface

    PURE FUNCTION detJac_interface(pDer) RESULT(dJ)
      REAL(8), INTENT(in):: pDer(1:3,1:3)
      REAL(8):: dJ

    END FUNCTION detJac_interface

    PURE FUNCTION invJac_interface(pDer) RESULT(invJ)
      REAL(8), INTENT(in):: pDer(1:3,1:3)
      REAL(8):: invJ(1:3,1:3)

    END FUNCTION invJac_interface

    PURE FUNCTION gatherCellArray_interface(self, Xi) RESULT(array)
      IMPORT:: meshCell
      CLASS(meshCell), INTENT(in):: self
      REAL(8), INTENT(in):: Xi(1:3)
      REAL(8):: array(1:3)

    END FUNCTION gatherCellArray_interface

    PURE FUNCTION elemK_interface(self, nNodes) RESULT(localK)
      IMPORT:: meshCell
      CLASS(meshCell), INTENT(in):: self
      INTEGER, INTENT(in):: nNodes
      REAL(8):: localK(1:nNodes,1:nNodes)

    END FUNCTION elemK_interface

    PURE FUNCTION elemF_interface(self, nNodes, source) RESULT(localF)
      IMPORT:: meshCell
      CLASS(meshCell), INTENT(in):: self
      INTEGER, INTENT(in):: nNodes
      REAL(8), INTENT(in):: source(1:nNodes)
      REAL(8):: localF(1:nNodes)

    END FUNCTION elemF_interface

    PURE FUNCTION inside_interface(Xi) RESULT(ins)
      IMPORT:: meshCell
      REAL(8), INTENT(in):: Xi(1:3)
      LOGICAL:: ins

    END FUNCTION inside_interface

    PURE FUNCTION phy2log_interface(self,r) RESULT(Xi)
      IMPORT:: meshCell
      CLASS(meshCell), INTENT(in):: self
      REAL(8), INTENT(in):: r(1:3)
      REAL(8):: Xi(1:3)

    END FUNCTION phy2log_interface

    SUBROUTINE neighbourElement_interface(self, Xi, neighbourElement)
      IMPORT:: meshCell, meshElement
      CLASS(meshCell), INTENT(in):: self
      REAL(8), INTENT(in):: Xi(1:3)
      CLASS(meshElement), POINTER, INTENT(out):: neighbourElement

    END SUBROUTINE neighbourElement_interface

  END INTERFACE

  !Containers for cells in the mesh
  TYPE:: meshCellCont
    CLASS(meshCell), ALLOCATABLE:: obj

  END TYPE meshCellCont

  !Generic mesh type
  TYPE, ABSTRACT:: meshGeneric
    !Dimension of the mesh
    INTEGER:: dimen
    !Geometry of the mesh
    CHARACTER(:), ALLOCATABLE:: geometry
    !Number of elements
    INTEGER:: numNodes, numCells
    !Array of nodes
    TYPE(meshNodeCont), ALLOCATABLE:: nodes(:)
    !Array of cell elements
    TYPE(meshCellCont),  ALLOCATABLE:: cells(:)
    !PROCEDURES SPECIFIC OF FILE TYPE
    PROCEDURE(readMesh_interface),    POINTER, PASS::   readMesh     => NULL()
    PROCEDURE(readInitial_interface), POINTER, NOPASS:: readInitial  => NULL()
    PROCEDURE(connectMesh_interface), POINTER, PASS::   connectMesh  => NULL()
    PROCEDURE(printColl_interface),   POINTER, PASS::   printColl    => NULL()
    CONTAINS
      !GENERIC PROCEDURES
      PROCEDURE, PASS:: doCollisions

  END TYPE meshGeneric

  interface
    module subroutine doCollisions(self)
      class(meshGeneric), intent(inout), target:: self

    end subroutine doCollisions

    module function findCellBrute(self, r) result(nVol)
      class(meshGeneric), intent(in):: self
      real(8), dimension(1:3), intent(in):: r
      integer:: nVol

    end function findCellBrute

  end interface

  ABSTRACT INTERFACE
    !Reads the mesh from a file
    SUBROUTINE readMesh_interface(self, filename)
      IMPORT meshGeneric
      CLASS(meshGeneric), INTENT(inout):: self
      CHARACTER(:), ALLOCATABLE, INTENT(in):: filename

    END SUBROUTINE readMesh_interface

    SUBROUTINE readInitial_interface(filename, density, velocity, temperature)
      CHARACTER(:), ALLOCATABLE, INTENT(in):: filename
      REAL(8), ALLOCATABLE, INTENT(out), DIMENSION(:):: density
      REAL(8), ALLOCATABLE, INTENT(out), DIMENSION(:,:):: velocity
      REAL(8), ALLOCATABLE, INTENT(out), DIMENSION(:):: temperature

    END SUBROUTINE readInitial_interface

    !Connects cell and edges to the mesh
    SUBROUTINE connectMesh_interface(self)
      IMPORT meshGeneric
      CLASS(meshGeneric), INTENT(inout):: self

    END SUBROUTINE connectMesh_interface

    !Prints number of collisions in each cell
    SUBROUTINE printColl_interface(self)
      IMPORT meshGeneric
      CLASS(meshGeneric), INTENT(in):: self

    END SUBROUTINE printColl_interface

  END INTERFACE

  !Particle mesh
  TYPE, EXTENDS(meshGeneric), PUBLIC:: meshParticles
    INTEGER:: numEdges
    !Array of boundary elements
    TYPE(meshEdgeCont), ALLOCATABLE:: edges(:)
    !Global stiffness matrix
    REAL(8), ALLOCATABLE, DIMENSION(:,:):: K
    !Permutation matrix for P L U factorization
    INTEGER, ALLOCATABLE, DIMENSION(:,:):: IPIV
    !PROCEDURES SPECIFIC OF FILE TYPE
    PROCEDURE(printOutput_interface),  POINTER, PASS:: printOutput  => NULL()
    PROCEDURE(printEM_interface),      POINTER, PASS:: printEM      => NULL()
    PROCEDURE(printAverage_interface), POINTER, PASS:: printAverage => NULL()
    CONTAINS
      !GENERIC PROCEDURES
      PROCEDURE, PASS:: constructGlobalK
      PROCEDURE, PASS:: doCoulomb

  END TYPE meshParticles

  interface
    module subroutine constructGlobalK(self)
      class(meshParticles), intent(inout):: self

    end subroutine constructGlobalK

    module subroutine doCoulomb(self)
      class(meshParticles), intent(in), target:: self

    end subroutine doCoulomb

  end interface

  ABSTRACT INTERFACE
    !Prints Species data
    SUBROUTINE printOutput_interface(self)
      IMPORT meshParticles
      CLASS(meshParticles), INTENT(in):: self

    END SUBROUTINE printOutput_interface

    !Prints EM info
    SUBROUTINE printEM_interface(self)
      IMPORT meshParticles
      CLASS(meshParticles), INTENT(in):: self

    END SUBROUTINE printEM_interface

    !Perform Coulomb Scattering
    !Prints average values
    SUBROUTINE printAverage_interface(self)
      IMPORT meshParticles
      CLASS(meshParticles), INTENT(in):: self

    END SUBROUTINE printAverage_interface

  END INTERFACE

  TYPE(meshParticles), TARGET:: mesh

  !Collision (MCC) mesh
  TYPE, EXTENDS(meshGeneric):: meshCollisions

  END TYPE meshCollisions

  TYPE(meshCollisions), TARGET:: meshColl

  ABSTRACT INTERFACE
    SUBROUTINE readMeshColl_interface(self, filename)
      IMPORT meshCollisions
      CLASS(meshCollisions), INTENT(inout):: self
      CHARACTER(:), ALLOCATABLE, INTENT(in):: filename

    END SUBROUTINE readMeshColl_interface

    SUBROUTINE connectMeshColl_interface(self)
      IMPORT meshParticles

      CLASS(meshParticles), INTENT(inout):: self

    END SUBROUTINE connectMeshColl_interface

  END INTERFACE

  !Pointer to mesh used for MC collisions
  CLASS(meshGeneric), POINTER:: meshForMCC => NULL()

  !Procedure to find a cell for a particle in meshColl
  PROCEDURE(findCellColl_interface), POINTER:: findCellColl => NULL()

  interface
    module subroutine findCellCollMesh(part)
      use moduleSpecies
      implicit none

      type(particle), intent(inout):: part

    end subroutine findCellCollMesh

    module subroutine findCellSameMesh(part)
      use moduleSpecies

      type(particle), intent(inout):: part

    end subroutine findCellSameMesh
  end interface

  ABSTRACT INTERFACE
    SUBROUTINE findCellColl_interface(part)
      USE moduleSpecies

      TYPE(particle), INTENT(inout):: part

    END SUBROUTINE findCellColl_interface

  END INTERFACE

  !Logical to indicate if an specific mesh for MC Collisions is used
  LOGICAL:: doubleMesh = .false.
  !Logical to indicate if MCC collisions are performed
  LOGICAL:: doMCCollisions = .FALSE.
  !Logical to indicate if Coulomb scattering is performed
  LOGICAL:: doCoulombScattering = .FALSE.
  !Logica to indicate if particles have to be listed in list inside the cells
  LOGICAL:: listInCells = .FALSE.
  !Complete path for the two meshes
  CHARACTER(:), ALLOCATABLE:: pathMeshColl, pathMeshParticle

  ! BOUNDARY PARTICLE DEFINITIONS
  !Generic type for boundaries
  type, abstract, public:: boundaryParticleGeneric
    integer:: n
    character(:), allocatable:: name 
    procedure(updateParticle_interface), pointer, pass:: update => null()
    contains
      procedure(applyParticle_interface), deferred, pass:: apply

  end type boundaryParticleGeneric

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

    end subroutine initWallTemperature

    module subroutine initIonization(boundary, mImpact, m0, n0, v0, T0, ion, effTime, crossSection, eThreshold, electronSecondary)
      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

    end subroutine initIonization

    module subroutine initQuasiNeutrality(boundary, s_incident)
      class(boundaryParticleGeneric), allocatable, intent(inout):: boundary
      integer, intent(in):: s_incident

    end subroutine initQuasiNeutrality

    module function boundaryParticleName_to_Index(boundaryName) result(bp)
      character(:), allocatable:: boundaryName
      integer:: bp

    end function boundaryParticleName_to_Index

  end interface

  abstract interface
    subroutine applyParticle_interface(self, edge, part)
      use moduleSpecies
      import boundaryParticleGeneric, meshEdge

      class(boundaryParticleGeneric), intent(in):: self
      class(meshEdge), intent(inout)::     edge
      class(particle), intent(inout)::     part

    end subroutine

    ! Update the values of the particle boundary model
    subroutine updateParticle_interface(self)
      import boundaryParticleGeneric

      class(boundaryParticleGeneric), intent(inout):: self

    end subroutine updateParticle_interface

  end interface

  !Reflecting boundary
  TYPE, PUBLIC, EXTENDS(boundaryParticleGeneric):: boundaryReflection
    CONTAINS
      procedure, pass:: apply => reflection

  END TYPE boundaryReflection

  !Absorption boundary
  TYPE, PUBLIC, EXTENDS(boundaryParticleGeneric):: boundaryAbsorption
    CONTAINS
      procedure, pass:: apply => absorption

  END TYPE boundaryAbsorption

  !Transparent boundary
  TYPE, PUBLIC, EXTENDS(boundaryParticleGeneric):: boundaryTransparent
    CONTAINS
      procedure, pass:: apply => transparent

  END TYPE boundaryTransparent

  !Symmetry axis
  TYPE, PUBLIC, EXTENDS(boundaryParticleGeneric):: boundaryAxis
    CONTAINS
      procedure, pass:: apply => symmetryAxis

  END TYPE boundaryAxis

  !Wall Temperature boundary
  TYPE, PUBLIC, EXTENDS(boundaryParticleGeneric):: boundaryWallTemperature
    !Thermal velocity of the wall: square root(Wall temperature X specific heat)
    REAL(8):: vTh
    CONTAINS
      procedure, pass:: apply => wallTemperature

  END TYPE boundaryWallTemperature

  !Ionization boundary
  TYPE, PUBLIC, EXTENDS(boundaryParticleGeneric):: boundaryIonization
    REAL(8):: m0, n0, v0(1:3), vTh !Properties of background neutrals.
    CLASS(speciesGeneric), POINTER:: species !Ion species
    CLASS(speciesCharged), POINTER:: electronSecondary !Pointer to species considerer as secondary electron
    TYPE(table1D):: crossSection
    REAL(8):: effectiveTime
    REAL(8):: eThreshold
    REAL(8):: deltaV
    CONTAINS
      procedure, pass:: apply => ionization

  END TYPE boundaryIonization

  ! Ensures quasi-neutrality by changing the reflection coefficient
  type, public, extends(boundaryParticleGeneric):: boundaryQuasiNeutrality
    real(8), allocatable:: alpha(:) ! Reflection parameter
    integer:: s_incident ! species index of the incident species
    type(meshEdgePointer), allocatable:: edges(:) !Array with edges
    contains
      procedure, pass:: apply => quasiNeutrality

  end type boundaryQuasiNeutrality

  !Wrapper for boundary types (one per species)
  interface
    module subroutine reflection(self, edge, part)
      use moduleSpecies

      class(boundaryReflection), intent(in):: self
      class(meshEdge), intent(inout)::        edge
      class(particle), intent(inout)::        part

    end subroutine reflection

    module subroutine absorption(self, edge, part)
      use moduleSpecies

      class(boundaryAbsorption), intent(in):: self
      class(meshEdge), intent(inout)::        edge
      class(particle), intent(inout)::        part

    end subroutine absorption

    module subroutine transparent(self, edge, part)
      use moduleSpecies

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

    end subroutine transparent

    module subroutine symmetryAxis(self, edge, part)
      use moduleSpecies

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

    end subroutine symmetryAxis

    module subroutine wallTemperature(self, edge, part)
      use moduleSpecies

      class(boundaryWallTemperature), intent(in):: self
      class(meshEdge), intent(inout)::             edge
      class(particle), intent(inout)::             part

    end subroutine wallTemperature

    module subroutine ionization(self, edge, part)
      use moduleSpecies

      class(boundaryIonization), intent(in):: self
      class(meshEdge), intent(inout)::        edge
      class(particle), intent(inout)::        part

    end subroutine ionization

    module subroutine quasiNeutrality(self, edge, part)
      use moduleSpecies

      class(boundaryQuasiNeutrality), intent(in):: self
      class(meshEdge), intent(inout)::             edge
      class(particle), intent(inout)::             part

    end subroutine quasiNeutrality

    ! Generic basic boundary conditions to use internally in the code
    module subroutine genericReflection(edge, part) 
      use moduleSpecies

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

    end subroutine genericReflection

    module subroutine genericAbsorption(edge, part) 
      use moduleSpecies

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

    end subroutine genericAbsorption

    module subroutine genericTransparent(edge, part) 
      use moduleSpecies

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

    end subroutine genericTransparent

  end interface

  TYPE:: boundaryParticleCont
    CLASS(boundaryParticleGeneric), ALLOCATABLE:: obj

  END TYPE boundaryParticleCont

  type:: boundaryParticlePointer
    class(boundaryParticleGeneric), pointer:: obj

  end type boundaryParticlePointer

  !Number of boundaries
  INTEGER:: nBoundariesParticle = 0
  !Array for boundaries
  type(boundaryParticleCont), allocatable, target:: boundariesParticle(:)

  ! Update the particle boundary models
  interface 
    module subroutine boundariesParticle_update()

    end subroutine boundariesParticle_update

  end interface

  ! BOUNDARY ELECTROMAGNETIC DEFINITIONS
  ! Generic type for electromagnetic boundary conditions
  type, public, abstract:: boundaryEMGeneric
    integer:: n
    character(:), allocatable:: name 
    integer:: nNodes
    type(meshNodePointer), allocatable:: nodes(:)

    procedure(updateEM_interface), pointer, pass:: update => null()
    contains
      procedure(applyEM_interface), deferred, pass:: apply

  end type boundaryEMGeneric

  interface
    module subroutine initDirichlet(self, config, object)
      use json_module

      class(boundaryEMGeneric), allocatable, intent(inout):: self
      type(json_file), intent(inout):: config
      character(:), allocatable, intent(in):: object

    end subroutine initDirichlet

    module subroutine initDirichletTime(self, config, object)
      use json_module

      class(boundaryEMGeneric), allocatable, intent(inout):: self
      type(json_file), intent(inout):: config
      character(:), allocatable, intent(in):: object

    end subroutine initDirichletTime

    module function boundaryEMName_to_Index(boundaryName) result(bp)
      character(:), allocatable:: boundaryName
      integer:: bp

    end function boundaryEMName_to_Index

  end interface

  abstract interface
    ! Apply boundary condition to the load vector for the Poission equation
    subroutine applyEM_interface(self, vectorF)
      import boundaryEMGeneric

      class(boundaryEMGeneric), intent(in):: self
      real(8), intent(inout):: vectorF(:)

    end subroutine applyEM_interface

    ! Update the values of the EM boundary model
    subroutine updateEM_interface(self)
      import boundaryEMGeneric

      class(boundaryEMGeneric), intent(inout):: self

    end subroutine updateEM_interface

  end interface

  ! Extended types
  TYPE, EXTENDS(boundaryEMGeneric):: boundaryEMDirichlet
    REAL(8):: potential
    CONTAINS
      ! boundaryEMGeneric DEFERRED PROCEDURES
      PROCEDURE, PASS:: apply => applyDirichlet

  END TYPE boundaryEMDirichlet

  TYPE, EXTENDS(boundaryEMGeneric):: boundaryEMDirichletTime
    real(8):: potential
    real(8):: timeFactor
    type(table1D):: temporalProfile
    contains
      ! boundaryEMGeneric DEFERRED PROCEDURES
      procedure, pass:: apply => applyDirichletTime

  END TYPE boundaryEMDirichletTime

  interface
    module subroutine applyDirichlet(self, vectorF)
      class(boundaryEMDirichlet), intent(in):: self
      real(8), intent(inout):: vectorF(:)

    end subroutine applyDirichlet

    module subroutine applyDirichletTime(self, vectorF)
      class(boundaryEMDirichletTime), intent(in):: self
      real(8), intent(inout):: vectorF(:)

    end subroutine applyDirichletTime

  end interface

  ! Container for boundary conditions
  TYPE:: boundaryEMCont
    CLASS(boundaryEMGeneric), ALLOCATABLE:: obj

  END TYPE boundaryEMCont

  INTEGER:: nBoundariesEM
  TYPE(boundaryEMCont), ALLOCATABLE, target:: boundariesEM(:)

  !Information of charge and reference parameters for rho vector
  REAL(8), ALLOCATABLE:: qSpecies(:)

  ! Update the EM boundary models
  interface 
    module subroutine boundariesEM_update()

    end subroutine boundariesEM_update

  end interface

  ! PHYSICAL SURFACES LINKING TO MESH ELEMENTS
  ! Link physical surface to edges
  type:: physicalSurface
    integer:: index
    class(meshNodePointer), allocatable:: nodes(:)
    class(meshEdgePointer), allocatable:: edges(:)
    class(boundaryParticlePointer), allocatable:: particles(:)
    class(boundaryEMGeneric), pointer:: EM => null()

  end type

  integer:: nPhysicalSurfaces
  type(physicalSurface), allocatable:: physicalSurfaces(:)

  ! Get ID from array based on physical surface
  interface
    module function physicalSurface_to_index(ps) result(index)
      integer:: ps
      integer:: index

    end function physicalSurface_to_index

  end interface

END MODULE moduleMesh
