#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <ctype.h>
#include "grib_int.h"
#include "grib_decode.h"
#include "file.h"

int
gribOpen(const char *filename, const char *mode)
{
  int fileID = fileOpen(filename, mode);

#if defined(__sun)
  if (fileID != FILE_UNDEFID && tolower(*mode) == 'r')
    {
      fileSetBufferType(fileID, FILE_BUFTYPE_MMAP);
    }
#endif

  return fileID;
}

void
gribClose(int fileID)
{
  fileClose(fileID);
}

off_t
gribGetPos(int fileID)
{
  return fileGetPos(fileID);
}

int
gribCheckSeek(int fileID, long *offset, int *version)
{
  int ierr = gribFileSeek(fileID, offset);

  *version = -1;
  if (!ierr)
    {
      char buffer[4];
      if (fileRead(fileID, buffer, 4) == 4) *version = buffer[3];
    }

  return ierr;
}

int
gribFileSeek(int fileID, long *offset)
{
  // position file pointer after GRIB
  const long GRIB = 0x47524942;
  long code = 0;
  int retry = 4096 * 4096;

  *offset = 0;

  void *fileptr = filePtr(fileID);

  while (retry--)
    {
      int ch = filePtrGetc(fileptr);
      if (ch == EOF) return -1;

      code = ((code << 8) + ch) & 0xFFFFFFFF;
      if (code == GRIB)
        {
          if (CGRIBEX_Debug) Message("record offset = %ld", *offset);
          return 0;
        }

      (*offset)++;
    }

  if (CGRIBEX_Debug) Message("record offset = %ld", *offset);

  return 1;
}

static inline unsigned
read3ByteMSBFirst(void *fileptr)
{
  unsigned b1 = (unsigned) (filePtrGetc(fileptr));
  unsigned b2 = (unsigned) (filePtrGetc(fileptr));
  unsigned b3 = (unsigned) (filePtrGetc(fileptr));
  return GET_UINT3(b1, b2, b3);
}

