/*
// pbio.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#include "file.h"
#include "cgribex.h"
#include "dmemory.h"
#include "pbio.h"


static long size = 0;
static int sizeSet = 0;
static char *envSize;

/*
// Debug flags.
*/
#define DEBUGOFF 1
#define DEBUG (debugSet > DEBUGOFF)

static char *debugLevel;
static int debugSet = 0;

static int  rawSize = 0;
static int *rawFlag = NULL;

/*
//------------------------------------------------------------------------
// pbopen - Open file
//------------------------------------------------------------------------
*/
void pbOpen(int *unit, const char *name, const char *mode, int *iret)
{
  /*
  // Purpose:
  //  Opens file, returns the index of a UNIX FILE pointer held in 
  //  an internal table (fileTable).
  //
  // First time through, reads value in environment variable PBIO_BUFSIZE
  // (if it is set) and uses it as the size to be used for internal file
  // buffers; the value is passed to setvbuf. If PBIO_BUFSIZE is not set,
  // a default value is used.
  //
  // Function  accepts:
  //    name = filename
  //    mode = r, w
  //
  // Function returns:
  //   INTEGER iret:
  //     -1 = Could not open file.
  //     -2 = Invalid file name.
  //     -3 = Invalid open mode specified
  //      0 = OK.
  */

  /*
  // See if DEBUG switched on.
  */
  if ( ! debugSet )
    {
      debugLevel = getenv("PBIO_DEBUG");
      if ( debugLevel == NULL )
        debugSet = DEBUGOFF;              /* off */
      else
	{
	  for ( int loop = 0; loop < (int) strlen(debugLevel) ; loop++ )
	    {
	      if ( ! isdigit((int) debugLevel[loop]) )
		{
		  printf("Invalid number string in PBIO_DEBUG: %s\n", debugLevel);
		  printf("PBIO_DEBUG must comprise only digits [0-9].\n");
		  debugSet = DEBUGOFF;
		}
	    }
	  debugSet = DEBUGOFF + atoi(debugLevel);
	}
      if ( DEBUG ) printf("PBIO_PBOPEN: debug switched on\n");
    }

  *unit = 0;
  *iret = 0;

  if ( DEBUG ) printf("PBIO_PBOPEN: file name = %s\n", name);

  switch (*mode)
    {
    case 'a':
    case 'A':
      break;
    case 'c':
    case 'C':
    case 'w':
    case 'W':
      break;
    case 'r':
    case 'R':
      break;
    default:
      *iret = -3;
      return;
    }

  if ( DEBUG ) printf("PBIO_PBOPEN: file open mode = %c\n", *mode);

  /*
  // Now allocate a buffer for the file, if necessary.
  */
  if ( ! sizeSet )
    {
      envSize = getenv("PBIO_BUFSIZE");
      if ( envSize == NULL )
        size = 0;             /* default */
      else
	{
	  for ( int loop = 0; loop < (int) strlen(envSize) ; loop++ )
	    {
	      if ( ! isdigit((int) envSize[loop]) )
		{
		  printf("Invalid number string in PBIO_BUFSIZE: %s\n", envSize);
		  printf("PBIO_BUFSIZE must comprise only digits [0-9].\n");
		  exit(EXIT_FAILURE);
		}
	    }
	  size = atol(envSize);
          if ( size <= 0 )
            {
              printf("Invalid buffer size in PBIO_BUFSIZE: %s\n", envSize);
              printf("Buffer size defined by PBIO_BUFSIZE must be positive.\n");
              exit(EXIT_FAILURE);
            }
          sizeSet = 1;
	}
    }

  *unit = fileOpen(name, mode);

  if ( *unit == -1 ) *iret = -1;
  else
    {

      if ( size > 0 ) fileSetBufferSize(*unit, size);

      if ( rawSize == 0 )
	{
	  rawSize = 8;
	  rawFlag = (int*) Malloc((size_t)rawSize*sizeof(int));
	}
      if ( *unit >= rawSize )
	{
	  rawSize = *unit*2;
	  rawFlag = (int*) Realloc(rawFlag, (size_t)rawSize*sizeof(int));
	}

      rawFlag[*unit] = 1;
    }

  if ( DEBUG ) printf("PBIO_PBOPEN: file ID = %d\n", *unit);

  if ( DEBUG ) printf("PBIO_PBOPEN: file buffer size = %ld\n", size);
}

