module classes_plmc_propagator

use classes_tower_sampler, only: Abstract_Tower_Sampler
use procedures_tower_sampler_factory, only: tower_sampler_destroy => destroy
use classes_generating_algorithm, only: Generating_Algorithm_Wrapper
use types_generating_observables_wrapper, only: Generating_Observables_Wrapper

implicit none

private

    type, abstract, public :: Abstract_PLMC_Propagator
    private
        type(Generating_Algorithm_Wrapper), pointer :: generating_algorithms(:) => null()
        class(Abstract_Tower_Sampler), allocatable :: selector
    contains
        procedure :: construct => Abstract_construct
        procedure :: destroy => Abstract_destroy
        procedure :: reset => Abstract_reset
        procedure :: try => Abstract_try
    end type Abstract_PLMC_Propagator

    type, extends(Abstract_PLMC_Propagator), public :: Concrete_PLMC_Propagator

    end type Concrete_PLMC_Propagator

    type, extends(Abstract_PLMC_Propagator), public :: Null_PLMC_Propagator
    contains
        procedure :: construct => Null_construct
        procedure :: destroy => Null_destroy
        procedure :: reset => Null_reset
        procedure :: try => Null_try
    end type Null_PLMC_Propagator

contains

!implementation Abstract_PLMC_Propagator

    subroutine Abstract_construct(this, generating_algorithms, selector)
        class(Abstract_PLMC_Propagator), intent(out) :: this
        type(Generating_Algorithm_Wrapper), target, intent(in) :: generating_algorithms(:)
        class(Abstract_Tower_Sampler), intent(in) :: selector

        this%generating_algorithms => generating_algorithms
        allocate(this%selector, source=selector)
    end subroutine Abstract_construct

    subroutine Abstract_destroy(this)
        class(Abstract_PLMC_Propagator), intent(inout) :: this

        call tower_sampler_destroy(this%selector)
        this%generating_algorithms => null()
    end subroutine Abstract_destroy

    subroutine Abstract_reset(this)
        class(Abstract_PLMC_Propagator), intent(inout) :: this

        integer :: nums_algorithms(size(this%generating_algorithms)), i_algorithm

        do i_algorithm = 1, size(this%generating_algorithms)
            call this%generating_algorithms(i_algorithm)%algorithm%reset_selectors()
            nums_algorithms(i_algorithm) = this%generating_algorithms(i_algorithm)%algorithm%&
                get_num_choices()
        end do
        call this%selector%reset(nums_algorithms)
    end subroutine Abstract_reset

    !> @bug Direct feed in array doesn't work: gfortran bug?
    subroutine Abstract_try(this, observables)
        class(Abstract_PLMC_Propagator), intent(in) :: this
        type(Generating_Observables_Wrapper), intent(inout) :: observables

        integer :: i_choice, i_random

        do i_choice = 1, this%selector%get_num_choices()
            i_random = this%selector%get()
            call this%generating_algorithms(i_random)%algorithm%try(observables)
        end do
    end subroutine Abstract_try

!end implementation Abstract_PLMC_Propagator

!implementation Null_PLMC_Propagator

    subroutine Null_construct(this, generating_algorithms, selector)
        class(Null_PLMC_Propagator), intent(out) :: this
        type(Generating_Algorithm_Wrapper), target, intent(in) :: generating_algorithms(:)
        class(Abstract_Tower_Sampler), intent(in) :: selector
    end subroutine Null_construct

    subroutine Null_destroy(this)
        class(Null_PLMC_Propagator), intent(inout) :: this
    end subroutine Null_destroy

    subroutine Null_reset(this)
        class(Null_PLMC_Propagator), intent(inout) :: this
    end subroutine Null_reset

    subroutine Null_try(this, observables)
        class(Null_PLMC_Propagator), intent(in) :: this
        type(Generating_Observables_Wrapper), intent(inout) :: observables
    end subroutine Null_try

!end implementation Null_PLMC_Propagator

end module classes_plmc_propagator