size_t
gribReadSize(int fileID)
{
  size_t rgribsize = 0;
  void *fileptr = filePtr(fileID);
  off_t pos = fileGetPos(fileID);

  unsigned gribsize = read3ByteMSBFirst(fileptr);

  int gribversion = filePtrGetc(fileptr);

  if (gribsize == 24 && gribversion != 1 && gribversion != 2) gribversion = 0;

  if (CGRIBEX_Debug) Message("gribversion = %d", gribversion);

  if (gribversion == 0)
    {
      unsigned gdssize = 0, bmssize = 0;
      unsigned issize = 4, essize = 4;

      unsigned pdssize = gribsize;
      fileSetPos(fileID, (off_t) 3, SEEK_CUR);
      if (CGRIBEX_Debug) Message("pdssize     = %u", pdssize);
      int flag = filePtrGetc(fileptr);
      if (CGRIBEX_Debug) Message("flag        = %d", flag);

      fileSetPos(fileID, (off_t) pdssize - 8, SEEK_CUR);

      if (flag & 128)
        {
          gdssize = read3ByteMSBFirst(fileptr);
          fileSetPos(fileID, (off_t) gdssize - 3, SEEK_CUR);
          if (CGRIBEX_Debug) Message("gdssize     = %u", gdssize);
        }

      if (flag & 64)
        {
          bmssize = read3ByteMSBFirst(fileptr);
          fileSetPos(fileID, (off_t) bmssize - 3, SEEK_CUR);
          if (CGRIBEX_Debug) Message("bmssize     = %u", bmssize);
        }

      unsigned bdssize = read3ByteMSBFirst(fileptr);
      if (CGRIBEX_Debug) Message("bdssize     = %u", bdssize);

      gribsize = issize + pdssize + gdssize + bmssize + bdssize + essize;
      rgribsize = (size_t) gribsize;
    }
  else if (gribversion == 1)
    {
      if (gribsize > JP23SET)  // Large GRIB record
        {
          unsigned pdssize = read3ByteMSBFirst(fileptr);
          if (CGRIBEX_Debug) Message("pdssize     = %u", pdssize);

          int flag = 0;
          for (int i = 0; i < 5; ++i) flag = filePtrGetc(fileptr);
          if (CGRIBEX_Debug) Message("flag        = %d", flag);

          fileSetPos(fileID, (off_t) pdssize - 8, SEEK_CUR);

          unsigned gdssize = 0;
          if (flag & 128)
            {
              gdssize = read3ByteMSBFirst(fileptr);
              fileSetPos(fileID, (off_t) gdssize - 3, SEEK_CUR);
              if (CGRIBEX_Debug) Message("gdssize     = %u", gdssize);
            }

          unsigned bmssize = 0;
          if (flag & 64)
            {
              bmssize = read3ByteMSBFirst(fileptr);
              fileSetPos(fileID, (off_t) bmssize - 3, SEEK_CUR);
              if (CGRIBEX_Debug) Message("bmssize     = %u", bmssize);
            }

          unsigned bdssize = read3ByteMSBFirst(fileptr);
          if (CGRIBEX_Debug) Message("bdssize     = %u", bdssize);
          if (bdssize <= 120)
            {
              enum
              {
                issize = 4
              };
              gribsize &= JP23SET;
              gribsize *= 120;
              bdssize = correct_bdslen(bdssize, gribsize, issize + pdssize + gdssize + bmssize);
              if (CGRIBEX_Debug) Message("bdssize     = %u", bdssize);

              gribsize = issize + pdssize + gdssize + bmssize + bdssize + 4;
            }
        }
      rgribsize = (size_t) gribsize;
    }
  else if (gribversion == 2)
    {
      /* we set gribsize the following way because it doesn't matter then
         whether int is 4 or 8 bytes long - we don't have to care if the size
         really fits: if it does not, the record can not be read at all */
      rgribsize = 0;
      enum
      {
        g2size_bytes = 8
      };
      unsigned char g2size[g2size_bytes];
      filePtrRead(fileptr, g2size, g2size_bytes);
      for (int i = 0; i < g2size_bytes; ++i) rgribsize = (rgribsize << 8) | g2size[i];
    }
  else
    {
      rgribsize = 0;
      Warning("GRIB version %d unsupported!", gribversion);
    }

  if (filePtrEOF(fileptr)) rgribsize = 0;

  if (CGRIBEX_Debug) Message("gribsize = %zu", rgribsize);

  fileSetPos(fileID, pos, SEEK_SET);

  return rgribsize;
}

size_t
gribGetSize(int fileID)
{
  long offset;
  int ierr = gribFileSeek(fileID, &offset);  // position file pointer after GRIB
  if (ierr > 0)
    {
      Warning("GRIB record not found!");
      return 0;
    }

  if (ierr == -1) return 0;

  size_t recSize = gribReadSize(fileID);

  if (CGRIBEX_Debug) Message("recsize = %zu", recSize);

  fileSetPos(fileID, (off_t) -4, SEEK_CUR);

  return recSize;
}

int
gribRead(int fileID, void *buffer, size_t *buffersize)
{
  long offset;
  int ierr = gribFileSeek(fileID, &offset);  // position file pointer after GRIB
  if (ierr > 0)
    {
      Warning("GRIB record not found!");
      *buffersize = 0;
      return -2;
    }

  if (ierr == -1)
    {
      *buffersize = 0;
      return -1;
    }

  size_t recSize = gribReadSize(fileID);
  size_t readSize = recSize;

  if (readSize > *buffersize)
    {
      readSize = *buffersize;
      ierr = -3;  // Tell the caller that the buffer was insufficient.
    }

  *buffersize = recSize;  // Inform the caller about the record size.

  // Write the stuff to the buffer that has already been read in gribFileSeek().
  memcpy(buffer, "GRIB", 4);

  readSize -= 4;
  // Read the rest of the record into the buffer.
  size_t nread = fileRead(fileID, (char *) buffer + 4, readSize);

  if (nread != readSize) ierr = 1;

  return ierr;
}

int
gribWrite(int fileID, void *buffer, size_t buffersize)
{
  int nwrite = (int) (fileWrite(fileID, buffer, buffersize));
  if (nwrite != (int) buffersize)
    {
      perror(__func__);
      nwrite = -1;
    }

  return nwrite;
}
