/* Copyright (C) 1996,1997,1998,1999 by Salvador E. Tropea (SET),
   see copyrigh file for details */
#include "ceditint.h"
   
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
#include <limits.h>
#include <fnmatch.h>
#include <stdio.h>
#ifdef __DJGPP__
#include <io.h>    // _chmod
#include <fcntl.h> // __use_lfn
#include <dpmi.h>  // GetShortNameOf: dpmi_regs, dpmi_int
#include <go32.h>  // GetShortNameOf: transfer buffer
#endif
#include <rhutils.h>
#define Uses_TScreen
#define Uses_TStringCollectionW
#include <settvuti.h>

extern const char *GetVariable(const char *variable);
static char PathOrig[PATH_MAX];
static char FileNameToLoad[PATH_MAX];
static char *PathOrigPos;

int edTestForFile(const char *name)
{
 struct stat st;

 if (stat(name,&st)==0)
    return S_ISREG(st.st_mode);
 return 0;
}

#ifndef __DJGPP__
char *GetHome(void)
{
 char *s=getenv("HOME");
 if (!s)
   {
    s=getenv("HOMEDIR");
    if (!s)
       s=".";
   }
 return s;
}

char *ExpandFileNameToUserHome(const char *s)
{
 strcpy(FileNameToLoad,GetHome());
 strcat(FileNameToLoad,"/.");
 strcat(FileNameToLoad,s);
 return FileNameToLoad;
}

char *ExpandFileNameToUserHomeNoDot(const char *s)
{
 strcpy(FileNameToLoad,GetHome());
 strcat(FileNameToLoad,"/");
 strcat(FileNameToLoad,s);
 return FileNameToLoad;
}

#else
// The DOS version is used only by edspecs.cc
char *ExpandFileNameToUserHome(const char *s)
{
 char *p=getenv("SET_FILES");
 if (p)
    strcpy(FileNameToLoad,p);
 else
    FileNameToLoad[0]=0;
 strcat(FileNameToLoad,"/");
 strcat(FileNameToLoad,s);
 return FileNameToLoad;
}
#endif

#ifdef STANDALONE
/**[txh]********************************************************************

  Description:
  This routine searchs the desired file in various directories and returns
the full qualified path for the location or the default location if the
file doesn't exist.@*
  Linux behavior: (a) The user's home (b) The SET_FILES directory.@*
  DOS behavior: (a) The directory where the editor was loaded (b) The
SET_FILES directory.@*

  Return:
  The full path for the file found or the place where the file should be.

***************************************************************************/

char *ExpandFileNameToThePointWhereTheProgramWasLoaded(const char *s)
{
 const char *v;

 #ifndef __DJGPP__
 ExpandFileNameToUserHomeNoDot(s);
 if (edTestForFile(FileNameToLoad))
    return FileNameToLoad;
 ExpandFileNameToUserHome(s);
 if (edTestForFile(FileNameToLoad))
    return FileNameToLoad;
 #endif

 #ifdef __DJGPP__
 // The point where the editor was loaded doesn't have any sense in UNIX
 if (PathOrigPos!=NULL)
   {
    *PathOrigPos=0;
    strcat(PathOrig,s);
   }
 else
    strcpy(PathOrig,s);
 if (edTestForFile(PathOrig))
    return PathOrig;
 #endif

 if ((v=GetVariable("SET_FILES"))!=NULL)
   {
    strcpy(FileNameToLoad,v);
    strcat(FileNameToLoad,"/");
    strcat(FileNameToLoad,s);
    return FileNameToLoad;
   }

 return PathOrig;
}

