/*
 EGGX を用いたプログラムステップ実行モニタリング
*/


#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>
#include  <unistd.h>
#include  <eggx.h>

#define PMAX 100  /* max program line number */
#define LMAX 800  /* max log line number */

void usage() {
  fprintf(stderr, "usage: tracer_pst [-vn]  [-[ifcs] var]... source.c log.dat \n");
  fprintf(stderr, "         v : verbose mode.\n");
  fprintf(stderr, "         S : SKIP mode.\n");
  fprintf(stderr, "         i : logging variable var as integer (use %%d form)\n");
  fprintf(stderr, "         f : logging variable var as float/real (use %%f form)\n");
  fprintf(stderr, "         c : logging variable var as char (use %%c form)\n");
  fprintf(stderr, "         s : logging variable var as string (use %%s form)\n");
  fprintf(stderr, "  source.c : program source file (uncommented) \n");
  fprintf(stderr, "   log.dat : log file \n");
  return;
}

int vmode=0;
char pfname[256]; /* ソースプログラムファイル */
char lfname[256]; /* ログファイル */

char pstr[PMAX][256]; /* ソース各行のコード (インデント含む) */
int pmax=0;           /* ソースコード行数 */

#define MMAX 16      /* モニタ変数最大個数 */
char mvar[MMAX][64]; /* モニタ変数名 ( i とか line とかいったもの) */
int mmax=0;          /* モニタ変数の個数 */

int llin[LMAX];        /* ログ各行の行番号 */
int lchg[LMAX][MMAX];  /* ログ各行の各モニタ変数値の変化を示すフラグ */
char lstr[LMAX][256];  /* ログ各行のプログラム出力(stdout出力結果)領域 */
int ltptr[LMAX];       /* モニタ変数記録テーブルへのポインタ */
int lmax=0;            /* ログ行数 */

#define TLEN 8    /* モニタ変数出力のための表示領域長さ */
#define TLOG 40   /* モニタ変数記録テーブルの最大保存行数 */
char tstr[TLOG][MMAX][64]; /* モニタ変数値記録テーブル */
int tmax=0; /* モニタ変数値テーブル tstr[] の現在行数 */

int win;    /* ウィンドウ番号 */

#define WW 600 /* ウィンドウの幅 */
#define HH 600 /* ウィンドウの高さ */

#define XOFF 80 /* 描画をはじめる X 位置のオフセット */
#define YOFF 40 /* 描画をはじめる Y 位置のオフセット */

int ssize=16; /* ソースコード表示文字サイズ */

/*
 Cut the extra LF or CR from the tail of string.
 */
int cutlf(char *str)
{
  int l;
  
  l=strlen(str);
  if( l != 0 ) {
    if( str[l-1] == 13 || str[l-1] == 10 ) {
      str[l-1]=0;
    }
  }
  return 0;
}

/* 
 一行、プログラム行を描く
*/
int do_drawline(int plin)
{
  int i,j,l;
  char s[256];

  l=strlen(pstr[plin]);
  j=0;
  for(i=0; i<l; i++) { /* drawstr () の出力文字列はフォーマット文字列 */
    if(pstr[plin][i]=='%') s[j++]='%';  /* % は %% とエスケープする */
    s[j++]=pstr[plin][i];
  }
  s[j]='\0';
  drawstr(win, XOFF, HH-YOFF-ssize*1.25*plin, ssize, 0.0, s);
  
  return 0;
}

