/*
*  ChkTeX, operating system specific code for ChkTeX.
*  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]>
*
*
*/


/*
* Some functions which have to be made different from OS to OS,
* unfortunately...:\
*
*/

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

#ifdef KPATHSEA
#include <kpathsea/variable.h>
#endif

#ifdef HAVE_SYS_STAT_H
#  include <sys/stat.h>
#endif
#ifdef HAVE_STAT_H
#  include <stat.h>
#endif

#if HAVE_DIRENT_H
#  include <dirent.h>
#  define NAMLEN(dirent) strlen((dirent)->d_name)
#else
#  define dirent direct
#  define NAMLEN(dirent) (dirent)->d_namlen
#  if HAVE_SYS_NDIR_H
#    include <sys/ndir.h>
#  endif
#  if HAVE_SYS_DIR_H
#    include <sys/dir.h>
#  endif
#  if HAVE_NDIR_H
#    include <ndir.h>
#  endif
#endif

#if defined(HAVE_OPENDIR) && defined(HAVE_CLOSEDIR) && \
   defined(HAVE_READDIR) && defined(HAVE_STAT) && \
   defined(S_IFDIR) && defined(SLASH)
#  define USE_RECURSE 1
#else
#  define USE_RECURSE 0
#endif

#if defined(HAVE_LIBTERMCAP) || defined(HAVE_LIBTERMLIB)
#  define USE_TERMCAP 1
#endif


#ifdef USE_TERMCAP
#  ifdef HAVE_TERMCAP_H
#    include <termcap.h>
#  elif HAVE_TERMLIB_H
#    include <termlib.h>
#  else
int tgetent(char *BUFFER, char *TERMTYPE);
char *tgetstr(char *NAME, char **AREA);
#  endif
static char term_buffer[2048];
#endif

/*  -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=-  */

/*
* This is the name of the global resource file.
*/

#ifndef SYSCONFDIR
#  if defined(__unix__)
#    define SYSCONFDIR "/usr/local/lib/"
#  elif defined(__MSDOS__)
#    define SYSCONFDIR "\\emtex\\data\\"
#  else
#    define SYSCONFDIR
#  endif
#endif
#define RCBASENAME              "chktexrc"

#ifdef __MSDOS__
#  define LOCALRCFILE             RCBASENAME
#elif defined(WIN32)
#  define LOCALRCFILE             RCBASENAME
#else
#  define LOCALRCFILE             "." RCBASENAME
#endif

char ConfigFile[BUFFER_SIZE] = LOCALRCFILE;
struct WordList ConfigFiles;

const char *ReverseOn;
const char *ReverseOff;


static int HasFile(char *Dir, const char *Filename, const char *App);

#if USE_RECURSE
static int SearchFile(char *Dir, const char *Filename, const char *App);
#endif /* USE_RECURSE */

/*  -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=-  */


/*
* Modify this one to suit your needs. In any case, it should fill
* the ConfigFile (sized BUFLEN) buffer above with full name & path
* for the configuration file. The macro RCFILE will give you the
* filename part of the file, if you need that.
*
* Note: This routine will be called several times. Your mission will
* be to look in each location, and see whether a .chktexrc file exist
* there.
*
* If you choose to do nothing, only the current directory will be
* searched.
*
*/


enum LookIn
{
   liMin,
   liSysDir,
   liUsrDir,
   liXdgDir,
   liEnvir,
   liCurDir,
   liNFound,
   liMax
};


