/* Copyright (C) 1996,1997 Robert H”hne, see COPYING.RH for details */
/* This file is part of RHIDE. */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <limits.h>
#include <unistd.h>
#include <fnmatch.h>

typedef struct {
  char *line;
  int file_no,line_no;
} line_entry;

#define DEFAULT_ORI_BASE "."
#define DEFAULT_DIFF_BASE "../diffs/tvision"
#define DEFAULT_PATCH_BASE "../tvision"

char *ori_files[] = {
  "APP.H",
  "BUFFERS.H",
  "COLORSEL.H",
  "CONFIG.H",
  "DIALOGS.H",
  "DRAWBUF.H",
  "EDITORS.H",
  "HARDWARE.H",
  "HELP.H",
  "HELPBASE.H",
  "MENUS.H",
  "MSGBOX.H",
  "OBJECTS.H",
  "OUTLINE.H",
  "RESOURCE.H",
  "STDDLG.H",
  "SYSTEM.H",
  "TEXTVIEW.H",
  "TKEYS.H",
  "TOBJSTRM.H",
  "TTYPES.H",
  "TV.H",
  "TVOBJS.H",
  "UTIL.H",
  "VALIDATE.H",
  "VIEWS.H",
  "COLORSEL.CPP",
  "DRIVERS.CPP",
  "DRIVERS2.CPP",
  "EDITSTAT.CPP",
  "GENINC.CPP",
  "GRP.CPP",
  "HARDWRVR.CPP",
  "HELP.CPP",
  "HELPBASE.CPP",
  "HISTLIST.CPP",
  "MAPCOLOR.CPP",
  "MENU.CPP",
  "MISC.CPP",
  "MSGBOX.CPP",
  "NEW.CPP",
  "NEWSTR.CPP",
  "NMBKGRND.CPP",
  "NMBUTTON.CPP",
  "NMCHDRDL.CPP",
  "NMCHKBOX.CPP",
  "NMCLRSEL.CPP",
  "NMCLUSTR.CPP",
  "NMCOLLCT.CPP",
  "NMDIALOG.CPP",
  "NMDIRBOX.CPP",
  "NMDIRCOL.CPP",
  "NMDSKTOP.CPP",
  "NMEDITOR.CPP",
  "NMFILCOL.CPP",
  "NMFILDLG.CPP",
  "NMFILLST.CPP",
  "NMFRAME.CPP",
  "NMGROUP.CPP",
  "NMHIST.CPP",
  "NMINPTLN.CPP",
  "NMLABEL.CPP",
  "NMLSTBOX.CPP",
  "NMLSTVWR.CPP",
  "NMMNUBAR.CPP",
  "NMMNUBOX.CPP",
  "NMMNUPOP.CPP",
  "NMMNUVW.CPP",
  "NMMULCHK.CPP",
  "NMOUTLIN.CPP",
  "NMPRMTXT.CPP",
  "NMRBTNS.CPP",
  "NMRESCOL.CPP",
  "NMSCOLL.CPP",
  "NMSCRBAR.CPP",
  "NMSCROLL.CPP",
  "NMSTCTXT.CPP",
  "NMSTDDLG.CPP",
  "NMSTLINE.CPP",
  "NMSTRCOL.CPP",
  "NMSTRLST.CPP",
  "NMVALIDA.CPP",
  "NMVIEW.CPP",
  "NMWINDOW.CPP",
  "PALETTE.CPP",
  "SBKGRND.CPP",
  "SBUTTON.CPP",
  "SCHDRDLG.CPP",
  "SCHECKBO.CPP",
  "SCLRSEL.CPP",
  "SCLUSTER.CPP",
  "SDESKTOP.CPP",
  "SDIALOG.CPP",
  "SDIRCOLL.CPP",
  "SDIRLIST.CPP",
  "SEDITORS.CPP",
  "SFILCOLL.CPP",
  "SFILDLG.CPP",
  "SFILELST.CPP",
  "SFINFPNE.CPP",
  "SFINPUTL.CPP",
  "SFRAME.CPP",
  "SGROUP.CPP",
  "SHISTORY.CPP",
  "SINPUTLI.CPP",
  "SLABEL.CPP",
  "SLISTBOX.CPP",
  "SLSTVIEW.CPP",
  "SMENUBAR.CPP",
  "SMENUBOX.CPP",
  "SMENUPOP.CPP",
  "SMNUVIEW.CPP",
  "SMULCHKB.CPP",
  "SOUTLINE.CPP",
  "SPARAMTE.CPP",
  "SRADIOBU.CPP",
  "SRESCOLL.CPP",
  "SSCRLBAR.CPP",
  "SSCROLLE.CPP",
  "SSTATICT.CPP",
  "SSTATUSL.CPP",
  "SSTRCOLL.CPP",
  "SSTRLST.CPP",
  "STDDLG.CPP",
  "STRMSTAT.CPP",
  "SVALID.CPP",
  "SVIEW.CPP",
  "SWINDOW.CPP",
  "SYSERR.CPP",
  "TAPPLICA.CPP",
  "TBKGRND.CPP",
  "TBUTTON.CPP",
  "TCHDRDLG.CPP",
  "TCHECKBO.CPP",
  "TCLUSTER.CPP",
  "TCMDSET.CPP",
  "TCOLLECT.CPP",
  "TDESKTOP.CPP",
  "TDIALOG.CPP",
  "TDIRCOLL.CPP",
  "TDIRLIST.CPP",
  "TEDITOR1.CPP",
  "TEDITOR2.CPP",
  "TEDITWND.CPP",
  "TEVENT.CPP",
  "TEXTVIEW.CPP",
  "TFILDLG.CPP",
  "TFILECOL.CPP",
  "TFILEDTR.CPP",
  "TFILLIST.CPP",
  "TFRAME.CPP",
  "TGROUP.CPP",
  "THISTORY.CPP",
  "THISTWIN.CPP",
  "THSTVIEW.CPP",
  "TINDICTR.CPP",
  "TINPUTLI.CPP",
  "TLABEL.CPP",
  "TLISTBOX.CPP",
  "TLSTVIEW.CPP",
  "TMEMO.CPP",
  "TMENUBAR.CPP",
  "TMENUBOX.CPP",
  "TMENUPOP.CPP",
  "TMNUVIEW.CPP",
  "TMOUSE.CPP",
  "TMULCHKB.CPP",
  "TOBJECT.CPP",
  "TOBJSTRM.CPP",
  "TOUTLINE.CPP",
  "TPARAMTE.CPP",
  "TPOINT.CPP",
  "TPROGRAM.CPP",
  "TRADIOBU.CPP",
  "TRESCOLL.CPP",
  "TRESFILE.CPP",
  "TSCREEN.CPP",
  "TSCRLBAR.CPP",
  "TSCROLLE.CPP",
  "TSORTCOL.CPP",
  "TSTATICT.CPP",
  "TSTATUSL.CPP",
  "TSTRCOLL.CPP",
  "TSTRLIST.CPP",
  "TVALIDAT.CPP",
  "TVIEW.CPP",
  "TVTEXT1.CPP",
  "TVTEXT2.CPP",
  "TWINDOW.CPP",
NULL
};

