module procedures_dipolar_interactions_factory

use data_input_prefixes, only: dipolar_interactions_prefix
use json_module, only: json_file
use procedures_errors, only: warning_continue
use procedures_boxes_factory, only: boxes_create => create, boxes_destroy => destroy
use classes_permittivity, only: Abstract_Permittivity
use classes_reciprocal_lattice, only: Abstract_Reciprocal_Lattice
use types_environment_wrapper, only: Environment_Wrapper
use procedures_environment_inquirers, only: total_volume_can_change, use_permittivity, &
    use_reciprocal_lattice
use types_mixture_wrapper, only: Mixture_Wrapper
use procedures_mixture_total_moments_factory, only: set_are_dipolar
use types_dipolar_interactions_dynamic_wrapper, only: Dipolar_Interactions_Dynamic_Wrapper
use types_dipolar_interactions_static_wrapper, only: Dipolar_Interactions_Static_Wrapper
use procedures_des_convergence_parameter_factory, only: des_convergence_parameter_create => create,&
    des_convergence_parameter_destroy => destroy
use procedures_des_real_factory, only: des_real_create => create, des_real_destroy => destroy
use procedures_des_reci_factory, only: des_reci_create => create, des_reci_destroy => destroy
use procedures_des_self_factory, only: des_self_create => create, des_self_destroy => destroy
use procedures_des_surf_mixture_factory, only: des_surf_mixture_create => create, &
    des_surf_mixture_destroy => destroy
use procedures_dlc_factory, only: dlc_create => create, dlc_destroy => destroy

implicit none

private
public :: create, destroy

interface destroy
    module procedure :: destroy_all
    module procedure :: destroy_static
end interface destroy

