module procedures_field_expression_factory

use, intrinsic :: iso_fortran_env, only: DP => REAL64
use json_module, only: json_file
use procedures_errors, only: error_exit
use procedures_checks, only: check_data_found
use classes_permittivity, only: Abstract_Permittivity
use classes_field_expression, only: Abstract_Field_Expression, Constant_Field_Expression, &
    Centered_Plates_Expression, Null_Field_Expression

implicit none

private
public :: create, destroy

contains

    subroutine create(field_expression, permittivity, field_applied, generating_data, prefix)
        class(Abstract_Field_Expression), allocatable, intent(out) :: field_expression
        class(Abstract_Permittivity), intent(in) :: permittivity
        logical, intent(in) :: field_applied
        type(json_file), intent(inout) :: generating_data
        character(len=*), intent(in) :: prefix

        call allocate_field_expression(field_expression, field_applied, generating_data, prefix)
        call set_field_expression(field_expression, permittivity, generating_data, prefix)
    end subroutine create

    subroutine allocate_field_expression(field_expression, field_applied, generating_data, prefix)
        class(Abstract_Field_Expression), allocatable, intent(out) :: field_expression
        logical, intent(in) :: field_applied
        type(json_file), intent(inout) :: generating_data
        character(len=*), intent(in) :: prefix

        character(len=:), allocatable :: field_name, data_field
        logical :: data_found

        if (field_applied) then
            data_field = prefix//"External Field.name"
            call generating_data%get(data_field, field_name, data_found)
            call check_data_found(data_field, data_found)
            select case (field_name)
                case ("constant")
                    allocate(Constant_Field_Expression :: field_expression)
                case ("plates")
                    allocate(Centered_Plates_Expression :: field_expression)
                case default
                    call error_exit(field_name//" field_name unknown. Choose:"//&
                        " 'constant' or 'plates'.")
            end select
        else
            allocate(Null_Field_Expression :: field_expression)
        end if
    end subroutine allocate_field_expression

    subroutine set_field_expression(field_expression, permittivity, generating_data, prefix)
        class(Abstract_Field_Expression), allocatable, intent(inout) :: field_expression
        class(Abstract_Permittivity), intent(in) :: permittivity
        type(json_file), intent(inout) :: generating_data
        character(len=*), intent(in) :: prefix

        character(len=:), allocatable :: data_field
        logical :: data_found

        select type (field_expression)
            type is (Null_Field_Expression)
                call field_expression%set()
            type is (Constant_Field_Expression)
                block
                    real(DP), allocatable :: field_vector(:)
                    data_field = prefix//"External Field.vector"
                    call generating_data%get(data_field, field_vector, data_found)
                    call check_data_found(data_field, data_found)
                    call field_expression%set(field_vector)
                end block
            type is (Centered_Plates_Expression)
                block
                    real(DP) :: gap, size_x, surface_density
                    data_field = prefix//"External Field.gap"
                    call generating_data%get(data_field, gap, data_found)
                    call check_data_found(data_field, data_found)
                    data_field = prefix//"External Field.size x"
                    call generating_data%get(data_field, size_x, data_found)
                    call check_data_found(data_field, data_found)
                    data_field = prefix//"External Field.surface density"
                    call generating_data%get(data_field, surface_density, data_found)
                    call check_data_found(data_field, data_found)
                    call field_expression%set(permittivity, gap, size_x, surface_density)
                end block
            class default
                call error_exit("field_expression type unknown.")
        end select
    end subroutine set_field_expression

    subroutine destroy(field_expression)
        class(Abstract_Field_Expression), allocatable, intent(inout) :: field_expression

        if (allocated(field_expression)) deallocate(field_expression)
    end subroutine destroy

end module procedures_field_expression_factory
