/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include 
#include "i4_make.hh"
#include 
#include "string.cc"
#include "error.cc"
#include "deps.cc"

#include 
#include 
#ifdef __linux
#include 
#include 
#define MKDIR(x) mkdir(x, 0xffff)
#endif

#ifdef _WINDOWS
#include 
#include 
#include 
#define MKDIR(x) _mkdir(x)
#endif

#include "array_tree.hh"


char *link_file="c:\\tmp\\link.lk";

mk_options_struct mk_options;

enum {MAX_LEN=1000};

char abs_path[MAX_LEN];
int current_build_type=0;

list failed_targets;
list files_to_clean;
list files_to_backup;

#ifdef _WINDOWS
int my_system (const char *command, int force=0)
{
  char *argv[4];

  if (current_build_type==BUILD_DEBUG ||
      current_build_type==BUILD_OPT ||
      current_build_type==BUILD_PROF ||
      force) 
  {   
    if (command == 0)
      return 1;
    argv[0] = "command";
    argv[1] = "/c";
    argv[2] = (char *) command;
    argv[3] = 0;


    OSVERSIONINFO os;
    os.dwOSVersionInfoSize=sizeof(os);
    GetVersionEx(&os);
    
    if (os.dwPlatformId==VER_PLATFORM_WIN32_NT)
      return system(command);
    else
      return _spawnv(_P_WAIT, "c:\\windows\\command.com", argv);

  }
  return 0;
}

#else


int my_system (const char *command, int force=0)
{
  if (current_build_type==BUILD_DEBUG ||
      current_build_type==BUILD_OPT ||
      current_build_type==BUILD_PROF ||
      force) 
  {   
    // warning- this breaks if you have a child process running already

    int pid, status;

    if (command == 0)
      return 1;
    pid = fork();
    if (pid == -1)
      return -1;
    if (pid == 0)
    {
      char *argv[4];
      argv[0] = "sh";
      argv[1] = "-c";
      argv[2] = (char*) command;
      argv[3] = 0;
      execv("/bin/sh", argv);
      exit(127);
    }
    do
    {
      wait(&status);
      return status;
    } while(1);
  }
  return 0;
}

#endif


void extract(char *fn)
{
  FILE *in=fopen(fn,"rb");
  while (1)
  {
    unsigned char l,s1,s2,s3,s4;
    if (fread(&l,1,1,in)!=1)
    {
      fclose(in);
      exit(0);
    }

    char fn[256],buf[4096];
    fread(fn, 1,l,in);
    FILE *out=fopen(fn,"wb");
    if (!out)
    {
      for (int i=0; isizeof(buf) ? sizeof(buf) :size;
        fread(buf, 1,rsize,in);
        fwrite(buf, 1, rsize, out);
        size-=rsize;
      }
      fclose(out);    
    }
    else printf("extract: could not open %s for writing\n", fn);
  }
}

void mk_options_struct::get(int argc, char **argv)
{
  int i;
  quiet=1;

  for (i=1; iBUF_SIZE)
  {
    buf=(char *)malloc(BUF_SIZE);
    boff=0;
  }

  memcpy(buf+boff,p,len);
  boff+=len;
  return buf+boff-len;
}


char *get_abs_path(char *filename, int force=0);


void add_to_backup(char *fname)
{
  char *full_name=get_abs_path(fname,1);   // force a full name

  if (files_to_backup.find(full_name)==-1)
    files_to_backup.add(full_name);
}


void pcwd()
{
  char buf[100];
  getcwd(buf, 100);
  printf("cwd = %s\n",buf);
}

char start_path[256];

void set_abs_path(char *filename)
{ 
  char tmp[MAX_LEN];
  strcpy(tmp, filename);

  char *last_slash=0, *p=tmp, *q;
  for (; *p; p++)
    if (*p=='/' || *p=='\\')
      last_slash=p;

  if (last_slash)
  {  
    last_slash[1]=0;
    strcpy(abs_path, get_abs_path(tmp));

    if (mk_options.no_tmp)
    {
      chdir(start_path);
      chdir(abs_path);
    }
  }

}