contains

    subroutine create(dipolar_interactions_dynamic, dipolar_interactions_static, environment, &
        mixture, generating_data)
        type(Dipolar_Interactions_Dynamic_Wrapper), allocatable, intent(out) :: &
            dipolar_interactions_dynamic(:)
        type(Dipolar_Interactions_Static_Wrapper), allocatable, intent(out) :: &
            dipolar_interactions_static(:)
        type(Environment_Wrapper), intent(in) :: environment
        type(Mixture_Wrapper), intent(in) :: mixture
        type(json_file), intent(inout) :: generating_data

        integer :: i_box
        logical :: are_dipolar(size(mixture%components, 1), size(mixture%components, 2))

        call set_are_dipolar(are_dipolar, mixture%components)
        do i_box = 1, size(are_dipolar, 2)
            call check_consistency(environment%reciprocal_lattices(i_box), environment%&
                permittivity, any(are_dipolar(:, i_box)))
        end do

        allocate(dipolar_interactions_dynamic(size(mixture%components, 2)))
        do i_box = 1, size(dipolar_interactions_dynamic)
            call des_convergence_parameter_create(dipolar_interactions_dynamic(i_box)%alpha, &
                any(are_dipolar), generating_data, dipolar_interactions_prefix)
        end do

        allocate(dipolar_interactions_static(size(mixture%components, 2)))
        do i_box = 1, size(dipolar_interactions_static)
            call boxes_create(dipolar_interactions_static(i_box)%box_size_memento_real, &
                environment%periodic_boxes(i_box), &
                total_volume_can_change(environment%beta_pressure) .or. &
                size(environment%periodic_boxes) > 1, any(are_dipolar(:, i_box)))
            call des_real_create(dipolar_interactions_static(i_box)%real_pair, &
                dipolar_interactions_static(i_box)%box_size_memento_real, environment%permittivity,&
                mixture%components_min_distances, any(are_dipolar(:, i_box)), &
                dipolar_interactions_dynamic(i_box)%alpha, generating_data, &
                dipolar_interactions_prefix//"Real.")
            call des_real_create(dipolar_interactions_dynamic(i_box)%real_components, environment%&
                periodic_boxes(i_box), mixture%components(:, i_box), are_dipolar(:, i_box), &
                dipolar_interactions_static(i_box))
        end do

        do i_box = 1, size(dipolar_interactions_static)
            allocate(dipolar_interactions_static(i_box)%box_size_memento_reci, &
                source=dipolar_interactions_static(i_box)%box_size_memento_real)
            call dipolar_interactions_static(i_box)%box_size_memento_reci%target(environment%&
                periodic_boxes(i_box))
            call des_reci_create(dipolar_interactions_static(i_box)%reci_weight, &
                dipolar_interactions_static(i_box)%box_size_memento_reci, environment%&
                reciprocal_lattices(i_box), environment%permittivity, any(are_dipolar(:, i_box)), &
                dipolar_interactions_dynamic(i_box)%alpha)
            call des_reci_create(dipolar_interactions_static(i_box)%reci_structure, environment%&
                periodic_boxes(i_box), dipolar_interactions_static(i_box)%box_size_memento_reci, &
                environment%reciprocal_lattices(i_box), mixture%components(:, i_box), &
                are_dipolar(:, i_box))
            call des_reci_create(dipolar_interactions_dynamic(i_box)%reci_visitor, environment%&
                periodic_boxes(i_box), environment%reciprocal_lattices(i_box), &
                    dipolar_interactions_static(i_box))
        end do

        do i_box = 1, size(dipolar_interactions_dynamic)
            call des_self_create(dipolar_interactions_dynamic(i_box)%self_components, environment%&
                periodic_boxes(i_box), environment%permittivity, mixture%components(:, i_box), &
                are_dipolar(:, i_box), dipolar_interactions_dynamic(i_box)%alpha)
            call des_surf_mixture_create(dipolar_interactions_dynamic(i_box)%surf_mixture, &
                environment%periodic_boxes(i_box), environment%permittivity, &
                mixture%total_moments(i_box))
        end do

        do i_box = 1, size(dipolar_interactions_static)
            call dlc_create(dipolar_interactions_static(i_box)%dlc_weight, environment%&
                periodic_boxes(i_box), environment%reciprocal_lattices(i_box), environment%&
                permittivity, any(are_dipolar(:, i_box)))
            call dlc_create(dipolar_interactions_static(i_box)%dlc_structures, environment%&
                periodic_boxes(i_box), environment%reciprocal_lattices(i_box), mixture%&
                components(:, i_box), are_dipolar(:, i_box))
            call dlc_create(dipolar_interactions_dynamic(i_box)%dlc_visitor, environment%&
                periodic_boxes(i_box), environment%reciprocal_lattices(i_box), &
                    dipolar_interactions_static(i_box))
        end do
    end subroutine create

    !> @todo if (allocated(dipolar_interactions_static, dipolar_interactions_dynamic)):
    !> improve error handling.
    subroutine destroy_all(dipolar_interactions_dynamic, dipolar_interactions_static)
        type(Dipolar_Interactions_Dynamic_Wrapper), allocatable, intent(inout) :: &
            dipolar_interactions_dynamic(:)
        type(Dipolar_Interactions_Static_Wrapper), allocatable, intent(inout) :: &
            dipolar_interactions_static(:)

        integer :: i_box

        if (.not.allocated(dipolar_interactions_static) .or. &
            .not.allocated(dipolar_interactions_dynamic)) then
            call warning_continue("procedures_dipolar_interactions_factory: destroy:"//&
                "dipolar_interactions_static or dipolar_interactions_static were not allocated.")
        end if

        do i_box = size(dipolar_interactions_static), 1, -1
            call dlc_destroy(dipolar_interactions_dynamic(i_box)%dlc_visitor)
            call dlc_destroy(dipolar_interactions_static(i_box)%dlc_structures)
            call dlc_destroy(dipolar_interactions_static(i_box)%dlc_weight)
        end do

        do i_box = size(dipolar_interactions_dynamic), 1, -1
            call des_surf_mixture_destroy(dipolar_interactions_dynamic(i_box)%surf_mixture)
            call des_self_destroy(dipolar_interactions_dynamic(i_box)%self_components)
        end do

        do i_box = size(dipolar_interactions_static), 1, -1
            call des_reci_destroy(dipolar_interactions_dynamic(i_box)%reci_visitor)
            call des_reci_destroy(dipolar_interactions_static(i_box)%reci_structure)
            call des_reci_destroy(dipolar_interactions_static(i_box)%reci_weight)
            call boxes_destroy(dipolar_interactions_static(i_box)%box_size_memento_reci)
        end do

        do i_box = size(dipolar_interactions_static), 1, -1
            call des_real_destroy(dipolar_interactions_dynamic(i_box)%real_components)
            call des_real_destroy(dipolar_interactions_static(i_box)%real_pair)
            call boxes_destroy(dipolar_interactions_static(i_box)%box_size_memento_real)
        end do
        deallocate(dipolar_interactions_static)

        do i_box = size(dipolar_interactions_dynamic), 1, -1
            call des_convergence_parameter_destroy(dipolar_interactions_dynamic(i_box)%alpha)
        end do
        deallocate(dipolar_interactions_dynamic)
    end subroutine destroy_all

    !> @note This subroutine should not be necessary,
    !> cf. [[classes_box_volume_change:Concrete_try]].
    subroutine destroy_static(dipolar_interactions_static)
        type(Dipolar_Interactions_Static_Wrapper), intent(inout) :: dipolar_interactions_static

        call dlc_destroy(dipolar_interactions_static%dlc_structures)
        call dlc_destroy(dipolar_interactions_static%dlc_weight)

        call des_reci_destroy(dipolar_interactions_static%reci_structure)
        call des_reci_destroy(dipolar_interactions_static%reci_weight)
        call boxes_destroy(dipolar_interactions_static%box_size_memento_reci)

        call des_real_destroy(dipolar_interactions_static%real_pair)
        call boxes_destroy(dipolar_interactions_static%box_size_memento_real)
    end subroutine destroy_static

    subroutine check_consistency(reciprocal_lattice, permittivity, dipoles_exist)
        class(Abstract_Reciprocal_Lattice), intent(in) :: reciprocal_lattice
        class(Abstract_Permittivity), intent(in) :: permittivity
        logical, intent(in) :: dipoles_exist

        if (dipoles_exist) then
            if (.not.use_reciprocal_lattice(reciprocal_lattice)) then
                call warning_continue("procedures_dipolar_interactions_factory: "//&
                    "check_consistency: dipoles exist but reciprocal_lattice unused.")
            end if
            if (.not.use_permittivity(permittivity)) then
                call warning_continue("procedures_dipolar_interactions_factory: "//&
                    "check_consistency: dipoles exist but permittivity unused.")
            end if
        end if
    end subroutine check_consistency

end module procedures_dipolar_interactions_factory
