/*
*  ChkTeX, utility functions.
*  Copyright (C) 1995-96 Jens T. Berger Thielemann
*
*  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 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*  Contact the author at:
*              Jens Berger
*              Spektrumvn. 4
*              N-0666 Oslo
*              Norway
*              E-mail: <[email protected]>
*
*
*/

#include "ChkTeX.h"
#include "Utility.h"
#include "Resource.h"
#include "OpSys.h"

typedef unsigned long HASH_TYPE;

/***************************** SUPPORT FUNCTIONS ************************/


/*
* Copies a string with a maximum length of `len' starting at `pos' in
* `source' into `dest'.
* Returns -1 if the pos value is beyond the length of the source value,
* else NULL.
*/


short substring(const char *source, char *dest, unsigned long pos, long len)
{
   const char *Start;
   short Retval = -1;

   if (len >= 0)
   {
       if (strlen(source) > pos)
       {
           Start = &source[pos];

           while ((len-- > 0) && (*dest++ = *Start++))
               ;

           if (len == -1)
               Retval = 0;
       }
   }
   else
       Retval = 0L;

   *dest = 0;

   return (Retval);
}


/*
* Determine whether a file exists.
*
*/


int fexists(const char *Filename)
{
   int Retval;

#if defined(F_OK) && defined(R_OK) && defined(HAVE_ACCESS)

   Retval = access(Filename, F_OK | R_OK) == 0;
#else

   FILE *fh;
   if ((fh = fopen(Filename, "r")))
   {
       Retval = TRUE;
       fclose(fh);
   }
   else
       Retval = FALSE;
#endif

   /* Ensure that it's not a directory */
   if (Retval && !IsRegFile(Filename))
   {
       Retval = FALSE;
   }

   return (Retval);
}



/*
* 'Safe' memset() replacement.
* Just tries to check the parameters, so that the risk of killing
* innocent memory is lowered.
* Please note that the `n' parameter now is an signed longword, not
* an size_t. Won't permit that `n' exceeds BUFLEN, nor negative sizes.
* Returns `to' if some memset()'ing was done, NULL if not.
*/

void *sfmemset(void *to, int c, long n)
{
   if (to && (n > 0))
   {
       n = min(n, BUFFER_SIZE);

       return (memset(to, c, (size_t) n));
   }
   return (NULL);
}


/*
* Quick replace function
* Replaces every occurrence of a character in a string with another one.
*/

void strrep(char *String,       /* String to replace within.    */
           const char From,    /* Character to search for.     */
           const char To)      /* Character to put instead.    */
{
   register int c;
   while ((c = *String++))
   {
       if (c == From)
           String[-1] = To;
   }
}

/*
* Replaces every char not in Prot with To in Buf
*/

void strxrep(char *Buf, const char *Prot, const char To)
{
   int c;

   while ((c = *Buf))
   {
       if (!strchr(Prot, c))
           *Buf = To;
       Buf++;
   }
}

/*
* Strips tail and/or front of a string
* Kills trailing/leading spaces. The macro/function LATEX_SPACE(char c)
* is used to decide whether a space should be skipped. This function
* should return TRUE if the character should be skipped, FALSE if not.
* Returns the string which was passed onto it.
*/


char *strip(char *str,          /* String to strip */
           const enum Strip flags)
/* One of the following: */
/* STRP_LFT - Strips leading  blanks */
/* STRP_RGT - Strips trailing blanks */
/* STRP_BTH - Strips on both sides   */
{
   char *bufptr = str;
   char *nlptr;
   char c;

   if (bufptr && (c = *bufptr))
   {
       if (flags & STRP_LFT)
       {
           if (LATEX_SPACE(c) && c)
           {
               do
               {
                   c = *++bufptr;
               }
               while (LATEX_SPACE(c) && c);
           }
       }

       if (flags & STRP_RGT)
       {
           if (c && *bufptr)
           {
               nlptr = bufptr;

               while (*++nlptr)
                   ;

               do
               {
                   *nlptr = 0;
                   c = *--nlptr;
               }
               while (LATEX_SPACE(c) && c && (nlptr > bufptr));

           }
           else
               *bufptr = 0;
       }
   }
   return (bufptr);
}


/*
* Converts all the chars in the string passed into lowercase.
*/