line_entry **ori_lines=NULL;
int ori_line_count=0;
int ori_line_size=0;
int diff = 1;

char *ori_base = NULL;
char *diff_base = NULL;
char *patch_base = NULL;

#define BLOCK 256

static void resize_ori_lines()
{
  ori_line_size += BLOCK;
  ori_lines = (line_entry **)realloc(ori_lines,ori_line_size * sizeof(line_entry *));
}

static void expand_tabs(char *s,char *ss)
{
  int i = 0, ii = 0;
  char *tmp;
#define R(x,xx) \
do {\
  if ((tmp = strstr(s,x)) != NULL)\
  {\
    *tmp = 0;\
    strcpy(ss,s);\
    strcat(ss,xx);\
    strcat(ss,tmp+strlen(x));\
    strcpy(s,ss);\
  }\
} while (0)

  R("<tvision\\","<");
  R(" _NEAR "," ");
  R(" _FAR "," ");
  R(" near "," ");
  R(" far "," ");

  do
  {
    if (s[i] == '\t')
    {
      ss[ii++] = ' ';
      while (ii%8)
      {
        ss[ii++] = ' ';
      }
    }
    else
      ss[ii++] = s[i];
    i++;
  } while (s[i-1]);
}

int sort_func(const void *k1,const void *k2)
{
  int ret;
  line_entry *l1 = *((line_entry **)k1);
  line_entry *l2 = *((line_entry **)k2);
  ret = strcmp(l1->line,l2->line);
  if (ret) return ret;
  if (l1->file_no < l2->file_no) return -1;
  if (l1->file_no > l2->file_no) return 1;
  if (l1->line_no < l2->line_no) return -1;
  if (l1->line_no > l2->line_no) return 1;
  return 0;
}

