module procedures_observables_changes_factory

use, intrinsic :: iso_fortran_env, only: DP => REAL64
use procedures_reals_factory, only: reals_create => create, reals_destroy => destroy
use module_changes_success, only: Concrete_Change_Counter, Concrete_Changes_Counter, &
    Concrete_Changes_Success, Concrete_Change_Counter_Line, reset_counters
use types_observables_changes, only: Concrete_Observables_Changes

implicit none

private
public :: create, destroy

interface create
    module procedure :: create_line
    module procedure :: create_element
    module procedure :: create_changes_counters
    module procedure :: create_triangle_counters
    module procedure :: create_square_counters, create_rectangle_counters
    module procedure :: create_teleportations_counters
    module procedure :: create_swaps_counters
    module procedure :: create_change_counters
    module procedure :: create_changes_successes
end interface create

interface destroy
    module procedure :: destroy_changes_successes
    module procedure :: destroy_changes_counters
    module procedure :: destroy_swaps_counters
    module procedure :: destroy_teleportations_counters
    module procedure :: destroy_rectangle_counters
    module procedure :: destroy_triangle_counters
    module procedure :: destroy_change_counters
    module procedure :: destroy_element
    module procedure :: destroy_line
end interface destroy

contains

    pure subroutine create_line(changes, num_boxes, num_components)
        type(Concrete_Observables_Changes), allocatable, intent(out) :: changes(:)
        integer, intent(in) :: num_boxes, num_components

        integer :: i_box

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

    pure subroutine destroy_line(changes)
        type(Concrete_Observables_Changes), allocatable, intent(inout) :: changes(:)

        integer :: i_box

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

    pure subroutine create_element(changes, num_components)
        type(Concrete_Observables_Changes), intent(out) :: changes
        integer, intent(in) :: num_components

        call create(changes%changes_counters, num_components)
        call create(changes%changes_sucesses, num_components)
        call create(changes%switches_counters, num_components)
        call reals_create(changes%switches_successes, num_components)
        call create(changes%transmutations_counters, num_components)
        allocate(changes%transmutations_successes(num_components, num_components))
        changes%transmutations_successes = 0._DP
    end subroutine create_element

    pure subroutine destroy_element(changes)
        type(Concrete_Observables_Changes), intent(out) :: changes

        if (allocated(changes%transmutations_successes)) &
            deallocate(changes%transmutations_successes)
        call destroy(changes%transmutations_counters)
        call reals_destroy(changes%switches_successes)
        call destroy(changes%switches_counters)
        call destroy(changes%changes_sucesses)
        call destroy(changes%changes_counters)
    end subroutine destroy_element

    pure subroutine create_changes_counters(counters, num_elements)
        type(Concrete_Changes_Counter), allocatable, intent(out) :: counters(:)
        integer, intent(in) :: num_elements

        integer :: i_counter

        allocate(counters(num_elements))
        do i_counter = 1, size(counters)
            call reset_counters(counters(i_counter))
        end do
    end subroutine create_changes_counters

    pure subroutine destroy_changes_counters(counters)
        type(Concrete_Changes_Counter), allocatable, intent(inout) :: counters(:)

        if (allocated(counters)) deallocate(counters)
    end subroutine destroy_changes_counters

    pure subroutine create_triangle_counters(counters, num_elements)
        type(Concrete_Change_Counter_Line), allocatable, intent(out) :: counters(:)
        integer, intent(in) :: num_elements

        integer :: i_counter

        allocate(counters(num_elements))
        do i_counter = 1, size(counters)
            allocate(counters(i_counter)%line(i_counter))
        end do
        call reset_counters(counters)
    end subroutine create_triangle_counters

    pure subroutine destroy_triangle_counters(counters)
        type(Concrete_Change_Counter_Line), allocatable, intent(inout) :: counters(:)

        integer :: i_counter

        if (allocated(counters)) then
            do i_counter = size(counters), 1, -1
                if (allocated(counters(i_counter)%line)) then
                    deallocate(counters(i_counter)%line)
                end if
            end do
            deallocate(counters)
        end if
    end subroutine destroy_triangle_counters

    pure subroutine create_square_counters(counters, num_elements)
        type(Concrete_Change_Counter), allocatable, intent(out) :: counters(:, :)
        integer, intent(in) :: num_elements

        allocate(counters(num_elements, num_elements))
        call reset_counters(counters)
    end subroutine create_square_counters

    pure subroutine create_rectangle_counters(counters, num_elements_1, num_elements_2)
        type(Concrete_Change_Counter), allocatable, intent(out) :: counters(:, :)
        integer, intent(in) :: num_elements_1, num_elements_2

        allocate(counters(num_elements_1, num_elements_2))
        call reset_counters(counters)
    end subroutine create_rectangle_counters

    pure subroutine destroy_rectangle_counters(counters)
        type(Concrete_Change_Counter), allocatable, intent(inout) :: counters(:, :)

        if (allocated(counters)) deallocate(counters)
    end subroutine destroy_rectangle_counters

    pure subroutine create_teleportations_counters(counters, num_boxes, num_components)
        type(Concrete_Change_Counter), allocatable, intent(out) :: counters(:, :, :)
        integer, intent(in) :: num_boxes, num_components

        allocate(counters(num_components, num_boxes, num_boxes))
        call reset_counters(counters)
    end subroutine create_teleportations_counters

    pure subroutine destroy_teleportations_counters(counters)
        type(Concrete_Change_Counter), allocatable, intent(inout) :: counters(:, :, :)

        if (allocated(counters)) deallocate(counters)
    end subroutine destroy_teleportations_counters

    pure subroutine create_swaps_counters(counters, num_boxes, num_components)
        type(Concrete_Change_Counter), allocatable, intent(out) :: counters(:, :, :, :)
        integer, intent(in) :: num_boxes, num_components

        allocate(counters(num_components, num_components, num_boxes, num_boxes))
        call reset_counters(counters)
    end subroutine create_swaps_counters

    pure subroutine destroy_swaps_counters(counters)
        type(Concrete_Change_Counter), allocatable, intent(inout) :: counters(:, :, :, :)

        if (allocated(counters)) deallocate(counters)
    end subroutine destroy_swaps_counters

    pure subroutine create_change_counters(counters, num_elements)
        type(Concrete_Change_Counter), allocatable, intent(out) :: counters(:)
        integer, intent(in) :: num_elements

        allocate(counters(num_elements))
        call reset_counters(counters)
    end subroutine create_change_counters

    pure subroutine destroy_change_counters(counters)
        type(Concrete_Change_Counter), allocatable, intent(inout) :: counters(:)

        if (allocated(counters)) deallocate(counters)
    end subroutine destroy_change_counters

    pure subroutine create_changes_successes(successes, num_elements)
        type(Concrete_Changes_Success), allocatable, intent(out) :: successes(:)
        integer, intent(in) :: num_elements

        allocate(successes(num_elements))
    end subroutine create_changes_successes

    pure subroutine destroy_changes_successes(successes)
        type(Concrete_Changes_Success), allocatable, intent(inout) :: successes(:)

        if (allocated(successes)) deallocate(successes)
    end subroutine destroy_changes_successes

end module procedures_observables_changes_factory
