/* Copyright (C) 1996,1997 Robert H”hne, see COPYING.RH for details */
/* This file is part of RHIDE. */
#define Uses_TDependency
#define Uses_TProject
#define Uses_TDepCollection
#define Uses_TDependency
#define Uses_TProjectWindow
#define Uses_TOptions
#define Uses_TDirList
#define Uses_TParamList
#define Uses_TMsgCollection
#define Uses_TProjectListBox
#define Uses_TProgram
#define Uses_TDeskTop
#define Uses_TScreen
#define Uses_TEvent
#define Uses_IDEConst
#define Uses_TNSSortedCollection
#define Uses_TIDEEditWindow
#define Uses_TStaticText
#define Uses_TIDEFileEditor
#define Uses_TFileViewer
#define Uses_LangTexte
#define Uses_TWindowList
#define Uses_MsgBox

#define Uses_ideFunctions
#define Uses_ideEnums
#define Uses_ideCommands
#define Uses_TIDEEditWindow
#define Uses_TFileName
#include <libide.h>

#define Uses_TSCollection
#define Uses_TCheckDialog
#define Uses_tvutilFunctions
#include <libtvuti.h>

#include <rhide.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <setjmp.h>
#include <ctype.h>
#ifdef __DJGPP__
#include <sys/exceptn.h>
#endif

#ifdef INTERNAL_DEBUGGER
#include <librhgdb.h>
#endif

int project_index(const char *name)
{
  ccIndex index;
  char *tmp;
  string_dup(tmp,name);
  BaseName(tmp);
  Boolean found = Project.dependencies->search(tmp,index);
  string_free(tmp);
  if (found == True) return index;
  return -1;
}

static TCheckDialog *CheckDialog = NULL;
static TCheckDialog *ExecDialog = NULL;

int RunProgram(const char *cmd,
                   Boolean redir_stderr,Boolean redir_stdout,
		   Boolean SwitchToUser)
{
  int retval;
  if (ExecDialog)
  {
    ExecDialog->update(cmd);
  }
  if (debug_commands)
  {
    fprintf(stderr,_("executing: "));
    fprintf(stderr,"%s",cmd);
    fprintf(stderr,"\n");
  }
  if (redir_stdout == True) cpp_outname = open_stdout();
  if (redir_stderr == True) cpp_errname = open_stderr();
  SaveAll();
  if (SwitchToUser == True)
  {
    TProgram::deskTop->setState(sfVisible,False);
    TProgram::application->suspend();
  }
#ifdef __DJGPP__
  __djgpp_exception_toggle();
#endif
  retval = system(cmd);
  external_program_executed = 1;
#ifdef __DJGPP__
  __djgpp_exception_toggle();
#endif
  if (redir_stdout == True) close_stdout();
  if (redir_stderr == True) close_stderr();
  if (SwitchToUser == True)
  {
    TProgram::application->resume();
    TProgram::deskTop->setState(sfVisible,True);
    SetProjectScreenMode();
    Repaint();
  }
  if (debug_commands)
  {
    fprintf(stderr,_("return value: %d\n"),retval);
  }
  return retval;
}

static Boolean up_to_date;

class FileTimeCollection : public TNSSortedCollection
{
public:
  FileTimeCollection(ccIndex alimit,ccIndex adelta) :
    TNSSortedCollection(alimit,adelta) {}
private:
  virtual int compare(void *,void *);
  virtual void freeItem(void *);
};

#if 0

struct FileTime {
  FileTime(const char *aname,long atime);
  ~FileTime();
  char *key;
  char *name;
  long time;
};

FileTime::FileTime(const char *aname,long atime)
{
  string_dup(name,aname);
  time = atime;
  BaseName(name,key);
}

FileTime::~FileTime()
{
  string_free(name);
  string_free(key);
}

int FileTimeCollection::compare(void *key1,void *key2)
{
  int retval = strcmp(((FileTime *)key1)->key,((FileTime *)key2)->key);
  if (retval) return retval;
  return strcmp(((FileTime *)key1)->name,((FileTime *)key2)->name);
}

#else

