/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#define _XOPEN_SOURCE 600
#include "input_file.h"

#include "cdi.h"
#include "dmemory.h"

#include <errno.h>
#include <fcntl.h>
#include <string.h>

// On Windows, define ssize_t and pread manually
#ifdef _WIN32
#define ssize_t __int64
#define pread read
#include <io.h>
#else
#include <unistd.h>
#endif

#if HAVE_LIBPTHREAD
#include <pthread.h>
#endif

static void cdiInputFile_destruct(CdiInputFile *me);

// For an explanation of the condestruct() pattern, see the comment in iterator_grib.c
// path != NULL -> construction
// path = NULL -> destruction
static CdiInputFile *
cdiInputFile_condestruct(CdiInputFile *me, const char *path)
{
#define super() (&me->super)
  if (!path) goto destruct;
  cdiRefObject_construct(super());
  me->path = strdup(path);
  if (!me->path) goto destructSuper;
  do {
    me->fileDescriptor = open(me->path, O_RDONLY);
  } while (me->fileDescriptor == -1 && (errno == EINTR || errno == EAGAIN));
  if (me->fileDescriptor == -1) goto freePath;
  // construction successfull, now we can set our own destructor
  super()->destructor = (void (*)(CdiReferencedObject *)) cdiInputFile_destruct;
  goto success;

  // ^        constructor code       ^
  // |                               |
  // v destructor/error-cleanup code v

destruct:
  close(me->fileDescriptor);
freePath:
  Free(me->path);
destructSuper:
  cdiRefObject_destruct(super());
  me = NULL;

success:
  return me;
#undef super
}

static CdiInputFile **openFileList = NULL;
static size_t openFileCount = 0, openFileListSize = 0;
#if HAVE_LIBPTHREAD
static pthread_mutex_t openFileListLock = PTHREAD_MUTEX_INITIALIZER;
#endif

// This either returns a new object, or retains and returns a preexisting open file.
CdiInputFile *
cdiInputFile_make(const char *path)
{
  CdiInputFile *result = NULL;
  xassert(path);
#if HAVE_LIBPTHREAD
  int error = pthread_mutex_lock(&openFileListLock);
  xassert(!error);
#endif
  {
    // Check the list of open files for the given path.
    for (size_t i = openFileCount; i-- && !result;)
    {
      if (!strcmp(path, openFileList[i]->path)) result = openFileList[i];
    }
    // If no open file was found, we open one, otherwise we just retain the existing one one more time.
    if (result) { cdiRefObject_retain(&result->super); }
    else
    {
      result = (CdiInputFile *) Malloc(sizeof(*result));
      if (!cdiInputFile_condestruct(result, path))
      {
        // An error occured during construction, avoid a memory leak.
        Free(result);
        result = NULL;
      }
      else
      {
        // Add the new file to the list of open files.
        if (openFileCount == openFileListSize)
        {
          openFileListSize *= 2;
          if (openFileListSize < 16) openFileListSize = 16;
          openFileList = (CdiInputFile **) Realloc(openFileList, openFileListSize);
        }
        xassert(openFileCount < openFileListSize);
        openFileList[openFileCount++] = result;
      }
    }
  }
#if HAVE_LIBPTHREAD
  error = pthread_mutex_unlock(&openFileListLock);
  xassert(!error);
#endif
  return result;
}

int
cdiInputFile_read(const CdiInputFile *me, off_t readPosition, size_t readSize, size_t *outActualReadSize, void *buffer)
{
  char *byteBuffer = (char *) buffer;
  size_t trash;
  if (!outActualReadSize) outActualReadSize = &trash;
  *outActualReadSize = 0;
  while (readSize)
  {
    ssize_t bytesRead = pread(me->fileDescriptor, byteBuffer, readSize, readPosition);
    if (bytesRead == -1) return (errno == EINVAL) ? CDI_EINVAL : CDI_ESYSTEM;
    if (bytesRead == 0) return CDI_EEOF;
    byteBuffer += bytesRead;
    readPosition += bytesRead;
    readSize -= (size_t) bytesRead;
    *outActualReadSize += (size_t) bytesRead;
  }
  return CDI_NOERR;
}

const char *
cdiInputFile_getPath(const CdiInputFile *me)
{
  return me->path;
}

void
cdiInputFile_destruct(CdiInputFile *me)
{
#if HAVE_LIBPTHREAD
  int error = pthread_mutex_lock(&openFileListLock);
  xassert(!error);
#endif
  {
    // Find the position of me in the list of open files.
    ssize_t position = (ssize_t) openFileCount;
    while (position > 0 && openFileList[--position] != me)
      ;
    // Remove me from the list
    openFileList[position] = openFileList[--openFileCount];
  }
#if HAVE_LIBPTHREAD
  error = pthread_mutex_unlock(&openFileListLock);
  xassert(!error);
#endif
  cdiInputFile_condestruct(me, NULL);
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