/*
//------------------------------------------------------------------------
// pbseek - Seek
//------------------------------------------------------------------------
*/
void pbSeek(int unit, int offset, int whence, int *iret)
{
  /*
  //
  // Purpose:
  //   Seeks to a specified location in file.
  //
  //  Function  accepts:
  //    unit = the index of a UNIX FILE pointer held in
  //           an internal table (fileTable).
  //
  //    offset = byte count
  //
  //    whence  = 0, from start of file
  //            = 1, from current position
  //            = 2, from end of file.  
  //
  //  Returns:
  //    iret:
  //      -2 = error in handling file,
  //      -1 = end-of-file
  //      otherwise,  = byte offset from start of file.
  */
  int my_offset = (int) offset;
  int my_whence = (int) whence;

  /*
  // Must use negative offset if working from end-of-file
  */
  if ( DEBUG )
    { 
      printf("PBIO_PBSEEK: file ID = %d\n", unit);
      printf("PBIO_PBSEEK: Offset = %d\n", my_offset);
      printf("PBIO_PBSEEK: Type of offset = %d\n", my_whence);
    }

  if ( my_whence == 2 ) my_offset = - abs(my_offset);

  *iret = (int) fileGetPos(unit);
  if ( DEBUG ) printf("PBIO_PBSEEK: current position = %d\n", *iret);
  if ( *iret == my_offset && my_whence == 0 )
    *iret = 0;
  else
    *iret = fileSetPos(unit, (off_t) my_offset, my_whence);

  if ( DEBUG ) printf("PBIO_PBSEEK: fileSetPos return code = %d\n", *iret);

  if ( *iret != 0 )
    {
      if ( ! fileEOF(unit) )
	{
	  *iret = -2;             /* error in file-handling */
	  perror("pbseek");
	}
      else
        *iret = -1;             /* end-of-file  */

      fileClearerr(unit);
      return;
    }

  /*
  // Return the byte offset from start of file
  */
  *iret = (int) fileGetPos(unit);

  if ( DEBUG )
    printf("PBIO_PBSEEK: byte offset from start of file = %d\n", *iret);

  return;
}

void pbSeek64(int unit, LONGLONG offset, int whence, LONGLONG *iret)
{
  /*
  //
  // Purpose:
  //   Seeks to a specified location in file.
  //
  //  Function  accepts:
  //    unit = the index of a UNIX FILE pointer held in
  //           an internal table (fileTable).
  //
  //    offset = byte count
  //
  //    whence  = 0, from start of file
  //            = 1, from current position
  //            = 2, from end of file.  
  //
  //  Returns:
  //    iret:
  //      -2 = error in handling file,
  //      -1 = end-of-file
  //      otherwise,  = byte offset from start of file.
  */
  off_t my_offset = (off_t) offset;
  off_t my_iret   = 0;

  /*
  // Must use negative offset if working from end-of-file
  */
  if ( DEBUG )
    { 
      printf("PBIO_PBSEEK64: file ID = %d\n", unit);
      printf("PBIO_PBSEEK64: Offset = %lld\n", (long long) my_offset);
      printf("PBIO_PBSEEK64: Type of offset = %d\n", whence);
    }

  if ( whence == SEEK_END ) my_offset = - llabs(my_offset);

  my_iret = fileGetPos(unit);
  if ( DEBUG ) printf("PBIO_PBSEEK64: current position = %lld\n",
		      (long long) my_iret);
  if ( my_iret == offset && whence == SEEK_SET )
    my_iret = 0;
  else
    my_iret = fileSetPos(unit, offset, whence);

  if ( DEBUG ) printf("PBIO_PBSEEK64: fileSetPos return code = %lld\n",
		      (long long) my_iret);

  if ( my_iret != 0 )
    {
      if ( ! fileEOF(unit) )
	{
	  my_iret = -2;             /* error in file-handling */
	  perror("pbseek64");
	}
      else
        my_iret = -1;             /* end-of-file  */

      fileClearerr(unit);
      *iret = my_iret;
      return;
    }

  /*
  // Return the byte offset from start of file
  */
  my_iret = fileGetPos(unit);

  if ( DEBUG )
    printf("PBIO_PBSEEK64: byte offset from start of file = %lld\n",
	   (long long) my_iret);

  *iret = my_iret;
  return;
}

/*
//------------------------------------------------------------------------
// PBTELL - Tells current file position (from FORTRAN)
//------------------------------------------------------------------------
*/
void pbTell(int unit, int *iret)
{
  /*
  //
  // Purpose:
  //   Tells current byte offset in file.
  //
  //  Function  accepts:
  //    unit = the index of a UNIX FILE pointer held in
  //           an internal table (fileTable).
  //
  //  Returns:
  //    iret:
  //      -2 = error in handling file,
  //      otherwise,  = byte offset from start of file.
  */


  /*
  // Return the byte offset from start of file
  */
  *iret = (int) fileGetPos(unit);

  if ( *iret < 0 )
    {
      if ( DEBUG )
	{           /* error in file-handling */
	  printf("PBIO_PBTELL: file ID = %d. ", unit);
	  printf("Error status = %d\n", *iret);
	}
      perror("pbtell");
      *iret = -2;
    }

  if ( DEBUG )
    {
      printf("PBIO_PBTELL: file ID = %d. ", unit);
      printf("Byte offset from start of file = %d\n",*iret);
    }

  return;
}
		
