/*
_____       _    _    Corso   Italia,  178
(_|__   .  (_   |_|_  56125           Pisa
(_|_) |)|(()_)()| |   tel.  +39  050 46380
  |   |               picosoft@picosoft.it

 Copyright (C) Picosoft s.r.l. 1995-2002

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2, or (at your option)
 any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
static char rcsid[] = "$Id: pIcheck.c,v 1.8 2002/05/06 11:58:03 picoSoft Exp $";
# include <stdio.h>
# include <fcntl.h>
# ifdef WIN32
# include <io.h>
# else
# include <unistd.h>
# endif
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include "isam.h"
# include "picoisam.h"


#define gaugeMeter(n) (n % 100 == 0 ? _gaugeMeter (n),1 : 0)
void
_gaugeMeter (long n)
{
   char num[48];
   int len, i;

   sprintf (num, "%ld", n);
   len = strlen(num);
   for (i = 0; i < len; i++)
      num[len+i] = '\b';
   num[len+i] = '\b';
   fflush(stdout);
   write (1, num, len+i);
}

int
notEqual (char *rec1, char *rec2, struct keydesc *kdesc)
{
   register int i;

   if (kdesc->k_nparts > 0)
      for (i = 0; i < kdesc->k_nparts; i++)
         if (memcmp (&rec1[kdesc->k_part[i].kp_start],
                     &rec2[kdesc->k_part[i].kp_start],
                           kdesc->k_part[i].kp_leng) != 0)
            return 1;
   return 0;
}


const char *
showIsamError ()
{
   switch (pIserrno) {
   case EDUPL:
      return "duplicate record";
   case ENOTOPEN:
      return "file not open";
   case EBADARG:
      return "illegal argument";
   case EBADKEY:
      return "illegal key desc";
   case ETOOMANY:
      return "too many files open";
   case EBADFILE:
      return "bad isam file format";
   case ENOTEXCL:
      return "non-exclusive access";
   case ELOCKED:
      return "record locked";
   case EKEXISTS:
      return "key already exists";
   case EPRIMKEY:
      return "is primary key";
   case EENDFILE:
      return "end/begin of file";
   case ENOREC:
      return "no record found";
   case ENOCURR:
      return "no current record";
   case EFLOCKED:
      return "file locked";
   case EFNAME:
      return "file name too long";
   case EBADMEM:
      return "can't alloc memory";
   case EBADOPENMODE:
      return "incompatible operation with open mode";
   case ENOTSUPP:
      return "function not supported";
   case EDISKFULL:
      return "disk full";
   case ERECCHANGED:
      return "rec changed";
   case ENOLOCKS:
      return "no more locks available";
   case EMISSINGFILE:
      return "missing file";
   case EPERMISSION:
      return "invalid permission";
   }
   return strerror(errno);
}

void
fileError (char *op)
{
   fprintf (stderr, "Error in %s: %s %d\n", op, showIsamError(), pIserrno);
}


const char *
showKeyPartType (int t)
{
   switch (t & 0x0F) {
   default:
      return "UNKNOWN!!!";
   case CHARTYPE:
     if (t & ISDESC)
        return "char descending";
     else
        return "char ascending";
   case INTTYPE:
     if (t & ISDESC)
        return "int descending";
     else
        return "int ascending";
   case LONGTYPE:
     if (t & ISDESC)
        return "long descending";
     else
        return "long ascending";
   case DOUBLETYPE:
     if (t & ISDESC)
        return "double descending";
     else
        return "double ascending";
   case FLOATTYPE:
     if (t & ISDESC)
        return "float descending";
     else
        return "float ascending";
   }
}

int
info(int isfld, struct dictinfo *dict)
{
   struct keydesc key;
   int i, j;
   int Return;

   pIsindexinfo(isfld, dict, 0);
   printf ("nkeys=%d, recsize=%d, idxsize=%d nrecords=%ld version=%d\n",
            dict->di_nkeys,
            dict->di_recsize,
            dict->di_idxsize,
            dict->di_nrecords,
            pIsGetVersion(isfld));
   for (i = 1, Return = 0; ; i++) {
      Return = pIsindexinfo(isfld, &key, i);
      if (Return == 0) {
         printf ("   key=%d flags=%s, nparts=%d\n", i,
                       key.k_flags == ISDUPS ? "ISDUPS" : "ISNODUPS",
                       key.k_nparts);
         for (j = 0; j < key.k_nparts; j++) {
             printf ("      part=%d start=%d, leng=%d type=%s\n", j,
                    key.k_part[j].kp_start,
                    key.k_part[j].kp_leng,
                    showKeyPartType (key.k_part[j].kp_type));
         }
      } else
         break;
   }
   if (pIserrno != EBADARG) {
      fileError ("info");
      Return = 4;
   } else if (--i != dict->di_nkeys) {
      fprintf (stderr, "Invalid key number (%d != %d)\n", i, dict->di_nkeys);
      Return = 4;
   } else
      Return = 0;
   return Return;
}

/*
** CheckOverflow()
**
** Se il valore n del contatore corrente di record e' maggiore
** di max, che e' il numero di record presenti nel file,
** restituisce 1, altrimenti 0.
*/

