module procedures_observables_energies_factory

use, intrinsic :: iso_fortran_env, only: DP => REAL64
use types_real_wrapper, only: Real_Line
use types_observables_energies, only: Concrete_Single_Energies, Concrete_Double_Energies, &
    Concrete_Observables_Energies
use procedures_reals_factory, only: reals_create => create, reals_destroy => destroy

implicit none

private
public :: create, destroy, set

interface create
    module procedure :: create_line
    module procedure :: create_element
end interface create

interface destroy
    module procedure :: destroy_element
    module procedure :: destroy_line
end interface

interface set
    module procedure :: set_energies
    module procedure :: add_single
    module procedure :: add_double
end interface set

contains

    pure subroutine create_line(energies, num_boxes, num_components)
        type(Concrete_Observables_Energies), allocatable, intent(out) :: energies(:)
        integer, intent(in) :: num_boxes, num_components

        integer :: i_box

        allocate(energies(num_boxes))
        do i_box = 1, size(energies)
            call create(energies(i_box), num_components)
        end do
    end subroutine create_line

    pure subroutine destroy_line(energies)
        type(Concrete_Observables_Energies), allocatable, intent(inout) :: energies(:)

        integer :: i_box

        if (allocated(energies)) then
            do i_box = size(energies), 1, -1
                call destroy(energies(i_box))
            end do
            deallocate(energies)
        end if
    end subroutine destroy_line

    pure subroutine create_element(energies, num_components)
        type(Concrete_Observables_Energies), intent(out) :: energies
        integer, intent(in) :: num_components

        allocate(energies%walls_energies(num_components))
        energies%walls_energies = 0._DP
        call reals_create(energies%short_energies, num_components)
        allocate(energies%field_energies(num_components))
        energies%field_energies = 0._DP
        call reals_create(energies%dipolar_energies, num_components)
    end subroutine create_element

    pure subroutine destroy_element(energies)
        type(Concrete_Observables_Energies), intent(inout) :: energies

        call reals_destroy(energies%dipolar_energies)
        if (allocated(energies%field_energies)) deallocate(energies%field_energies)
        call reals_destroy(energies%short_energies)
        if (allocated(energies%walls_energies)) deallocate(energies%walls_energies)
    end subroutine destroy_element

    pure subroutine add_single(energies, deltas, i_component)
        type(Concrete_Observables_Energies), intent(inout) :: energies
        type(Concrete_Single_Energies), intent(in) :: deltas
        integer, intent(in) :: i_component

        energies%walls_energies(i_component) = energies%walls_energies(i_component) + deltas%&
            walls_energy
        call add_energies(energies%short_energies, deltas%short_energies, i_component)
        energies%field_energies(i_component) = energies%field_energies(i_component) + deltas%&
            field_energy
        call add_energies(energies%dipolar_energies, deltas%dipolar_energies, i_component)
        energies%dipolar_shared_energy = energies%dipolar_shared_energy + deltas%&
            dipolar_shared_energy
    end subroutine add_single

    pure subroutine add_double(energies, deltas, ij_components)
        type(Concrete_Observables_Energies), intent(inout) :: energies
        type(Concrete_Double_Energies), intent(in) :: deltas
        integer, intent(in) :: ij_components(:)

        integer :: i_partner

        do i_partner = 1, size(ij_components)
            energies%walls_energies(ij_components(i_partner)) = energies%&
                walls_energies(ij_components(i_partner)) + deltas%walls_energies(i_partner)
            call add_energies(energies%short_energies, deltas%short_energies(:, i_partner), &
                ij_components(i_partner))
            energies%field_energies(ij_components(i_partner)) = energies%&
                field_energies(ij_components(i_partner)) + deltas%field_energies(i_partner)
            call add_energies(energies%dipolar_energies, deltas%dipolar_energies(:, i_partner),  &
                ij_components(i_partner))
        end do
        energies%dipolar_shared_energy = energies%dipolar_shared_energy + deltas%&
            dipolar_shared_energy
    end subroutine add_double

    pure subroutine add_energies(energies, deltas, i_component)
        type(Real_Line), intent(inout) :: energies(:)
        real(DP), intent(in) :: deltas(:)
        integer, intent(in) :: i_component

        integer :: j_component, i_observable, j_observable

        do j_component = 1, size(deltas)
            j_observable = max(i_component, j_component)
            i_observable = min(i_component, j_component)
            energies(j_observable)%line(i_observable) = energies(j_observable)%&
                line(i_observable) + deltas(j_component)
        end do
    end subroutine add_energies

    pure subroutine set_energies(target_energies, source_energies)
        type(Concrete_Observables_Energies), intent(inout) :: target_energies
        type(Concrete_Observables_Energies), intent(in) :: source_energies

        target_energies%walls_energies = source_energies%walls_energies
        call set_energies_triangle(target_energies%short_energies, source_energies%short_energies)
        target_energies%field_energies = source_energies%field_energies
        call set_energies_triangle(target_energies%dipolar_energies, source_energies%&
            dipolar_energies)
        target_energies%dipolar_shared_energy = source_energies%dipolar_shared_energy
    end subroutine set_energies

    pure subroutine set_energies_triangle(energies_target, energies_source)
        type(Real_Line), intent(inout) :: energies_target(:)
        type(Real_Line), intent(in) :: energies_source(:)

        integer :: i_component

        do i_component = 1, size(energies_target)
            energies_target(i_component)%line = energies_source(i_component)%line
        end do
    end subroutine set_energies_triangle

end module procedures_observables_energies_factory
