/*
 *  ReadFile.m
 *  ROOTSL
 *
 *  Created by Fons Rademakers on 22/05/09.
 *  Copyright 2009 CERN. All rights reserved.
 *
 */

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <errno.h>

#import <Foundation/Foundation.h>


#if defined(__i386__)
#   define R__BYTESWAP
#endif
#if defined(__x86_64__)
#   define R__BYTESWAP
#endif


struct FileHeader_t {
   long long   end;
   long long   seekFree;
   long long   seekInfo;
   long long   seekKeys;
   int         version;
   int         begin;
   int         compress;
   int         nbytesName;
   int         dateC;
   int         timeC;
   int         dateM;
   int         timeM;
   char        units;
   char       *uuid;
   char       *title;
   const char *name;
};


static void FromBufChar(char **buf, char *x)
{
   // Read a char from the buffer and advance the buffer.

   *x = **buf;
   *buf += 1;
}

static void FromBufShort(char **buf, short *x)
{
   // Read a short from the buffer and advance the buffer.

#ifdef R__BYTESWAP
   char *sw = (char *)x;
   sw[0] = (*buf)[1];
   sw[1] = (*buf)[0];
#else
   memcpy(x, *buf, sizeof(short));
#endif
   *buf += sizeof(short);
}

static void FromBufInt(char **buf, int *x)
{
   // Read an int from the buffer and advance the buffer.

#ifdef R__BYTESWAP
   char *sw = (char *)x;
   sw[0] = (*buf)[3];
   sw[1] = (*buf)[2];
   sw[2] = (*buf)[1];
   sw[3] = (*buf)[0];
#else
   memcpy(x, *buf, sizeof(int));
#endif
   *buf += sizeof(int);
}

static void FromBufLL(char **buf, long long *x)
{
   // Read a long long from the buffer and advance the buffer.

#ifdef R__BYTESWAP
   char *sw = (char *)x;
   sw[0] = (*buf)[7];
   sw[1] = (*buf)[6];
   sw[2] = (*buf)[5];
   sw[3] = (*buf)[4];
   sw[4] = (*buf)[3];
   sw[5] = (*buf)[2];
   sw[6] = (*buf)[1];
   sw[7] = (*buf)[0];
#else
   memcpy(x, *buf, sizeof(long long));
#endif
   *buf += sizeof(long long);
}

static void FromBufUUID(char **buf, char **uuid, int versiondir)
{
   // Read UUID from the buffer and return it as string in uuid.
   // Returned string must be freed by the caller.
   // We'll never come here if version < 2.

   unsigned int   timeLow;
   unsigned short version, timeMid, timeHiAndVersion;
   unsigned char  clockSeqHiAndReserved, clockSeqLow, node[6];

   if (versiondir > 2)
      FromBufShort(buf, (short*)&version);  //version
   FromBufInt(buf, (int*)&timeLow);
   FromBufShort(buf, (short*)&timeMid);
   FromBufShort(buf, (short*)&timeHiAndVersion);
   FromBufChar(buf, (char*)&clockSeqHiAndReserved);
   FromBufChar(buf, (char*)&clockSeqLow);
   int i;
   for (i = 0; i < 6; i++)
      FromBufChar(buf, (char*)&node[i]);

   *uuid = malloc(40);
   sprintf(*uuid, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
           timeLow, timeMid, timeHiAndVersion, clockSeqHiAndReserved,
           clockSeqLow, node[0], node[1], node[2], node[3], node[4],
           node[5]);
}

static void FromBufStr(char **buf, char **str)
{
   // Read string from the buffer and return it as string in str.
   // Returned string must be freed by the caller.

   unsigned char nwh;
   int           nchars;

   FromBufChar(buf, (char*)&nwh);
   if (nwh == 255)
      FromBufInt(buf, &nchars);
   else
      nchars = nwh;

   int i;
   *str = malloc(nchars+1);
   for (i = 0; i < nchars; i++)
      FromBufChar(buf, &(*str)[i]);
   (*str)[nchars] = '\0';
}