static char _line[512];
static char line[8*512];

void read_ori_files()
{
  int index=0;
  int fileno=0;
  line_entry *le;
  int lineno;
  char fname[PATH_MAX];
  FILE *f;
  while (ori_files[index])
  {
    strcpy(fname,ori_base);
    strcat(fname,"/");
    strcat(fname,ori_files[index]);
    f = fopen(fname,"rt");
    if (!f)
    {
      fprintf(stderr,"Error opening file %s (aborting ...)\n",fname);
      exit(-1);
    }
    fprintf(stdout,"reading: %s\n",fname);
    fileno++;
    lineno=0;
    while (fgets(_line,sizeof(_line)-1,f) != NULL)
    {
      lineno++;
      _line[strlen(_line)-1] = 0; // remove the last \n
      if (!_line[0]) continue;
      expand_tabs(_line,line);
      if (ori_line_count >= ori_line_size) resize_ori_lines();
      le = (line_entry *)malloc(sizeof(line_entry));
      le->line = strdup(line);
      le->file_no = fileno;
      le->line_no = lineno;
      ori_lines[ori_line_count++] = le;
    }
    fclose(f);
    index++;
  }
  qsort(ori_lines,ori_line_count,sizeof(line_entry *),sort_func);
}

int sort_func1(const void *k1,const void *k2)
{
  line_entry *l1 = *((line_entry **)k1);
  line_entry *l2 = *((line_entry **)k2);
  return strcmp(l1->line,l2->line);
}

int search_ori_line(char *line)
{
  line_entry le,*_le,*__le;
  le.line = line;
  __le = &le;
  _le = (line_entry *)bsearch(&__le,ori_lines,ori_line_count,sizeof(line_entry *),sort_func1);
  if (!_le) return -1;
  return ((int)_le - (int)ori_lines)/sizeof(line_entry *);
}

static void write_diff_file(char *from_file,char *to_file)
{
  FILE *fi,*fo;
  char line[256];
  int index;
  fi = fopen(from_file,"rt");
  if (!fi)
  {
    fprintf(stderr,"Error opening file %s (aborting ...)\n",from_file);
    exit(-1);
  }
  if ((fgets(line,255,fi)) && (strstr(line,"H”hne") != NULL))
  {
    fclose(fi);
    return;
  }
  fseek(fi,0,SEEK_SET);
  fo = fopen(to_file,"w+t");
  if (!fo)
  {
    fprintf(stderr,"Error creating file %s (aborting ...)\n",to_file);
    exit(-1);
  }
  fprintf(stdout,"writing: %s\n",to_file);
  while (fgets(line,255,fi))
  {
    line[strlen(line)-1] = 0;
    if (line[0] && (index = search_ori_line(line)) >= 0)
    {
#if 0
      sprintf(line,"@%05d@%05d@%03d@",index,ori_lines[index]->file_no,
                                     ori_lines[index]->line_no);
#else
      sprintf(line,"@%05d@",index);
#endif
    }
    fprintf(fo,"%s\n",line);
  }
  fclose(fi);
  fclose(fo);
}

static void write_patch_file(char *from_file,char *to_file)
{
  FILE *fi,*fo;
  char line[256];
  int index;
  fi = fopen(from_file,"rt");
  if (!fi)
  {
    fprintf(stderr,"Error opening file %s (aborting ...)\n",from_file);
    exit(-1);
  }
  fo = fopen(to_file,"w+t");
  if (!fo)
  {
    fprintf(stderr,"Error creating file %s (aborting ...)\n",to_file);
    exit(-1);
  }
  fprintf(stdout,"writing: %s\n",to_file);
  while (fgets(line,255,fi))
  {
    line[strlen(line)-1] = 0;
    if (line[0] != '@') fprintf(fo,"%s\n",line);
    else
    {
      sscanf(line,"@%d@",&index);
      fprintf(fo,"%s\n",ori_lines[index]->line);
    }
  }
  fclose(fi);
  fclose(fo);
}

