/* Copyright (C) 1996,1997,1998,1999 by Salvador E. Tropea (SET),
   see copyrigh file for details */
#include <ceditint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define Uses_TDialog
#define Uses_TDeskTop
#define Uses_TProgram
#define Uses_TApplication
#define Uses_TObject
#define Uses_TInputLine
#define Uses_TLabel
#define Uses_THistory
#define Uses_TRect
#define Uses_TCheckBoxes
#define Uses_TRadioButtons
#define Uses_TButton
#define Uses_TButton
#define Uses_MsgBox
#define Uses_TSItem
#define Uses_TCEditor
#define Uses_TInputLinePiped
#define Uses_TInputLinePipedConst
#define Uses_TListBox
#define Uses_TSOSListBox
#define Uses_fpstream
#include <ceditor.h>
#define Uses_SETAppHelper
#define Uses_SETAppConst
#include <setapp.h>

// Prototypes for the message win.
#include <edmsg.h>
#include <splinman.h>
#include <rhutils.h>
#include <edhists.h>
#include <ftw.h>
#include <sys/stat.h>
#include <dirent.h>
#include <dyncat.h>
#include <ctype.h>
#include <pathtool.h>

// From edprj.cc generates the list of project items
extern int WriteNamesOfProjectTo(FILE *f);
// From editmain.cc generates the list of opened editors
extern int WriteNamesOfOpenedTo(FILE *f);
extern ushort execDialog( TDialog *d, void *data );

//const int tilpNoPipe=1,tilpNoCopy=2,tilpNoPaste=4;

// This collection is a list of the visited directories to avoid loops when
// a link points back in the tree.
static TStringCollection *Visited=0;
// Static to avoid consuming 1Kb by recursion in the stack
static char TempDirName[PATH_MAX];

void FullInputString(int x, int y, int w, int size,int histID, char *name,
                     unsigned flags, TDialog *d)
{
 TInputLine *inp=new TInputLinePiped(TRect(x+1,y+1,x+w,y+2),size,flags);
 d->insert(inp);
 d->insert(new TLabel(TRect(x,y,x+w,y+1),name,inp));
 d->insert(new THistory(TRect(x+w,y+1,x+w+3,y+2),inp,histID));
}

const int maxDirLen=80;

typedef struct
{
 char pattern[200];
 char files[80];
 char dirs[maxDirLen];
 ushort sourP;
 ushort typeP;
 ushort where;
 ushort recurse;
 ushort ops;
} GrepBox;
// empty,empty,Pattern box,Basic reg.,search in project,ignore case
GrepBox box={"","*.[ch]*",".",0,0,2,0,1};

const int Col1=1,Col2=40,End1=39,End2=72;

static
char *SaveClipToTemp(void)
{
 char *name=new char[L_tmpnam];
 if (!name || !TCEditor::clipboard || !TCEditor::clipboard->hasSelection())
   {
    messageBox(_("The clipboard is empty"),mfOKButton);
    return 0;
   }
 tmpnam(name);
 FILE *f=fopen(name,"wt");
 fwrite(TCEditor::clipboard->buffer+TCEditor::clipboard->selStart,
        TCEditor::clipboard->selEnd-TCEditor::clipboard->selStart,1,f);
 fclose(f);
 return name;
}

static char ActualPath[PATH_MAX];

static
char *ParseFun(char *buf, FileInfo &fI, char *&fileName)
{
 char *endOfName,*endOfLine=0;
 int offset=0;

 // Look for file name and line number
 // It fails if: The file is absolute and starts with a number
 if (ucisalpha(buf[0]) && buf[1]==':' && (!ucisdigit(buf[2])))
    offset=2;
 endOfName=strchr(buf+offset,':');
 if (endOfName)
    endOfLine=strchr(endOfName+1,':');
 if (!endOfName || !endOfLine || !ucisdigit(endOfName[1]))
    return 0;

 char *ret;
 if (offset)
   {
    ret=strdup(buf);
    *endOfName=0;
    fileName=strdup(buf);
   }
 else
   {
    DynStrCatStruct msgLine;
    DynStrCatInit(&msgLine,ActualPath);
    DynStrCat(&msgLine,buf);
    ret=msgLine.str;
   
    DynStrCatStruct File;
    DynStrCatInit(&File,ActualPath);
    DynStrCat(&File,buf,endOfName-buf);
    fileName=File.str;
   }

 *endOfLine=0;
 fI.Line=atoi(endOfName+1);
 fI.Column=1;

 return ret;
}

#ifdef __DJGPP__
/*
  This routine checks if grep is there. If we can't find it we must put a warning
*/
int CheckForGREP(void)
{
 static int isGREPInstalled=0;

 if (!isGREPInstalled)
   {
    // We must rediret the error to avoid getting it in the stderr file
    char *err=open_stderr();
    int ret=system("grep");
    close_stderr();
    unlink(err);

    if (ret>0)
       isGREPInstalled=1;
    else
       messageBox(_("You must install grep to use it!"), mfError | mfOKButton);
   }

 return isGREPInstalled;
}
#endif