int SetupVars(void)
{
   char *Env;
#ifdef __MSDOS__

   char *Ptr;
#endif

   static enum LookIn i = liMin;
   static int FoundFile;

   while (++i < liMax)
   {
       switch (i)
       {
       case liCurDir:         /* Current directory */
           strcpy(ConfigFile, LOCALRCFILE);
           break;
       case liEnvir:          /* Environment defined */
#ifdef __MSDOS__
           if ((Env = getenv("CHKTEXRC")) || (Env = getenv("CHKTEX_HOME")))
#elif defined(TEX_LIVE)
           if ((Env = kpse_var_value("CHKTEXRC")))
#else
           if ((Env = getenv("CHKTEXRC")))
#endif

           {
               strcpy(ConfigFile, Env);
               tackon(ConfigFile, LOCALRCFILE);
#ifdef TEX_LIVE
               free(Env);
#endif
           }
           else
#ifdef __MSDOS__
           if ((Env = getenv("EMTEXDIR")))
           {
               strcpy(ConfigFile, Env);
               tackon(ConfigFile, "data");
               tackon(ConfigFile, LOCALRCFILE);
           }
           else
#endif

               *ConfigFile = 0;
           break;

       case liXdgDir: /* Cross-desktop group dir for resource files */

           /* XDG is really unix specific, but it shouldn't hurt to
            * support it on Windows, should someone set the variables.  */
           if ((Env = getenv("XDG_CONFIG_HOME")) && *Env)
           {
               strcpy(ConfigFile, Env);
               tackon(ConfigFile, RCBASENAME);
           }
           else if ((Env = getenv("HOME")) && *Env)
           {
               strcpy(ConfigFile, Env);
               tackon(ConfigFile, ".config");
               tackon(ConfigFile, RCBASENAME);
           }
           else
               *ConfigFile = 0;

           break;

       case liUsrDir:         /* User dir for resource files */
#if defined(__unix__)

           if ((Env = getenv("HOME")) || (Env = getenv("LOGDIR")))
           {
               strcpy(ConfigFile, Env);
               tackon(ConfigFile, LOCALRCFILE);
           }
           else
#elif defined(__MSDOS__)

           strcpy(ConfigFile, PrgName);
           if ((Ptr = strrchr(ConfigFile, '\\')) ||
               (Ptr = strchr(ConfigFile, ':')))
               strcpy(++Ptr, RCBASENAME);
           else
#endif
               *ConfigFile = 0;

           break;
       case liSysDir:         /* System dir for resource files */
#ifdef TEX_LIVE
           if ((Env = kpse_var_value("CHKTEX_CONFIG")))
           {
               strcpy(ConfigFile, Env);
               free(Env);
           }
           else if ((Env = kpse_var_value("TEXMFMAIN")))
           {
               strcpy(ConfigFile, Env);
               tackon(ConfigFile, "chktex");
               tackon(ConfigFile, RCBASENAME);
               free(Env);
           }
           else
               *ConfigFile = 0;
#else /* TEX_LIVE */
#if defined(__unix__) || defined(__MSDOS__)
           strcpy(ConfigFile, SYSCONFDIR);
           tackon(ConfigFile, RCBASENAME);
#else
           *ConfigFile = 0;
#endif
#endif /* TEX_LIVE */

           break;
       case liNFound:
       case liMin:
       case liMax:
           *ConfigFile = 0;
           if (!FoundFile)
               PrintPrgErr(pmNoRsrc);
       }

       if (*ConfigFile && fexists(ConfigFile))
           break;
   }
   FoundFile |= *ConfigFile;

   return (*ConfigFile);
}


/*  -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=-  */

/*
* This function should initialize the global variables ReverseOn
* and ReverseOff to magic cookies, which when printed, makes the
* text in between stand out.
*/

void SetupTerm(void)
{
#ifdef USE_TERMCAP
   char *termtype = getenv("TERM");
   int success;
   char *buffer;
   static char str_so[3] = "so", str_se[3] = "se";

   if (termtype)
   {

       success = tgetent(term_buffer, termtype);
       if (success < 0)
           PrintPrgErr(pmNoTermData);
       if (success == 0)
           PrintPrgErr(pmNoTermDefd);

       buffer = (char *) malloc(sizeof(term_buffer));
       ReverseOn = tgetstr(str_so, &buffer);
       ReverseOff = tgetstr(str_se, &buffer);

       if (ReverseOn && ReverseOff)
           return;
   }
#endif

   ReverseOn = PRE_ERROR_STR;
   ReverseOff = POST_ERROR_STR;
}

/*  -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=-  */

/*
* Concatenates the `File' string to the `Dir' string, leaving the result
* in the `Dir' buffer. Takes care of inserting `directory' characters;
* if we've got the strings "/usr/foo" and "bar", we'll get
* "/usr/foo/bar".
*
* Behaviour somewhat controlled by the macros SLASH and DIRCHARS in the
* OpSys.h file.
*
*/

void tackon(char *Dir, const char *File)
{
   int EndC;
   unsigned long SLen;

   if (Dir && (SLen = strlen(Dir)))
   {
       EndC = Dir[SLen - 1];
       if (!(strchr(DIRCHARS, EndC)))
       {
           Dir[SLen++] = SLASH;
           Dir[SLen] = 0L;
       }
   }

   strcat(Dir, File);
}

/*
* This function should add the appendix App to the filename Name.
* If the resulting filename gets too long due to this, it may
* overwrite the old appendix.
*
* Name may be assumed to be a legal filename under your OS.
*
* The appendix should contain a leading dot.
*/

void AddAppendix(char *Name, const char *App)
{
#ifdef __MSDOS__
   char *p;

   if ((p = strrchr(Name, '.')))
       strcpy(p, App);
   else
       strcat(Name, App);
#else
   /*
    * NOTE! This may fail if your system has a claustrophobic file
    * name length limit.
    */
   strcat(Name, App);
#endif

}