struct FileTime {
  FileTime(const char *aname,long atime);
  ~FileTime();
  char *key;
  TFileName *name;
  long time;
};

FileTime::FileTime(const char *aname,long atime)
{
  name = new TFileName((char *)aname);
  time = atime;
  BaseName(name->Name(),key);
}

FileTime::~FileTime()
{
  delete name;
  string_free(key);
}

int FileTimeCollection::compare(void *key1,void *key2)
{
  int retval = strcmp(((FileTime *)key1)->key,((FileTime *)key2)->key);
  if (retval) return retval;
  return strcmp(((FileTime *)key1)->name->Name(),((FileTime *)key2)->name->Name());
}

#endif

void FileTimeCollection::freeItem(void *item)
{
  delete((FileTime *)item);
}

static FileTimeCollection *file_times = NULL;

long TimeOfFile(const char *name,Boolean update,Boolean is_full_name)
{
  FileTime *ft;
  ccIndex index;
  long retval;
  char *full_name;
  TCEditWindow *e;
  if (debug_files)
  {
    fprintf(stderr,_("checking file time for '%s'\n"),name);
  }
  if (is_full_name == True) full_name = (char *)name;
  else
  {
    full_name = (char *)alloca(512);
    strcpy(full_name,name);
    FExpand(full_name,False);
  }
  e = is_on_desktop(full_name,True);
  if (e) 
  {
    if (!NoFileCache && file_times != NULL)
    {
      ft = new FileTime(full_name,0);
      if (file_times->search(ft,index) == True)
      {
        file_times->atRemove(index);
      }
      delete(ft);
    }
    if (debug_files)
    {
      fprintf(stderr,_("found '%s' on desktop, time: %08lx\n"),name,
                     ((TIDEFileEditor *)(e->editor))->edittime);
    }
    if (update == True)
    {
      if (debug_files)
      {
        fprintf(stderr,_("and updating it"));
        fprintf(stderr,"\n");
      }
      return ((TIDEFileEditor *)(e->editor))->edittime =
             time_of_file(full_name);
    }
    else
      return ((TIDEFileEditor *)(e->editor))->edittime;
  }
  if (NoFileCache)
  {
    retval = time_of_file(full_name);
    if (debug_files)
    {
      fprintf(stderr,_("found '%s' not in filetime cache, time: %08lx\n"),
                     name,retval);
    }
    return retval;
  }
  if (file_times == NULL) file_times = new FileTimeCollection(100,100);
  ft = new FileTime(full_name,0);
  if (file_times->search(ft,index) == True)
  {
    if (debug_files)
    {
      fprintf(stderr,_("found '%s' in filetime cache "),name);
    }
    if (update == True)
    {
      if (debug_files)
      {
        fprintf(stderr,_("and updating it"));
      }
      ((FileTime *)file_times->at(index))->time = retval = time_of_file(full_name);
    }
    else
    {
      retval = ((FileTime *)file_times->at(index))->time;
    }
    if (debug_files)
    {
      fprintf(stderr,_(",time: %08lx\n"),retval);
    }
    delete(ft);
  }
  else
  {
    retval = time_of_file(full_name);
    ft->time = retval;
    file_times->insert(ft);
    if (debug_files)
    {
      fprintf(stderr,_("found '%s' not in filetime cache, time: %08lx\n"),
                     name,retval);
    }
  }
  return retval;
}

static void indent(int depth)
{
  int i;
  for (i=0;i<depth*2;i++) fputc(' ',stderr);
}

static jmp_buf cancel_make;

static void InitExecDialog()
{
  TRect r = TProgram::deskTop->getExtent();
  r.b.y -= 7;
  ExecDialog = new TCheckDialog(r,_("executing: "));
  TProgram::deskTop->lock();
  TProgram::deskTop->insert(ExecDialog);
  ExecDialog->hide();
  TProgram::deskTop->unlock();
  r.a.y = 5;
  r.b.y = r.a.y + 4;
  r.a.x += 2;
  r.b.x -= 2;
  CheckDialog = new TCheckDialog(r,_("checking"));
  TProgram::deskTop->insert(CheckDialog);
}

