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

#include <sys/time.h>
#include <unistd.h>

#include "config.h"

#include <string>
#ifdef USING_STD_STRING
using std::string;
#endif

#ifdef DMALLOC
#include <dmalloc.h>
#endif

#include "echo.h"
#include "defines.h"
#include "support.h"
#include "tcltk.h"

extern char currentwindow[20];

void parse_ansi_code(char *data, int *bold, int *underline, int *fg, int *bg, int *reversefg, int *reversebg, char *pathname) {
  int reverse=0;
  int n;
  char* position=strtok(data,";");
  while(position) {
    n=atoi(position);
    switch(n) {
    case 0:
      *bold=0;
      *underline=0;
      *fg=TT_IntF(TT_ARGS,"set ::dynamic::theme_[windowtype %s]_foreground",pathname);
      *bg=TT_IntF(TT_ARGS,"set ::dynamic::theme_[windowtype %s]_background",pathname);
      *reversefg=*fg;
      *reversebg=*bg;
      break;
    case 1:
      *bold=1;
      break;
    case 4:
      *underline=1;
      break;
    case 7:
      reverse=1;
      break;
    case 30: *reversefg=1; *fg=1; break;
    case 31: *reversefg=4; *fg=4; break;
    case 32: *reversefg=9; *fg=9; break;
    case 33: *reversefg=8; *fg=8; break;
    case 34: *reversefg=12; *fg=12; break;
    case 35: *reversefg=13; *fg=13; break;
    case 36: *reversefg=11; *fg=11; break;
    case 37: *reversefg=0; *fg=0; break;
    case 40: *reversebg=1; *bg=1; break;
    case 41: *reversebg=4; *bg=4; break;
    case 42: *reversebg=9; *bg=9; break;
    case 43: *reversebg=8; *bg=8; break;
    case 44: *reversebg=12; *bg=12; break;
    case 45: *reversebg=13; *bg=13; break;
    case 46: *reversebg=11; *bg=11; break;
    case 47: *reversebg=0; *bg=0; break;
    }
    position=strtok(NULL,";");
  }
  if(reverse) {
    *fg=*reversebg;
    *bg=*reversefg;
  }
  // ;...m
  // 0 - All attributes off
  // 1 - Bold on
  // 4 - Underscore
  // 5 - Blink (Not handled!)
  // 7 - Reverse video on
  // 8 - Concealed on (Not handled!)
  // Foreground
  // 30 - Black (1)
  // 31 - Red (4)
  // 32 - Green (9)
  // 33 - Yellow (8)
  // 34 - Blue (12)
  // 35 - Magenta (13)
  // 36 - Cyan (11)
  // 37 - White (0)
  // Foreground
  // 40 - Black (1)
  // 41 - Red (4)
  // 42 - Green (9)
  // 43 - Yellow (8)
  // 44 - Blue (12)
  // 45 - Magenta (13)
  // 46 - Cyan (11)
  // 47 - White (0)
}

void echotags(Tcl_Obj *strobj, char *path, int newtext) {
  Tcl_Obj *objptr;
  int len;
  char* str;
 
  if(!path) path=currentwindow;
  // What's the deal here?
  if(!strlen(path)) return;

  str=Tcl_GetStringFromObj(strobj,&len);
  if(len) {
    TT_EvalF(TT_ARGS,"%s.text configure -state normal",path);
    
    objptr=Tcl_NewStringObj(path,strlen(path));
    Tcl_IncrRefCount(objptr);
    Tcl_AppendStringsToObj(objptr,".text insert end ",NULL);
    Tcl_AppendToObj(objptr, str, len);
    
    if(TCL_ERROR==Tcl_GlobalEvalObj(TT_Interp,objptr)) {
      Tcl_VarEval(TT_Interp,"bgerror \"File: ",__FILE__," Line: ",strnum(__LINE__),"\"",0);
    }

    TT_EvalF(TT_ARGS,"%s.text configure -state disabled",path);

    Tcl_DecrRefCount(objptr);
  }

  if(!strcmp(path,currentwindow)) {
    TT_EvalF(TT_ARGS,"\
if { [lindex \"[%s.text_vscroll get]\" 1]==1.0 } { %s.text yview moveto 1 }\
",path,path);
  } else {
    if(newtext) TT_EvalF(TT_ARGS,"activebutton %s",path);
    TT_EvalF(TT_ARGS,"%s.text yview moveto 1",path);
  }
}