/*
 モニタ変数ラベル or 内容を描く
 引数は行位置、カラム位置、出力文字列（左詰め）の順
 行指定 -1 がラベル位置となるように
*/ 
int do_drawvar(int lin, int col, char *s)
{
  float x, y;

  x=WW-XOFF-ssize/2*TLEN*(mmax-col-1); /* 横位置計算 */
  y=HH-YOFF-ssize*2-ssize*1.5*lin;     /* 縦位置計算 */

  if(lin == -1) { /* 変数ラベルは周囲に枠でもつけておきましょう */
    drawrect(win, x-ssize*0.5, y-ssize*0.25, ssize*0.5*(strlen(s)+2), ssize*1.5);
  }

  drawstr(win, x, y, ssize, 0.0, s);
  
  return 0;
}
/*
 do_drawvar() そっくりさん
 目立つ下線を入れるためだけのもの
*/ 
int do_drawvar_line(int lin, int col, char *s)
{
  int loop;

  for(loop=0; loop<3; loop++) {
    drawstr(win, 
	    WW-XOFF-ssize/2*TLEN*(mmax-col-1), /* 横位置計算 */
	    HH-YOFF-ssize*2-ssize*1.5*lin-2-loop, /* 縦位置計算 */
	    ssize, 0.0, s);
  }
  
  return 0;
}

 
/* 
 ソースを読んでメモリに情報抽出
*/
int do_pread()
{
  char buf[1024];
  FILE *pfp;

  if( ( pfp=fopen(pfname, "r") ) == 0 ) {
    fprintf(stderr, "tracer ; cannot open program file (%s)\n", pfname);
  };

  fgets(buf, sizeof(buf), pfp);
  while( ! feof(pfp) ) {
    cutlf(buf);
    if(strlen(buf)>4) { /* XXX か空白インデントあり or 先頭の #define 行 */
      if(strncmp(buf, "#def", 4)==0) { /* #defing 行は無視 */
	strcpy(pstr[pmax],""); 
      } else {
	strcpy(pstr[pmax],&buf[4]); 
      };
    } else { /* 有り得ない筈だが、おそらくただの空白行 */
      strcpy(pstr[pmax],""); 
    };
    pmax++;
    fgets(buf, sizeof(buf), pfp);
  };

  fclose(pfp);

  if(vmode) printf("source code line number = %d\n",pmax);

  return 0;	
}

/*
 モニタ変数の内容をチェックしてログ領域に押し込む
 モニタ変数値テーブル tstr[] には変化があったときだけ追加される
 （この時だけ tmax が加算される）
 */
int do_lstore_var(char *varstr)
{
  int col,i,l,k;
  char ss[256], s[MMAX][64]; /* 一回ぶんの作業領域 */
  
  strcpy(ss, varstr); /* 書き潰しながら処理するので作業領域で処理 */

  /* モニタ変数領域（"10 20" と言った感じ）を要素分解して s[] に収める */
  col=0;
  l=strlen(ss);
  k=0;
  for(i=0; i<l; i++) {
    if(ss[i]==' ') {
      ss[i]='\0';
      strcpy(s[col++], &ss[k]);
      k=i+1;
    }
  }
  strcpy(s[col], &ss[k]);

  /* s[] と tstr[] の最終行を比較、変化した時だけログ変数値テーブルに追加 */
  i=0;
  for(col=0; col<mmax; col++) {
    strcpy(tstr[tmax][col], s[col]); /* まず追加して */
    if(tmax == 0) { /* 最初の一行目は常に変化あり */
      lchg[lmax][col]=1; /* フラグをつける */
      i++; /* 変化数をカウント */
    } else {
      if(strcmp(tstr[tmax-1][col], s[col])==0) { /* 変化無し */
	lchg[lmax][col]=0; /* フラグを落とす */
      } else { /* 変化あり */
	lchg[lmax][col]=1; /* フラグをつける */
	i++; /* 変化数をカウント */
      }
    }
  }
  if(i>0) { /* 変化がひとつでもあった */
    tmax++;
  }

  return 0;
}