static void GetDateAndTime(unsigned int datetime, int *date, int *time)
{
   // Function that returns the date and time. The input is
   // in TDatime format (as obtained via TDatime::Get()).
   // Date is returned in the format 950223  February 23 1995.
   // Time is returned in the format 102459 10h 24m 59s.

   unsigned int year  = datetime>>26;
   unsigned int month = (datetime<<6)>>28;
   unsigned int day   = (datetime<<10)>>27;
   unsigned int hour  = (datetime<<15)>>27;
   unsigned int min   = (datetime<<20)>>26;
   unsigned int sec   = (datetime<<26)>>26;
   *date =  10000*(year+1995) + 100*month + day;
   *time =  10000*hour + 100*min + sec;
}

static int ReadBuffer(int fd, char *buffer, int len)
{
   ssize_t siz;
   while ((siz = read(fd, buffer, len)) < 0 && errno == EINTR)
      errno = 0;
   return (int)siz;
}

static int ReadHeader(int fd, struct FileHeader_t *fh, NSMutableSet *titleSet)
{
   // Read ROOT file header structure.
   // Returns -1 in case of error, 0 otherwise.

   const int len = 300;
   char *header = malloc(len);

   if (ReadBuffer(fd, header, len) != len) {
      free(header);
      return -1;
   }

   if (strncmp(header, "root", 4)) {
      free(header);
      return -1;
   }

   char *buffer = header + 4;  // skip the "root" file identifier

   FromBufInt(&buffer, &fh->version);
   FromBufInt(&buffer, &fh->begin);

   int dummy;
   long long dummyll;

   if (fh->version < 1000000) {
      // < 2GB file
      int seekfree, seekinfo;
      FromBufInt(&buffer, &dummy); fh->end = (long long) dummy;
      FromBufInt(&buffer, &seekfree); fh->seekFree = (long long) seekfree;
      FromBufInt(&buffer, &dummy); // nbytes free
      FromBufInt(&buffer, &dummy); // nfree
      FromBufInt(&buffer, &fh->nbytesName);
      FromBufChar(&buffer, &fh->units);
      FromBufInt(&buffer, &fh->compress);
      FromBufInt(&buffer, &seekinfo); fh->seekInfo = (long long) seekinfo;
      FromBufInt(&buffer, &dummy); // nbytes info
   } else {
      // > 2GB file
      FromBufLL(&buffer, &fh->end);
      FromBufLL(&buffer, &fh->seekFree);
      FromBufInt(&buffer, &dummy);  // nbytes free
      FromBufInt(&buffer, &dummy);  // nfree
      FromBufInt(&buffer, &fh->nbytesName);
      FromBufChar(&buffer, &fh->units);
      FromBufInt(&buffer, &fh->compress);
      FromBufLL(&buffer, &fh->seekInfo);
      FromBufInt(&buffer, &dummy);  // nbytes info
   }

   int nk = sizeof(int)+sizeof(short)+2*sizeof(int)+2*sizeof(short)+2*sizeof(int);
   int nbytes = fh->nbytesName + (22 + 2*sizeof(int) + 18);  //nbytesName + TDirectoryFile::Sizeof();
   if (fh->version >= 40000)
      nbytes += 12;

   if (nbytes + fh->begin > 300) {
      free(header);
      header = malloc(nbytes);
      lseek(fd, (off_t)fh->begin, SEEK_SET);
      if (ReadBuffer(fd, header, nbytes) != nbytes) {
         free(header);
         return -1;
      }
      buffer = header + fh->nbytesName;
   } else {
      buffer = header + fh->begin + fh->nbytesName;
      nk += fh->begin;
   }

   short dversion, versiondir;
   FromBufShort(&buffer, &dversion);
   versiondir = dversion%1000;
   FromBufInt(&buffer, &dummy);
   GetDateAndTime((unsigned int)dummy, &fh->dateC, &fh->timeC);
   FromBufInt(&buffer, &dummy);
   GetDateAndTime((unsigned int)dummy, &fh->dateM, &fh->timeM);
   FromBufInt(&buffer, &dummy);  // nbytes keys
   FromBufInt(&buffer, &dummy);  // nbytes name
   if (dversion > 1000) {
      FromBufLL(&buffer, &dummyll); // seek dir
      FromBufLL(&buffer, &dummyll); // seek parent
      FromBufLL(&buffer, &fh->seekKeys);
   } else {
      int skeys;
      FromBufInt(&buffer, &dummy); // seek dir
      FromBufInt(&buffer, &dummy); // seek parent
      FromBufInt(&buffer, &skeys); fh->seekKeys = (long long)skeys;
   }
   if (versiondir > 1)
      FromBufUUID(&buffer, &fh->uuid, versiondir);
   else
      fh->uuid = strdup("-");

   buffer = header + nk;
   char *str;
   FromBufStr(&buffer, &str); free(str);  // "TFile"
   FromBufStr(&buffer, &str); free(str); // orig filename
   FromBufStr(&buffer, &fh->title);

#ifdef DEBUG
   NSLog(@"%s: version = %d, begin = %d, end = %lld, units = %hhd, compress = %d",
         fh->name, fh->version, fh->begin, fh->end, fh->units, fh->compress);
#endif

   [titleSet addObject: [NSString stringWithUTF8String: fh->title]];

   free(header);

   return 0;
}