int
CheckOverflow(long n, long max)
{
   if (n >  max) {
     fprintf (stderr, 
          "\nCorrupted tree: record counter value %ld > record in file [%ld]\n",
          n, max);
     return 1;
   } else
     return 0;
}

int
check(int isfld, struct dictinfo *dict, char *fileName, int fullcheck)
{
   struct keydesc key, key0;
   int i;
   long srec;
   long nrec;
   long urec;
   int Return = info (isfld, dict);
   int isfp;

   if (Return == 0) {
      char *recordNew = malloc (dict->di_recsize + 1);
      char *recordOld = malloc (dict->di_recsize + 1);
      if ((isfp = pIsopen (fileName, ISINPUT|ISMANULOCK)) < 0) {
         fprintf (stderr, "Cannot open %s (%d)\n", fileName, pIserrno);
         return 3;
      }
      key0.k_flags = 0;
      key0.k_nparts = 0;
      if (pIsstart (isfp, &key0, 0, 0, ISFIRST)) {
         if (pIserrno != EENDFILE) {
            fprintf(stderr, "Cannot start first %s (%d)\n", fileName, pIserrno);
            return 4;
         }
      }
      printf ("Sequential read  ...");
      for (srec = 0; pIsread(isfp, recordNew, ISNEXT) == 0; ){
         srec++;
         gaugeMeter (srec);
         if (CheckOverflow(srec, dict->di_nrecords) == 1)
            return 14;
      }
      printf (" Ok (%ld records found)\n", srec);
      for (i = 1; i <= dict->di_nkeys; i++) {
         Return = pIsindexinfo(isfld, &key, i);
         if (Return == 0) {
            memset (recordNew, 0, dict->di_recsize + 1);
            memset (recordOld, 0, dict->di_recsize + 1);
            nrec = 0;
            urec = 0;
            printf ("Checking index %d phase1 ...", i);
            gaugeMeter (nrec);
            if (CheckOverflow(nrec, dict->di_nrecords) == 1)
               return 15;
            pIsstart (isfld, &key, 0, recordNew, ISFIRST);
            if (srec == 0 && (pIserrno == EENDFILE || pIserrno == ENOREC))
               ;
            else if (pIserrno == 0) { 
               do {
                  pIsread (isfld, recordNew, ISNEXT);
                  if (pIserrno == 0) {
                     nrec++;
                     gaugeMeter (nrec);
                     if (CheckOverflow(nrec, dict->di_nrecords) == 1)
                        return 16;
                     if (notEqual (recordNew, recordOld, &key)) {
                        urec++;
                        memcpy (recordOld, recordNew, dict->di_recsize);
                        pIsread (isfld, recordNew, ISEQUAL);
                        if (pIserrno != 0) { 
                           fileError ("read key");
                           return 5;
                        } else if (memcmp (recordNew, recordOld,
                                                      dict->di_recsize)) {
                           fprintf (stderr, "\nCorrupted tree, recnum=%ld\n",
                                             pIsrecnum);
                           return 6;
                        }
                     }
                  } else if (pIserrno != EENDFILE) {
                     fileError ("read next");
                     return 7;
                  }
               } while (pIserrno == 0);
            } else if (pIserrno != EENDFILE) {
               fileError ("start first");
               return 8;
            }
            if (pIserrno != ENOREC && pIserrno != EENDFILE) {
               fileError ("reading");
               return 9;
            } else if (nrec != srec) {
               fprintf (stderr, 
                   " Invalid records count idx %d: expected %ld, found %ld\n",
                   i, srec, nrec);
               return 18;
            } else {
               if (fullcheck) {
                  printf (" ok (%ld univoque keys), phase2 ...", urec);
                  fflush (stdout);
                  if (pIsstart (isfp, &key0, 0, 0, ISFIRST)){
                     if (pIserrno != EENDFILE) {
                        fprintf (stderr, "Cannot start first %s (%d)\n",
                                 fileName, pIserrno);
                        return 10;
                     }
                  }
                  for ( ; pIsread (isfp, recordOld, ISNEXT) == 0; ) {
                     nrec = pIsrecnum;
                     gaugeMeter(nrec);
                     if (CheckOverflow(nrec, dict->di_nrecords) == 1)
                        return 17;
                     pIsstart (isfld, &key, 0, recordOld, ISEQUAL);
                     if (pIserrno == 0) {
                        do {
                           pIsread (isfld, recordNew, ISNEXT);
                           if (pIserrno != 0) {
                              fileError ("read next");
                              return 11;
                           } else if (notEqual (recordNew, recordOld, &key)) {
                              fprintf (stderr, "\nCorrupted tree, recnum=%ld\n",
                                       pIsrecnum);
                              return 12;
                           }
                        } while (pIsrecnum != nrec);
                     } else {
                        fileError ("start equal");
                        return 13;
                     }
                  }
                  printf (" ok         \n");
               } else {
                  printf (" ok (%ld univoque keys)\n", urec);
               }
            }
         }
      }
      free (recordNew);
      free (recordOld);
   }
   return Return;
}