/*  -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=-  */


/*
* Locates a file, given a wordlist containing paths. If a
* dir ends in a double SLASH, we'll search it recursively.
*
* We assume that
*   a) a deeper level in the dir. tree. has a longer path than
*      one above.
*   b) adding a level doesn't change any of the previous levels.
*
* If this function returns TRUE, Dest is guaranteed to contain
* path & name of the found file.
*
* FALSE indicates that the file was not found; Dest is then
* unspecified.
*/


int LocateFile(const char *Filename,    /* File to search for */
              char *Dest,      /* Where to put final file */
              const char *App, /* Extra optional appendix */
              struct WordList *wl)     /* List of paths, entries
                                        * ending in // will be recursed
                                        */
{
   unsigned long i;
#if USE_RECURSE

   unsigned long Len;
#endif

   FORWL(i, *wl)
   {
       strcpy(Dest, wl->Stack.Data[i]);

#if USE_RECURSE

       Len = strlen(Dest);

       if (Len && (Dest[Len - 1] == SLASH) && (Dest[Len - 2] == SLASH))
       {
           Dest[Len - 1] = Dest[Len - 2] = 0;
           if (SearchFile(Dest, Filename, App))
               return (TRUE);
       }
       else
#endif /* USE_RECURSE */

       {
           if (HasFile(Dest, Filename, App))
               return (TRUE);
       }
   }
   return (FALSE);
}

static int HasFile(char *Dir, const char *Filename, const char *App)
{
   int DirLen = strlen(Dir);

   tackon(Dir, Filename);
   if (fexists(Dir))
       return (TRUE);

   if (App)
   {
       AddAppendix(Dir, App);
       if (fexists(Dir))
           return (TRUE);
   }

   Dir[DirLen] = 0;
   return (FALSE);

}


/*
* If Filename is contains a directory component, then add a fully qualified
* directory to the TeXInputs WordList.
*
* I'm not sure how it will work with some Windows paths,
* e.g. C:path\to\file.tex since it doesn't really understand the leading C:
* But I'm not sure it would even work with a path like that, and I have no
* way to test it.
*
* Behaviour somewhat controlled by the macros SLASH and DIRCHARS in the
* OpSys.h file.
*
*/

void AddDirectoryFromRelativeFile(const char * Filename, struct WordList *TeXInputs)
{
   if ( ! Filename )
       return;

   /* There are no path delimiters, so it's just a file, return null */
   if ( ! strstr( Filename, DIRCHARS ) )
       return;


   char buf[BUFFER_SIZE];
   if ( strchr(DIRCHARS,Filename[0]) ) {
       strcpy(buf,Filename);
   } else {
       getcwd(buf, BUFFER_SIZE);
       tackon(buf,Filename);
   }

   /* Keep up to the final SLASH -- that will be the directory. */
   char * end = strrchr(buf,SLASH);
   *end = '\0';
   InsertWord(buf,TeXInputs);
}


#if USE_RECURSE
static int SearchFile(char *Dir, const char *Filename, const char *App)
{
   struct stat *statbuf;
   struct dirent *de;
   DIR *dh;

   int DirLen = strlen(Dir);
   int Found = FALSE;

   DEBUG(("Searching %s for %s\n", Dir, Filename));

   if (HasFile(Dir, Filename, App))
       return (TRUE);
   else
   {
       if ((statbuf = malloc(sizeof(struct stat))))
       {
           if ((dh = opendir(Dir)))
           {
               while (!Found && (de = readdir(dh)))
               {
                   Dir[DirLen] = 0;
                   if (strcmp(de->d_name, ".") && strcmp(de->d_name, ".."))
                   {
                       tackon(Dir, de->d_name);

                       if (!stat(Dir, statbuf))
                       {
                           if ((statbuf->st_mode & S_IFMT) == S_IFDIR)
                               Found = SearchFile(Dir, Filename, App);
                       }
                   }
               }
               closedir(dh);
           }
           else
               PrintPrgErr(pmNoOpenDir, Dir);
           free(statbuf);
       }
   }
   return (Found);
}
#endif /* USE_RECURSE */

#if defined(HAVE_STAT)
int IsRegFile(const char *Filename)
{
   int Retval = FALSE;
   struct stat *statbuf;
   if ((statbuf = malloc(sizeof(struct stat))))
   {
       if (!stat(Filename, statbuf))
       {
           if ((statbuf->st_mode & S_IFMT) == S_IFREG)
               Retval = TRUE;
       }
       free(statbuf);
   }

   return Retval;
}
#else
int IsRegFile(const char *Filename) { printf("WTF\n");return TRUE; }
#endif