#define __STDC_FORMAT_MACROS
#include <inttypes.h>

#define _LARGE_FILES

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>

#include "fbioc.h"

const uint32_t fortranheader = 4;

void c_endian(int *endian)
{
    union {
        uint32_t i;
        char c[4];
    } bint = {0x01020304};

    if (bint.c[0] == 1) 
      *endian = 0;   /*    big endian */
    else
      *endian = 1;   /* little endian */
}

void c_swap(char *a, const int width, const int size)
{
    int  i, j;
    char swap[8];

    if (width == 1) return;

    if (width != 2 && width != 4 && width != 8) {
      fprintf(stderr,"swap: width in not 1,2,4 or 8 width=%d size=%d\n",width,size);
      exit(1);
    }

    for (j=0; j<size; j++) {
      for (i=0; i<width; i++) swap[i] = a[j*width+i];
      for (i=0; i<width; i++) a[j*width+i] = swap[width-i-1];
    }
}

void c_fbopen (int *iunit, char *fname, int *flen, char *mode, int *ierr)
{

   char *lfname;
   int len;
   int oflag = -1;

   lfname = (char *) malloc(*flen+1);
   if (!lfname) {
      perror("malloc");
      *ierr = -1;
   }
   strncpy(lfname,fname,*flen);
   len = *flen;
   while (len && lfname[--len] == ' ');
   lfname[++len]='\0';

   if ( *mode == 'R') oflag = O_RDONLY;
   if ( *mode == 'W') oflag = O_RDWR | O_CREAT | O_TRUNC;
   if ( *mode == 'O') oflag = O_RDWR;

   if ( oflag == -1 ) {
      fprintf(stderr," Wrong mode in fbopen %s \n", mode);
      *ierr = -1;
   }

   *iunit = open(lfname,oflag,0666);

   if (*iunit == -1) {
      fprintf(stderr," Error Open File: %s \n",lfname);
      *ierr = -1;
   } else {
      fprintf(stderr," File: %s opened \n",lfname);
      *ierr = 0;
   }
}

void c_fbread (int *iunit, char *buffer, const int *width, const int *size, const int *swap, int *ierr)
{

   int rd, rd4;
   uint32_t reclen;
   uint32_t reclen1, reclen2;

   *ierr = 0;

   reclen = (*width) * (*size);

   rd4 = read(*iunit,(void *)&reclen1,fortranheader);
   if ( *swap == 1 ) c_swap((char *)&reclen1,fortranheader,1);
   if (reclen1 != reclen) {
      fprintf(stderr," reclen1 = %d  reclen = %d  rd = %d \n", reclen1, reclen, rd4);
      fprintf(stderr," error in c_fbread 1 \n");
      *ierr = -1;
   }

   rd = read(*iunit,buffer,reclen);
   if (rd != reclen) {
      fprintf(stderr," rd = %d   reclen = %d \n", rd, reclen);
      fprintf(stderr," error in c_fbread 2 \n");
      *ierr = -1;
   }
   if ( *swap == 1 ) c_swap(buffer,*width,*size);

   rd4 = read(*iunit,(void *)&reclen2,fortranheader);
   if ( *swap == 1 ) c_swap((char *)&reclen2,fortranheader,1);
   if (reclen2 != reclen1) {
      fprintf(stderr," reclen1 = %d  reclen2 = %d \n", reclen1, reclen2);
      fprintf(stderr," error in c_fbread 3 \n");
      *ierr = -1;
   }

}

void c_fbread_4bytes (int *iunit, char *buffer, int *ierr)
{
   int rd4;
   rd4 = read(*iunit,buffer,4);
   if (rd4 != 4) {
      fprintf(stderr,"c_fbread_4bytes: rd4 = %d not equal 4 \n", rd4);
      fprintf(stderr," error in c_fbread_4bytes \n");
      *ierr = -1;
   }
}

void c_fbwrite (int *iunit, char *buffer, const int *width, const int *size, const int *swap, int *ierr)
{

   int rd, rd4;
   off_t before, after;
   uint32_t reclen,real_reclen;
   char *swapped_buffer;

   *ierr = 0;
  
   real_reclen = (*width) * (*size);
   reclen = real_reclen;
   if ( *swap == 1 ) c_swap((char *)&reclen,fortranheader,1);       
   rd4 = write(*iunit,(char *)&reclen,fortranheader);

   if ( *swap == 1 ) {
      swapped_buffer = (char *)malloc(real_reclen);
      memcpy(swapped_buffer,buffer,real_reclen);
      c_swap(swapped_buffer,*width,*size);
      rd = write(*iunit,swapped_buffer,real_reclen);
      free(swapped_buffer);
   } else {
      rd = write(*iunit,buffer,real_reclen);
   }

   rd4 = write(*iunit,(char *)&reclen,fortranheader);

   if (rd != real_reclen) {
      fprintf(stderr," rd = %d   real_reclen = %d \n", rd, real_reclen);
      fprintf(stderr," Error in fbwrite \n");
      *ierr = -1;
   }

}