static void DeleteExecDialog()
{
  TProgram::deskTop->remove(CheckDialog);
  TProgram::deskTop->remove(ExecDialog);
  destroy(CheckDialog);
  destroy(ExecDialog);
  CheckDialog = NULL;
  ExecDialog = NULL;
}

Boolean Compile(TDependency *_dep)
{
  TDependency *dep;
  const char * name = NULL;
  if (!SaveAll())
    return False;
  ShowMessages(NULL,True);
  Boolean retval = False;
  if ((dep = _dep) == NULL)
  {
    if (project_window && ((project_window->liste->state & sfFocused) != 0))
    {
      dep = (TDependency *)Project.dependencies->at(project_window->liste->focused);
    }
    else
    {
      int i;
      TWindow *w = (TWindow *)TProgram::deskTop->current;
      if (!w) return False;
      TEvent event;
      event.what = evBroadcast;
      event.message.command = cmEditorAnswer;
      w->handleEvent(event);
      if (event.what != evNothing) return False;
      name = w->getTitle(100);
      i = project_index(name);
      if (i>=0) dep = (TDependency *)Project.dependencies->at(i);
      else dep = NULL;
    }
  }
  if (ShowWhatDoing)
  {
    InitExecDialog();
    CheckDialog->hide();
    ExecDialog->show();
  }
  if (!dep) 
  {
    dep = new TDependency();
    InitFName(dep->source_name,name);
    char *dname;
    BaseName(name,dname,False);
    string_cat(dname,STANDARD_OBJECT_SUFFIX);
    InitFName(dep->dest_name,dname);
    string_free(dname);
    dep->source_file_type = get_file_type(name);
    dep->dest_file_type = FILE_OBJECT;
    dep->compile_id = how_to_compile(dep->source_file_type,
                                     dep->dest_file_type);
    retval = compile_dep(dep);
    destroy(dep);
  }
  else
  {
    retval = compile_dep(dep);
  }
  if (ShowWhatDoing)
    DeleteExecDialog();
  return retval;
}