void gettags_current(char *path, string &currentpiece, int bold, int underline, int fg, int bg, Tcl_Obj* objptr, int *substance) {
  char tag[10];
  char *dst;
  int flags;
  int length;
  tag[0]=0;
  if(currentpiece.length()>0) {
    if(*substance) Tcl_AppendStringsToObj(objptr," ",NULL);
    *substance=1;
    sprintf(tag,"%d,%d",fg%16,bg%16);
    if(bold) strcat(tag,"b");
    if(underline) strcat(tag,"u");
    
    dst=(char *)malloc(Tcl_ScanCountedElementFixed(currentpiece.data(),currentpiece.length(),&flags));
    flags|=TCL_DONT_USE_BRACES;
    length=Tcl_ConvertCountedElement(currentpiece.data(), currentpiece.length(), dst, flags);
    Tcl_AppendToObj(objptr,dst,length);
    free(dst);
    
    Tcl_AppendStringsToObj(objptr," ",tag,NULL);
    
    currentpiece="";
  }
}

Tcl_Obj *gettags(Tcl_Obj *strobj, char *path) {
  int bold=0;
  int underline=0;
  int default_foreground;
  int default_background;
  int reversefg, reversebg;
  int fg, bg;
  string currentpiece;
  char ansicode[TEMPLEN];
  char col[3]={0};
  int temp;
  int n, i;
  int parsed;
  int state;
  int dobreak;
  Tcl_Obj *objptr;
  int substance;
  char *message;
  int length;

  // Path must be a valid pathname or a null pointer, not an empty string.
  // What if we remove this entirely forcing it to be a valid single pathname?
  if(!path) path=currentwindow;

  default_foreground=TT_IntF(TT_ARGS,"set ::dynamic::theme_[windowtype %s]_foreground",path);
  default_background=TT_IntF(TT_ARGS,"set ::dynamic::theme_[windowtype %s]_background",path);
  reversefg=default_foreground;
  reversebg=default_background;
  fg=default_foreground;
  bg=default_background;

  message=Tcl_GetStringFromObj(strobj,&length);

  objptr=Tcl_NewStringObj("",0);
  Tcl_IncrRefCount(objptr);

  if(length) {
    substance=0;
    
    for(n=0;n<length;n++) {
      parsed=0;
      if(message[n]==0x02) {
	parsed=1;
	gettags_current(path,currentpiece,bold,underline,fg,bg,objptr,&substance);
	bold=1-bold;
      }
      if(message[n]==0x16) {
	parsed=1;
	gettags_current(path,currentpiece,bold,underline,fg,bg,objptr,&substance);
	temp=fg;
	fg=bg;
	bg=temp;
      }
      if(message[n]==0x1f) {
	parsed=1;
	gettags_current(path,currentpiece,bold,underline,fg,bg,objptr,&substance);
	underline=1-underline;
      }
      if(message[n]==0x0f) {
	parsed=1;
	gettags_current(path,currentpiece,bold,underline,fg,bg,objptr,&substance);
	bold=0;
	underline=0;
	reversebg=default_background;
	reversefg=default_foreground;
	bg=default_background;
	fg=default_foreground;
      }
      if(message[n]==0) {
	parsed=1;
      }
      if(message[n]==0x03) {
	parsed=1;
	gettags_current(path,currentpiece,bold,underline,fg,bg,objptr,&substance);
	state=0;
	while(state!=100) {
	  n++;
	  switch(state) {
          case 0:
            /* We just found a \x03 */
            if(message[n]==',') {
	      reversefg=default_foreground;
              fg=default_foreground;
              state=10;
            } else if((message[n]>='0')&&(message[n]<='9')) {
              col[0]=message[n];
              col[1]=0;
              state=1;
            } else {
              reversebg=default_background;
	      reversefg=default_foreground;
	      bg=default_background;
              fg=default_foreground;
              n--; state=100;
            }
            break;
          case 1:
            /* If we are here, we have a first char */
            if(message[n]==',') {
	      reversefg=atoi(col);
              fg=atoi(col);
              state=10;
            } else if((message[n]>='0')&&(message[n]<='9')) {
              col[1]=message[n];
	      reversefg=atoi(col);
              fg=atoi(col);
              state=9;
            } else {
	      reversefg=atoi(col);
              fg=atoi(col);
              n--; state=100;
            }
	    break;
          case 9:
            /* If this state occurs we are waiting for a comma or an EOC */
            if(message[n]==',') {
              state=10;
            } else {
              n--; state=100;
            }
            break;
          case 10:
            /* If we are here, we have a comma */
            if((message[n]>='0')&&(message[n]<='9')) {
              col[0]=message[n];
              col[1]=0;
              state=11;
            } else {
	      reversebg=default_background;
              bg=default_background;
              n--; state=100;
            }
            break;
          case 11:
            /* If we are here, we have a first char of second set */
            if((message[n]>='0')&&(message[n]<='9')) {
              col[1]=message[n];
	      reversebg=atoi(col);
              bg=atoi(col);
              state=100;
            } else {
	      reversebg=atoi(col);
              bg=atoi(col);
              n--; state=100;
            }
	    break;
	  default:
            break;
	  }
	}
      }
      if(message[n]==0x1b) {
	n++;
	if(message[n]=='[') {
	  n++;
	  gettags_current(path,currentpiece,bold,underline,fg,bg,objptr,&substance);
	  strcpy(ansicode,message+n);
	  dobreak=0;
	  for(i=0;i<(signed)strlen(ansicode);i++) {
	    switch(ansicode[i]) {
	    case '0':
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
	    case ';':
	      n++;
	      break;
	    case '\033':
	      dobreak=1;
	      break;
	    case 'm':
	      ansicode[i]=0;
	      parse_ansi_code(ansicode,&bold,&underline,&fg,&bg,&reversefg,&reversebg,path);
	    default:
	      n++;
	      dobreak=1;
	      break;
	    }
	    if(dobreak) break;
	  }
	}
	parsed=1;
	n--;
	// [0;34;42mtest
      }
      if(!parsed) {
	currentpiece+=message[n];
      }
    }
    gettags_current(path,currentpiece,bold,underline,fg,bg,objptr,&substance);
  }
  return objptr;
}

