module procedures_complete_coordinates_reader

use, intrinsic :: iso_fortran_env, only: DP => REAL64
use data_constants, only: num_dimensions
use data_strings, only: max_line_length, max_word_length
use procedures_checks, only: check_file_exists, check_in_range
use types_raw_coordinates, only: Concrete_Raw_Coordinates
use types_component_coordinates_reader_selector, only: Component_Coordinates_Reader_Selector

implicit none

private
public :: read, deallocate

interface read
    module procedure :: read_complete_coordinates
    module procedure :: read_box_size
end interface read

contains

    subroutine read_complete_coordinates(box_size, raw_coordinates, num_components, i_component, &
        filename, selector)
        real(DP), intent(out) :: box_size(:)
        type(Concrete_Raw_Coordinates), intent(out) :: raw_coordinates
        integer, intent(in) :: num_components, i_component
        character(len=*), intent(in) :: filename
        type(Component_Coordinates_Reader_Selector), intent(in) :: selector

        integer, dimension(num_components) :: nums_particles, nums_lbounds, nums_ubounds
        integer :: global_i_particle, i_particle
        integer :: coordinates_unit
        character(len=1) :: comment_character
        character(len=max_word_length) :: field

        call check_file_exists(filename)
        open(newunit=coordinates_unit, recl=max_line_length, file=filename, status="old", &
            action="read")
        read(coordinates_unit, *) comment_character, field, box_size
        read(coordinates_unit, *) comment_character, field, nums_particles
        read(coordinates_unit, *) comment_character
        call set_bounds(nums_lbounds, nums_ubounds, nums_particles)
        call allocate_coordinates(raw_coordinates, nums_particles(i_component), selector)
        i_particle = 0
        do global_i_particle = 1, sum(nums_particles)
            if (nums_lbounds(i_component) <= global_i_particle .and. global_i_particle <= &
                nums_ubounds(i_component)) then
                i_particle = i_particle + 1
                call read_coordinates(raw_coordinates, coordinates_unit, selector, i_particle)
            else
                read(coordinates_unit, *)
            end if
        end do
        close(coordinates_unit)
    end subroutine read_complete_coordinates

    pure subroutine set_bounds(nums_lbounds, nums_ubounds, nums_particles)
        integer, intent(inout) :: nums_lbounds(:), nums_ubounds(:)
        integer, intent(in) :: nums_particles(:)

        integer :: i_component

        nums_lbounds(1) = 1
        nums_ubounds(1) = nums_particles(1)
        do i_component = 2, size(nums_particles)
            nums_lbounds(i_component) = nums_ubounds(i_component - 1) + 1
            nums_ubounds(i_component) = nums_lbounds(i_component) + nums_particles(i_component) - 1
        end do
    end subroutine set_bounds

    pure subroutine allocate_coordinates(raw_coordinates, num_particles, selector)
        type(Concrete_Raw_Coordinates), intent(out) :: raw_coordinates
        integer, intent(in) :: num_particles
        type(Component_Coordinates_Reader_Selector), intent(in) :: selector

        if (selector%read_positions .and. selector%read_orientations) then
            allocate(raw_coordinates%positions(num_dimensions, num_particles))
            allocate(raw_coordinates%orientations(num_dimensions, num_particles))
        else if (selector%read_positions .and. .not.selector%read_orientations) then
            allocate(raw_coordinates%positions(num_dimensions, num_particles))
            allocate(raw_coordinates%orientations(num_dimensions, 0))
        else if (.not.selector%read_positions .and. selector%read_orientations) then
            allocate(raw_coordinates%positions(num_dimensions, 0))
            allocate(raw_coordinates%orientations(num_dimensions, num_particles))
        else
            allocate(raw_coordinates%positions(num_dimensions, 0))
            allocate(raw_coordinates%orientations(num_dimensions, 0))
        end if
    end subroutine allocate_coordinates

    subroutine read_coordinates(raw_coordinates, coordinates_unit, selector, i_particle)
        type(Concrete_Raw_Coordinates), intent(inout) :: raw_coordinates
        type(Component_Coordinates_Reader_Selector), intent(in) :: selector
        integer, intent(in) :: coordinates_unit
        integer, intent(in) :: i_particle

        integer :: i_component_dummy

        if (selector%read_positions .and. selector%read_orientations) then
            read(coordinates_unit, *) i_component_dummy, raw_coordinates%positions(:, i_particle), &
                raw_coordinates%orientations(:, i_particle)
        else if (selector%read_positions .and. .not.selector%read_orientations) then
            read(coordinates_unit, *) i_component_dummy, raw_coordinates%positions(:, i_particle)
        else if (.not.selector%read_positions .and. selector%read_orientations) then
            read(coordinates_unit, *) i_component_dummy, raw_coordinates%orientations(:, i_particle)
        end if
    end subroutine read_coordinates

    subroutine read_box_size(box_size, filename)
        real(DP), intent(out) :: box_size(:)
        character(len=*), intent(in) :: filename

        integer :: coordinates_unit
        character(len=1) :: comment_character
        character(len=max_word_length) :: field

        call check_file_exists(filename)
        open(newunit=coordinates_unit, recl=max_line_length, file=filename, status="old", &
            action="read")
        read(coordinates_unit, *) comment_character, field, box_size
        close(coordinates_unit)
    end subroutine read_box_size

    subroutine deallocate(raw_coordinates)
        type(Concrete_Raw_Coordinates), intent(inout) :: raw_coordinates

        if (allocated(raw_coordinates%orientations)) deallocate(raw_coordinates%orientations)
        if (allocated(raw_coordinates%positions)) deallocate(raw_coordinates%positions)
    end subroutine deallocate

end module procedures_complete_coordinates_reader