/* 
 ログ一行ぶんをメモリに格納
 buf には "@@@ 7 0 1 @@@ KSU " といった内容が入る。
 @@@ 以後最初の 7 は行番号、それ以降 @@@ まではモニタ用変数の値、
 それ以降はプログラムの出力（ただし二つ目の @@@ 直後の空白は違う）
*/
int do_lstore(char *buf, int num)
{
  char s[256];
  int i,l,k;

  /* 行番号を抽出 */
  i=4; /* 最初の "@@@ " はスキップ可能 */
  l=strlen(buf);
  while(i<l) {
    if(buf[i]==' ') { /* 区切り文字 " " 発見 */
      strncpy(s, &buf[4], i-4); /* "@@@ " から i 位置 -4 までが行番号 */
      s[i-4]=(char)0;
      llin[num]=atoi(s); /* 数値型で行番号を確保 */
      break;
    }
    i++;
  }
  k=i+1; /* " " の次の位置からチェック再開 */
  
  /* 変数モニタリング領域を捜す */
  while(i<l) {
    if(strncmp(&buf[i]," @@@ ",5)==0) { /* 次の " @@@ " を捜す */
      strncpy(s, &buf[k], i-k); /* i の位置 - 前回記録位置 -1  */
      s[i-k]=(char)0;
      do_lstore_var(s); /* 文字列形式のモニタ値を分解してメモリに確保 */
      ltptr[num]=tmax; /* do_lstore_var() で tmax が設定されているはず */
      break;
    }
    i++;   
  }
  k=i=i+5; /* " @@@ " の 5 バイト分をスキップしたところからチェック再開 */
  
  /* プログラム出力領域を捜す */
  if(i<l) { /* プログラム出力あり */
    strcpy(lstr[num], &buf[i]); /* 文字列形式のまま出力結果を確保 */
  }

  return 0;
}

/*
 改行込みの出力を追加する
*/
int do_lappend(char *buf, int num)
{
  sprintf(lstr[num], "%s\n%s", lstr[num], buf);
  
  return 0;
}

/* 
 ログを読んでメモリに確保
*/
int do_lread()
{
  char buf[1024];
  FILE *lfp;

  if( ( lfp=fopen(lfname, "r") ) == 0 ) {
    fprintf(stderr, "tracer ; cannot open log file (%s)\n", lfname);
  };

  lmax=0;
  fgets(buf, sizeof(buf), lfp);
  while( ! feof(lfp) ) {
    cutlf(buf);
    if( strncmp(buf, "@@@ ", 4) == 0 ) { /* ログ行発見 */
      do_lstore(buf, lmax); /* 解析してメモリに確保 */
      lmax++;
    } else { /* 改行込みの出力があった */
      do_lappend(buf, lmax-1); /* 前回保存行の出力エリアに追加 */
    };
    fgets(buf, sizeof(buf), lfp);
  }; 
  
  fclose(lfp);

// デバッグ用
/*    { int i; 
      for(i=0;i<lmax;i++) printf("#log %2d#%d#%d#\n",i,llin[i],ltptr[i]);
      for(i=0;i<tmax;i++) printf("#tbl %2d#%s#%s#\n",i,tstr[i][0],tstr[i][1]);
    } /* */


  if(vmode) printf("log line number = %d\n",lmax);

  return 0;	
}

/*
 最初の空白・タブ以外の文字の位置を返す
 先頭なら 0
 全部空白・タブなら -1
*/
int checkline(char *s)
{
  int i, l;
   
  l=strlen(s);
  for(i=0; i<l; i++) {
    if( (s[i]!=' ' )&&( s[i]!='\t' )) break;
  };
  if(i==l) i= -1;  
 
  return i;
}

/*
 モニタ変数の値を描く
*/
int do_vardisp(int step)
{
  int i, col;
  newpen(win, 1);
  for(i=0; i<ltptr[step]; i++) {
    for(col=0; col<mmax; col++) {
      do_drawvar(i, col, tstr[i][col]);
    }
  }  

  return 0;
}
 
/*
 モニタ変数の内容をチェックして表示する
 過去の変数内容から変化があった場合はアニメーションが欲しい
 do_varpush() は通常 step+1 で呼び出されるため、ここでの step
 変数の扱いは若干奇妙。あまりにひどいツギハギプログラムだ。
 */
int do_varpush(int step)
{
  int col,l,k;
  char s[256];

  /* モニタ変数値を描く */  
  do_vardisp(step);

  /* 変化があった項目にはアンダーラインを入れる */
  newpen(win, 5); 
  for(col=0; col<mmax; col++) {
    if(lchg[step][col]==1) { /* 変化あり。同じ長さの _ を用意 */
      strcpy(s, tstr[ltptr[step]-1][col]);
      l=strlen(s);
      for(k=0; k<l; k++) s[k]='_'; s[k]='\0';
      do_drawvar_line(ltptr[step]-1, col, s); 
    }
  }

  return 0;
}