void echo_n(Tcl_Obj *strobj, char *path, int newtext) {
  Tcl_Obj *objs[4];
  Tcl_Obj *objptr;
  Tcl_Obj *newobjptr=0;
  int len;

  // Why DO we need this?
  if(!strlen(path)) return;

  /*
    K, first check for the event_echo.  If it's there, run it with our magical
    text that we got.
    Then, take the returned text object and shove it through gettags.
    If gettags returns something with lenth !zero, output a newline and do
    echotags on the result from gettags.  Proceed to decrement the gettags
    object result counter.
  */
  
  if(TT_Int(TT_ARGS,"expr {\"[info procs event_echo]\"!=\"\"}")) {
    objs[0]=Tcl_NewStringObj("event_echo",10);
    objs[1]=strobj;
    objs[2]=Tcl_NewStringObj(path,strlen(path));
    if(newtext) {
	objs[3]=Tcl_NewStringObj("1",1);
    } else {
	objs[3]=Tcl_NewStringObj("0",1);
    }
    objptr=Tcl_NewListObj(4,objs);
    Tcl_IncrRefCount(objptr);
    if(TCL_ERROR==Tcl_GlobalEvalObj(TT_Interp,objptr)) {
      Tcl_VarEval(TT_Interp,"bgerror \"File: ",__FILE__," Line: ",strnum(__LINE__),"\"",0);
    }
    Tcl_DecrRefCount(objptr);
    objptr=Tcl_GetObjResult(TT_Interp);
    Tcl_IncrRefCount(objptr);
    Tcl_GetStringFromObj(objptr,&len);
    if(len) {
      newobjptr=gettags(objptr,path);
    }
    Tcl_DecrRefCount(objptr);
  } else {
    objptr=strobj;
    newobjptr=gettags(objptr,path);
  }
  if(newobjptr) {
    TT_EvalF(TT_ARGS,"%s.text configure -state normal",path);
    TT_EvalF(TT_ARGS,"%s.text insert end \"\\n\"",path);
    echotags(newobjptr,path,newtext);
    Tcl_DecrRefCount(newobjptr);
  }
}

void echo(char *message, char *path, int newtext) {
  Tcl_Obj *strobj;
  strobj=Tcl_NewStringObj(message,strlen(message));
  Tcl_IncrRefCount(strobj);
  echo_n(strobj,path,newtext);
  Tcl_DecrRefCount(strobj);
}


syntax highlighted by Code2HTML, v. 0.9.1