module classes_field_expression

use, intrinsic :: iso_fortran_env, only: DP => REAL64
use data_constants, only: num_dimensions, PI
use procedures_errors, only: error_exit
use procedures_checks, only: check_array_size, check_positive
use classes_permittivity, only: Abstract_Permittivity
use procedures_field_expression_micro, only: plate_expression

implicit none

private

    type, abstract, public :: Abstract_Field_Expression
    contains
        procedure(Abstract_get), deferred :: get
    end type Abstract_Field_Expression

    abstract interface

        pure function Abstract_get(this, position) result(expression)
        import :: DP, num_dimensions, Abstract_Field_Expression
            class(Abstract_Field_Expression), intent(in) :: this
            real(DP), intent(in) :: position(:)
            real(DP) :: expression(num_dimensions)
        end function Abstract_get

    end interface

    type, extends(Abstract_Field_Expression), public :: Constant_Field_Expression
    private
        real(DP) :: vector(num_dimensions) = 0._DP
    contains
        procedure :: set => Constant_set
        procedure :: get => Constant_get
    end type Constant_Field_Expression

    !> This field is produced by 2 charged plates of opposite charge.
    type, extends(Abstract_Field_Expression), public :: Centered_Plates_Expression
    private
        real(DP) :: coulomb = 0._DP
        real(DP) :: size_x = 0._DP
        real(DP), dimension(2) :: center_lower = 0._DP, center_upper = 0._DP
        real(DP) :: surface_density = 0._DP
    contains
        procedure :: set => Plates_set
        procedure :: get => Plates_get
    end type Centered_Plates_Expression

    type, extends(Abstract_Field_Expression), public :: Null_Field_Expression
    contains
        procedure :: set => Null_set
        procedure :: get => Null_get
    end type Null_Field_Expression

contains

!implementation Constant_Field_Expression

    subroutine Constant_set(this, vector)
        class(Constant_Field_Expression), intent(inout) :: this
        real(DP), intent(in) :: vector(:)

        call check_array_size("Constant_Field_Expression", "vector", vector, num_dimensions)
        this%vector = vector
    end subroutine Constant_set

    pure function Constant_get(this, position) result(expression)
        class(Constant_Field_Expression), intent(in) :: this
        real(DP), intent(in) :: position(:)
        real(DP) :: expression(num_dimensions)

        expression = this%vector
    end function Constant_get

!end implementation Constant_Field_Expression

!implementation Centered_Plates_Expression

    subroutine Plates_set(this, permittivity, gap, size_x, surface_density)
        class(Centered_Plates_Expression), intent(inout) :: this
        class(Abstract_Permittivity), intent(in) :: permittivity
        real(DP), intent(in) :: gap, size_x
        real(DP), intent(in) :: surface_density

        this%coulomb = 1._DP / (4._DP*PI * permittivity%get())
        this%surface_density = surface_density
        call check_positive("Centered_Plates_Expression: set", "size_x", size_x)
        this%size_x = size_x
        call check_positive("Centered_Plates_Expression: set", "gap", gap)
        this%center_lower = [0._DP, -gap/2._DP]
        this%center_upper = [0._DP, +gap/2._DP]
    end subroutine Plates_set

    !> Let an infinitely thin plate of charge density \( \sigma \)
    !> which is finite in \( \vec{e}_x \) direction but not in \( \vec{e}_y \),
    !> cf. modules/environment/field/plate_field.tex.
    !> This plate creates an electric field at \( (x, z) \) of expression:
    !> \[
    !>      \vec{E}(x, z) = \frac{1}{4\pi \epsilon_0} \sigma \vec{b}(x, z)
    !> \]
    !> where \( \vec{b}(x, z) \) is defined in
    !> [[procedures_field_expression_micro:plate_expression]].
    pure function Plates_get(this, position) result(expression)
        class(Centered_Plates_Expression), intent(in) :: this
        real(DP), intent(in) :: position(:)
        real(DP) :: expression(num_dimensions)

        real(DP), dimension(2) :: position_lower_13, position_upper_13

        position_lower_13 = [position(1), position(3)] - this%center_lower
        position_upper_13 = [position(1), position(3)] - this%center_upper
        expression = this%coulomb * this%surface_density * &
            (plate_expression(this%size_x, position_lower_13) - &
             plate_expression(this%size_x, position_upper_13))
    end function Plates_get

!end implementation Centered_Plates_Expression

!implementation Null_Field_Expression

    subroutine Null_set(this)
        class(Null_Field_Expression), intent(inout) :: this
    end subroutine Null_set

    pure function Null_get(this, position) result(expression)
        class(Null_Field_Expression), intent(in) :: this
        real(DP), intent(in) :: position(:)
        real(DP) :: expression(num_dimensions)
        expression = 0._DP
    end function Null_get

!end implementation Null_Field_Expression

end module classes_field_expression
