/*
 * Copyright (C) by Argonne National Laboratory
 *     See COPYRIGHT in top-level directory
 */

#include "ad_xfs.h"
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif

/* style: allow:free:2 sig:0 */

static void ADIOI_XFS_Aligned_Mem_File_Read(ADIO_File fd, void *buf, int len,
                                            ADIO_Offset offset, int *err);

void ADIOI_XFS_ReadContig(ADIO_File fd, void *buf, MPI_Aint count,
                          MPI_Datatype datatype, int file_ptr_type,
                          ADIO_Offset offset, ADIO_Status * status, int *error_code)
{
    int diff, size, nbytes;
    MPI_Count err = -1, datatype_size, len;
    void *newbuf;
    static char myname[] = "ADIOI_XFS_READCONTIG";

    if (count == 0) {
        err = 0;
        goto fn_exit;
    }

    MPI_Type_size_x(datatype, &datatype_size);
    len = datatype_size * count;

    fd->fp_sys_posn = -1;       /* set it to null, since we are using pread */

    if (file_ptr_type == ADIO_INDIVIDUAL)
        offset = fd->fp_ind;

    if (!(fd->direct_read))     /* direct I/O not enabled */
        err = pread(fd->fd_sys, buf, len, offset);
    else {      /* direct I/O enabled */

        /* (1) if mem_aligned && file_aligned
         * use direct I/O to read up to correct io_size
         * use buffered I/O for remaining  */

        if (!(((long) buf) % fd->d_mem) && !(offset % fd->d_miniosz))
            ADIOI_XFS_Aligned_Mem_File_Read(fd, buf, len, offset, &err);

        /* (2) if !file_aligned
         * use buffered I/O to read up to file_aligned
         * At that point, if still mem_aligned, use (1)
         * else copy into aligned buf and then use (1) */
        else if (offset % fd->d_miniosz) {
            diff = fd->d_miniosz - (offset % fd->d_miniosz);
            diff = MPL_MIN(diff, len);
            nbytes = pread(fd->fd_sys, buf, diff, offset);

            buf = ((char *) buf) + diff;
            offset += diff;
            size = len - diff;
            if (!(((long) buf) % fd->d_mem)) {
                ADIOI_XFS_Aligned_Mem_File_Read(fd, buf, size, offset, &err);
                nbytes += err;
            } else {
                newbuf = (void *) memalign(XFS_MEMALIGN, size);
                if (newbuf) {
                    ADIOI_XFS_Aligned_Mem_File_Read(fd, newbuf, size, offset, &err);
                    if (err > 0)
                        memcpy(buf, newbuf, err);
                    nbytes += err;
                    ADIOI_Free(newbuf);
                } else
                    nbytes += pread(fd->fd_sys, buf, size, offset);
            }
            err = nbytes;
        }

        /* (3) if !mem_aligned && file_aligned
         * copy into aligned buf, then use (1)  */
        else {
            newbuf = (void *) memalign(XFS_MEMALIGN, len);
            if (newbuf) {
                ADIOI_XFS_Aligned_Mem_File_Read(fd, newbuf, len, offset, &err);
                if (err > 0)
                    memcpy(buf, newbuf, err);
                ADIOI_Free(newbuf);
            } else
                err = pread(fd->fd_sys, buf, len, offset);
        }
    }

    if (file_ptr_type == ADIO_INDIVIDUAL)
        fd->fp_ind += err;

  fn_exit:
#ifdef HAVE_STATUS_SET_BYTES
    if (status && err != -1)
        MPIR_Status_set_bytes(status, datatype, err);
#endif

    if (err == -1) {
        *error_code = MPIO_Err_create_code(MPI_SUCCESS, MPIR_ERR_RECOVERABLE,
                                           myname, __LINE__, MPI_ERR_IO, "**io",
                                           "**io %s", strerror(errno));
    } else
        *error_code = MPI_SUCCESS;
}


void ADIOI_XFS_Aligned_Mem_File_Read(ADIO_File fd, void *buf, int len, ADIO_Offset offset, int *err)
{
    int ntimes, rem, newrem, i, size, nbytes;
    unsigned read_chunk_sz = fd->hints->fs_hints.xfs.read_chunk_sz;

    /* memory buffer is aligned, offset in file is aligned,
     * io_size may or may not be of the right size.
     * use direct I/O to read up to correct io_size,
     * use buffered I/O for remaining. */

    if (!(len % fd->d_miniosz) && (len >= fd->d_miniosz) && (len <= read_chunk_sz))
        *err = pread(fd->fd_direct, buf, len, offset);
    else if (len < fd->d_miniosz)
        *err = pread(fd->fd_sys, buf, len, offset);
    else if (len > read_chunk_sz) {
        ntimes = len / (read_chunk_sz);
        rem = len - ntimes * read_chunk_sz;
        nbytes = 0;
        for (i = 0; i < ntimes; i++) {
            nbytes += pread(fd->fd_direct, ((char *) buf) + i * read_chunk_sz,
                            read_chunk_sz, offset);
            offset += read_chunk_sz;
        }
        if (rem) {
            if (!(rem % fd->d_miniosz))
                nbytes += pread(fd->fd_direct,
                                ((char *) buf) + ntimes * read_chunk_sz, rem, offset);
            else {
                newrem = rem % fd->d_miniosz;
                size = rem - newrem;
                if (size) {
                    nbytes += pread(fd->fd_direct,
                                    ((char *) buf) + ntimes * read_chunk_sz, size, offset);
                    offset += size;
                }
                nbytes += pread(fd->fd_sys,
                                ((char *) buf) + ntimes * read_chunk_sz + size, newrem, offset);
            }
        }
        *err = nbytes;
    } else {
        rem = len % fd->d_miniosz;
        size = len - rem;
        nbytes = pread(fd->fd_direct, buf, size, offset);
        nbytes += pread(fd->fd_sys, (char *) buf + size, rem, offset + size);
        *err = nbytes;
    }
}
