submodule(moduleMesh) boundaryEM
  CONTAINS
    ! Dirichlet boundary condition
    ! Init
    module SUBROUTINE initDirichlet(self, config, object)
      use json_module
      USE moduleRefParam, ONLY: Volt_ref
      use moduleErrors
      IMPLICIT NONE

      CLASS(boundaryEMGeneric), ALLOCATABLE, INTENT(inout):: self
      type(json_file), intent(inout):: config
      character(:), allocatable, intent(in):: object
      REAL(8):: potential
      logical:: found

      select type(self)
      type is(boundaryEMDirichlet)
        call config%get(object // '.potential', potential, found)
        if (.not. found) then
          call criticalError('Required parameter "potential" for Dirichlet boundary condition not found', 'readBoundaryEM')

        end if

        self%potential = potential / Volt_ref

      end select

    end subroutine initDirichlet

    ! Apply
    module SUBROUTINE applyDirichlet(self, vectorF)
      IMPLICIT NONE

      CLASS(boundaryEMDirichlet), INTENT(in):: self
      REAL(8), INTENT(inout):: vectorF(:)
      integer:: n

      DO n = 1, self%nNodes
        associate(node => self%nodes(n)%obj)
          node%emData%phi  = self%potential
          vectorF(node%n)  = node%emData%phi

        end associate

      END DO

    END SUBROUTINE applyDirichlet

    ! Dirichlet time-dependent boundary condition
    ! Init
    module subroutine initDirichletTime(self, config, object)
      use json_module
      use moduleRefParam, ONLY: Volt_ref, ti_ref
      use moduleErrors
      use moduleOutput, only: path
      implicit none

      class(boundaryEMGeneric), allocatable, intent(inout):: self
      type(json_file), intent(inout):: config
      character(:), allocatable, intent(in):: object
      real(8):: potential
      character(:), allocatable:: temporalProfile
      logical:: found
      character(:), allocatable:: temporalProfilePath

      select type(self)
      type is(boundaryEMDirichletTime)
        call config%get(object // '.potential', potential, found)
        if (.not. found) then
          call criticalError('Required parameter "potential" for Dirichlet Time boundary condition not found', &
                             'readBoundaryEM')

        end if

        call config%get(object // '.temporalProfile', temporalProfile, found)
        if (.not. found) then
          call criticalError('Required parameter "temporalProfile" for Dirichlet Time boundary condition not found', &
                             'readBoundaryEM')

        end if
        temporalProfilePath = path // temporalProfile

        self%potential = potential / Volt_ref

        call self%temporalProfile%init(temporalProfile)

        call self%temporalProfile%convert(1.D0/ti_ref, 1.D0)

        self%update => updateDirichletTime

      end select

    END SUBROUTINE initDirichletTime

    ! Apply
    module SUBROUTINE applyDirichletTime(self, vectorF)
      USE moduleMesh
      USE moduleCaseParam, ONLY: timeStep, tauMin
      IMPLICIT NONE

      CLASS(boundaryEMDirichletTime), INTENT(in):: self
      REAL(8), INTENT(inout):: vectorF(:)
      integer:: n

      DO n = 1, self%nNodes
        self%nodes(n)%obj%emData%phi  = self%potential * self%timeFactor
        vectorF(self%nodes(n)%obj%n)  = self%nodes(n)%obj%emData%phi

      END DO

    END SUBROUTINE applyDirichletTime

    ! Update
    subroutine updateDirichletTime(self)
      implicit none

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

      select type(self)
      type is(boundaryEMDirichletTime)
        self%timeFactor = self%temporalProfile%get(DBLE(timeStep)*tauMin)

      end select

    end subroutine updateDirichletTime

    ! Neumann constant value
    ! Init
    module subroutine initNeumann(self, config, object)
      use json_module
      use moduleRefParam, only: EF_ref
      use moduleErrors, only: criticalError
      implicit none

      class(boundaryEMGeneric), allocatable, intent(inout):: self
      type(json_file), intent(inout):: config
      character(:), allocatable, intent(in):: object
      real(8):: electricField
      logical:: found

      select type(self)
      type is(boundaryEMNeumann)
        call config%get(object // '.electricField', electricField, found)
        if (.not. found) then
          call criticalError('Required parameter "electricField" for Neumann boundary condition not found', 'readBoundaryEM')

        end if

        self%electricField = electricField / EF_ref

      end select

    end subroutine initNeumann

    ! Apply
    module subroutine applyNeumann(self, vectorF)
      implicit none

      class(boundaryEMNeumann), intent(in):: self
      real(8), intent(inout):: vectorF(:)
      integer:: n

      do n = 1, self%nNodes
        associate(node => self%nodes(n)%obj)
          vectorF(node%n) = vectorF(node%n) - self%electricField

        end associate

      end do

    end subroutine applyNeumann

    ! Floating surface
    ! Init
    module subroutine initFloating(self, config, object)
      use json_module
      use moduleRefParam, only: Volt_ref, L_ref
      use moduleConstParam, only: eps_0
      use moduleErrors
      implicit none

      class(boundaryEMGeneric), allocatable, intent(inout):: self
      type(json_file), intent(inout):: config
      character(:), allocatable, intent(in):: object
      real(8):: potential, capacitance
      logical:: found

      select type(self)
      type is(boundaryEMFloating)
        call config%get(object // '.potential', potential, found)
        if (.not. found) then
          call criticalError('Required parameter "potential" for Floating boundary condition not found', &
                             'readBoundaryEM')

        end if

        self%potential = potential / Volt_ref

        call config%get(object // '.capacitance', capacitance, found)
        if (.not. found) then
          call warningError('Parameter "capacitance" for floating boundary condition not found. Using the default value of 1 nF.')
          capacitance = 1.0d-9

        end if
    
        ! Inverse of non-dimensional capacitance
        self%invC = 1.0d0 / (capacitance / (eps_0 * L_ref))

        self%charge = 0.0d0

        self%update => updateFloating

        self%print  => writeFloating

        allocate(self%edges(0))

      end select

    end subroutine initFloating
    
    ! Apply
    module subroutine applyFloating(self, vectorF)
      use moduleMesh
      implicit none

      class(boundaryEMFloating), intent(in):: self
      real(8), intent(inout):: vectorF(:)
      integer:: n

      do n = 1, self%nNodes
        self%nodes(n)%obj%emData%phi  = self%potential
        vectorF(self%nodes(n)%obj%n)  = self%nodes(n)%obj%emData%phi

      end do

    end subroutine applyFloating

    ! Update
    subroutine updateFloating(self)
      use moduleMesh, only: qSpecies, meshNode
      use moduleCaseParam, only: tauMin
      implicit none

      class(boundaryEMGeneric), intent(inout):: self
      integer:: e, n, s
      integer, allocatable:: nodes(:)
      class(meshEdge), pointer:: edge
      real(8), allocatable:: mom_nodes(:)
      class(meshNode), pointer:: node
      real(8):: mom_center, edgeDensityCurrent, chargeTau

      select type(self)
      type is(boundaryEMFloating)
        do e = 1, size(self%edges)
          edge => self%edges(e)%obj

          chargeTau = 0.0d0
          edgeDensityCurrent = 0.0d0

          nodes = edge%getNodes(edge%nNodes)
          allocate(mom_nodes(1:edge%nNodes))
          do s = 1, nSpecies
            mom_center = 0.0d0
            do n = 1, self%nNodes
              node => mesh%nodes(nodes(n))%obj

              ! Minus sign to get the flux exiting the edge
              mom_nodes(n) = - dot_product(node%output(s)%mom, edge%normal)

            end do

            mom_center = edge%gatherF(edge%centerXi(), edge%nNodes, mom_nodes)

            edgeDensityCurrent = edgeDensityCurrent + qSpecies(s) * mom_center

          end do

          chargeTau = edgeDensityCurrent * edge%surface * tauMin

          ! Accumulate charge of speceis on surface
          self%charge = self%charge + chargeTau

        end do

        self%potential = self%potential + chargeTau * self%invC

      end select

    end subroutine updateFloating

    ! Write
    subroutine writeFloating(self, fileID)
      use moduleOutput, only: fmtColReal
      use moduleConstParam, only: qe
      use moduleRefParam, only: Volt_ref, v_ref, n_ref, L_ref, ti_ref
      implicit none

      class(boundaryEMGeneric), intent(inout):: self
      integer, intent(in):: fileID

      write(fileID, '(A)') self%name
      select type(self)
      type is(boundaryEMFloating)
        write(fileID, '(A,",",A)') 'Total charge (C)', 'Potential (V)'
        write(fileID, '(*('//fmtColReal//'))') self%charge*qe*v_ref*n_ref*L_ref**2*ti_ref, self%potential*Volt_ref

      end select

    end subroutine writeFloating

    ! Get the index of the boundary based on the name
    module function boundaryEMName_to_Index(boundaryName) result(bp)
      use moduleErrors
      implicit none

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

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

        end if

      end do

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

    ! Update all EM boundaries
    module subroutine boundariesEM_update()
      implicit none

      integer:: b

      do b = 1, nBoundariesEM
        if (associated(boundariesEM(b)%obj%update)) then
          call boundariesEM(b)%obj%update()

        end if

      end do

    end subroutine boundariesEM_update

    ! Write all EM boundaries
    module subroutine boundariesEM_write()
      use moduleCaseparam, only: timeStep
      use moduleOutput, only: fileID_boundaryEM, &
                              formatFileName, &
                              informFileCreation,&
                              boundaryEMOutput,&
                              generateFilePath, &
                              prefix
      implicit none

      integer:: b
      character(:), allocatable:: fileName

      if (boundaryEMOutput) then
        fileName = formatFileName(prefix, 'boundariesEM', 'csv', timeStep)
        call informFileCreation(fileName)
        open(fileID_boundaryEM, file = generateFilePath(fileName))

        do b = 1, nBoundariesEM
          if (associated(boundariesEM(b)%obj%update)) then
            call boundariesEM(b)%obj%print(fileID_boundaryEM)

          end if

        end do

        close(fileID_boundaryEM)

      end if

    end subroutine boundariesEM_write

end submodule boundaryEM