#ifndef HAVE_STRLWR
char *strlwr(char *String)
{
   char *Bufptr;
   char TmpC;

   for (Bufptr = String; (TmpC = *Bufptr); Bufptr++)
       *Bufptr = tolower((unsigned char)TmpC);

   return (String);
}
#endif

/*
* Returns a duplicate of the string passed.
*/

#ifndef HAVE_STRDUP
char *strdup(const char *String)
{
   char *Retval = NULL;
   size_t Len;

   if (String)
   {
       Len = strlen(String) + 1;
       if ((Retval = malloc(Len)))
           memcpy(Retval, String, Len);
   }

   return (Retval);
}
#endif

/*
* Does the same as strdup, but adds a zero-filled padding, length extra bytes.
*/

char *strdupx(const char *String, int Extra)
{
   char *Retval = NULL;
   size_t Len;

   if (String)
   {
       Len = strlen(String) + 1 + Extra;
       if ((Retval = malloc(Len)))
           strncpy(Retval, String, Len);
   }

   return (Retval);
}

/*
* Case-insensitive comparison of two strings.
*/

#ifndef HAVE_STRCASECMP
int strcasecmp(const char *a, const char *b)
{
   int aa, bb;

   do
   {
       aa = *a++;
       bb = *b++;
   }
   while (aa && (tolower((unsigned char)aa) == tolower((unsigned char)bb)));
   /* bb != 0 is implicit */

   return (tolower((unsigned char)aa) - tolower((unsigned char)bb));
}
#endif

/*
* Not all reallocs are intelligent enough to handle NULL's as
* parameters. This fixes this.
*/

void *saferealloc(void *b, size_t n)
{
   void *Retval = NULL;

   if (b)
   {
       if (n)
           Retval = realloc(b, n);
       else
           free(b);
   }
   else
       Retval = malloc(n);

   return (Retval);
}

/*
* Repeatedly writes the From string over To so that we overwrite Len bytes.
* Does nothing if passed empty/NULL string.
*/

void strwrite(char *To, const char *From, unsigned long Len)
{
   unsigned long i, j;
   unsigned long FromLen = strlen(From);

   Len = min(Len, BUFFER_SIZE);

   if (To && From)
   {
       switch (FromLen)
       {
       case 0:
           break;
       case 1:
           memset(To, *From, Len);
           break;
       default:
           for (i = j = 0; i < Len; i++, j++)
           {
               if (j >= FromLen)
                   j = 0;
               To[i] = From[j];
           }
       }
   }
}

/*
* Checks whether Cmp comes after Str.
*
*/

int strafter(const char *Str, const char *Cmp)
{
   return (strncmp(Str, Cmp, strlen(Cmp)));
}

/*
* Checks whether Cmp comes before Str. Returns 0 if so, non-zero if not.
*
*/

int strinfront(const char *Str, const char *Cmp)
{
   int CmpLen;

   if ((CmpLen = strlen(Cmp)))
   {
       Cmp += CmpLen;
       Str++;

       while ((*--Cmp == *--Str) && (--CmpLen > 0))
           ;

       return (CmpLen);
   }
   else
       return (1);
}


/*************************** HASH INDEX **************************/

/*
* Hashes a string. The string ought be rather short.
*
* The algorithm was designed by Peter Weinberger. This version was
* adapted from Dr Dobb's Journal April 1996 page 26.
*/

static unsigned long HashWord(const char *str)
{
   register unsigned long h = 0, hbit, c;

   while ((c = *str++))
   {
       h = (h << 4) ^ c;
       if ((hbit = h & 0xf0000000))
           h ^= hbit >> 24;
       h &= ~hbit;
   }

   return (h);
}

/*
* Inserts a string into a hash index. Note: You'll have to
* duplicate the string yourself.
*/

void InsertHash(char *a, struct Hash *h)
{
   struct HashEntry **he, *newhe;

   if (!h->Index)
   {
       if (!((h->Index = calloc(HASH_SIZE, sizeof(struct HashEntry *)))))
           PrintPrgErr(pmWordListErr);
   }

   he = &h->Index[HashWord(a) % HASH_SIZE];

   if ((newhe = malloc(sizeof(struct HashEntry))))
   {
       newhe->Next = *he;
       newhe->Str = a;
       *he = newhe;
   }
   else
       PrintPrgErr(pmWordListErr);
}

/*
* Checks whether a string previously has been registered in a
* hash index.
*/