char *get_abs_path(char *filename, int force)
{
  if (mk_options.no_tmp && !force)
    return filename;
  else
  {
      
    char tmp[MAX_LEN];
    strcpy(tmp, abs_path);
    char *s, *d;

    if ((filename[0]=='/' || filename[0]=='\\') || (filename[1]==':'))
      return filename;

    for (s=filename, d=tmp + strlen(tmp); *s; )
    {
      if (s[0]=='.' && s[1]=='.' && (s[2]=='/' || s[2]=='\\'))
      {
        d-=2;
        while (*d!='/' && *d!='\\') 
        {
          d--;
          if (d4)
    {
      if (filename[l-3]=='.' && filename[l-2]=='r' && filename[l-1]=='c')
        ext_type=EXT_RC_RES;
      else if (filename[l-3]=='.' && filename[l-2]=='c' && filename[l-1]=='c')
        ext_type=EXT_O;
      else
      {
        printf("make_out_name : don't know what extension to use for %s",filename);
        exit(0);
      }


    } else       
    {
      printf("make_out_name : filename to short : %s",filename);
      exit(0);

    }
  }

  if (mk_options.no_tmp)
  {
    d=tmp;
    char *start=filename;
    for (s=filename; *s && *s!='.'; s++);
    if (*s)
    {
      memcpy(d,filename, s-filename);
      d+=s-filename;
    }
    else
    {
      strcpy(d,filename);
      d+=strlen(d);
    }
    *d=0;
  }
  else
  {
    if (outdir)
    {
      strcpy(tmp,outdir);
      use_full_path=0;
    }
    else
    {
      if (mk_options.tmp_dir)
        sprintf(tmp,"%s%c", mk_options.tmp_dir, mk_options.slash_char);
      else
      {
        char *t=getenv("I4_TMP");
        if (t)
          sprintf(tmp,"%s%c", t, mk_options.slash_char);      
        else
        {
          if (mk_options.os==OS_LINUX)    
            strcpy(tmp,"/tmp/");
          else
            strcpy(tmp,"c:\\tmp\\");
        }
      }
    
    }
    d=tmp+strlen(tmp);


    if (!use_full_path)
    {
      char *start=filename;
      for (s=filename; *s; s++)
        if (*s=='\\' || *s=='/' || *s==':')
          start=s+1;

      strcpy(d,start);
      d+=strlen(d);
    }
    else
    {
      for (s=filename; *s; s++)
      {
        if (*s=='\\' || *s=='/' || *s==':')
        {
          d[0]=d[1]='_';
          d++;
        } 
        else if (*s=='.')
        {
          d[0]='-';
        } else *d=*s;
        d++;    
      }
    }
  }
  
  if (current_build_type==BUILD_DEBUG)
    strcpy(d,"_debug");
  else if (current_build_type==BUILD_OPT)
    strcpy(d,"_opt");
  else if (current_build_type==BUILD_PROF)
    strcpy(d,"_prof");
  else if (current_build_type==BUILD_CLEAN)
    strcpy(d,"_*");
  d+=strlen(d);

  if (mk_options.os==OS_LINUX)
  {
    switch (ext_type)
    {
      case EXT_O :  strcpy(d,".o"); break;
      case EXT_LIB :  
      {
        if (mk_options.unix_libs_are_shared)
          strcpy(d,".so");
        else
          strcpy(d,".a"); 
      } break;

      case EXT_DEP :  strcpy(d,".dep"); break;
      case EXT_PLUGIN:
      case EXT_DLL :  strcpy(d,".dll"); break;
      case EXT_RAM_FILE : strcpy(d,".cc"); break;
    }
  }
  else
  {
    switch (ext_type)
    {
      case EXT_O :  strcpy(d,".obj"); break;
      case EXT_LIB :  strcpy(d,".lib"); break;
      case EXT_EXE :  strcpy(d,".exe"); break;
      case EXT_DEP :  strcpy(d,".dep"); break;
      case EXT_PDB :  strcpy(d,".pdb"); break;
      case EXT_PCH :  strcpy(d,".pch"); break;
      case EXT_PLUGIN:
      case EXT_DLL :  strcpy(d,".dll"); break;
      case EXT_RAM_FILE : strcpy(d,".cc"); break;
      case EXT_RC_RES :  strcpy(d,".res"); break;
    }

  }

  
  char *ret=buf_alloc(tmp, strlen(tmp)+1);

  if (current_build_type==BUILD_CLEAN)
  {
    char *full_name=get_abs_path(ret,1);   // force a full name
    if (files_to_clean.find(full_name)==-1)
      files_to_clean.add(full_name);
  }

  return ret;
}



unsigned long check_sum32(void *buf, int buf_len)
{
  unsigned char c1=0,c2=0,c3=0,c4=0;
  while (buf_len)
  {
    c1+=*((unsigned char *)buf);
    c2+=c1;
    buf=(void *)(((unsigned char *)buf)+1);
    c3+=c2;
    c4+=c3;
    buf_len--;
  }
  return (c1|(c2<<8)|(c3<<16)|(c4<<24));
}


class mk_file_mod_node
{
public:
  int left, right;
  unsigned long checksum;
  unsigned long mod_time;
  
  int operator>(const mk_file_mod_node &b) const { return  (checksum > b.checksum); }
  int operator<(const mk_file_mod_node &b) const { return  (checksum < b.checksum); } 
  mk_file_mod_node(unsigned long checksum) : checksum(checksum) {}
  mk_file_mod_node() {}
};


i4_array_tree mod_tree;


void set_mod_time(char *filename, int time_to_set)
{
  mk_file_mod_node n(check_sum32(filename,strlen(filename)));
  int i=mod_tree.find(n);
  if (i!=-1)
  {
    mod_tree.get(i).mod_time=time_to_set;
    return ;
  }

  n.mod_time=time_to_set;
  mod_tree.add(n);
 
}

int get_mod_time(char *filename, int force_stat=0)
{
  mk_file_mod_node n(check_sum32(filename,strlen(filename)));
  int i=mod_tree.find(n);
  if (i!=-1 && !force_stat)
    return mod_tree.get(i).mod_time;

  unsigned long t;
#ifdef _WINDOWS
  struct _stat s;
  if (_stat(filename, &s)==0)
   t=s.st_mtime;
  else
    return 0;
#elif __linux
   struct stat s;
  if (stat(filename, &s)==0)
    t=s.st_mtime;
  else
    return 0;
#else
#error define os here
#endif

  if (i!=-1)
    mod_tree.get(i).mod_time=t;

  n.mod_time=t;
  mod_tree.add(n);
  return t;
}


enum { ALREADY_UP_TO_DATE,
       CHANGED,
       BUILD_ERROR,
       NO_MORE_TARGETS
};

void clean_file(char *outname)
{
  char cmd[200];
  if (outname[strlen(outname)-1]!='*')
  {
#ifdef _WINDOWS
    sprintf(cmd,"del %s", outname);
#else
    sprintf(cmd,"rm %s", outname);
#endif


    show_command(cmd,1);

    my_system(cmd, 1);
  }
}

int build_file(char *filename, 
               mk_target_struct &target,
               mk_target_struct &top_target)
               
{
  char *p=filename, *last_dot=0, cmd[MAX_LEN], inc[MAX_LEN], def[MAX_LEN];
  inc[0]=0;
  def[0]=0;
  int i;

  for (p=filename; *p; p++)
    if (*p=='.' && p[1]!='.')
      last_dot=p;

  if (last_dot)
  {
    if (strcmp(last_dot,".cc")==0)
    {
      char *source_name=get_abs_path(filename);

      char *outname=make_out_name(source_name,EXT_O);
      if (failed_targets.find(outname)!=-1)
        return BUILD_ERROR;

      switch (current_build_type)
      {
        case BUILD_BACKUP:
        {
          add_to_backup(source_name);
          list *dep=get_deps(source_name, &target.inc);
          if (dep)
          {
            for (i=0; isize(); i++)
              add_to_backup((*dep)[i]);
          }
        } break;


        case BUILD_OPT :
        case BUILD_DEBUG :
        case BUILD_PROF :
          int rebuild=0;

          int m1=get_mod_time(outname);
          if (m1)
          {
            list *dep=get_deps(source_name, &target.inc);
            if (mk_options.show_includes && dep)
            {
              printf( "%s\n",filename);
              for (i=0; isize(); i++)
                printf( "    %s\n",(*dep)[i]);
            }
            
            if (dep)
            {
              for (i=0; !rebuild && isize(); i++)
              {
                int src_mod_time=get_mod_time( (*dep)[i]);
                if (src_mod_time>m1)
                {
                  if (mk_options.show_deps)
                    printf( "%s newer than %s\n", (*dep)[i], outname);
                  rebuild=1;
                }
              }
            }
          } else rebuild=1;
              
          if (rebuild)
          {
            if (mk_options.os==OS_LINUX)
            {
              for (i=0; iltime)
      {
        if (mk_options.show_deps)
          printf( "%s newer than %s\n",oname, lname);
        object_files_have_changed=1;
      }

      d+=strlen(d);
    }
  }
  else
  {
    if (dll)
      sprintf(tmp,
              "link /MACHINE:IX86 /DEBUG /debugtype:coff /debugtype:both /DLL "
              "/nologo /PDB:%s /OUT:%s @%s%s", 
              make_out_name(get_abs_path(lname), EXT_PDB, 0),
              lname, link_file, 
              mk_options.quiet ? "> c:\\tmp\\null" : "");
    else
      sprintf(tmp, "lib /nologo /OUT:%s @%s%s", lname, link_file, 
              mk_options.quiet ? "> c:\\tmp\\null" : "");
    FILE *fp=fopen(link_file, "wb");
   

    for (i=0; iltime)
      {
        if (mk_options.show_deps)
          printf( "%s newer than %s\n",oname, lname);

        object_files_have_changed=1;
      }

    }

    if (dll)
    {
      for (i=0; iltime)
        {
          if (mk_options.show_deps)
            printf( "%s newer than %s\n",oname, lname);

          object_files_have_changed=1;
        }
      }

      if (target.def_file)
        sprintf(tmp+strlen(tmp), " /def:\"%s\"", target.def_file);
    }

    fclose(fp);
  }



  if (object_files_have_changed)
  {
    if (mk_options.quiet && (current_build_type!=BUILD_CLEAN && current_build_type!=BUILD_BACKUP))
      printf("Creating library %s\n", target.target);
    else
      show_command(tmp);

    if (mk_options.no_compile)
    {
      set_mod_time(lname, 0x7fffffff);
      return CHANGED;
    }
    else
    {
      if (my_system(tmp)==0)
      {
        get_mod_time(lname, 1);  // get time of output file

        // don't need to relink executable if using shared libraries
        if (mk_options.is_unix() && mk_options.unix_libs_are_shared)
          return ALREADY_UP_TO_DATE;

        return CHANGED;
      }
      else
      {
        failed_targets.add(lname);       // don't try to build this again
        return BUILD_ERROR;
      }
    }
  }
  else 
    return ALREADY_UP_TO_DATE;
}

int build_exe(int deps_have_changed, mk_target_struct &target)
{
  char tmp[50000];
  int i;

  char *ename=make_out_name(get_abs_path(target.target), EXT_EXE, 0, target.outdir);
  int etime=get_mod_time(ename);
  if (!etime)
    deps_have_changed=1;
  
  FILE *fp;
  if (mk_options.os==OS_LINUX)
  {
    char *sym_str=mk_options.no_syms ? "" : "-g ";
    
    
    sprintf(tmp, "g++ -rdynamic %s%s-o %s ",
            sym_str,
            current_build_type==BUILD_DEBUG ? "" : 
            current_build_type==BUILD_OPT ? "-O2 " :
            "-O2 -pg ",
            ename);
  }
  else
  {
    sprintf(tmp, "link  /SUBSYSTEM:%s /MACHINE:IX86 /DEBUG "
            "/NOLOGO /PDB:%s /NODEFAULTLIB /OUT:%s @%s",       // /INCREMENTAL:NO
            target.app_type==WIN32_CONSOLE_APP ? "CONSOLE" : "WINDOWS",
            make_out_name(get_abs_path(target.target), EXT_PDB, 0),
            ename, link_file);

    fp=fopen(link_file,"wb");
    if (!fp) mk_error("could not open %s for writing\n",link_file);
  }


  char *d=tmp+strlen(tmp);
    
  for (i=0; ietime)
    {
      if (mk_options.show_deps)
        printf( "%s newer than %s\n",oname, ename);
      deps_have_changed=1;
    }

    if (mk_options.os==OS_LINUX)
      sprintf(d, "%s ", oname);
    else
      fprintf(fp, "%s\n", oname);

    d+=strlen(d);
  }


  for (i=target.libs.size()-1; i>=0; i--)
  {     
    if (mk_options.os==OS_LINUX)
      sprintf(d, "%s ", target.libs[i]);
    else
      fprintf(fp, "%s\n", target.libs[i]);

    if (!deps_have_changed && get_mod_time(target.libs[i])>etime)
    {
      if (mk_options.show_deps)
        printf( "%s newer than %s\n", target.libs[i], ename);
      deps_have_changed=1;
    }

    d+=strlen(d);
  }

  if (mk_options.os==OS_WIN32)
    fclose(fp);

  if (deps_have_changed)
  {
    if (current_build_type!=BUILD_CLEAN && current_build_type!=BUILD_BACKUP)
    {
      if (mk_options.quiet)
        printf("Linking %s\n", target.target);
      else
        printf("%s\n", tmp);
    }

    if (mk_options.no_compile)
    {
      set_mod_time(ename, 0x7fffffff);
      return CHANGED;
    }
    else
    {
      if (my_system(tmp)==0)
      {
        get_mod_time(ename, 1);  // get time of output file
        return CHANGED;
      }
      else
        return BUILD_ERROR;
    }
  }
  else return ALREADY_UP_TO_DATE;
  
}

int build_target(mk_target_struct &target,
                 mk_target_struct &top_target)
//                  list &exe_src,
//                  list &exe_inc,
//                  list &exe_def,
//                  list &lib_files,
//                  list &dll_libs)
{
  int i;
  int change=0;

  if (mk_options.targets_built.find(target.target)==-1)
  {
    mk_options.targets_built.add(target.target);

    if (strcmp(target.target_type,"list")==0)
    {
      for (i=0; ifile && *d!='.') d--;

            if (strcmp(top_target.target_type,"plugin")==0)
            {
              if (strcmp(d,".def")==0)
                top_target.def_file=file;            
              else if (strcmp(d,".a")==0 || 
                       (strstr(file, ".so")!=0) ||
                       strcmp(d,".lib")==0 ||
                       strcmp(d,".res")==0)
              {
                if (strcmp(d,".res")==0)
                  add_to_backup(file);

                if (top_target.libs.find(file)==-1) 
                  top_target.libs.add(file);
              }
              else if (top_target.src.find(file)==-1)
                top_target.src.add(file);
            }
          }
          else if (strcmp(t, "add_to_executable")==0)
          {
            char *fn=get_token(p);

            char *file=get_abs_path(fn);
            char *d=file+strlen(file);
            while (d>file && *d!='.') d--;

            if (strcmp(top_target.target_type,"executable")==0 || 
                strcmp(top_target.target_type,"plugin")==0)
            {
              if (strcmp(d,".def")==0)
                top_target.def_file=file;            
              else if (strcmp(d,".a")==0 || 
                       (strstr(file, ".so")!=0) ||
                       strcmp(d,".lib")==0 ||
                       strcmp(d,".res")==0)
              {
                if (strcmp(d,".res")==0)
                  add_to_backup(file);

                if (top_target.libs.find(file)==-1) 
                  top_target.libs.add(file);
              }
              else if (top_target.src.find(file)==-1)
                top_target.src.add(file);
            }
          }
          else if (strcmp(t,"backup")==0)
          {            
            char *fname=get_token(p);
            add_to_backup(fname);
          }
          else if (strcmp(t,"add_include_directory")==0)
          {
            char *i=get_abs_path(get_token(p), 1);

            if (target.inc.find(i)==-1) 
              target.inc.add(i);

            if (top_target.inc.find(i)==-1)
              top_target.inc.add(i);

            mk_debug("add_include_directory (dir=%s)\n", i);
          }
          else if (strcmp(t,"add_define")==0)
          {
            char *i=get_token(p);

            if (target.defines.find(i)==-1) 
              target.defines.add(i);

            mk_debug("add_define (define=%s)\n", i);
          }
          else if (strcmp(t,"add_global_define")==0)
          {
            char *i=get_token(p);

            if (mk_global_defines.find(i)==-1) 
              mk_global_defines.add(i);

            mk_debug("add_global_define (define=%s)\n", i);
          }
          else if (strcmp(t, "ram_file")==0)
          {
            char *in_name=get_token(p);
            int l=line_on;
            skip_white(p);

            if (current_build_type==BUILD_BACKUP)
            {
              add_to_backup(in_name);
              if (line_on==l)
                get_token(p);
            }
            else
            {
              if (line_on==l)              
                build_ram_file(in_name, get_token(p), top_target); 
              else
                build_ram_file(in_name, in_name, top_target);
            }


          }
          else if (strcmp(t, "use")==0)
          {

            t=get_token(p);
            char *use_file=0;


            if (strcmp(t,"file")==0)
            {
              if (strcmp(get_token(p),"="))
                mk_error("expected = after file");
              use_file=get_token(p);
              t=get_token(p);

              add_to_backup(use_file);
            }

            mk_debug("use (file=%s) (target=%s)\n", use_file, t);


            mk_target_struct tar;
            int cur_line=line_on;   // save current line & file info
            char *cur_file=file_on;
            char *cur_contents=file_contents;

            char old_abs[MAX_LEN];
            char *f;
            if (use_file)
            {
              f=load_file(use_file, 1);
              if (!f)
                mk_error("could not load project file '%s'", use_file);
              
              strcpy(old_abs, abs_path);
              set_abs_path(use_file);
            }
            else f=file_contents;


            if (get_target(tar, top_target, f,t)==NO_MORE_TARGETS)
                mk_error("could not find target '%s'", t);
            
            // add include paths to local target
            for (int i=0; i>24;
          s2=(size&0xff0000)>>16;
          s3=(size&0xff00)>>8;
          s4=(size&0xff)>>0;
          fwrite(&s1, 1,1, out);
          fwrite(&s2, 1,1, out);
          fwrite(&s3, 1,1, out);
          fwrite(&s4, 1,1, out);

          char buf[4096];
          int rsize=0;
          do
          {
            rsize=fread(buf, 1, 4096, in);
            fwrite(buf, rsize, 1, out);
          } while (rsize);
          t_files++;
          fclose(in);

        }
        else printf("backup: couldn't open %s\n", files_to_backup[i]);
      }

      fclose(out);
      printf("backed up %d files to backup.i4\n", t_files);
    }
    else printf("backup: couldn't backup.i4 for writing\n");    
  }
}

int i4_make_main(int argc, char **argv)
{
  setbuf(stdout, 0);

#ifdef _WINDOWS

  mk_options.os=OS_WIN32;
  _getcwd(abs_path, sizeof(abs_path));

#elif __linux

  mk_options.os=OS_LINUX;
  getcwd(abs_path, sizeof(abs_path));

#else

  #error add new os here 
#endif



  strcat(abs_path, "/");

  mk_options.get(argc, argv);
  
  char *file_start=load_file(mk_options.project_file, 1), *b;
  if (!file_start)
    mk_error("Project file not found : '%s'", mk_options.project_file);


  mk_target_struct target;  
  getcwd(start_path, 256);
          
  for (current_build_type=1; current_build_type<=BUILD_LAST; current_build_type*=2)
  {           
    if (mk_options.build_flags & current_build_type)
    {  
      if (current_build_type==BUILD_BACKUP)
        add_to_backup(mk_options.project_file);

      if (!mk_options.targets_to_build.size())
      {  
        b=file_start;
        int ret;

        while (ret=get_target(target, target, b)!=NO_MORE_TARGETS)
        {
          if (build_target(target, target)==BUILD_ERROR)
            if (!mk_options.continue_on_error)
            {
              perror("Stopping : ");
              exit(-1);
            }
                
          target.reset();
          mk_options.targets_built.clear();
        }
      }

      for (int i=0; i