module procedures_moved_coordinates_factory

use, intrinsic :: iso_fortran_env, only: DP => REAL64
use json_module, only: json_file
use procedures_errors, only: error_exit
use procedures_checks, only: check_data_found
use classes_periodic_box, only: Abstract_Periodic_Box
use classes_component_coordinates, only: Abstract_Component_Coordinates
use procedures_mixture_inquirers, only: component_has_positions, component_has_orientations
use classes_moved_coordinates, only: Abstract_Moved_Coordinates, Null_Moved_Coordinates
use classes_translated_positions, only: Concrete_Translated_Positions
use classes_rotated_orientations, only: Concrete_Rotated_Orientations
use module_move_tuning, only: Concrete_Move_Tuning_Parameters

implicit none

private
public :: create, destroy

interface create
    module procedure :: create_translated_positions
    module procedure :: create_rotated_orientations
end interface create

contains

    subroutine create_translated_positions(translated_positions, periodic_box, positions, &
        tuning_parameters, generating_data, prefix)
        class(Abstract_Moved_Coordinates), allocatable, intent(out) :: translated_positions
        class(Abstract_Periodic_Box), intent(in) :: periodic_box
        class(Abstract_Component_Coordinates), intent(in) :: positions
        type(Concrete_Move_Tuning_Parameters), intent(in) :: tuning_parameters
        type(json_file), intent(inout) :: generating_data
        character(len=*), intent(in) :: prefix

        real(DP), allocatable :: initial_delta(:)
        character(len=:), allocatable :: data_field
        logical :: data_found

        if (component_has_positions(positions)) then
            allocate(Concrete_Translated_Positions :: translated_positions)
        else
            allocate(Null_Moved_Coordinates :: translated_positions)
        end if

        select type (translated_positions)
            type is (Concrete_Translated_Positions)
                data_field = prefix//"Small Move.initial delta"
                call generating_data%get(data_field, initial_delta, data_found)
                call check_data_found(data_field, data_found)
                call translated_positions%construct(periodic_box, positions, initial_delta, &
                    tuning_parameters)
            type is (Null_Moved_Coordinates)
            class default
                call error_exit("procedures_moved_coordinates_factory: "//&
                    "create_translated_positions: translated_positions: type unknown.")
        end select
    end subroutine create_translated_positions

    subroutine create_rotated_orientations(rotated_orientations, orientations, tuning_parameters, &
        generating_data, prefix)
        class(Abstract_Moved_Coordinates), allocatable, intent(out) :: rotated_orientations
        type(Concrete_Move_Tuning_Parameters), intent(in) :: tuning_parameters
        class(Abstract_Component_Coordinates), intent(in) :: orientations
        type(json_file), intent(inout) :: generating_data
        character(len=*), intent(in) :: prefix

        real(DP) :: initial_delta
        character(len=:), allocatable :: data_field
        logical :: data_found

        if (component_has_orientations(orientations)) then
            allocate(Concrete_Rotated_Orientations :: rotated_orientations)
        else
            allocate(Null_Moved_Coordinates :: rotated_orientations)
        end if

        select type (rotated_orientations)
            type is (Concrete_Rotated_Orientations)
                data_field = prefix//"Small Rotation.initial delta"
                call generating_data%get(data_field, initial_delta, data_found)
                call check_data_found(data_field, data_found)
                call rotated_orientations%construct(orientations, initial_delta, tuning_parameters)
            type is (Null_Moved_Coordinates)
            class default
                call error_exit("create_rotated_orientations: rotated_orientations: type unknown")
        end select
    end subroutine create_rotated_orientations

    subroutine destroy(moved_coordinates)
        class(Abstract_Moved_Coordinates), allocatable, intent(inout) :: moved_coordinates

        if (allocated(moved_coordinates)) then
            call moved_coordinates%destroy()
            deallocate(moved_coordinates)
        end if
    end subroutine destroy

end module procedures_moved_coordinates_factory