extern int pIsrebuild (int isfld);

int
rebuild(int isfld, struct dictinfo *dict, struct keydesc ** keys)
{
   int Return;
   char c;
   if ((Return = info (isfld, dict)) == 0) {
      fprintf(stderr,"WARNING!! this operation delete and rebuild the index\n");
      fprintf(stderr,"Do you want to continue? (y/n)\n");
      c = getchar();
      if (c == 'y' || c == 'Y') {
         printf ("rebuilding ... ");
         fflush (stdout);
         Return = pIsrebuild (isfld);
         if (Return == 0)
            printf ("Ok               \n");
         else
            fileError ("rebuilding");
      }
   }
   return Return;
}

int
rebuildAll(int isfld, struct dictinfo *dict, char *fileName)
{
   int Return;
   char c;
   if ((Return = info (isfld, dict)) == 0) {
      fprintf(stderr,"WARNING!! this operation delete and rebuild the whole file (index + data)\n");
      fprintf(stderr,"You need as more free disk space as the file size\n");
      fprintf(stderr,"Do you want to continue? (y/n)\n");
      c = getchar();
      if (c == 'y' || c == 'Y') {
         if ((Return = pIsindexinfo(isfld, dict, 0)) != 0) {
            fileError ("isindexinfo(0)");
         } else {
            int i;
            struct keydesc *keys;
            keys = malloc (sizeof(struct keydesc) * (1 + dict->di_nkeys));
            for (i = 1, Return = 0; i <= dict->di_nkeys && Return == 0; i++)
               Return = pIsindexinfo(isfld, &keys[i], i);
            if (Return != 0) {
               fileError ("isindexinfo(n)");
            } else {
               char *newFileNameIdx = malloc (strlen(fileName) + 8);
               char *newFileNameDat = malloc (strlen(fileName) + 8);
               int newIsfld;
               strcpy (newFileNameIdx,"$$$");
               strcat (newFileNameIdx, fileName);
               strcpy (newFileNameDat,"$$$");
               strcat (newFileNameDat, fileName);
               if ((newIsfld = pIsbuild (newFileNameIdx, dict->di_recsize,
                                          &keys[1], ISINOUT|ISEXCLLOCK)) < 0) {
                  fileError (newFileNameIdx);
                  Return = -1;
               } else {
                  strcat (newFileNameIdx, ".idx");
                  strcat (newFileNameDat, ".dat");
                  for (i = 2; i <= dict->di_nkeys && Return == 0; i++)
                     Return = pIsaddindex (newIsfld, &keys[i]);
                  if (Return != 0) {
                     fileError ("isaddindex");
                  } else {
                     struct keydesc key0;
                     char *record = malloc (dict->di_recsize);
                     key0.k_flags = 0;
                     key0.k_nparts = 0;
                     if (pIsstart (isfld, &key0, 0, 0, ISFIRST) != 0 &&
                         pIserrno != EENDFILE) {
                        fileError("start first");
                        Return = -1;
                     } else {
                        unsigned long recnum = 0;
                        while (Return==0 && pIsread(isfld,record,ISNEXT)==0) {
                           recnum = pIsrecnum;
                           if ((Return = pIswrite (newIsfld, record)) != 0)
                              break;
                        }
                        if (pIserrno != EENDFILE) {
                           if (Return == 0) {
                              fileError ("isread");
                              Return = -1;
                           } else {
                              char buff[64];
                              sprintf (buff, "iswrite recnum=%ld", recnum);
                              fileError (buff);
                           }
                           unlink (newFileNameDat);
                           unlink (newFileNameIdx);
                        } else {
                           char *oldFileNameIdx = malloc(strlen(fileName) + 5);
                           char *oldFileNameDat = malloc(strlen(fileName) + 5);
                           strcpy (oldFileNameIdx, fileName);
                           strcat (oldFileNameIdx, ".idx");
                           strcpy (oldFileNameDat, fileName);
                           strcat (oldFileNameDat, ".dat");

                           pIserrno = 999;
                           if (unlink (oldFileNameDat) != 0)
                              fileError ("unlink dat");
                           else if (rename (newFileNameDat,oldFileNameDat) != 0)
                              fileError ("rename dat");
                           else if (unlink (oldFileNameIdx) != 0)
                              fileError ("unlink idx");
                           else if (rename (newFileNameIdx,oldFileNameIdx) != 0)
                              fileError ("rename idx");

                           free (oldFileNameIdx);
                           free (oldFileNameDat);
                        }
                     }
                     free (record);
                  }
                  pIsclose (isfld);
                  pIsclose(newIsfld);
               }
               free (newFileNameDat);
               free (newFileNameIdx);
            }
            free (keys);
         }
      }
   }
   return Return;
}