static void write_diff_files_recursive(
  char *fromdir,char *todir)
{
  char tmp[PATH_MAX],*Tmp;
  char _tmp[PATH_MAX],*_Tmp;
  DIR *dir;
  struct dirent *de;
  dir = opendir(fromdir);
  if (!dir) return;
  strcpy(tmp,fromdir);
  strcat(tmp,"/");
  Tmp = tmp + strlen(tmp);
  strcpy(_tmp,todir);
  strcat(_tmp,"/");
  _Tmp = _tmp + strlen(_tmp);
  while ((de = readdir(dir)) != NULL)
  {
    struct stat st;
    strcpy(Tmp,de->d_name);
    strcpy(_Tmp,de->d_name);
    if (stat(tmp,&st) == 0)
    {
      if (S_ISDIR(st.st_mode))
      {
        if (strcmp(Tmp,".") == 0 || strcmp(Tmp,"..") == 0)
          continue;
        if (mkdir(_tmp,0755) != 0 && access(_tmp, X_OK) != 0)
        {
          fprintf(stderr,"could'nt create directory %s (aborting ...)\n",_tmp);
          exit(-1);
        }
        write_diff_files_recursive(tmp,_tmp);
      }
      else
      {
        if ((fnmatch("*.cc",Tmp,0) == 0) || (fnmatch("*.h",Tmp,0) == 0)
            || (fnmatch("*.c",Tmp,0) == 0))
        {
          if (diff) write_diff_file(tmp,_tmp);
          else write_patch_file(tmp,_tmp);
        }
      }
    }
  }
}  
      
void write_diff_files()
{
  if (diff) write_diff_files_recursive(patch_base,diff_base);
  else write_diff_files_recursive(diff_base,patch_base);
}

#define check_opt(o,d)\
if (strcmp(argv[i],o) == 0)\
{\
  i++;\
  if (i < argc)\
  {\
    d = strdup(argv[i]);\
  }\
  else\
  {\
    fprintf(stderr,"Option '%s' needs an argument\n",o);\
    exit(-1);\
  }\
}

static void BaseName(const char * name,char **bname,int with_suffix)
{
  const char *ext;
  int len;
  const char *_name = strrchr(name,'/');
  if (!_name)
    _name = name;
  else
    _name++;
  ext = _name + strlen(_name);
  if (!with_suffix)
  {
    const char *_ext = strrchr(_name,'.');
    if (_ext)
      ext = _ext;
  }
  len = ext - _name;
  *bname = (char *)malloc(len+1);
  memcpy(*bname, _name, len);
  (*bname)[len] = 0;
}
int main(int argc,char *argv[])
{
  int i;
  char *name;
  BaseName(argv[0],&name,0);
  if (strcmp(name,"tvdiff") == 0) diff = 1;
  else diff = 0;
  for (i=1;i<argc;i++)
  {
    if (strcmp(argv[i],"-diff") == 0)
    {
      diff = 1;
    }
    else
    if (strcmp(argv[i],"-patch") == 0)
    {
      diff = 0;
    }
    else
    check_opt("-ob",ori_base)
    else
    check_opt("-db",diff_base)
    else
    check_opt("-pb",patch_base)
    else
    {
      fprintf(stderr,"usage : %s [options]\n"
                       "  -ob dir   : base directory, where the ori files are\n"
                       "  -db dir   : base directory, where the diff files are\n"
                       "  -pb dir   : base directory, where the patched files are\n"
                       "  -h        : this help\n"
                       ,name);
      exit(1);
    }
  }
  if (!ori_base) ori_base = strdup(DEFAULT_ORI_BASE);
  if (!diff_base) diff_base = strdup(DEFAULT_DIFF_BASE);
  if (!patch_base) patch_base = strdup(DEFAULT_PATCH_BASE);
  read_ori_files();
  write_diff_files();
  return 0;
}