void c_fbclose (int *iunit, int *ierr)
{
   *ierr = close(*iunit);
}

void c_fbseekset (int *iunit, uint64_t *seek, int *ierr)
{
   off_t seek_result;
   seek_result = lseek(*iunit, *seek, SEEK_SET);
   if ( seek_result != *seek ) {
      fprintf(stderr," in c_fbseekset seek_result = %ld   seek = "PRIu64" \n", seek_result, *seek );
      *ierr = -1;
   } else {
      *ierr = 0;
   }
}

void c_fbseekcur (int *iunit, uint64_t *seek, int *ierr)
{
   *ierr = lseek(*iunit, *seek, SEEK_CUR);
}

void c_fbseek_record (int *iunit, int *swap, int *ierr)
{
   int rd4;
   uint32_t reclen1, reclen2;
   off_t seek_result;

   *ierr = 0;

   rd4 = read(*iunit,(char *)&reclen1,fortranheader);
   if ( *swap == 1 ) c_swap((char *)&reclen1,fortranheader,1);
   if (rd4 != fortranheader) {
       *ierr=1;
       return;
   }

   seek_result = lseek(*iunit, (off_t) reclen1, SEEK_CUR);

   rd4 = read(*iunit,(char *)&reclen2,fortranheader);
   if ( *swap == 1 ) c_swap((char *)&reclen2,fortranheader,1);
   if (rd4 != fortranheader) {
       *ierr=1;
       return;
   }
   if ( reclen1 != reclen2 ) {
      fprintf(stderr," in c_fbseek_record reclen1 = %d reclen2 = %d \n", reclen1,  reclen2);
      *ierr = -1;
   }
}

void c_fbseekend (int *iunit, uint64_t *seek, int *ierr)
{
   *ierr = lseek(*iunit, *seek, SEEK_END);
}

void c_fbtell (int *iunit, uint64_t *offset, int *ierr)
{
   *offset = lseek(*iunit, (off_t)0, SEEK_CUR);
   *ierr = 0;
}

void c_fbsize (int *iunit, uint64_t *size, int *ierr)
{
   *size = lseek(*iunit, (off_t)0, SEEK_END);
   *ierr = 0;
}


void c_fbwrite_record (int *iunit, char *name, int *rank, int *dtype, int *dsize, int *bounds, char *data, int *header, int *swap, int *ierr)
{
    int r;
    int width, size;

    /* name */
    width=1;
    size=32;
    c_fbwrite (iunit,name,&width,&size,swap,ierr);

    /* rank */
    width=sizeof(*rank);
    size=1;
    c_fbwrite (iunit,(char *)rank,&width,&size,swap,ierr);

    /* dtype */
    width=sizeof(*dtype);
    size=1;
    c_fbwrite (iunit,(char *)dtype,&width,&size,swap,ierr);

    /* bounds */
    width=4;
    size=2*(*rank);
    c_fbwrite (iunit,(char *)bounds,&width,&size,swap,ierr);

    /* header */
    width=4;
    size=512;
    c_fbwrite (iunit,(char *)header,&width,&size,swap,ierr);

    /* data */
    size = 1;
    for (r=0; r<*rank; r++) {
        size = size * (bounds[r*2+1] - bounds[r*2+0] + 1);
    }
    c_fbwrite (iunit,(char *)data,dsize,&size,swap,ierr);
}

void c_fbread_record (int *iunit, char *name, int *rank, int *dtype, int *dsize, int *bounds, char *data, int *header, int *swap, int *ierr)
{
    int width, size;
    int r;
    char vname[32];
    int myrank,mydtype;
    int *mybounds;

    /* name */
    width=1;
    size=32;
    c_fbread (iunit,vname,&width,&size,swap,ierr);

    /* rank */
    width=sizeof(*rank);
    size=1;
    c_fbread(iunit,(char *)&myrank,&width,&size,swap,ierr);
    if ( myrank != *rank) {
        fprintf(stderr," error in c_fbread_record %s myrank != *rank %d %d \n", name, myrank , *rank);
        *ierr = -1;
        return;
    }

    /* dtype */
    width=sizeof(*dtype);
    size=1;
    c_fbread(iunit,(char *)&mydtype,&width,&size,swap,ierr);
    if ( mydtype != *dtype) {
        fprintf(stderr," error in c_fbread_record %s mydtype != *dtype %d %d \n", name, mydtype , *dtype);
        *ierr = -1;
        return;
    }

    /* bounds */
    width=4;
    size=2*(*rank);
    c_fbread(iunit,(char *)bounds,&width,&size,swap,ierr);

    /* header */
    width=4;
    size=512;
    c_fbread(iunit,(char *)header,&width,&size,swap,ierr);

    /* data */
    size = 1;
    for (r=0; r<*rank; r++) {
        size = size * (bounds[r*2+1] - bounds[r*2+0] + 1);
    }
    c_fbread(iunit,data,dsize,&size,swap,ierr);

}