void
usage ()
{
   fprintf (stderr, "pIcheck version [%s]\n", rcsid);
   fprintf (stderr, "usage: pIcheck [-icfr] picoIsam-file\n\
       -i file info\n\
       -c full check\n\
       -f fast check\n\
       -r reuild indexes\n\
       -a rebuild all & compress\n");
}

int
main(int argc, char *argv[])
{
   int i;
   char *fileName = 0;
   int isfld;
   struct dictinfo dict;
   enum tOption {
      INFO,
      CHECK,
      FASTCHECK,
      REBUILD,
      REBUILD_ALL
   } option = INFO;

   for (i = 1; i < argc; i++)
      if (argv[i][0] == '-') {
         switch (argv[i][1]) {
         case 'i':
            option = INFO;
            break;
         case 'r':
            option = REBUILD;
            break;
         case 'c':
            option = CHECK;
            break;
         case 'f':
            option = FASTCHECK;
            break;
         case 'a':
            option = REBUILD_ALL;
            break;
         default:
            usage();
            return 2;
         }
      } else
         if (fileName == 0)
            fileName = argv[i];
         else {
            usage();
            return 2;
         }
   if (fileName == 0) {
      usage();
      return 2;
   }
 
   isfld = pIsopen (fileName, ISINOUT|ISEXCLLOCK);
   if (pIserrno == EFLOCKED && option != REBUILD && option != REBUILD_ALL) {
      isfld = pIsopen (fileName, ISINPUT|ISMANULOCK);
      printf ("Warning, file in use!! Results may be inaccurate\n");
   }
   if (isfld < 0) {
      fileError("open");
      return 3;
   }

   switch (option) {
   case CHECK:
      return check(isfld, &dict, fileName, 1);
   case FASTCHECK:
      return check(isfld, &dict, fileName, 0);
   case REBUILD:
      return rebuild(isfld, &dict, 0);
   case REBUILD_ALL:
      return rebuildAll(isfld, &dict, fileName);
   default:
      return info(isfld, &dict);
   }
}