char *HasHash(const char *a, const struct Hash *h)
{
   struct HashEntry *he;
   HASH_TYPE i;                /* Special magic to optimize SAS/C */

   /* Do we have a hash? */
   if (!h->Index)
       return NULL;

   /* Find entry in hash */
   i = HashWord(a);
   i %= HASH_SIZE;
   he = h->Index[i];

   /* Search in the entry for the item */
   while (he)
   {
       if (!strcmp(he->Str, a))
           return (he->Str);
       else
           he = he->Next;
   }
   return (NULL);
}

/*
* Clears a hash table.
*/

void ClearHash(struct Hash *h)
{
   int i;
   struct HashEntry *he, *next;
   if (h && h->Index)
   {
       /* Free all the memory */
       for ( i = 0; i < HASH_SIZE; ++i )
       {
           he = h->Index[i];
           while ( he )
           {
               next = he->Next;
               free( he );
               he = next;
           }
       }
       /* Reset the hash table */
       memset(h->Index, '\0', HASH_SIZE * sizeof(struct HashEntry *));
   }
}

/*
* Rehashes a wordlist. If you change any of the elem's, you must
* call this.
*
*/

static void ReHash(struct WordList *WL)
{
   unsigned long i = 0;

   ClearHash(&WL->Hash);
   FORWL(i, *WL) InsertHash(WL->Stack.Data[i], &WL->Hash);
}

/*************************** WORDLIST HANDLING **************************/

/*
* Inserts a duplicate of `Word' into the `Wordlist' structure. You do thus
* not need to make a duplicate of `Word' yourself.
*/

int InsertWord(const char *Word, struct WordList *WL)
{
   char *WrdCpy;
   unsigned long Len;

   if ((WrdCpy = strdupx(Word, WALLBYTES)))
   {
       if (StkPush(WrdCpy, &WL->Stack))
       {
           Len = strlen(Word);
           if (WL->MaxLen < Len)
               WL->MaxLen = Len;

           InsertHash(WrdCpy, &WL->Hash);
           return (TRUE);
       }

       free(WrdCpy);
   }

   return (FALSE);
}

/*
* Clears a WordList; removing all items.
*/

void ClearWord(struct WordList *WL)
{
   char *Word;
   if (WL)
   {
       while ( (Word = StkPop( &WL->Stack )) )
       {
           free(Word);
       }
       WL->Stack.Used = 0;
       WL->MaxLen = 0;
       ClearHash(&WL->Hash);
       if (WL->NonEmpty)
           InsertWord("", WL);
   }
}

/*
* Query whether a `Word' is previously InsertWord()'ed into the WL
* structure. Does case-sensitive comparison.
*
* Returns the data in the list.
*/


char *HasWord(const char *Word, struct WordList *WL)
{
   return HasHash(Word, &WL->Hash);
}

/*
* Make all the words in a list lower case for later case-insensitive
* comparison.
*/

void MakeLower(struct WordList *wl)
{
   unsigned long i;
   FORWL(i, *wl) strlwr(wl->Stack.Data[i]);
   ReHash(wl);
}

/*
* Calls strrep on each argument in a list.
*/

void ListRep(struct WordList *wl, const char From, const char To)
{
   unsigned long i;
   FORWL(i, *wl) strrep(wl->Stack.Data[i], From, To);
   ReHash(wl);
}



/************************** GENERIC STACK  ******************************/

/*
* Push something onto a stack. Returns TRUE if successful, else FALSE.
* Note: You can not push a NULL Data element.
*/

int StkPush(void *Data, struct Stack *Stack)
{
   unsigned long NewSize;
   void **NewBuf;

   if (Data && Stack)
   {
       if (Stack->Used >= Stack->Size)
       {
           NewSize = Stack->Size + MINPUDDLE;

           if ((NewBuf = saferealloc(Stack->Data,
                                     (size_t) NewSize * sizeof(void *))))
           {
               Stack->Size = NewSize;
               Stack->Data = NewBuf;
           }
           else
               return (FALSE);
       }

       Stack->Data[Stack->Used++] = Data;
       return (TRUE);
   }

   return (FALSE);
}

/*
* Pops an element from the stack.
*
*/

