!--------------------------------------------------------------------------------------------------!
!   CP2K: A general program to perform molecular dynamics simulations                              !
!   Copyright 2000-2025 CP2K developers group <https://cp2k.org>                                   !
!                                                                                                  !
!   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
!--------------------------------------------------------------------------------------------------!

! **************************************************************************************************
!> \par History
!>    cjm, Feb-20-2001 : added all the extended variables to
!>    system_type
!>    gt 23-09-2002 : major changes. Pointer part is allocated/deallocated
!>                    and initialized here. Atomic coordinates can now be
!>                    read also from &COORD section in the input file.
!>                    If &COORD is not found, .dat file is read.
!>                    If & coord is found and .NOT. 'INIT', parsing of the .dat
!>                    is performed to get the proper coords/vel/eta variables
!>     CJM 31-7-03  : Major rewrite.  No more atype
! **************************************************************************************************
MODULE atoms_input
   USE atomic_kind_types,               ONLY: atomic_kind_type,&
                                              get_atomic_kind
   USE cell_types,                      ONLY: cell_type,&
                                              pbc,&
                                              scaled_to_real
   USE cp_linked_list_input,            ONLY: cp_sll_val_next,&
                                              cp_sll_val_type
   USE cp_log_handling,                 ONLY: cp_logger_get_default_io_unit,&
                                              cp_to_string
   USE cp_parser_methods,               ONLY: read_float_object
   USE cp_units,                        ONLY: cp_unit_to_cp2k
   USE input_section_types,             ONLY: section_vals_get,&
                                              section_vals_get_subs_vals,&
                                              section_vals_list_get,&
                                              section_vals_remove_values,&
                                              section_vals_type,&
                                              section_vals_val_get
   USE input_val_types,                 ONLY: val_get,&
                                              val_type
   USE kinds,                           ONLY: default_string_length,&
                                              dp
   USE memory_utilities,                ONLY: reallocate
   USE particle_types,                  ONLY: particle_type
   USE shell_potential_types,           ONLY: shell_kind_type
   USE string_table,                    ONLY: id2str,&
                                              s2s,&
                                              str2id
   USE string_utilities,                ONLY: uppercase
   USE topology_types,                  ONLY: atom_info_type,&
                                              topology_parameters_type
#include "./base/base_uses.f90"

   IMPLICIT NONE

   PRIVATE
   PUBLIC :: read_atoms_input, read_shell_coord_input
   CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'atoms_input'

CONTAINS