/*
//------------------------------------------------------------------------
//  pbread - Read
//------------------------------------------------------------------------
*/
void pbRead(int unit, void *buffer, int nbytes, int *iret)
{
  /*
  // Purpose:
  //  Reads a block of bytes from a file..
  //
  //  Function  accepts:
  //    unit = the index of a UNIX FILE pointer held in
  //           an internal table (fileTable).
  //
  //    nbytes = number of bytes to read.
  //
  //  Returns:
  //    iret:
  //      -2 = error in reading file,
  //      -1 = end-of-file,
  //      otherwise, = number of bytes read.
  */

  if ( DEBUG )
    {
      printf("PBIO_READ: file ID = %d. ", unit);
      printf("Number of bytes to read = %d\n", nbytes);
    }

  if ( (*iret = (int)fileRead(unit, buffer, (size_t)nbytes) ) != nbytes )
    {
      if ( ! fileEOF(unit) )
	{
	  *iret = -2;             /*  error in file-handling  */
	  perror("pbread");
	  fileClearerr(unit);
	  return;
	}
      else
	{
	  *iret = -1;             /*  end-of-file */
	  fileClearerr(unit);
	}
    }

    if ( DEBUG )
      {
	printf("PBIO_READ: file ID = %d. ", unit);
	printf("Number of bytes read = %d\n", nbytes);
      }

    return;
}
 
/*
//------------------------------------------------------------------------
//  pbwrite - Write (from FORTRAN)
//------------------------------------------------------------------------
*/
void pbWrite(int unit, void *buffer, int nbytes, int *iret)
{
  /*
  // Purpose:
  //  Writes a block of bytes to a file.
  //
  //  Function  accepts:
  //    unit = the index of a UNIX FILE pointer held in
  //           an internal table (fileTable).
  //
  //    nbytes = number of bytes to write.
  //
  //  Returns:
  //    iret:
  //      -1 = Could not write to file.
  //     >=0 = Number of bytes written.
  */
  unsigned char cbuf[4];

  if ( DEBUG )
    {
      printf("PBIO_WRITE: file ID = %d. ", unit);
      printf("Number of bytes to write = %d\n", nbytes);
    }

  if ( rawFlag[unit] == 0 )
    {
      /* If we are at the beginning of the file, write 4 zero bytes */
      if ( fileGetPos(unit) == 0 )
	{
	  cbuf[0] = cbuf[1] = cbuf[2] = cbuf[3] = 0;
	  if ( fileWrite(unit, cbuf, 4) != 4 ) perror("pbwrite");
	}

      cbuf[0] = (unsigned char)((nbytes>>24) & 0xff);
      cbuf[1] = (unsigned char)((nbytes>>16) & 0xff);
      cbuf[2] = (unsigned char)((nbytes>> 8) & 0xff);
      cbuf[3] = (unsigned char)((nbytes    ) & 0xff);

      /* 4 header Bytes with the record length (BIG Endian) */
      if ( fileWrite(unit, cbuf, 4) != 4 ) perror("pbwrite");
    }

  if ( (*iret = (int)fileWrite(unit, buffer, (size_t)nbytes) ) != nbytes)
    {
      perror("pbwrite");
      *iret = -1;
    }

  if ( rawFlag[unit] == 0 )
    {
      /* 4 trailing bytes */
      if ( fileWrite(unit, cbuf, 4) != 4 ) perror("pbwrite");
    }

  if ( DEBUG )
    {
      printf("PBIO_WRITE: file ID = %d. ", unit);
      printf("PBIO_WRITE: number of bytes written = %d\n", *iret);
    }

  return;
}

/*
//------------------------------------------------------------------------
//   pbclose - close
//------------------------------------------------------------------------
*/
void pbClose(int unit, int *iret)
{
  /*
  // Purpose:
  //  Closes file.
  //
  //  Function  accepts:
  //    unit = the index of a UNIX FILE pointer held in
  //           an internal table (fileTable).
  ////  Returns:
  //    iret:
  //      0 = OK.
  //      otherwise = error in handling file.
  */

  if( DEBUG )
    printf("PBIO_CLOSE: file ID = %d\n", unit);

  if ( unit >= 0 )
    {
      if ( rawFlag[unit] == 0 )
	{
	  unsigned char cbuf[4];
	  
	  /* Write 4 trailing 0 Bytes */

	  cbuf[0] = cbuf[1] = cbuf[2] = cbuf[3] = 0;
	  fileWrite(unit, cbuf, 4);
	}

      fileClose(unit);

      *iret = 0;
    }
  else
    {
      *iret = -1;
    }

  return;
}