Boolean time_of_dep(TDependency *dep,long &target_time,Boolean build,
  int depth,int recur_depth)
{
  long source_time = 0;
  char *dest_name = NULL;
  Boolean retval=True;
  if (dep->compile_id == COMPILE_PROJECT)
  {
    char *dname;
    const char *pname;
    dname = (char *)alloca(strlen(FName(dep->dest_name))+1);
    expandPath(FName(dep->dest_name),dname,NULL);
    if (*dname)
      dname[strlen(dname)-1] = 0;
    pname = FName(dep->source_name);
    AddToStack();
    if (PushProject(dname,pname) == False)
    {
      ResetProjectStack();
      longjmp(cancel_make,-1);
    }
    retval = time_of_dep(project,target_time,build,depth+1,depth+1);
    if (retval == False)
    {
      ResetProjectStack();
      longjmp(cancel_make,-1);
    }
    if (dep->dest_name) delete dep->dest_name;
    string_dup(dname,FName(project->dest_name));
    FExpand(dname);
    PopProject();
    RemoveFromStack();
    AbsToRelPath(project_directory,dname);
    InitFName(dep->dest_name,dname);
    string_free(dname);
    return retval;
  }
  if (dep->dest_file_type == FILE_NOTHING)
  {
    target_time = LONG_MAX;
  }
  else
  {
    FindFile(FName(dep->dest_name),dest_name);
    if (CheckDialog && depth < recur_depth+2) CheckDialog->update(dest_name);
    target_time = TimeOfFile(dest_name,False,True);
  }
  {
    if (debug_dependencies)
    {
      indent(depth);
      fprintf(stderr,"%s: %s\n",_("checking"),dest_name);
    }
    if (dep->source_name == NULL) source_time = target_time;
    else
    {
      char *sname;
      if (debug_dependencies)
      {
        indent(depth+1);
        fprintf(stderr,"%s %s\n",_("checking"),FName(dep->source_name));
      }
      if (FindFile(FName(dep->source_name),sname) == False &&
          TimeOfFile(FName(dep->source_name),False,False) == 0)
      {
        if (debug_dependencies)
        {
          indent(depth+1);
          fprintf(stderr,"%s does not exist\n",sname);
        }
        BigmessageBox(mfError | mfOKButton,_("Could not find the source file "
                                          "'%s'. Make sure, that the file "
                                          "exist or check the settings in "
                                          "'Options/Directories' for the "
                                          "correct paths."),FName(dep->source_name));
        return False;
      }
      source_time = TimeOfFile(sname,False,True);
      if (debug_dependencies)
      {
        indent(depth+1);
        fprintf(stderr,"%s %s\n",FName(dep->source_name),_("is up to date"));
        indent(depth);
        fprintf(stderr,"%s %s %s %s %s\n",
            FName(dep->source_name),_("is"),(source_time > target_time ?
              _("newer") : _("older")),_("than"),dest_name);
      }
      string_free(sname);
    }
    if ((build == True || source_time<=target_time) && 
         dep->dependencies != NULL &&
         dep->compile_id != COMPILE_LINK_PASCAL_AUTOMAKE)
    {
      int i,count=dep->dependencies->getCount();
      long akt_time;
      for (i=0;i<count;i++)
      {
        TDependency *_dep = (TDependency *)dep->dependencies->at(i);
        retval = time_of_dep(_dep,akt_time,build,depth+1,recur_depth);
        if (retval == False) return retval;
        if (akt_time>source_time) source_time = akt_time;
        if (debug_dependencies)
        {
          char *dname;
          indent(depth);
          FindFile(FName(_dep->dest_name),dname);
          fprintf(stderr,"%s %s %s %s %s\n",
            dname,_("is"),(akt_time > target_time ? _("newer"):_("older")),
                  _("than"),dest_name);
          string_free(dname);
        }
      }
    }
  }
  if (build == False && source_time<=target_time)
  {
    string_free(dest_name);
    return retval;
  }
  if (debug_dependencies)
  {
    indent(depth);
    fprintf(stderr,"%s %s\n",_("must rebuild"),dest_name);
  }
  if (CheckDialog) CheckDialog->hide();
  if (ExecDialog) ExecDialog->show();
  retval = compile_dep(dep);
  if (ExecDialog) ExecDialog->hide();
  if (CheckDialog) CheckDialog->show();
  if (debug_dependencies)
  {
    indent(depth);
    fprintf(stderr,"%s %s  ===>  %s\n",_("rebuilding of"),dest_name,
            (retval == True ? _("success") : _("error") ));
  }
  up_to_date = False;
  if (retval == True) target_time = TimeOfFile(dest_name,True,True);
  string_free(dest_name);
  return retval;
}

void ClearFileHash()
{
  if (debug_files)
  {
    fprintf(stderr,_("clearing the filetime cache\n"));
  }
  destroy(file_times);
  file_times = NULL;
}