! **************************************************************************************************
!> \brief ...
!> \param topology ...
!> \param overwrite ...
!> \param subsys_section ...
!> \param save_mem ...
!> \author CJM
! **************************************************************************************************
   SUBROUTINE read_atoms_input(topology, overwrite, subsys_section, save_mem)

      TYPE(topology_parameters_type)                     :: topology
      LOGICAL, INTENT(IN), OPTIONAL                      :: overwrite
      TYPE(section_vals_type), POINTER                   :: subsys_section
      LOGICAL, INTENT(IN), OPTIONAL                      :: save_mem

      CHARACTER(len=*), PARAMETER                        :: routineN = 'read_atoms_input'

      CHARACTER(len=2*default_string_length)             :: line_att
      CHARACTER(len=default_string_length)               :: error_message, my_default_index, strtmp, &
                                                            unit_str
      INTEGER                                            :: default_id, end_c, handle, iatom, j, &
                                                            natom, output_unit, start_c, wrd
      LOGICAL                                            :: explicit, is_ok, my_overwrite, &
                                                            my_save_mem, scaled_coordinates
      REAL(KIND=dp)                                      :: r0(3), unit_conv
      TYPE(atom_info_type), POINTER                      :: atom_info
      TYPE(cell_type), POINTER                           :: cell
      TYPE(cp_sll_val_type), POINTER                     :: list
      TYPE(section_vals_type), POINTER                   :: coord_section
      TYPE(val_type), POINTER                            :: val

      my_overwrite = .FALSE.
      my_save_mem = .FALSE.
      error_message = ""
      output_unit = cp_logger_get_default_io_unit()
      IF (PRESENT(overwrite)) my_overwrite = overwrite
      IF (PRESENT(save_mem)) my_save_mem = save_mem
      NULLIFY (coord_section)
      coord_section => section_vals_get_subs_vals(subsys_section, "COORD")
      CALL section_vals_get(coord_section, explicit=explicit)
      IF (.NOT. explicit) RETURN

      CALL timeset(routineN, handle)
      !-----------------------------------------------------------------------------
      !-----------------------------------------------------------------------------
      ! 1. get cell and topology%atom_info
      !-----------------------------------------------------------------------------
      atom_info => topology%atom_info
      cell => topology%cell_muc
      CALL section_vals_val_get(coord_section, "UNIT", c_val=unit_str)
      CALL section_vals_val_get(coord_section, "SCALED", l_val=scaled_coordinates)
      unit_conv = cp_unit_to_cp2k(1.0_dp, TRIM(unit_str))

      !-----------------------------------------------------------------------------
      !-----------------------------------------------------------------------------
      ! 2. Read in the coordinates from &COORD section in the input file
      !-----------------------------------------------------------------------------
      CALL section_vals_val_get(coord_section, "_DEFAULT_KEYWORD_", &
                                n_rep_val=natom)
      topology%natoms = natom
      IF (my_overwrite) THEN
         CPASSERT(SIZE(atom_info%r, 2) == natom)
         CALL cp_warn(__LOCATION__, &
                      "Overwriting coordinates. Active coordinates read from &COORD section."// &
                      " Active coordinates READ from &COORD section ")
         CALL section_vals_list_get(coord_section, "_DEFAULT_KEYWORD_", list=list)
         DO iatom = 1, natom
            is_ok = cp_sll_val_next(list, val)
            CALL val_get(val, c_val=line_att)
            ! Read name and atomic coordinates
            start_c = 1
            DO wrd = 1, 4
               DO j = start_c, LEN(line_att)
                  IF (line_att(j:j) /= ' ') THEN
                     start_c = j
                     EXIT
                  END IF
               END DO
               end_c = LEN(line_att) + 1
               DO j = start_c, LEN(line_att)
                  IF (line_att(j:j) == ' ') THEN
                     end_c = j
                     EXIT
                  END IF
               END DO
               IF (LEN_TRIM(line_att(start_c:end_c - 1)) == 0) &
                  CPABORT("incorrectly formatted line in coord section'"//line_att//"'")
               IF (wrd == 1) THEN
                  atom_info%id_atmname(iatom) = str2id(s2s(line_att(start_c:end_c - 1)))
               ELSE
                  READ (line_att(start_c:end_c - 1), *) atom_info%r(wrd - 1, iatom)
               END IF
               start_c = end_c
            END DO
         END DO
      ELSE
         ! Element is assigned on the basis of the atm_name
         topology%aa_element = .TRUE.

         CALL reallocate(atom_info%id_molname, 1, natom)
         CALL reallocate(atom_info%id_resname, 1, natom)
         CALL reallocate(atom_info%resid, 1, natom)
         CALL reallocate(atom_info%id_atmname, 1, natom)
         CALL reallocate(atom_info%id_element, 1, natom)
         CALL reallocate(atom_info%r, 1, 3, 1, natom)
         CALL reallocate(atom_info%atm_mass, 1, natom)
         CALL reallocate(atom_info%atm_charge, 1, natom)

         CALL section_vals_list_get(coord_section, "_DEFAULT_KEYWORD_", list=list)
         DO iatom = 1, natom
            ! we use only the first default_string_length characters of each line
            is_ok = cp_sll_val_next(list, val)
            CALL val_get(val, c_val=line_att)
            default_id = str2id(s2s(""))
            atom_info%id_molname(iatom) = default_id
            atom_info%id_resname(iatom) = default_id
            atom_info%resid(iatom) = 1
            atom_info%id_atmname(iatom) = default_id
            atom_info%id_element(iatom) = default_id
            topology%molname_generated = .TRUE.
            ! Read name and atomic coordinates
            start_c = 1
            DO wrd = 1, 6
               DO j = start_c, LEN(line_att)
                  IF (line_att(j:j) /= ' ') THEN
                     start_c = j
                     EXIT
                  END IF
               END DO
               end_c = LEN(line_att) + 1
               DO j = start_c, LEN(line_att)
                  IF (line_att(j:j) == ' ') THEN
                     end_c = j
                     EXIT
                  END IF
               END DO
               IF (LEN_TRIM(line_att(start_c:end_c - 1)) == 0) &
                  CALL cp_abort(__LOCATION__, &
                                "Incorrectly formatted input line for atom "// &
                                TRIM(ADJUSTL(cp_to_string(iatom)))// &
                                " found in COORD section. Input line: <"// &
                                TRIM(line_att)//"> ")
               SELECT CASE (wrd)
               CASE (1)
                  atom_info%id_atmname(iatom) = str2id(s2s(line_att(start_c:end_c - 1)))
               CASE (2:4)
                  CALL read_float_object(line_att(start_c:end_c - 1), &
                                         atom_info%r(wrd - 1, iatom), error_message)
                  IF (LEN_TRIM(error_message) /= 0) &
                     CALL cp_abort(__LOCATION__, &
                                   "Incorrectly formatted input line for atom "// &
                                   TRIM(ADJUSTL(cp_to_string(iatom)))// &
                                   " found in COORD section. "//TRIM(error_message)// &
                                   " Input line: <"//TRIM(line_att)//"> ")
               CASE (5)
                  READ (line_att(start_c:end_c - 1), *) strtmp
                  atom_info%id_molname(iatom) = str2id(strtmp)
                  atom_info%id_resname(iatom) = atom_info%id_molname(iatom)
                  topology%molname_generated = .FALSE.
               CASE (6)
                  READ (line_att(start_c:end_c - 1), *) strtmp
                  atom_info%id_resname(iatom) = str2id(strtmp)
               END SELECT
               start_c = end_c
               IF (start_c > LEN_TRIM(line_att)) EXIT
            END DO
            IF (topology%molname_generated) THEN
               ! Use defaults, if no molname was specified
               WRITE (my_default_index, '(I0)') iatom
               atom_info%id_molname(iatom) = str2id(s2s(TRIM(id2str(atom_info%id_atmname(iatom)))//TRIM(my_default_index)))
               atom_info%id_resname(iatom) = atom_info%id_molname(iatom)
            END IF
            atom_info%id_element(iatom) = atom_info%id_atmname(iatom)
            atom_info%atm_mass(iatom) = 0.0_dp
            atom_info%atm_charge(iatom) = -HUGE(0.0_dp)
         END DO
      END IF
      !-----------------------------------------------------------------------------
      !-----------------------------------------------------------------------------
      ! 3. Convert coordinates into internal cp2k coordinates
      !-----------------------------------------------------------------------------
      DO iatom = 1, natom
         IF (scaled_coordinates) THEN
            r0 = atom_info%r(:, iatom)
            CALL scaled_to_real(atom_info%r(:, iatom), r0, cell)
         ELSE
            atom_info%r(:, iatom) = atom_info%r(:, iatom)*unit_conv
         END IF
      END DO
      IF (my_save_mem) CALL section_vals_remove_values(coord_section)

      CALL timestop(handle)
   END SUBROUTINE read_atoms_input

! **************************************************************************************************
!> \brief ...
!> \param particle_set ...
!> \param shell_particle_set ...
!> \param cell ...
!> \param subsys_section ...
!> \param core_particle_set ...
!> \param save_mem ...
!> \author MI
! **************************************************************************************************
   SUBROUTINE read_shell_coord_input(particle_set, shell_particle_set, cell, &
                                     subsys_section, core_particle_set, save_mem)

      TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set, shell_particle_set
      TYPE(cell_type), POINTER                           :: cell
      TYPE(section_vals_type), POINTER                   :: subsys_section
      TYPE(particle_type), DIMENSION(:), OPTIONAL, &
         POINTER                                         :: core_particle_set
      LOGICAL, INTENT(IN), OPTIONAL                      :: save_mem

      CHARACTER(len=*), PARAMETER :: routineN = 'read_shell_coord_input'

      CHARACTER(len=2*default_string_length)             :: line_att
      CHARACTER(len=default_string_length)               :: name_kind, unit_str
      CHARACTER(len=default_string_length), &
         ALLOCATABLE, DIMENSION(:)                       :: at_name, at_name_c
      INTEGER                                            :: end_c, handle, ishell, j, nshell, &
                                                            output_unit, sh_index, start_c, wrd
      INTEGER, ALLOCATABLE, DIMENSION(:)                 :: at_index, at_index_c
      LOGICAL                                            :: core_scaled_coordinates, explicit, &
                                                            is_ok, is_shell, my_save_mem, &
                                                            shell_scaled_coordinates
      REAL(KIND=dp)                                      :: dab, mass_com, rab(3), unit_conv_core, &
                                                            unit_conv_shell
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: r, rc
      TYPE(atomic_kind_type), POINTER                    :: atomic_kind
      TYPE(cp_sll_val_type), POINTER                     :: list
      TYPE(section_vals_type), POINTER                   :: core_coord_section, shell_coord_section
      TYPE(shell_kind_type), POINTER                     :: shell
      TYPE(val_type), POINTER                            :: val

      my_save_mem = .FALSE.
      NULLIFY (atomic_kind, list, shell_coord_section, shell, val)
      output_unit = cp_logger_get_default_io_unit()

      IF (PRESENT(save_mem)) my_save_mem = save_mem
      NULLIFY (shell_coord_section, core_coord_section)
      shell_coord_section => section_vals_get_subs_vals(subsys_section, "SHELL_COORD")
      CALL section_vals_get(shell_coord_section, explicit=explicit)
      IF (.NOT. explicit) RETURN

      CALL timeset(routineN, handle)
      CPASSERT(ASSOCIATED(particle_set))
      !-----------------------------------------------------------------------------
      !-----------------------------------------------------------------------------
      ! 2. Read in the coordinates from &SHELL_COORD section in the input file
      !-----------------------------------------------------------------------------
      CALL section_vals_val_get(shell_coord_section, "UNIT", c_val=unit_str)
      CALL section_vals_val_get(shell_coord_section, "SCALED", l_val=shell_scaled_coordinates)
      unit_conv_shell = cp_unit_to_cp2k(1.0_dp, TRIM(unit_str))
      CALL section_vals_val_get(shell_coord_section, "_DEFAULT_KEYWORD_", &
                                n_rep_val=nshell)

      IF (ASSOCIATED(shell_particle_set)) THEN
         CPASSERT((SIZE(shell_particle_set, 1) == nshell))
         ALLOCATE (r(3, nshell), at_name(nshell), at_index(nshell))
         CALL cp_warn(__LOCATION__, &
                      "Overwriting shell coordinates. "// &
                      "Active coordinates READ from &SHELL_COORD section. ")
         CALL section_vals_list_get(shell_coord_section, "_DEFAULT_KEYWORD_", list=list)
         DO ishell = 1, nshell
            ! we use only the first default_string_length characters of each line
            is_ok = cp_sll_val_next(list, val)
            CALL val_get(val, c_val=line_att)
            start_c = 1
            DO wrd = 1, 5
               DO j = start_c, LEN(line_att)
                  IF (line_att(j:j) /= ' ') THEN
                     start_c = j
                     EXIT
                  END IF
               END DO
               end_c = LEN(line_att) + 1
               DO j = start_c, LEN(line_att)
                  IF (line_att(j:j) == ' ') THEN
                     end_c = j
                     EXIT
                  END IF
               END DO
               IF (wrd /= 5 .AND. end_c >= LEN(line_att) + 1) &
                  CPABORT("incorrectly formatted line in coord section'"//line_att//"'")
               IF (wrd == 1) THEN
                  at_name(ishell) = line_att(start_c:end_c - 1)
                  CALL uppercase(at_name(ishell))
               ELSE IF (wrd == 5) THEN
                  READ (line_att(start_c:end_c - 1), *) at_index(ishell)
               ELSE
                  READ (line_att(start_c:end_c - 1), *) r(wrd - 1, ishell)
               END IF
               start_c = end_c
            END DO
         END DO

         IF (PRESENT(core_particle_set)) THEN
            CPASSERT(ASSOCIATED(core_particle_set))
            core_coord_section => section_vals_get_subs_vals(subsys_section, "CORE_COORD")
            CALL section_vals_get(core_coord_section, explicit=explicit)
            IF (explicit) THEN
               CALL section_vals_val_get(core_coord_section, "UNIT", c_val=unit_str)
               CALL section_vals_val_get(core_coord_section, "SCALED", l_val=core_scaled_coordinates)
               unit_conv_core = cp_unit_to_cp2k(1.0_dp, TRIM(unit_str))
               CALL section_vals_val_get(core_coord_section, "_DEFAULT_KEYWORD_", &
                                         n_rep_val=nshell)

               CPASSERT((SIZE(core_particle_set, 1) == nshell))
               ALLOCATE (rc(3, nshell), at_name_c(nshell), at_index_c(nshell))
               CALL cp_warn(__LOCATION__, &
                            "Overwriting cores coordinates. "// &
                            "Active coordinates READ from &CORE_COORD section. ")
               CALL section_vals_list_get(core_coord_section, "_DEFAULT_KEYWORD_", list=list)
               DO ishell = 1, nshell
                  ! we use only the first default_string_length characters of each line
                  is_ok = cp_sll_val_next(list, val)
                  CALL val_get(val, c_val=line_att)
                  start_c = 1
                  DO wrd = 1, 5
                     DO j = start_c, LEN(line_att)
                        IF (line_att(j:j) /= ' ') THEN
                           start_c = j
                           EXIT
                        END IF
                     END DO
                     end_c = LEN(line_att) + 1
                     DO j = start_c, LEN(line_att)
                        IF (line_att(j:j) == ' ') THEN
                           end_c = j
                           EXIT
                        END IF
                     END DO
                     IF (wrd /= 5 .AND. end_c >= LEN(line_att) + 1) &
                        CPABORT("incorrectly formatted line in coord section'"//line_att//"'")
                     IF (wrd == 1) THEN
                        at_name_c(ishell) = line_att(start_c:end_c - 1)
                        CALL uppercase(at_name_c(ishell))
                     ELSE IF (wrd == 5) THEN
                        READ (line_att(start_c:end_c - 1), *) at_index_c(ishell)
                     ELSE
                        READ (line_att(start_c:end_c - 1), *) rc(wrd - 1, ishell)
                     END IF
                     start_c = end_c
                  END DO
               END DO
               IF (my_save_mem) CALL section_vals_remove_values(core_coord_section)
            END IF ! explicit
         END IF ! core_particle_set

         !-----------------------------------------------------------------------------
         ! 3. Check corrispondence and convert coordinates into internal cp2k coordinates
         !-----------------------------------------------------------------------------
         DO ishell = 1, nshell
            atomic_kind => particle_set(at_index(ishell))%atomic_kind
            CALL get_atomic_kind(atomic_kind=atomic_kind, &
                                 name=name_kind, shell_active=is_shell, mass=mass_com, shell=shell)
            CALL uppercase(name_kind)
            IF ((TRIM(at_name(ishell)) == TRIM(name_kind)) .AND. is_shell) THEN
               sh_index = particle_set(at_index(ishell))%shell_index
               IF (shell_scaled_coordinates) THEN
                  CALL scaled_to_real(r(:, ishell), shell_particle_set(sh_index)%r(:), cell)
               ELSE
                  shell_particle_set(sh_index)%r(:) = r(:, ishell)*unit_conv_shell
               END IF
               shell_particle_set(sh_index)%atom_index = at_index(ishell)

               IF (PRESENT(core_particle_set) .AND. .NOT. explicit) THEN
                  core_particle_set(sh_index)%r(1) = (mass_com*particle_set(at_index(ishell))%r(1) - &
                                                      shell%mass_shell*shell_particle_set(sh_index)%r(1))/shell%mass_core
                  core_particle_set(sh_index)%r(2) = (mass_com*particle_set(at_index(ishell))%r(2) - &
                                                      shell%mass_shell*shell_particle_set(sh_index)%r(2))/shell%mass_core
                  core_particle_set(sh_index)%r(3) = (mass_com*particle_set(at_index(ishell))%r(3) - &
                                                      shell%mass_shell*shell_particle_set(sh_index)%r(3))/shell%mass_core
                  core_particle_set(sh_index)%atom_index = at_index(ishell)
                  rab = pbc(shell_particle_set(sh_index)%r, core_particle_set(sh_index)%r, cell)
               ELSE IF (explicit) THEN
                  IF (core_scaled_coordinates) THEN
                     CALL scaled_to_real(rc(:, ishell), core_particle_set(sh_index)%r(:), cell)
                  ELSE
                     core_particle_set(sh_index)%r(:) = rc(:, ishell)*unit_conv_core
                  END IF
                  core_particle_set(sh_index)%atom_index = at_index_c(ishell)
                  rab = pbc(shell_particle_set(sh_index)%r, core_particle_set(sh_index)%r, cell)
                  CPASSERT(TRIM(at_name(ishell)) == TRIM(at_name_c(ishell)))
                  CPASSERT(at_index(ishell) == at_index_c(ishell))
               ELSE
                  rab = pbc(shell_particle_set(sh_index)%r, particle_set(at_index(ishell))%r, cell)
               END IF

               dab = SQRT(rab(1)*rab(1) + rab(2)*rab(2) + rab(3)*rab(3))
               IF (shell%max_dist > 0.0_dp .AND. shell%max_dist < dab) THEN
                  IF (output_unit > 0) THEN
                     WRITE (output_unit, *) "WARNING : shell and core for atom ", at_index(ishell), " seem to be too distant."
                  END IF
               END IF

            ELSE
               CPABORT("shell coordinate assigned to the wrong atom. check the shell indexes in the input")
            END IF
         END DO
         DEALLOCATE (r, at_index, at_name)
         DEALLOCATE (rc, at_index_c, at_name_c)

      END IF

      IF (my_save_mem) CALL section_vals_remove_values(shell_coord_section)

      CALL timestop(handle)

   END SUBROUTINE read_shell_coord_input

END MODULE atoms_input