/*
 （一つ前の行の）実行アニメーション
 　但し step に -1 が入っていたら実行アニメーションはスキップ
 （その実行によって更新された）モニタ変数を反映
 （一つ前の行の）プログラム出力を反映
*/
int do_exec(int step)
{
  int i,k,l,m;
  char s[256];

  l=llin[step]-1; /* プログラム行番号を取得（先頭を 1 行めと数える） */

  /* 実行アニメーション */
  newpen(win, 7);
  if( step < 0 ) { 
    /* step = -1 は先頭に戻った時の処理。実行アニメはskip */
  } else {
    if( (i=checkline(pstr[l])) > 0 ) { /* カラ行などでは何もしない */
      strcpy(s, pstr[l]);
      k=strlen(pstr[l]);
      if(k>=5) { /* 5 バイト以上の場合は一行が 80ms で終わるように */
	m=80/k;
      } else {
	m=16;
      }
      for(; i<k; i++) {
	s[i-1]=' '; s[i]='_'; s[i+1]=(char)0;
	drawstr(win, XOFF, HH-YOFF-(ssize+ssize/4)*l, ssize, 0.0, s);
	drawstr(win, XOFF, HH-YOFF-(ssize+ssize/4)*l-1, ssize, 0.0, s);
	msleep(m);
      }
    }
  }

  if(mmax!=0) { /* モニタ変数があるなら表示 */
    newpen(win, 1);
    if((step+1)<lmax) { /* 最終行でないことを確認 */
      do_varpush(step+1);
    } else { /* 最終行の場合は一つ前のものを維持して出しておこう */
      do_varpush(step);
    }
  }

  /* プログラム出力を標準出力に出す */
  printf("%s", lstr[step]); fflush(stdout);

  return 0;
}

/*
 次に実行する予定の行にマークをつける
*/
#define NMARK 5
float xx[]={0.0, 18.0,  6.0, 6.0, 0.0};
float yy[]={0.0,  0.0, 12.0, 6.0, 6.0};
int do_mark(int step)
{
  int i,l;
  float x[NMARK],y[NMARK];

  if(step<lmax) { /* 最終行でない時だけ実行 */
    l=llin[step]-1; /* プログラム行番号を取得（先頭を 1 行めと数える） */

    /* 矢印を描く */
    newpen(win, 7);
    for(i=0; i<NMARK; i++) {
      x[i]=xx[i]+XOFF-ssize*1.5;
      y[i]=yy[i]+HH-YOFF-(ssize+ssize/4)*l;
    }
    fillpoly(win, x, y, NMARK, 1);
  } else { /* 最終行の場合はプログラム終了の予定マークをつける */
    l=pmax+1; /* プログラム最終行よりまだ下にマークする */
    
    newpen(win, 7);
    fillrect(win, XOFF-ssize*1.5, HH-YOFF-(ssize+ssize/4)*l, ssize, ssize);
    drawstr(win, XOFF, HH-YOFF-(ssize+ssize/4)*l, ssize, 0.0, "EXIT program");
  }
  
  return 0;
}
 
/* 操作ガイドを背景に描く */
int show_opguide( )
{
  int size=12;
  float y;
  
  y=80.0; 
  newpen(win, 1);
  
  y-=1.3*size; drawstr(win, XOFF+50, y, size, 0.0, 
		       "+, Downarrow or <RET> : next step");
  y-=1.3*size; drawstr(win, XOFF+50, y, size, 0.0, 
		       "-, Uparrow : step back");
  y-=1.3*size; drawstr(win, XOFF+50, y, size, 0.0, 
		       "< : rewind to initial position");
  y-=1.3*size; drawstr(win, XOFF+50, y, size, 0.0, 
		       "> : go to the end of program");

  return 0;
}
 
/* 
 背景描画
 step = -1 のときは変数モニタ結果などを書かない（ラベルのみ）
 */