static int ReadKeys(int fd, struct FileHeader_t *fh, NSMutableSet *nameSet, NSMutableSet *titleSet)
{
   // Loop over all keys and print information.
   // Returns -1 in case of error, 0 otherwise.

   int    nbytes;
   int    objlen;
   int    date;
   int    time;
   short  keylen;
   short  cycle;
   char  *classname;
   char  *name;
   char  *title;

   const int len = 256;
   int   nread, datime;
   short versionkey;
   char *header, *buffer;

   long long idcur = fh->begin;

   nread = len;
   while (idcur < fh->end) {
   again:
      header = malloc(nread);
      lseek(fd, (off_t)idcur, SEEK_SET);
      if (idcur+nread > fh->end) nread = fh->end-idcur-1;
      if (ReadBuffer(fd, header, nread) != nread) {
         free(header);
         return -1;
      }
      buffer = header;
      FromBufInt(&buffer, &nbytes);
      if (!nbytes) {
         // read error
         free(header);
         break;
      }
      if (nbytes < 0) {
         // a gap
         free(header);
         idcur -= nbytes;
         continue;
      }

      FromBufShort(&buffer, &versionkey);
      FromBufInt(&buffer, &objlen);
      FromBufInt(&buffer, &datime);
      GetDateAndTime((unsigned int)datime, &date, &time);
      FromBufShort(&buffer, &keylen);
      FromBufShort(&buffer, &cycle);
      if (versionkey > 1000) {
         long long dummyll;
         FromBufLL(&buffer, &dummyll); // seekkey
         FromBufLL(&buffer, &dummyll); // seekpdir
      } else {
         int dummy;
         FromBufInt(&buffer, &dummy); // seekkey
         FromBufInt(&buffer, &dummy); // seekpdir
      }
      if (keylen > nread) {
         free(header);
         nread = keylen;
         goto again;
      }
      FromBufStr(&buffer, &classname);
      FromBufStr(&buffer, &name);
      FromBufStr(&buffer, &title);
      if (idcur == fh->seekFree) {
         free(classname);
         classname = strdup("FreeSegments");
         name[0] = '\0';
         title[0] = '\0';
      }
      if (idcur == fh->seekInfo) {
         free(classname);
         classname = strdup("StreamerInfo");
         name[0] = '\0';
         title[0] = '\0';
      }
      if (idcur == fh->seekKeys) {
         free(classname);
         classname = strdup("KeysList");
         name[0] = '\0';
         title[0] = '\0';
      }
      float cx;
      if (objlen != nbytes-keylen)
         cx = (float)(objlen+keylen)/(float)nbytes;
      else
         cx = 1.0;

      if (strlen(name))
         [nameSet addObject: [NSString stringWithUTF8String: name]];
      if (strlen(title))
         [titleSet addObject: [NSString stringWithUTF8String: title]];

      free(classname);
      free(name);
      free(title);
      free(header);

      nread = len;

      idcur += nbytes;
   }

   return 0;
}

int ReadFile(NSString *fullPath, NSMutableSet *nameSet, NSMutableSet *titleSet)
{
   // Read ROOT file structure for specified file.
   // Returns -1 in case of error, 0 otherwise.

   struct FileHeader_t fh;
   fh.name = [fullPath UTF8String];
   int fd = open(fh.name, O_RDONLY, 0644);
   if (fd == -1)
      return -1;

   if (ReadHeader(fd, &fh, titleSet) == -1) {
      close(fd);
      return -1;
   }

   if (ReadKeys(fd, &fh, nameSet, titleSet) == -1) {
      free(fh.uuid);
      free(fh.title);
      close(fd);
      return -1;
   }

   free(fh.uuid);
   free(fh.title);

   close(fd);

   return 0;
}