Boolean Make(Boolean force_messages)
{
  Boolean retval;
  long akttime;
  int need_make = hasmodified || !already_maked;
  if (!SaveAll())
    return False;
  if (!project_name)
  {
    if (Project.dependencies)
    {
      while (Project.dependencies->getCount()>0)
      {
        Project.dependencies->atFree(0);
      }
    }
    if (!windows) return False;
    int count = windows->getCount(),i;
    for (i=0;i<count;i++)
    {
      TEvent event;
      TWindow *window = DESKTOPWINDOW(i);
      event.what = evBroadcast;
      event.message.command = cmEditorAnswer;
      window->handleEvent(event);
      if (event.what == evNothing)
      {
        const char *title = window->getTitle(256);
        COMPILE_ID id = how_to_compile(title,STANDARD_OBJECT_SUFFIX);
        if (id != COMPILE_NONE && id != COMPILE_UNKNOWN)
          AddProjectItem(title);
      }
    }
    if (!Project.dependencies || Project.dependencies->getCount() == 0)
      return False;
    if (Project.dependencies->getCount() == 1)
    {
      char *name;
      BaseName(FName(((TDependency *)Project.dependencies->at(0))->source_name),
               name,False);
      string_cat(name,STANDARD_EXE_SUFFIX);
      SetMainTargetName(name);
      string_free(name);
    }
    else
    {
      SetMainTargetName("aout"STANDARD_EXE_SUFFIX);
    }
  }
  if (ShowWhatDoing)
  {
    InitExecDialog();
  }
  if (force_messages == True) ShowMessages(NULL,True);
  up_to_date = True;
  retval = True;
  if (need_make)
  {
    if (setjmp(cancel_make) == 0)
    { 
      // simulate a build when using '--automake'
      if (Project.source_name && *FName(Project.source_name))
        retval = time_of_dep(project,akttime,True,0,0);
      else
        retval = time_of_dep(project,akttime,False,0,0);
    }
    else
    {
      retval = False;
      up_to_date = False;
    }
  }
  if (retval == False)
  {
    up_to_date = False;
    already_maked = 0;
  }
  else
  {
    if (PRJSTACKCOUNT)
      already_maked = 0;
    else
      already_maked = 1;
  }
  if (up_to_date == True && force_messages == True)
  {
    TMsgCollection *errs;
    char *tmp;
    string_dup(tmp,FName(project->dest_name));
    string_cat(tmp," ");
    string_cat(tmp,_("is up to date"));
    errs = new TMsgCollection();
    errs->insert(new msg_rec(NULL,tmp));
    string_free(tmp);
    ShowMessages(errs,True);
  }
  if (ExecDialog)
  {
    DeleteExecDialog();
  }
  if (!SaveProjectOnlyWhenClosing) SaveProject();
  return retval;
}

Boolean Build()
{
  ShowMessages(NULL,True);
  ClearFileHash();
  ClearFindCache();
  MakeClear();
  already_maked = 0;
  return Make();
}

#ifdef INTERNAL_DEBUGGER
static void ScanForTraceback(TFileViewer *viewer)
{
  int count = viewer->Count();
  int i=0;
  TMsgCollection *msgs = new TMsgCollection();
  const char *tmp;
  const char *_tmp;
  const char *buffer;
  int trace_start = 0;
  for (i=0;i<count;i++)
  {
    buffer = (*viewer)[i];
    if (trace_start)
    {
      tmp = buffer;
      while (*tmp == ' ') tmp++;
      if (tmp[0] == '0' && tmp[1] == 'x')
      {
        _tmp = tmp+2;
        while (isxdigit(*_tmp)) _tmp++;
        int len = (int)(_tmp - tmp);
        char dummy[len+1];
        memcpy(dummy,tmp,len);
        dummy[len] = 0;
        char function[256],file[256],temp[512];
        int line;
        symify(dummy,function,file,&line,0);
        if (*function)
        {
          strcpy(temp,_("in function "));
          strcat(temp,function);
        }
        else
        {
          strcpy(temp,tmp);
          strcat(temp," ???");
        }
        msgs->insert(new msg_rec(file,temp,msgMessage,line));
      }
    }
    else
    {
      if (strstr(buffer,"Call frame traceback EIPs") != NULL)
      {
        trace_start = 1;
        InitRHGDB();
        msgs->insert(new msg_rec(NULL,_("Call frame traceback:")));
      }
    }
  }
  if (msgs->getCount()>0) ShowMessages(msgs,False);
  else destroy(msgs);
}
#endif

class TIDEFileWindow : public TWindow
{
public:
  TIDEFileWindow(const char *);
  TFileViewer *FileViewer;
};

TIDEFileWindow::TIDEFileWindow(const char *fileName) :
    TWindow( TProgram::deskTop->getExtent(), fileName, wnNoNumber ),
    TWindowInit( &TIDEFileWindow::initFrame )
{
    options |= ofTileable;
    TRect r( getExtent() );
    r.grow(-1, -1);
    insert((FileViewer = new TFileViewer( r,
                            standardScrollBar(sbHorizontal | sbHandleKeyboard),
                            standardScrollBar(sbVertical | sbHandleKeyboard),
                            fileName) ));
    FileViewer->growMode = gfGrowHiX | gfGrowHiY;
    growMode = gfGrowLoY | gfGrowHiX | gfGrowHiY;
    flags |= wfGrow | wfZoom;
}