int show_mat(int step)
{
  int i;
  
  layer(win, 0, 1);
  gclr(win);

  /* ソースコードイメージを描く */
  newpen(win, 1);
  for(i=0; i<pmax; i++) {
    do_drawline(i); 
  }

  if(mmax!=0) { /* モニタ変数があるならモニタ変数ラベルと値ログを描く */
    newpen(win, 5);
    for(i=0; i<mmax; i++) {
      do_drawvar(-1, i, mvar[i]);
    }
    if(step != -1) {
      do_vardisp(step);
    };
  }
 
  show_opguide(); /* 操作ガイドを描く */

  copylayer(win, 1, 0);
  layer(win, 0, 0);
  
  return 0;
}

/*
 ログをトレースしつつ実行
*/
int do_ldisp()
{
  int i,k;

  do_vardisp(0); /* 最初の一行目の変数描画などをする */
  do_mark(0); /* 最初の行位置にマーク */

  ggetch(win);

  /* ログを一行ずつトレースしつつ実行 */
  i=0;
  while(i<lmax) {
    show_mat(i); /* 背景描画 */
    do_exec(i); /* 実行描写 */
    do_mark(i+1); /* 次の実行位置にマーク */

    k=ggetch(win); /* キーコードは (-)45, (+)43, (RET)13  */
    // printf("key=%d\n",k);
    /* 残念ながら巻き戻しなどはログ表示のために無理あり */
    switch(k) {
    case 13: // 改行
    case 31: // カーソル下 
    case '+': 
    case ' ':
      i++; break;
    case 30: // カーソル上 
    case '-':
      if(i<0) {
	printf("\n====== you rewind to the initial position of the program.\n");
      } else {
	i--; /* -1 （初期状態）より前に行かないように */
      }
      break;
    case '<':
    case 29: // カーソル左
      printf("\n====== you rewind to the initial position of the program.\n");
      i= -1; break; /* show_mat(-1), do_exec(-1) が正しく動くので OK */
    case '>':
    case 28: // カーソル右
      i=lmax-1; break;
    }
  };
  
  return 0;
}

/*
 入り口
 */
int main(int argc, char *argv[])
{
  int ch;
  extern char *optarg;
  extern int optind;

  /* option recognise */
  while ((ch = getopt(argc, argv, "vni:f:c:s:")) != EOF) {
    switch((char)ch) {
    case 'v': /* verbose mode */
      vmode=1;
      break;
    case 'n': /* quiet mode */
      vmode=0;
      break;
    case 'i': /* monitoring variable as integer */
    case 'f': /* monitoring variable as float */  
    case 'c': /* monitoring variable as character */
    case 's': /* monitoring variable as string */ 
      strcpy(mvar[mmax++], optarg);
      break;
    case '?': /* Help message */
    default:
      usage();
      fprintf(stderr,"tracer : unrecognized parameter exist.\n");
      exit(1);
    };
  };
  /* Skip the position of argc,argv[] */
  argc -= optind;
  argv += optind;
  
  if(argc!=2) {
    usage();
    fprintf(stderr,"tracer : 2 parameter is required.\n");
    exit(1);
  };
  strcpy(pfname, argv[0]);
  strcpy(lfname, argv[1]);

  win=gopen(WW,HH);
  winname(win, "Program Execution Simlation");

  do_pread(); /* ソースコードを読んで背景情報を得る */
  do_lread(); /* ログファイルを一度読んで最大カウント数を得る */

  show_mat(-1); /* 背景描画 */

  /* READY to GO を表示、キー入力待ち */
  newpen(win, 1);
  fillrect(win, WW-195, 20, 20, 20);
  drawstr(win, WW-170, 25, 16, 0.0, "Press <RET> to start");
  ggetch(win); 

  show_mat(-1); /* 背景描画 */

  do_ldisp(); /* トレース開始 */
  
  show_mat(-1); /* 背景描画 */

  /* 終了表示とキー入力待ち */
  newpen(win, 1);
  fillrect(win, WW-195, 20, 20, 20);
  drawstr(win, WW-170, 25, 16, 0.0, "Press <RET> to exit");
  ggetch(win);
  gclose(win); 

  return(0);

}
  