static
void RunGrep(char *command)
{
 char *out;
 char *err;
 #ifdef __DJGPP__
 if (!CheckForGREP())
    return;
 #endif
 char b[PATH_MAX+60];
 out=open_stdout();
 err=open_stderr();
 getcwd(ActualPath,PATH_MAX);
 sprintf(b,_("Running grep in: %s"),ActualPath);
 if (ActualPath[strlen(ActualPath)-1]!='/')
    strcat(ActualPath,"/");
 EdShowMessage(b);
 #ifdef __DJGPP__
 // Force the command.com because bash fails
 char *envShell=getenv("SHELL");
 char *envShellCopy=0;
 if (envShell)
   {
    envShellCopy=(char *)alloca(strlen(envShell)+1+6);
    sprintf(envShellCopy,"SHELL=%s",envShell);
    putenv("SHELL=");
   }
 // Call it
 system(command);
 // Now restore the enviroment
 if (envShell)
    putenv(envShellCopy);
 #else
 // Call it
 system(command);
 #endif
 close_stdout();
 close_stderr();
 DumpFileToMessage(err,"From stderr:");
 DumpFileToMessage(out,"From stdout:",ParseFun);
}

// That's similar to ftw, I didn't used ftw because isn't in the libc.info of my
// Linux so I don't know if that's so common.
static
void look_in(char *command)
{
 DIR *d;
 struct dirent *f;
 char *name,*pwdHere;
 ccIndex pos;

 // Keep record of our current location
 getcwd(TempDirName,PATH_MAX);
 pwdHere=strdup(TempDirName);
 if (Visited->search(pwdHere,pos))
   { // Hey we already scanned it!
    delete pwdHere;
    return;
   }
 Visited->atInsert(pos,pwdHere);
 RunGrep(command);
 
 d=opendir(".");
 if (d)
   {
    while ((f=readdir(d))!=0)
      {
       name=f->d_name;
       // Skip . and .. they aren't useful
       if (name[0]=='.')
         {
          if (name[1]==0 || (name[1]=='.' && name[2]==0))
             continue;
         }
       if (IsADirectory(name)) // It also checks valid access
         { // Recurse
          chdir(name);
          look_in(command);
          // We must go back to the previous location, .. isn't useful
          // when we taked a link
          chdir(pwdHere);
         }
      }
    closedir(d);
   }
}

static
void RunRecurseGrep(char *command, int recurse)
{
 char dirTemp[maxDirLen];
 // A copy to use strtok
 strcpy(dirTemp,box.dirs);

 char StartPoint[PATH_MAX];
 getcwd(StartPoint,PATH_MAX);

 destroy(Visited); // Just in case
 Visited=new TStringCollection(10,5);

 char *s=strtok(dirTemp,";, ");
 while (s)
   {
    if (chdir(s))
      {
       char b[PATH_MAX+60];
       sprintf(b,_("chdir error: %s"),s);
       EdShowMessage(b);
      }
    else
      {
       if (recurse)
          look_in(command);
       else
          RunGrep(command);
      }

    s=strtok(NULL,";, ");
    chdir(StartPoint);
   }

 destroy(Visited);
 Visited=0;
}

static
void ArrangeGrepCommand(char *command, char *param)
{
 #ifdef __DJGPP__
 #define InitParametersFile()
 #define SetAccessToParameters(a)
 // In DOS I use a response file to pass a very long list of parameters
 sprintf(command,"grep @%s",param);
 #else
 #define InitParametersFile() fputs("grep ",f)
 #define SetAccessToParameters(a) chmod(a,S_IXUSR | S_IRUSR)
 // Response files aren't supported in UNIX :-(, so I just use a file that
 // in fact is a script
 strcpy(command,param);
 #endif
}

