module classes_component_coordinates_reader

use, intrinsic :: iso_fortran_env, only: DP => REAL64
use data_constants, only: num_dimensions
use classes_num_particles, only: Abstract_Num_Particles
use classes_component_coordinates, only: Abstract_Component_Coordinates

implicit none

private

    type, abstract, public :: Abstract_Component_Coordinates_Reader
        class(Abstract_Num_Particles), pointer :: num_particles => null()
    contains
        procedure(Abstract_destroy), deferred :: destroy
        procedure(Abstract_read), deferred :: read
    end type Abstract_Component_Coordinates_Reader

    abstract interface

        subroutine Abstract_destroy(this)
        import :: Abstract_Component_Coordinates_Reader
            class(Abstract_Component_Coordinates_Reader), intent(inout) :: this
        end subroutine Abstract_destroy

        !> @todo integrity check?
        subroutine Abstract_read(this, coordinates_unit, num_particles)
        import :: Abstract_Component_Coordinates_Reader
            class(Abstract_Component_Coordinates_Reader), intent(in) :: this
            integer, intent(in) :: coordinates_unit
            integer, intent(in) :: num_particles
        end subroutine Abstract_read

    end interface

    type, extends(Abstract_Component_Coordinates_Reader), public :: &
        Concrete_Component_Coordinates_Reader
    private
        class(Abstract_Component_Coordinates), pointer :: positions => null()
        class(Abstract_Component_Coordinates), pointer :: orientations => null()
    contains
        procedure :: construct => Coordinates_construct
        procedure :: destroy => Coordinates_destroy
        procedure :: read => Coordinates_read
    end type Concrete_Component_Coordinates_Reader

    type, extends(Abstract_Component_Coordinates_Reader), public :: &
        Concrete_Component_Positions_Reader
    private
        class(Abstract_Component_Coordinates), pointer :: positions => null()
    contains
        procedure :: construct => Positions_construct
        procedure :: destroy => Positions_destroy
        procedure :: read => Positions_read
    end type Concrete_Component_Positions_Reader

    type, extends(Abstract_Component_Coordinates_Reader), public :: &
        Concrete_Component_Orientations_Reader
    private
        class(Abstract_Component_Coordinates), pointer :: orientations => null()
    contains
        procedure :: construct => Orientations_construct
        procedure :: destroy => Orientations_destroy
        procedure :: read => Orientations_read
    end type Concrete_Component_Orientations_Reader

    type, extends(Abstract_Component_Coordinates_Reader), public :: &
        Null_Component_Coordinates_Reader
    contains
        procedure :: destroy => Null_destroy
        procedure :: read => Null_read
    end type Null_Component_Coordinates_Reader

    type, public :: Component_Coordinates_Reader_wrapper
        class(Abstract_Component_Coordinates_Reader), allocatable :: reader
    end type Component_Coordinates_Reader_wrapper

contains

!implementation Concrete_Component_Coordinates_Reader

    subroutine Coordinates_construct(this, num_particles, positions, orientations)
        class(Concrete_Component_Coordinates_Reader), intent(out) :: this
        class(Abstract_Num_Particles), target, intent(in) :: num_particles
        class(Abstract_Component_Coordinates), target, intent(in) :: positions, orientations

        this%num_particles => num_particles
        this%positions => positions
        this%orientations => orientations
    end subroutine Coordinates_construct

    subroutine Coordinates_destroy(this)
        class(Concrete_Component_Coordinates_Reader), intent(inout) :: this

        this%orientations => null()
        this%positions => null()
        this%num_particles => null()
    end subroutine Coordinates_destroy

    subroutine Coordinates_read(this, coordinates_unit, num_particles)
        class(Concrete_Component_Coordinates_Reader), intent(in) :: this
        integer, intent(in) :: coordinates_unit
        integer, intent(in) :: num_particles

        real(DP), dimension(:, :), allocatable :: positions, orientations
        integer :: i_component, i_particle

        call this%num_particles%set(num_particles)
        allocate(positions(num_dimensions, this%num_particles%get()))
        allocate(orientations(num_dimensions, this%num_particles%get()))
        do i_particle = 1, size(positions, 2)
            read(coordinates_unit, *) i_component, positions(:, i_particle), &
                orientations(:, i_particle)
        end do
        call this%positions%set_all(positions)
        call this%orientations%set_all(orientations)
    end subroutine Coordinates_read

!end implementation Concrete_Component_Coordinates_Reader

!implementation Concrete_Component_Positions_Reader

    subroutine Positions_construct(this, num_particles, positions)
        class(Concrete_Component_Positions_Reader), intent(out) :: this
        class(Abstract_Num_Particles), target, intent(in) :: num_particles
        class(Abstract_Component_Coordinates), target, intent(in) :: positions

        this%num_particles => num_particles
        this%positions => positions
    end subroutine Positions_construct

    subroutine Positions_destroy(this)
        class(Concrete_Component_Positions_Reader), intent(inout) :: this

        this%positions => null()
        this%num_particles => null()
    end subroutine Positions_destroy

    subroutine Positions_read(this, coordinates_unit, num_particles)
        class(Concrete_Component_Positions_Reader), intent(in) :: this
        integer, intent(in) :: coordinates_unit
        integer, intent(in) :: num_particles

        real(DP), dimension(:, :), allocatable :: positions
        integer :: i_component, i_particle

        call this%num_particles%set(num_particles)
        allocate(positions(num_dimensions, this%num_particles%get()))
        do i_particle = 1, size(positions, 2)
            read(coordinates_unit, *) i_component, positions(:, i_particle)
        end do
        call this%positions%set_all(positions)
    end subroutine Positions_read

!end implementation Concrete_Component_Positions_Reader

!implementation Concrete_Component_Orientations_Reader

    subroutine Orientations_construct(this, num_particles, orientations)
        class(Concrete_Component_Orientations_Reader), intent(out) :: this
        class(Abstract_Num_Particles), target, intent(in) :: num_particles
        class(Abstract_Component_Coordinates), target, intent(in) :: orientations

        this%num_particles => num_particles
        this%orientations => orientations
    end subroutine Orientations_construct

    subroutine Orientations_destroy(this)
        class(Concrete_Component_Orientations_Reader), intent(inout) :: this

        this%orientations => null()
        this%num_particles => null()
    end subroutine Orientations_destroy

    subroutine Orientations_read(this, coordinates_unit, num_particles)
        class(Concrete_Component_Orientations_Reader), intent(in) :: this
        integer, intent(in) :: coordinates_unit
        integer, intent(in) :: num_particles

        real(DP), dimension(:, :), allocatable :: orientations
        integer :: i_component, i_particle

        call this%num_particles%set(num_particles)
        allocate(orientations(num_dimensions, this%num_particles%get()))
        do i_particle = 1, size(orientations, 2)
            read(coordinates_unit, *) i_component, orientations(:, i_particle)
        end do
        call this%orientations%set_all(orientations)
    end subroutine Orientations_read

!end implementation Concrete_Component_Orientations_Reader

!implementation Null_Component_Coordinates_Reader

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

    subroutine Null_read(this, coordinates_unit, num_particles)
        class(Null_Component_Coordinates_Reader), intent(in) :: this
        integer, intent(in) :: coordinates_unit
        integer, intent(in) :: num_particles
    end subroutine Null_read

!end implementation Null_Component_Coordinates_Reader

end module classes_component_coordinates_reader