void CheckStderr()
{
  if (ShowStderr)
  {
    TIDEFileWindow *window;
    char *tmp;
    TRect rect = TProgram::deskTop->getExtent();
    rect.a.y = rect.b.y-7;
    string_dup(tmp,_("contents of stderr from "));
    string_cat(tmp,FName(project->dest_name));
    if (windows != NULL)
    {
      int count = windows->getCount(),i;
      for (i=0;i<count;i++)
      {
        window = (TIDEFileWindow *)DESKTOPWINDOW(i);
        if (strcmp(tmp,window->getTitle(100)) == 0)
        {
	    window->close();
          RemoveWindow(window);
          break;
        }
      }
    }
    struct stat st;
    if (stat(cpp_errname,&st) == 0 && st.st_size > 0)
    {
      window = new TIDEFileWindow(cpp_errname);
      delete (char *)(window->title);
      (char *)window->title = tmp;
      window->palette = wpGrayWindow;
      window->locate(rect);
      AddWindow(window);
#ifdef INTERNAL_DEBUGGER
      ScanForTraceback(window->FileViewer);
#endif
    }
  }
  if (ShowStderr && !debug_tempfiles) remove(cpp_errname);
}

void CheckStdout()
{
  if (ShowStdout)
  {
    TIDEFileWindow *window;
    char *tmp;
    TRect rect = TProgram::deskTop->getExtent();
    rect.b.y -= 7;
    string_dup(tmp,_("contents of stdout from "));
    string_cat(tmp,FName(project->dest_name));
    if (windows != NULL)
    {
      int count = windows->getCount(),i;
      for (i=0;i<count;i++)
      {
        window = (TIDEFileWindow *)DESKTOPWINDOW(i);
        if (strcmp(tmp,window->getTitle(100)) == 0)
        {
	    window->close();
          RemoveWindow(window);
          break;
        }
      }
    }
    struct stat st;
    if (stat(cpp_outname,&st) == 0 && st.st_size > 0)
    {
      window = new TIDEFileWindow(cpp_outname);
      delete (char *)(window->title);
      (char *)window->title = tmp;
      window->palette = wpGrayWindow;
      window->locate(rect);
      AddWindow(window);
    }
  }
  if (ShowStdout && !debug_tempfiles) remove(cpp_outname);
}

Boolean RunMainTarget()
{
  char *spec,*cmd;
  Boolean redir_stdout = False,redir_stderr = False;
#ifdef INTERNAL_DEBUGGER
  if (BreakPointCount() > 0)
  {
    CONTINUE();
  }
  else
#endif
  {
    int exit_code;
    if (Project.dest_file_type != FILE_COFF &&
        Project.dest_file_type != FILE_EXE) return False;
    if (ShowStderr) redir_stderr = True;
    if (ShowStdout) redir_stdout = True;
    string_dup(spec,FName(project->dest_name));
    FExpand(spec);
    string_cat(spec," $(PROG_ARGS)");
    cmd = BuildCompiler(project,spec);
    string_free(spec);
    exit_code = RunProgram(cmd,redir_stderr,redir_stdout,True);
    string_free(cmd);
    RESET();
    messageBox(mfInformation|mfOKButton,
      _("Program exit code: %d (0x%04x)"),exit_code,exit_code);
    if (ShowUserAfterExit)
    {
      TEvent event;
      event.what = evCommand;
      event.message.command = cmUserScreen;
      TProgram::application->handleEvent(event);
    }
    CheckStderr();
    CheckStdout();
  }
  return True;
}

#ifdef FILETIMEDEBUG
static __attribute__ (( __destructor__ ))
void print_filetimes()
{
  if (!file_times)
    return;
  int i,count = file_times->getCount();
  fprintf(stderr,"FILETIMES(%d)\n",count);
  for (i=0;i<count;i++)
  {
    FileTime *ft = (FileTime *)file_times->at(i);
    fprintf(stderr,"%s(%s)\n",ft->name,ft->key);
  }
}
#endif