void grepWindow(char *patStart)
{
 TDialog *d=new TDialog(TRect(0,0,74,18),_("Powered Grep by SET"));
 d->options|=ofCentered;
 d->helpCtx=cmeGrepDialog;

 // ACDEFGIJKLNORPSTUWX
 // Pattern section
 FullInputString(Col1,1,End1-Col1-3,200,hID_TextSearchEditor,_("~P~attern box"),0,d);
 FullInputString(Col2,1,End2-Col2-3,80,hID_GrepFiles,_("Files to ~s~earch"),tilpNoPipe,d);
 FullInputString(Col1,3,End2-Col1-3,maxDirLen,hID_GrepPlaces,_("~D~irectories to search"),tilpNoPipe,d);

 TRadioButtons *sourP=new TRadioButtons(TRect(Col1+1,7,End1,10),
     new TSItem(_("`Pattern box' is the pa~t~tern"),
     new TSItem(_("`Pattern box' is a fi~l~e name"),
     new TSItem(_("Use the ~c~lipboard selection"),0 ))));
 d->insert(sourP);
 d->insert(new TLabel(TRect(Col1,6,End1,7),_("Source of Pattern"),sourP));

 TRadioButtons *typeP=new TRadioButtons(TRect(Col1+1,11,End1,14),
     new TSItem(_("Basic regular expression (-~G~)"),
     new TSItem(_("~E~xtended regular expression (-E)"),
     new TSItem(_("~F~ixed separated by CR (-F)"),0 ))));
 d->insert(typeP);
 d->insert(new TLabel(TRect(Col1,10,End1,11),_("Type of Pattern"),typeP));

 TRadioButtons *where=new TRadioButtons(TRect(Col2+1,7,End2,10),
     new TSItem(_("~U~se `Files to search'"),
     new TSItem(_("Search in ope~n~ed windows"),
     new TSItem(_("Search in pro~j~ect"),0 ))));
 d->insert(where);
 d->insert(new TLabel(TRect(Col2,6,End2,7),_("Pl~a~ces to search"),where));
 d->insert(new TCheckBoxes(TRect(Col2+1,10,End2,11),
     new TSItem(_("~R~ecurse in subdirs"),0 )));

 TCheckBoxes *ops=new TCheckBoxes( TRect(Col2+1,12,End2,16),
     new TSItem(_("~I~gnore case (-i)"),
     new TSItem(_("~W~hole words only (-w)"),
     new TSItem(_("Whole line (-~x~)"),
     new TSItem(_("In~v~erse matching (-v)"), 0 )))));
 d->insert(ops);
 d->insert(new TLabel(TRect(Col2,11,End2,12),_("~O~ptions"),ops));

 d->insert(new TButton(TRect(2,15,14,17),_("O~K~"),cmOK,bfDefault));
 d->insert(new TButton(TRect(18,15,30,17),_("Cancel"),cmCancel,bfNormal));

 d->selectNext(False);

 if (patStart)
   {
    strcpy(box.pattern,patStart);
    delete patStart;
   }

 if (execDialog(d,&box)!=cmCancel)
   {
    if (box.dirs[0]==0)
      {
       messageBox(_("You must provide at least one directory to search"),mfOKButton);
       return;
      }
    char b[12];
    char param[L_tmpnam];
    char command[L_tmpnam*2+80];
    char *clipTemp=0;
    int ok=1,absolute=0;

    tmpnam(param);
    FILE *f=fopen(param,"wb");
    if (!f)
       return;
    InitParametersFile();
    int i=1;
    b[0]='-';
    // Always numbers
    b[i++]='n';
    // Options from the dialog
    if (box.ops & 1)
       b[i++]='i';
    if (box.ops & 2)
       b[i++]='w';
    if (box.ops & 4)
       b[i++]='x';
    if (box.ops & 8)
       b[i++]='v';
    // Type of grep
    switch (box.typeP)
      {
       case 1:
            b[i++]='E';
            break;
       case 2:
            b[i++]='F';
            break;
       default:
            b[i++]='G';
            break;
      }
    b[i++]=' ';
    b[i]=0;
    fputs(b,f);
    // Pattern
    switch (box.sourP)
      {
       case 1:
            fputs("-f ",f);
            fputs(box.pattern,f);
            break;
       case 2:
            if ((clipTemp=SaveClipToTemp())==0)
               ok=0;
            else
              {
               fputs("-f ",f);
               fputs(clipTemp,f);
              }
            break;
       default:
            fputs("-e ",f);
            if (strchr(box.pattern,'\"')!=0)
              { // If have " let as-is
               fputs(box.pattern,f);
              }
            else
              { // Try to preserve the spaces
               fputc('\"',f);
               fputs(box.pattern,f);
               fputc('\"',f);
              }
      }
    fputc(' ',f);
    // Files
    switch (box.where)
      {
       case 1:
            if (!WriteNamesOfOpenedTo(f))
              {
               messageBox(_("There aren't opened files"),mfOKButton);
               ok=0;
              }
            absolute=1;
            break;
       case 2:
            if (!WriteNamesOfProjectTo(f))
              {
               messageBox(_("No files in project"),mfOKButton);
               ok=0;
              }
            absolute=1;
            break;
       default:
            fputs(box.files,f);
      }
    fputs(" /dev/null",f);
    fclose(f);
    SetAccessToParameters(param);

    if (ok)
      {
       SaveAllEditors();
       EdShowMessage(_("Powered grep"),True);
       if (absolute)
         {
          if (box.recurse)
            {
             sprintf(command,_("Recurse & dirs. ignored, using internal names"));
             EdShowMessage(command);
            }
          ArrangeGrepCommand(command,param);
          RunGrep(command);
         }
       else
         {
          ArrangeGrepCommand(command,param);
          RunRecurseGrep(command,box.recurse);
         }
       EdShowMessage(_("End of grep search"));
       EdJumpToMessage(0);
       SpLinesUpdate();
      }

    if (box.sourP==2)
      {
       unlink(clipTemp);
       delete clipTemp;
      }
    unlink(param);
   }
}

void SaveGrepData(fpstream &s)
{
 s << (char)1; // version
 s.writeBytes(&box,sizeof(GrepBox));
}

void LoadGrepData(fpstream &s)
{
 char version;
 s >> version;
 s.readBytes(&box,sizeof(GrepBox));
}
