module procedures_component_coordinates_reader_factory

use procedures_errors, only: error_exit
use classes_num_particles, only: Abstract_Num_Particles
use classes_component_coordinates, only: Abstract_Component_Coordinates
use types_component_wrapper, only: Component_Wrapper
use procedures_mixture_inquirers, only: component_has_positions, component_has_orientations
use types_component_coordinates_reader_selector, only: Component_Coordinates_Reader_Selector
use classes_component_coordinates_reader, only: Abstract_Component_Coordinates_Reader, &
    Concrete_Component_Coordinates_Reader, Concrete_Component_Positions_Reader, &
    Concrete_Component_Orientations_Reader, Null_Component_Coordinates_Reader, &
    Component_Coordinates_Reader_wrapper

implicit none

private
public :: create, destroy

interface create
    module procedure :: create_rectangle
    module procedure :: create_element
end interface create

interface destroy
    module procedure :: destroy_element
    module procedure :: destroy_rectangle
end interface destroy

contains

    subroutine create_rectangle(components_coordinates, components)
        type(Component_Coordinates_Reader_wrapper), allocatable, intent(out) :: &
            components_coordinates(:, :)
        type(Component_Wrapper), intent(in) :: components(:, :)

        type(Component_Coordinates_Reader_Selector) :: selector_i
        integer :: i_box, i_component

        allocate(components_coordinates(size(components, 1), size(components, 2)))
        do i_box = 1, size(components_coordinates, 2)
            do i_component = 1, size(components_coordinates, 1)
                associate(num_particles_i => components(i_component, i_box)%num_particles, &
                    positions_i => components(i_component, i_box)%positions, &
                    orientations_i => components(i_component, i_box)%orientations)
                    selector_i%read_positions = component_has_positions(positions_i)
                    selector_i%read_orientations = component_has_orientations(orientations_i)
                    call create(components_coordinates(i_component, i_box)%reader, num_particles_i,&
                        positions_i, orientations_i, selector_i)
                end associate
            end do
        end do
    end subroutine create_rectangle

    subroutine destroy_rectangle(components_coordinates)
        type(Component_Coordinates_Reader_wrapper), allocatable, intent(inout) :: &
            components_coordinates(:, :)

        integer :: i_box, i_component

        if (allocated(components_coordinates)) then
            do i_box = size(components_coordinates, 2), 1, -1
                do i_component = size(components_coordinates, 1), 1, -1
                    call destroy(components_coordinates(i_component, i_box)%reader)
                end do
            end do
            deallocate(components_coordinates)
        end if
    end subroutine destroy_rectangle

    subroutine create_element(coordinates, num_particles, positions, orientations, selector)
        class(Abstract_Component_Coordinates_Reader), allocatable, intent(out) :: coordinates
        class(Abstract_Num_Particles), intent(in) :: num_particles
        class(Abstract_Component_Coordinates), intent(in) :: positions, orientations
        type(Component_Coordinates_Reader_Selector), intent(in) :: selector

        call allocate_element(coordinates, selector)
        call construct_element(coordinates, num_particles, positions, orientations)
    end subroutine create_element

    subroutine allocate_element(coordinates, selector)
        class(Abstract_Component_Coordinates_Reader), allocatable, intent(out) :: coordinates
        type(Component_Coordinates_Reader_Selector), intent(in) :: selector

        if (selector%read_positions .and. selector%read_orientations) then
            allocate(Concrete_Component_Coordinates_Reader :: coordinates)
        else if (selector%read_positions .and. .not.selector%read_orientations) then
            allocate(Concrete_Component_Positions_Reader :: coordinates)
        else if (.not.selector%read_positions .and. selector%read_orientations) then
            allocate(Concrete_Component_Orientations_Reader :: coordinates)
        else
            allocate(Null_Component_Coordinates_Reader :: coordinates)
        end if
    end subroutine allocate_element

    subroutine construct_element(coordinates, num_particles, positions, orientations)
        class(Abstract_Component_Coordinates_Reader), intent(inout) :: coordinates
        class(Abstract_Num_Particles), intent(in) :: num_particles
        class(Abstract_Component_Coordinates), intent(in) :: positions, orientations

        select type (coordinates)
            type is (Concrete_Component_Coordinates_Reader)
                call coordinates%construct(num_particles, positions, orientations)
            type is (Concrete_Component_Positions_Reader)
                call coordinates%construct(num_particles, positions)
            type is (Concrete_Component_Orientations_Reader)
                call coordinates%construct(num_particles, orientations)
            type is (Null_Component_Coordinates_Reader)
            class default
                call error_exit("procedures_component_coordinates_reader_factory: construct: "//&
                    "coordinates type unknown.")
        end select
    end subroutine construct_element

    subroutine destroy_element(coordinates)
        class(Abstract_Component_Coordinates_Reader), allocatable, intent(inout) :: coordinates

        if (allocated(coordinates)) then
            call coordinates%destroy()
            deallocate(coordinates)
        end if
    end subroutine destroy_element

end module procedures_component_coordinates_reader_factory