int FindFile(const char * name,char * & fullName)
{
 char *base;

 if (strlen(name)>=PATH_MAX)
    return 0;
 fullName=new char[PATH_MAX];
 if (!fullName)
    return 0;
 strcpy(fullName,name);
 if (!edTestForFile(fullName))
   {
    base=getenv("DJDIR");
    if (!base)
      {
       delete fullName;
       return 0;
      }
    strcpy(fullName,base);
    strcat(fullName,"/include/");
    strcat(fullName,name);
    if (!edTestForFile(fullName))
      {
       delete fullName;
       return 0;
      }
   }
 fexpand(fullName);
 return 1;
}
#endif // STANDALONE

/**[txh]********************************************************************

  Description:
  Replaces old extension of name by the provided extension.

  Return:
  name

***************************************************************************/

char *ReplaceExtension(char *name, char *ext, char *old)
{
 char *pos;
 pos=strrchr(name,'/');
 if (!pos)
    pos=name;
 pos=strstr(pos,old);
 if (pos)
    strcpy(pos,ext);
 else
    strcat(name,ext);
 return name;
}

/**[txh]********************************************************************

  Description:
  Replaces the extension of name by the provided extension. If none is found
the extension is added and if the file name starts with dot the extension
is added too.

  Return:
  name.

***************************************************************************/

char *ReplaceExtension(char *name, char *ext)
{
 char *dot,*slash;
 int flen=strlen(name);

 dot=strrchr(name,'.');
 slash=strrchr(name,'/');
 if (dot<slash) // dot in directories, not in file
    dot=0;
 if (!dot || dot==slash+1) // .files should be .files.bkp and not .bkp ;-)
    dot=name+flen;
 strcpy(dot,ext);

 return name;
}

/**[txh]********************************************************************

  Description:
  That's used to create back-up file names with xxxx~ stile.

  Return:
  fname.

***************************************************************************/

char *AddToNameOfFile(char *fname, char *add)
{
 #ifdef __DJGPP__
 //if (_use_lfn(fname))
 //   return strcat(fname,add);
 // Not so easy boy, 8+3 limitations:
 char *dir,*name,*ext;
 int lext;
 split_fname(fname,dir,name,ext);
 lext=strlen(ext);
 if (lext==0)
    string_cat(ext,".",add,0);
 else
 if (lext<4)
    string_cat(ext,add);
 else
   {
    for (lext=3; lext>1 && ext[lext]==*add; lext--);
    ext[lext]=*add;
   }
 strcpy(fname,dir);
 strcat(fname,name);
 strcat(fname,ext);
 string_free(dir);
 string_free(name);
 string_free(ext);
 return fname;
 #else
 // In UNIX that's much more easy
 return strcat(fname,add);
 #endif
}

int DeleteWildcard(char *mask)
{
 int deleted=0;
 DIR *d;
 d=opendir(".");
 
 if (d)
   {
    struct dirent *de;
    while ((de=readdir(d))!=0)
      {
       if (!fnmatch(mask,de->d_name,0))
         {
          unlink(de->d_name);
          deleted++;
         }
      }
    closedir(d);
   }
 return deleted;
}


char *GetPathRelativeToRunPoint(char *dest, const char *binReplace, char *file)
{
 char *ret;

 if (PathOrigPos!=NULL)
   {
    *PathOrigPos=0;
    strcpy(dest,PathOrig);
    char *s=strstr(dest,"bin");
    if (!s)
       s=strstr(dest,"BIN");
    if (s)
       strcpy(s,binReplace);
    ret=dest+strlen(dest)-1;
   }
 else
   {
    *dest=0;
    ret=dest;
   }

 strcat(dest,file);

 return ret;
}


void SetReferencePath(char *orig)
{
 PathOrigPos=strrchr(orig,'/');
 if (PathOrigPos)
   {
    PathOrigPos++;
    *PathOrigPos=0;
    strcpy(PathOrig,orig);
    PathOrigPos=PathOrig+(int)(PathOrigPos-orig);
   }
 else
    PathOrig[0]=0;
}