void *StkPop(struct Stack *Stack)
{
   void *Retval = NULL;

   if (Stack && (Stack->Used > 0))
   {
       Retval = Stack->Data[--Stack->Used];

#ifdef NO_DIRTY_TRICKS

       {
           void **NewBuf;

           if (Stack->Used < (Stack->Size / 2))
           {
               unsigned long NewSize;
               NewSize = Stack->Size - MINPUDDLE;
               NewSize = max(NewSize, MINPUDDLE);

               if (NewBuf = saferealloc(Stack->Data,
                                        (size_t) NewSize * sizeof(void *)))
               {
                   Stack->Size = NewSize;
                   Stack->Data = NewBuf;
               }
           }
       }
#endif

   }
   return (Retval);
}

/*
* Returns the topmost element of the stack.
*/

void *StkTop(struct Stack *Stack)
{
   if (Stack && (Stack->Used > 0))
       return (Stack->Data[Stack->Used - 1]);
   else
       return (NULL);
}

/****************************** INPUT STACK *****************************/

int PushFileName(const char *Name, struct Stack *stack)
{
   FILE *fh = NULL;
   static char NameBuf[BUFFER_SIZE];

   if (Name && stack)
   {
       if (LocateFile(Name, NameBuf, ".tex", &TeXInputs))
       {
           if ((fh = fopen(NameBuf, "r")))
           {
               return (PushFile(NameBuf, fh, stack));
           }
       }
       PrintPrgErr(pmNoTeXOpen, Name);
   }
   return (FALSE);
}


int PushFile(const char *Name, FILE * fh, struct Stack *stack)
{
   struct FileNode *fn;
   uint64_t *filesupp;

   if (Name && fh && stack)
   {
       if ((fn = malloc(sizeof(struct FileNode))))
       {
           if ((fn->Name = strdup(Name)))
           {
               fn->fh = fh;
               fn->Line = 0L;
               if ((filesupp = malloc(sizeof(uint64_t))))
               {
                   *filesupp = 0;
                   StkPush(filesupp, &FileSuppStack);
               }
               else
                   PrintPrgErr(pmNoStackMem);
               if ((filesupp = malloc(sizeof(uint64_t))))
               {
                   *filesupp = 0;
                   StkPush(filesupp, &UserFileSuppStack);
               }
               else
                   PrintPrgErr(pmNoStackMem);

               if (StkPush(fn, stack))
                   return (TRUE);
               free(fn->Name);
           }
           free(fn);
       }
       PrintPrgErr(pmNoStackMem);
   }

   return (FALSE);
}

char *FGetsStk(char *Dest, unsigned long len, struct Stack *stack)
{
   static short HasSeenLong = 0;
   struct FileNode *fn;
   char *Retval = NULL;
   size_t Retlen = 0;

   if ((fn = StkTop(stack)))
   {
       do
       {
           Retval = fgets(Dest, (int)len, fn->fh);
           if (Retval) {
               Retlen = strlen(Retval);

               if (Retval[Retlen-1] == '\n' || Retlen < len-1)
                   fn->Line++;
               /* We only want the long lines warning once per file */
               else if (!HasSeenLong)
               {
                   PrintPrgErr(pmLongLines, len-2);
                   HasSeenLong = 1;
               }
               break;
           }

           fn = StkPop(stack);
           fclose(fn->fh);
           if ( fn->Name != NULL )
               free(fn->Name);
           free(fn);
           HasSeenLong = 0;

           StkPop(&FileSuppStack);
           StkPop(&UserFileSuppStack);
       }
       while (!Retval && (fn = StkTop(stack)));
   }

   return (Retval);
}

const char *CurStkName(struct Stack *stack)
{
   struct FileNode *fn;
   static const char *LastName = "";

   if (PseudoInName && (stack->Used <= 1))
       return (PseudoInName);
   else
   {
       if ((fn = StkTop(stack)))
       {
           if ( stack->Used == 1 && strlen(LastName) == 0 && fn->Name )
           {
               LastName = strdup(fn->Name);
           }
           return (fn->Name);
       }
       else
           return (LastName);
   }
}


FILE *CurStkFile(struct Stack * stack)
{
   struct FileNode *fn;

   if ((fn = StkTop(stack)))
       return (fn->fh);
   else
       return (NULL);
}

unsigned long CurStkLine(struct Stack *stack)
{
   struct FileNode *fn;
   static unsigned long LastLine = 0L;

   if ((fn = StkTop(stack)))
       return (LastLine = fn->Line);
   else
       return (LastLine);
}

