/****************************************************************************
 * libs/libc/stdio/lib_libfread_unlocked.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>

#include <nuttx/fs/fs.h>

#include "libc.h"

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lib_fread_unlocked
 ****************************************************************************/

ssize_t lib_fread_unlocked(FAR void *ptr, size_t count, FAR FILE *stream)
{
  FAR char *dest = ptr;
  ssize_t bytes_read;
  size_t remaining = count;
#ifndef CONFIG_STDIO_DISABLE_BUFFERING
  size_t gulp_size;
  int ret;
#endif
  int fd;

  /* Make sure that reading from this stream is allowed */

  if (!stream)
    {
      _NX_SETERRNO(EBADF);
      return ERROR;
    }
  else if ((stream->fs_oflags & O_RDOK) == 0)
    {
      stream->fs_flags |= __FS_FLAG_ERROR;
      _NX_SETERRNO(EBADF);
      return ERROR;
    }
  else
    {
      fd = (int)(intptr_t)stream->fs_cookie;
#if CONFIG_NUNGET_CHARS > 0
      /* First, re-read any previously ungotten characters */

      while (stream->fs_nungotten > 0 && remaining > 0)
        {
          /* Decrement the count of ungotten bytes to get an index */

          stream->fs_nungotten--;

          /* Return the last ungotten byte */

          *dest++ = stream->fs_ungotten[stream->fs_nungotten];

          /* That's one less byte that we have to read */

          remaining--;
        }
#endif

#ifndef CONFIG_STDIO_DISABLE_BUFFERING
      /* Is there an I/O buffer? */

      if (stream->fs_bufstart != NULL)
        {
          /* If the buffer is currently being used for write access, then
           * flush all of the buffered write data.  We do not support
           * concurrent buffered read/write access.
           */

          ret = lib_wrflush_unlocked(stream);
          if (ret < 0)
            {
              if (count - remaining > 0)
                {
                  goto shortread;
                }
              else
                {
                  _NX_SETERRNO(ret);
                  goto errout_with_errno;
                }
            }

          /* Now get any other needed chars from the buffer or the file. */

          while (remaining > 0)
            {
              /* Is there readable data in the buffer? */

              gulp_size = stream->fs_bufread - stream->fs_bufpos;

              /* Avoid empty buffers or read requests greater than the size
               *  buffer remaining
               */

              if (gulp_size > 0)
                {
                  if (gulp_size > remaining)
                    {
                      /* Clip the gulp size to the requested byte count */

                      gulp_size = remaining;
                    }

                  memcpy(dest, stream->fs_bufpos, gulp_size);

                  remaining -= gulp_size;
                  stream->fs_bufpos += gulp_size;
                  dest += gulp_size;
                }

              /* The buffer is empty OR we have already supplied the number
               * of bytes requested in the read.  Check if we need to read
               * more from the file.
               */

              if (remaining > 0)
                {
                  size_t buffer_available;

                  /* We need to read more data into the buffer from the
                   * file
                   */

                  /* Mark the buffer empty */

                  stream->fs_bufpos = stream->fs_bufread =
                                      stream->fs_bufstart;

                  /* How much space is available in the buffer? */

                  buffer_available = stream->fs_bufend - stream->fs_bufread;

                  /* Will the number of bytes that we need to read fit into
                   * the buffer space that is available? If the read size is
                   * larger than the buffer, then read some of the data
                   * directly into the user's buffer.
                   */

                  if (remaining > buffer_available)
                    {
                      if (stream->fs_iofunc.read != NULL)
                        {
                          bytes_read = stream->fs_iofunc.read(
                                                          stream->fs_cookie,
                                                          dest, remaining);
                        }
                      else
                        {
                          bytes_read = _NX_READ(fd, dest, remaining);
                        }

                      if (bytes_read < 0)
                        {
                          if (count - remaining > 0)
                            {
                              goto shortread;
                            }
                          else
                            {
                              /* An error occurred on the read. */

                              _NX_SETERRNO(bytes_read);
                              goto errout_with_errno;
                            }
                        }
                      else if (bytes_read == 0)
                        {
                          /* We are at the end of the file.  But we may
                           * already have buffered data.  In that case,
                           * we will report the EOF indication later.
                           */

                          goto shortread;
                        }
                      else
                        {
                          /* Some (perhaps all) bytes were read. Adjust the
                           * dest pointer and remaining bytes to be read.
                           */

                          DEBUGASSERT(bytes_read <= remaining);
                          dest      += bytes_read;
                          remaining -= bytes_read;
                        }
                    }
                  else
                    {
                      /* The number of bytes required to satisfy the read
                       * is less than or equal to the size of the buffer
                       * space that we have left. Read as much as we can
                       * into the buffer.
                       */

                      if (stream->fs_iofunc.read != NULL)
                        {
                          bytes_read = stream->fs_iofunc.read(
                                              stream->fs_cookie,
                                              stream->fs_bufread,
                                              buffer_available);
                        }
                      else
                        {
                          bytes_read = _NX_READ(fd, stream->fs_bufread,
                                                buffer_available);
                        }

                      if (bytes_read < 0)
                        {
                          if (count - remaining > 0)
                            {
                              goto shortread;
                            }
                          else
                            {
                              /* An error occurred on the read.  The error
                               * code is in the 'errno' variable.
                               */

                              _NX_SETERRNO(bytes_read);
                              goto errout_with_errno;
                            }
                        }
                      else if (bytes_read == 0)
                        {
                          /* We are at the end of the file.  But we may
                           * already have buffered data.  In that case,
                           * we will report the EOF indication later.
                           */

                          goto shortread;
                        }
                      else
                        {
                          /* Some (perhaps all) bytes were read */

                          stream->fs_bufread += bytes_read;
                        }
                    }
                }
            }
        }
      else
#endif
        {
          /* Now get any other needed chars from the file. */

          while (remaining > 0)
            {
              if (stream->fs_iofunc.read != NULL)
                {
                  bytes_read = stream->fs_iofunc.read(stream->fs_cookie,
                                                      dest,
                                                      remaining);
                }
              else
                {
                  bytes_read = _NX_READ(fd, dest, remaining);
                }

              if (bytes_read < 0)
                {
                  if (count - remaining > 0)
                    {
                      break;
                    }
                  else
                    {
                      /* An error occurred on the read.  The error code is
                       * in the 'errno' variable.
                       */

                      _NX_SETERRNO(bytes_read);
                      goto errout_with_errno;
                    }
                }
              else if (bytes_read == 0)
                {
                  /* We are at the end of the file.  But we may already
                   * have buffered data.  In that case, we will report
                   * the EOF indication later.
                   */

                  break;
                }
              else
                {
                  DEBUGASSERT(bytes_read <= remaining);
                  dest      += bytes_read;
                  remaining -= bytes_read;
                }
            }
        }

#ifndef CONFIG_STDIO_DISABLE_BUFFERING
      /* Here after a successful (but perhaps short) read.  A short read can
       * only occur is read() returns 0 (end-of-file).
       */

shortread:
#endif

      /* Set or clear the EOF indicator.  If we get here because of a
       * short read and the total number of bytes read is zero, then
       * we must be at the end-of-file.
       */

      if (remaining == 0)
        {
          stream->fs_flags &= ~__FS_FLAG_EOF;
        }
      else
        {
          stream->fs_flags |= __FS_FLAG_EOF;
        }

      return count - remaining;
    }

  /* Error exits */

errout_with_errno:
  stream->fs_flags |= __FS_FLAG_ERROR;
  return ERROR;
}
