#include "alutInternal.h"
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#if HAVE_STAT
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#define structStat struct stat
#elif HAVE__STAT
#define stat(p,b) _stat((p),(b))
#define structStat struct _stat
#else
#error No stat-like function on this platform
#endif

struct InputStream_struct
{
  ALboolean isFileStream;
  char *fileName;
  size_t remainingLength;
  union
  {
    FILE *fileDescriptor;       /* for file streams */
    const ALvoid *data;         /* for memory streams */
  } u;
};

/****************************************************************************
 * The functions below know the internal InputStream representation.
 ****************************************************************************/

InputStream *_alutInputStreamConstructFromFile(const char *fileName)
{
  InputStream *stream;
  structStat statBuf;
  FILE *fileDescriptor;
  char *fileNameBuffer;

  stream = (InputStream *) _alutMalloc(sizeof(InputStream));
  if (stream == NULL)
  {
    return NULL;
  }

  if (stat(fileName, &statBuf))
  {
    _alutSetError(ALUT_ERROR_IO_ERROR);
    free(stream);
    return NULL;
  }

  fileDescriptor = fopen(fileName, "rb");
  if (fileDescriptor == NULL)
  {
    _alutSetError(ALUT_ERROR_IO_ERROR);
    free(stream);
    return NULL;
  }

  fileNameBuffer = (char *)_alutMalloc(strlen(fileName) + 1);
  if (fileNameBuffer == NULL)
  {
    free(stream);
    return NULL;
  }

  stream->isFileStream = AL_TRUE;
  stream->fileName = strcpy(fileNameBuffer, fileName);
  stream->remainingLength = statBuf.st_size;
  stream->u.fileDescriptor = fileDescriptor;
  return stream;
}

InputStream *_alutInputStreamConstructFromMemory(const ALvoid * data, size_t length)
{
  InputStream *stream = (InputStream *) _alutMalloc(sizeof(InputStream));

  if (stream == NULL)
  {
    return NULL;
  }

  stream->isFileStream = AL_FALSE;
  stream->fileName = NULL;
  stream->remainingLength = length;
  stream->u.data = data;
  return stream;
}

ALboolean _alutInputStreamDestroy(InputStream * stream)
{
  ALboolean status = (stream->isFileStream && fclose(stream->u.fileDescriptor)) ? AL_FALSE : AL_TRUE;

  if (stream->fileName)
  {
    free(stream->fileName);
  }
  free(stream);
  return status;
}

const char *_alutInputStreamGetFileName(const InputStream * stream)
{
  return stream->fileName;
}

size_t _alutInputStreamGetRemainingLength(const InputStream * stream)
{
  return stream->remainingLength;
}

ALboolean _alutInputStreamEOF(InputStream * stream)
{
  if (stream->isFileStream)
  {
    int c = fgetc(stream->u.fileDescriptor);

    if (c != EOF)
    {
      ungetc(c, stream->u.fileDescriptor);
    }
    return (c == EOF) ? AL_TRUE : AL_FALSE;
  }
  else
  {
    return (stream->remainingLength == 0) ? AL_TRUE : AL_FALSE;
  }
}

static ALboolean streamRead(InputStream * stream, void *ptr, size_t numBytesToRead)
{
  if (stream->isFileStream)
  {
    size_t numBytesRead = fread(ptr, 1, numBytesToRead, stream->u.fileDescriptor);

    if (numBytesToRead != numBytesRead)
    {
      _alutSetError(ferror(stream->u.fileDescriptor) ? ALUT_ERROR_IO_ERROR : ALUT_ERROR_CORRUPT_OR_TRUNCATED_DATA);
      return AL_FALSE;
    }
    return AL_TRUE;
  }
  else
  {
    if (stream->remainingLength < numBytesToRead)
    {
      _alutSetError(ALUT_ERROR_CORRUPT_OR_TRUNCATED_DATA);
      return AL_FALSE;
    }
    memcpy(ptr, stream->u.data, numBytesToRead);
    stream->u.data = ((const char *)(stream->u.data) + numBytesToRead);
    return AL_TRUE;
  }
}

/****************************************************************************
 * The utility functions below do not know the internal InputStream
 * representation.
 ****************************************************************************/

ALvoid *_alutInputStreamRead(InputStream * stream, size_t length)
{
  ALvoid *data = _alutMalloc(length);

  if (data == NULL)
  {
    return NULL;
  }

  if (!streamRead(stream, data, length))
  {
    free(data);
    return NULL;
  }

  return data;
}

ALboolean _alutInputStreamSkip(InputStream * stream, size_t numBytesToSkip)
{
  ALboolean status;
  char *buf;

  if (numBytesToSkip == 0)
  {
    return AL_TRUE;
  }
  buf = (char *)_alutMalloc(numBytesToSkip);
  if (buf == NULL)
  {
    return AL_FALSE;
  }
  status = streamRead(stream, buf, numBytesToSkip);
  free(buf);
  return status;
}

ALboolean _alutInputStreamReadUInt16LE(InputStream * stream, UInt16LittleEndian * value)
{
  unsigned char buf[2];

  if (!streamRead(stream, buf, sizeof(buf)))
  {
    return AL_FALSE;
  }
  *value = ((UInt16LittleEndian) buf[1] << 8) | ((UInt16LittleEndian) buf[0]);
  return AL_TRUE;
}

ALboolean _alutInputStreamReadInt32BE(InputStream * stream, Int32BigEndian * value)
{
  unsigned char buf[4];

  if (!streamRead(stream, buf, sizeof(buf)))
  {
    return AL_FALSE;
  }
  *value = ((Int32BigEndian) buf[0] << 24) | ((Int32BigEndian) buf[1] << 16) | ((Int32BigEndian) buf[2] << 8) | ((Int32BigEndian) buf[3]);
  return AL_TRUE;
}

ALboolean _alutInputStreamReadUInt32LE(InputStream * stream, UInt32LittleEndian * value)
{
  unsigned char buf[4];

  if (!streamRead(stream, buf, sizeof(buf)))
  {
    return AL_FALSE;
  }
  *value =
    ((UInt32LittleEndian) buf[3] << 24) | ((UInt32LittleEndian) buf[2] << 16) | ((UInt32LittleEndian) buf[1] << 8) | ((UInt32LittleEndian) buf[0]);
  return AL_TRUE;
}