long CurStkMode(struct Stack *stack)
{
   long * Mode;
   if ((Mode = StkTop(stack)))
       return *Mode;
   /* printf("Empty stack\n"); */
   return FALSE;
}

long *PushMode(long mode, struct Stack *stack)
{
   long *m;

   if ((m = malloc(sizeof(long))))
   {
       *m = mode;
       StkPush(m, stack);
       return m;
   }
   return NULL;
}
/************************** CHARACTER STACK ******************************/

/*
* Pushes the character on the stack.
*/

struct ErrInfo *PushChar(const char c, const unsigned long Line,
                        const unsigned long Column, struct Stack *Stk,
                        const char *LineCpy)
{
   char Buf[2];

   Buf[0] = c;
   Buf[1] = 0;

   return (PushErr(Buf, Line, Column, 1, LineCpy, Stk));
}

struct ErrInfo *PushErr(const char *Data, const unsigned long Line,
                       const unsigned long Column,
                       const unsigned long ErrLen, const char *LineCpy,
                       struct Stack *Stk)
{
   struct ErrInfo *ci;

   if ((ci = malloc(sizeof(struct ErrInfo))))
   {
       if ((ci->Data = strdup(Data)))
       {
           if ((ci->File = strdup(CurStkName(&InputStack))))
           {
               if ((ci->LineBuf = strdup(LineCpy)))
               {
                   ci->Line = Line;
                   ci->ErrLen = ErrLen;
                   ci->Column = Column;
                   ci->Flags = efNone;

                   if (StkPush(ci, Stk))
                       return (ci);

                   free(ci->LineBuf);
               }
               else
                   PrintPrgErr(pmStrDupErr);

               free(ci->File);
           }
           else
               PrintPrgErr(pmStrDupErr);

           free(ci->Data);
       }
       else
           PrintPrgErr(pmStrDupErr);
       free(ci);
   }

   return (NULL);
}

/*
* Finds the uppermost entry in the stack with a data matching
* String.
*/

struct ErrInfo *TopMatch(struct Stack *Stack, char *String)
{
   int i;
   struct ErrInfo *retval = NULL;

   if (Stack && String)
   {
       for (i = Stack->Used - 1; i >= 0; i--)
       {
           if (!strcmp(String, ((struct ErrInfo *) Stack->Data[i])->Data))
           {
               retval = (struct ErrInfo *) Stack->Data[i];
               break;
           }
       }
   }
   return (retval);
}

/*
* Returns and removes a character from the stack, returns NULL if
* the stack is empty.
*/


struct ErrInfo *PopErr(struct Stack *Stack)
{
   return ((struct ErrInfo *) StkPop(Stack));
}

/*
* Same as PopChar(), but lets the error alone on the stack.
*/


struct ErrInfo *TopErr(struct Stack *Stack)
{
   return ((struct ErrInfo *) StkTop(Stack));
}

/*
* Free all resources associated with a struct FreeInfo.
*/

void FreeErrInfo(struct ErrInfo *ei)
{
   if (ei)
   {
       if (ei->Data)
           free(ei->Data);
       if (ei->File)
           free(ei->File);
       if (ei->LineBuf)
           free(ei->LineBuf);

       free(ei);
   }
}


/************************* OPEN/CLOSE COUNTING **************************/

/*
* Returns the index a given bracket (`()[]{}') character has in the
* BrOrder array. Returns ~0 if the character was not a bracket.
*/

long BrackIndex(const char c)
{
   switch (c)
   {
   case '(':
       return (0);
   case ')':
       return (1);
   case '[':
       return (2);
   case ']':
       return (3);
   case '{':
       return (4);
   case '}':
       return (5);
   default:
       return (~0L);
   }
}

/*
* Counts brackets for you. Give it a bracket, and it will update the
* corresponding counter.
*/

void AddBracket(const char c)
{
   long Index;

   if ((Index = BrackIndex(c)) != -1)
       Brackets[Index]++;

}

/*
* Returns the character that matches the given bracket, NULL if `c'
* wasn't a bracket character.
*/

char MatchBracket(const char c)
{
   unsigned long Index;
   char Char = 0;


   if ((Index = BrackIndex(c)) != ~0UL)
       Char = BrOrder[Index ^ 1];

   return (Char);
}