char *RedirectStdErrToATemp(int &StdErrOri,int &StdErrNew)
{
 char aux[PATH_MAX];
 #ifdef __DJGPP__
 char *s=(char *)GetVariable("SET_FILES");
 #else
 // In UNIX the user can't write to the share directory so we must avoid creating
 // such a files
 char *s=GetHome();
 #endif
 char *ret=0;

 sprintf(aux,"%s/erXXXXXX",s);
 StdErrNew=mkstemp(aux);
 if (StdErrNew>0)
   {
    ret=strdup(aux);
    StdErrOri=dup(fileno(stderr));
    fflush(stderr);
    dup2(StdErrNew,fileno(stderr));
   }
 return ret;
}

const int eachRead=16300;

int FileCopy(const char *orig, const char *dest)
{
 // First I alloc a 16Kb buffer
 char *buf=new char[eachRead];
 if (!buf)
    return 0;

 // Create destination
 FILE *d=fopen(dest,"wb");
 if (!d)
   {
    delete buf;
    return 0;
   }

 // Open source
 FILE *o=fopen(orig,"rb");
 if (!o)
   {
    fclose(d);
    delete buf;
    return 0;
   }

 int read;
 do
   {
    read=fread(buf,1,eachRead,o);
    fwrite(buf,read,1,d);
   }
 while (read==eachRead);

 int ret=!(ferror(o) || ferror(d));
 fclose(o);
 fclose(d);
 delete buf;

 return ret;
}

/**[txh]********************************************************************

  Description:
  That's a direct replacement for access(name,D_OK) which Linux lacks.

  Return:
  0 not a directory or no access allowed.

***************************************************************************/

int IsADirectory(const char *name)
{
 #ifdef __linux__
 struct stat s;
 return stat(name,&s)==0 && S_ISDIR(s.st_mode) && !access(name,X_OK);
 #else
 return !access(name,D_OK);
 #endif
}

/**[txh]********************************************************************

  Description:
  Checks if a file is a symbolic link.

  Return:
  0 not a link.

***************************************************************************/

int IsASoftLink(const char *name)
{
 #ifdef S_ISLNK
 struct stat s;
 return lstat(name,&s)==0 && S_ISLNK(s.st_mode);
 #else
 return 0;
 #endif
}

/**[txh]********************************************************************

  Description:
  TMPDIR variable could be not defined or pointing to an invalid drive. As
the librhutils assumes TMPDIR is working I must ensure it or the
stderr/stdout could misserably fail.

***************************************************************************/

void CheckForValidTMPDIR()
{
 char *tmp=getenv("TMPDIR");

 // If the variable is defined && is a directory all is ok
 if (!(tmp && IsADirectory(tmp)))
   {
    // Ok, this system lacks it or worst it point to nowhere
    tmp=getenv("TEMP");
    if (!tmp || !IsADirectory(tmp))
      { // No luck with TEMP
       tmp=getenv("TMP");
       if (!tmp || !IsADirectory(tmp))
         { // No luck with TMP
          tmp="/tmp"; // It should work in UNIX
          if (!IsADirectory(tmp))
            {
             tmp="c:/"; // It should work in DOS
             if (!IsADirectory(tmp))
               { // Hey! nothing is good!
                tmp="."; // Well, give up and try in current directory
               }
            }
         }
      }
   }

 char b[PATH_MAX];
 sprintf(b,"TMPDIR=%s",tmp);
 #ifdef __DJGPP__
 // Mixing forward and backslashes could produce problems (path\/file is invalid).
 char *s=b;
 for (; *s; s++)
     if (*s=='\\')
        *s='/';
 #endif
 putenv(b);
 //fprintf(stderr,"Using: %s\n",tmp);
}


/**[txh]********************************************************************

  Description:
  Changes the status of the file to hidden. IMPORTANT! if the OS needs to
change the name of the file to make it hidden (UNIX case) the name provided
will be altered so it should have enough space for it.

  Return:
  0 succes, 1 fail

***************************************************************************/