/*
//------------------------------------------------------------------------
//  pbflush - flush
//------------------------------------------------------------------------
*/
void pbFlush(int unit)
{
  /*
  // Purpose:	Flushes file.
  */

  if ( DEBUG )
    printf("PBIO_FLUSH: file ID = %d\n", unit);

  fileFlush(unit);
}

/*
//------------------------------------------------------------------------
//  GRIBREAD
//------------------------------------------------------------------------
*/
void gribread(void *buffer, int buffsize, int *readsize, int *status, int unit)
{
  /*
  //  Called as a FORTRAN subroutine:
  //
  //    CALL GRIBREAD( KARRAY, KINLEN, KOUTLEN, IRET, KUNIT )
  //
  */
  size_t holdsize = (size_t)buffsize;

  /*
  // Read GRIB product
  */

  *status = gribRead(unit, buffer, &holdsize);

  *readsize = (int) holdsize;

  if ( DEBUG )
    {
      printf("PBIO_GRIBREAD: file ID = %d. ", unit);
      printf("Number of bytes read = %d\n", *readsize);
    }

  return;
}

/*
//------------------------------------------------------------------------
//  PBSIZE
//------------------------------------------------------------------------
*/
void pbSize(int unit, int *plen)
{
  /*
  //  Returns the size in bytes of the next GRIB product.
  //
  //  Called from FORTRAN:
  //      CALL PBSIZE(KUNIT, LENGTH)
  //
  //  unit  = file id returned from PBOPEN.
  //  plen  = size in bytes of the next product.
  //        = -2 if error allocating memory for internal buffer.
  //
  //  The input file is left positioned where it started.
  */

  /*
  //  Use a smallish buffer for processing; this should suffice for all cases
  //  except versions -1 and 0 of GRIB and large BUFR products
  */

  off_t recpos = fileGetPos(unit);

  if ( DEBUG )
    {
      printf("PBIO_SIZE: file ID = %d. ", unit);
      printf("Current file position = %ld\n", (long) recpos);
    }

  long offset;
  int ierr = gribFileSeek(unit, &offset);
  if ( ierr > 0 ) printf("GRIB record not found\n");

  if ( ierr == 0 )
    {
      size_t recsize = gribReadSize(unit);
      if ( recsize <= INT_MAX )
        *plen = (int)recsize;
      else
        *plen = 0;
    }
  else
    *plen = 0;

  /*
  //  Put the file pointer back where it started
  */
  if ( DEBUG )
    {
      printf("PBIO_SIZE: file pointer set back to: %ld\n", (long) recpos);
      printf("PBIO_SIZE: Product size = %d\n", *plen);
    }

  (void) fileSetPos(unit, recpos, SEEK_SET);

  return;
}


void pbGrib(int unit, void *buffer, int inlen, int *outlen, int *iret)
{
  /*
    PBGRIB

    PURPOSE
    _______

    Reads next GRIB product from a file.

    INTERFACE
    _________

    CALL PBGRIB(KUNIT,KARRAY,KINLEN,KOUTLEN,KRET)


    Input parameters
    ________________

    KUNIT  - 	Unit number for the file returned from PBOPEN
    KARRAY -	FORTRAN array big enough to hold the GRIB product
    KINLEN -	Size in BYTES of the FORTRAN array


    Output parameters
    ________________

    KOUTLEN -	Size in BYTES of the GRIB product read	
    KRET    -	 0  if a GRIB product has been successfully read
                -1  if end-of-file is hit before a GRIB product is read
		-3  if the size of KARRAY is not sufficient for the 
                   GRIB product


    Common block usage
    __________________

    None.


    Method
    ______

    Calls GRIBREAD.


    Externals
    _________

    GRIBREAD - Read next GRIB product.


    AUTHOR
    ______

    J.D.Chambers       ECMWF


    MODIFICATIONS
    _____________

    None.
  */

  *iret = 0;

  /*
    Get the GRIB product
  */
  int nread, retval;
  gribread(buffer, inlen, &nread, &retval, unit);

  /*
    Escape if the user buffer is too small to hold even the early sections of
    the product or EOF encountered
  */
  if ( retval == -4 )
    {
      *outlen = nread;
      *iret = -1;
      return;
    }
  /*
    Escape if no GRIB product is found in the file
  */
  if ( retval == -1 )
    {
      *outlen = 0;
      *iret = -1;
      return;
    }
  /*
    Check if the array is big enough for the GRIB product
  */
  if ( retval == -3 )
    {
      *outlen = nread;
      *iret = -3;
      return;
    }
  /*
    Set success code if product retrieved
  */
  if ( nread >= 0 )
    {
      *outlen = nread;
      *iret = 0;
    }

  return;
}

void pbSetRaw(int unit, int raw)
{
  rawFlag[unit] = raw;
}
