module readinput
  !> Generic file input routines for uDALES
  !> Provides standardized reading of commonly-used input file formats
  use mpi
  use modglobal, only : ifinput
  use modmpi,    only : myid, comm3d, mpierr, MY_REAL
  use decomp_2d, only : zstart, zend
  implicit none

  private

  public :: read_sparse_ijk, read_sparse_real

contains

  !> Generic routine to read files containing sparse i,j,k grid point coordinates
  !> and return only the points that belong to the current MPI rank
  !> 
  !> Used by:
  !>  - heatpump.inp (2 header lines)
  !>  - solid_*.txt files (1 header line)
  !>  - fluid_boundary_*.txt files (1 header line)
  !>
  !> @param filename      Input filename to read
  !> @param npts          Number of data rows in file
  !> @param npts_loc      Output: number of points on this rank
  !> @param ids_loc       Output: indices of local points in global array (npts_loc)
  !> @param pts_loc       Output: local point coordinates remapped to local indices (npts_loc, 3)
  !> @param nskip         Number of header/comment lines to skip (default=1)
  
  subroutine read_sparse_ijk(filename, npts, npts_loc, ids_loc, pts_loc, nskip, pts_glob_out)
    use modglobal, only : ib, ie, jb, je
    implicit none

    character(len=*), intent(in)              :: filename      
    integer, intent(in)                       :: npts          
    integer, intent(out)                      :: npts_loc      
    integer, allocatable, intent(out)         :: ids_loc(:)    
    integer, allocatable, intent(out)         :: pts_loc(:,:)  
    integer, intent(in), optional             :: nskip   
    integer, allocatable, intent(out), optional :: pts_glob_out(:,:)   

    integer, allocatable :: pts_glob(:,:)
    logical, allocatable :: lpts(:)
    integer :: n, m, nh, ierr
    character(80) :: chmess

    ! Set default number of header lines to skip
    nh = 1
    if (present(nskip)) nh = nskip

    ! Allocate temporary arrays
    allocate(pts_glob(npts, 3))
    allocate(lpts(npts))

    ! Read file on rank 0 only
    if (myid == 0) then
      open(ifinput, file=filename, status='old', iostat=ierr)
      if (ierr /= 0) then
        write(*, '(A,A)') 'ERROR: Cannot open file: ', trim(filename)
        write(*, '(A,I0)') '  iostat error code: ', ierr
        stop 1
      end if

      ! Skip header lines
      do n = 1, nh
        read(ifinput, '(a80)', iostat=ierr) chmess
        if (ierr /= 0) then
          write(*, '(A,I0,A,A)') 'ERROR: Cannot read header line ', n, ' from ', trim(filename)
          stop 1
        end if
      end do

      ! Read integer triplets (i, j, k coordinates)
      do n = 1, npts
        read(ifinput, *, iostat=ierr) pts_glob(n, 1), pts_glob(n, 2), pts_glob(n, 3)
        if (ierr /= 0) then
          write(*, '(A,I0,A,A)') 'ERROR: Cannot read data line ', n, ' from ', trim(filename)
          write(*, '(A,I0)') '  iostat error code: ', ierr
          stop 1
        end if
      end do

      close(ifinput)
    end if

    ! Broadcast to all MPI ranks
    call MPI_BCAST(pts_glob, npts*3, MPI_INTEGER, 0, comm3d, mpierr)

    ! Determine whether points are on this rank and count them
    m = 0
    do n = 1, npts
      if ((pts_glob(n,1) >= zstart(1) .and. pts_glob(n,1) <= zend(1)) .and. &
          (pts_glob(n,2) >= zstart(2) .and. pts_glob(n,2) <= zend(2))) then
        lpts(n) = .true.
        m = m + 1
      else
        lpts(n) = .false.
      end if
    end do

    ! Pack local points into compact arrays
    npts_loc = m
    allocate(ids_loc(npts_loc))
    allocate(pts_loc(npts_loc, 3))
    
    m = 0
    do n = 1, npts
      if (lpts(n)) then
        m = m + 1
        ids_loc(m) = n
        ! Store global coordinates (no conversion)
        pts_loc(m,1) = pts_glob(n,1) - zstart(1) + 1
        pts_loc(m,2) = pts_glob(n,2) - zstart(2) + 1
        pts_loc(m,3) = pts_glob(n,3) - zstart(3) + 1

        if ((pts_loc(m,1) < ib) .or. (pts_loc(m,1) > ie) .or. (pts_loc(m,2) < jb) .or. (pts_loc(m,2) > je)) then
          write(*,*) "problem in ", filename, ": ", n, pts_glob(n,1), pts_glob(n,2), pts_loc(m,1), pts_loc(m,2)
          stop 1
        end if

      end if
    end do

    ! Clean up or return temporary arrays
    if (present(pts_glob_out)) then
      ! Transfer ownership of global array to caller
      call move_alloc(pts_glob, pts_glob_out)
    else
      ! Clean up global array
      deallocate(pts_glob)
    end if
    deallocate(lpts)

  end subroutine read_sparse_ijk

  !> Read one real value per sparse-point row and return only the values
  !> corresponding to the local sparse-point ids from a companion coordinate file.
  !>
  !> This is intended for files aligned with a sparse coordinate list such as
  !> veg.inp / sveg.inp, where ordering provides the mapping.
  subroutine read_sparse_real(filename, npts, ids_loc, values_loc, nskip)
    implicit none

    character(len=*), intent(in)      :: filename
    integer, intent(in)               :: npts
    integer, intent(in)               :: ids_loc(:)
    real, allocatable, intent(out)    :: values_loc(:)
    integer, intent(in), optional     :: nskip

    real, allocatable :: values_glob(:)
    integer :: n, nh, ierr
    character(80) :: chmess

    nh = 1
    if (present(nskip)) nh = nskip

    allocate(values_glob(npts))
    values_glob = 0.

    if (myid == 0) then
      open(ifinput, file=filename, status='old', iostat=ierr)
      if (ierr /= 0) then
        write(*, '(A,A)') 'ERROR: Cannot open file: ', trim(filename)
        write(*, '(A,I0)') '  iostat error code: ', ierr
        stop 1
      end if

      do n = 1, nh
        read(ifinput, '(a80)', iostat=ierr) chmess
        if (ierr /= 0) then
          write(*, '(A,I0,A,A)') 'ERROR: Cannot read header line ', n, ' from ', trim(filename)
          stop 1
        end if
      end do

      do n = 1, npts
        read(ifinput, *, iostat=ierr) values_glob(n)
        if (ierr /= 0) then
          write(*, '(A,I0,A,A)') 'ERROR: Cannot read data line ', n, ' from ', trim(filename)
          write(*, '(A,I0)') '  iostat error code: ', ierr
          stop 1
        end if
      end do

      close(ifinput)
    end if

    call MPI_BCAST(values_glob, npts, MY_REAL, 0, comm3d, mpierr)

    allocate(values_loc(size(ids_loc)))
    do n = 1, size(ids_loc)
      values_loc(n) = values_glob(ids_loc(n))
    end do

    deallocate(values_glob)
  end subroutine read_sparse_real

end module readinput