int MakeFileHidden(char *file)
{
 if (!file) return 0;
 int ret=0;

 #ifdef __DJGPP__
 // Get current status
 int mode=_chmod(file,0,0);
 if (mode==-1)
    return 1;
 // Already hidden?
 if (mode & 2)
    return 0;
 // Set the attribute
 _chmod(file,1,mode | 2);
 #else
 // Look for the real name:
 char *name=strrchr(file,'/');
 if (name)
    name++;
 else
    name=file;
 // Already hidden?
 if (*name=='.')
    return 0;
 // Ugh! here UNIX is pretty bad:
 int l=strlen(file)+2;
 char *s=new char[l];
 strcpy(s,file);
 l=name-file;
 strcpy(s+l+1,name);
 s[l]='.';
 ret=rename(file,s);
 strcpy(file,s);
 delete s;
 #endif
 return ret;
}

static TStringCollectionW *filesToKill=0;

void AddToFilesToKill(char *name)
{
 if (!filesToKill)
    filesToKill=new TStringCollectionW(6,4);
    
 ccIndex i;
 if (filesToKill->search(filesToKill->keyOf(name),i)==0)
    // A simple insert here will leak memory because the dupped name won't
    // be released.
    filesToKill->atInsert(i,strdup(name));
}

static
void killIt(void *name, void *)
{
 unlink((char *)name);
}

void KillFilesToKill()
{
 if (!filesToKill)
    return;
 filesToKill->forEach(killIt,0);
}

void ReleaseFilesToKill()
{
 destroy0(filesToKill);
}

TStringCollectionW *GetFilesToKill()
{
 return filesToKill;
}

void SetFilesToKill(TStringCollectionW *files)
{
 ReleaseFilesToKill();
 filesToKill=files;
}

/**[txh]********************************************************************

  Description:
  Determines if 2 file names belongs to the same file. The function uses the
inode/device to find it.

  Return:
  !=0 if the files are the same.

***************************************************************************/

int CompareFileNames(char *origFile, char *destFile)
{
 struct stat st1,st2;

 if (stat(origFile,&st1) || stat(destFile,&st2))
    return 0;

 return st1.st_dev==st2.st_dev && st1.st_ino==st2.st_ino;
}

/**[txh]********************************************************************

  Description:
  If the current directory is invalid aborts the execution.

***************************************************************************/

void CheckIfCurDirValid(void)
{
 long *aux=(long *)PathOrig;
 *aux=0xFFFEFDFC;
 getcwd(PathOrig,PATH_MAX);
 if (*aux==(long)0xFFFEFDFC)
   {
    TScreen::suspend();
    fprintf(stderr,_("\nError! please run the editor from a valid directory\n\n"));
    fflush(stderr);
    exit(1);
   }
}

const int maxSFNSize=68;

#ifdef __DJGPP__
/**[txh]********************************************************************

  Description:
  Returns the short file name of the passed file. The shortName pointer must
point to a buffer with at least maxSFNSize bytes.

  Return:
  A pointer to the converted name if succesful or the original name if it
fails.

***************************************************************************/

char *GetShortNameOf(char *longName, char *shortName)
{
 __dpmi_regs r;
 unsigned long tbuf=__tb;

 r.x.ax=0x7100;
 if (_USE_LFN)
   {
     dosmemput (longName,strlen(longName)+1,tbuf+maxSFNSize);
     r.x.ax=0x7160;
     r.x.es=r.x.ds=tbuf >> 4;
     r.x.di=0;
     r.x.si=maxSFNSize;
     r.x.cx=0x8001;
     __dpmi_int(0x21, &r);
   }
 if ((r.x.flags & 1)==0 && r.x.ax!=0x7100)
   {
    dosmemget(tbuf,maxSFNSize,shortName);
    return shortName;
   }
 return longName;
}
#else
char *GetShortNameOf(char *longName, char *)
{
 return longName;
}
#endif
