#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>

#if defined (apollo) || defined (__FreeBSD__)
#include <stdlib.h>
#else
#include <malloc.h>
#endif
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>

#include "proto_decl.h"
#include "browser_init.h"
#include "global_parse.h"
#include "result_types.h"
#include "file_dict.h"
#include "proc_dict.h"
#include "class_dict.h"
#include "global_dict.h"
#include "browser_pars.h"
#include "parse.h"
#include "browser_eve.h"
#include "page.h"

#include "BR_Global.h"
#include "BR_Interface.h"
 
extern Browser br;

/*-------------------------------------------------------------------------
*/
static ScopeType compute_scope(infos)
    BR_ULong infos;
{
  return (infos & BR_INFO_PUBLIC)
    ? PUBLIC_SCOPE
    : ((infos & BR_INFO_PROTECTED)
       ? PROTECTED_SCOPE
       : ((infos & BR_INFO_PRIVATE) ? PRIVATE_SCOPE : UNKNOWN_SCOPE));
}

static int yaerror;
static char * filename;

void
browser_handler(str_prefix, str_syntax_unit, infos, line_num)
    BR_Str*  str_prefix;
    BR_Str*  str_syntax_unit;
    BR_ULong infos;
    BR_ULong line_num;
{
  static BR_Str* erroned_prefix = 0;
  
  if (infos & BR_INFO_ERROR) {
    if (OpVerbose()) {
      if (! yaerror)
	fprintf(stderr, "Browser : parse error in %s line(s) %d",
		filename, line_num);
      else
	fprintf(stderr, " %d", line_num);
    }
    yaerror = 1;
    return;
  }
  if (str_prefix) {
    if (*str_prefix == '?')
      return;
    if (erroned_prefix) {
      BR_Str * s1, *s2;
      
      for (s1 = str_prefix, s2 = erroned_prefix; (*s1)&&(*s2); s1 += 1, s2 += 1)
	if (*s1 != *s2)
	  break;
      
      if (! *s1) {
	if (! *s2)
	  /* member of redefined class */
	  return;
	/* out of redefined class */
	free(erroned_prefix);
	erroned_prefix = 0;
      }
      else if (! *s2) {
	if (*s1 == ':')
	  /* sub class of redefined class */
	  return;
	/* out of redefined class */
	free(erroned_prefix);
	erroned_prefix = 0;
      }
    }
    if (((infos & BR_INFO_CLASS) && (infos & BR_INFO_DEF)) ||
	(infos & (BR_INFO_STRUCT | BR_INFO_UNION | BR_INFO_TYPEDEF))) {
      char * n = (char *)
	malloc(strlen(str_prefix) + strlen(str_syntax_unit) + 3);
      
      sprintf(n, "%s::%s", str_prefix, str_syntax_unit);
      if ((*str_syntax_unit == '?') ||
	  (add_class_decl(n, line_num, (infos & BR_INFO_TYPEDEF) ? 1 : 0, 0) ==
	   BERROR))
	erroned_prefix = n;
      else
	free(n);
    }
    else if (infos & BR_INFO_PARENT)
      add_parent(str_prefix, str_syntax_unit, line_num, compute_scope(infos));
    else if (infos & BR_INFO_PROC) {
      if ((!(infos & BR_INFO_DEF)) ||
	  (infos & (BR_INFO_PUBLIC | BR_INFO_PROTECTED | BR_INFO_PRIVATE))) {
	int decl = ((infos >> 9) & 7)	/* private protected public */
	  | ((infos >> 12) & 0x3f8)	/* inline constructor .. pure */
	    | ((infos >> 14) & 0x400);	/* friend */
	
	add_method_decl(str_prefix, str_syntax_unit, compute_scope(infos),
			decl, line_num, 0);
      }
      if (infos & BR_INFO_DEF) {
	add_method_impl(str_prefix, str_syntax_unit, line_num, 0);
      }
    }
    else if (infos & BR_INFO_DATA) {
      int decl;
      
      decl = ((infos >> 9) & 7)		/* private protected public */
	  | ((infos >> 10) & 8);	/* static */
      add_attribute_decl(str_prefix, str_syntax_unit, compute_scope(infos),
			 decl, line_num, 0);
    }
  }
  else if (*str_syntax_unit == '?')
    ;
  else if (infos & BR_INFO_PROC) {
    if (infos & BR_INFO_DEF)
      add_proc_impl(str_syntax_unit, C_PROC, line_num,
		    ((infos & BR_INFO_STATIC) ? STATIC_PROC : 0) +
		    ((infos & BR_INFO_INLINE) ? INLINE_PROC : 0) +
		    ((infos & BR_INFO_TEMPLATE) ? TEMPLATE_PROC : 0),
		    0);
  }
  else if (infos & BR_INFO_DATA) {
    if (infos & BR_INFO_DEF)
      add_global_impl(str_syntax_unit, (infos & BR_INFO_STATIC) ? 1 : 0,
		      line_num, 0);
  }
  else if (((infos & BR_INFO_CLASS) && (infos & BR_INFO_DEF)) ||
	   (infos & (BR_INFO_STRUCT | BR_INFO_UNION | BR_INFO_TYPEDEF))) {
    /* new toplevel class */
    if (erroned_prefix)
      free(erroned_prefix);
    erroned_prefix =
      (add_class_decl(str_syntax_unit, line_num,
		      ((infos & BR_INFO_TYPEDEF) ? TYPEDEF_TYPE : 0) +
		      ((infos & BR_INFO_TEMPLATE) ? TEMPLATE_TYPE : 0),
		      0)
       == BERROR)
	? (BR_Str*) strdup(str_syntax_unit) : (BR_Str*) 0;
  }
}


/*-------------------------------------------------------------------------
*/

int browser_yyparse(fn)
    char * fn;
{
  yaerror = 0;
  filename = fn;

  if ((! br.mode->pp_flag) || (! br.mode->pp_path)) {
    BR_parse_file(fn, browser_handler, 0);
  }
  else if (! br.mode->pp_options) {
    static char* filter_cmd[] = { BR_Null, BR_Null, BR_Null };

    filter_cmd[0] = br.mode->pp_path;
    filter_cmd[1] = fn;
    BR_parse_file(fn, browser_handler, filter_cmd);
  }
  else {
    char ** filter_cmd;
    char * options;
    char * p;
    unsigned nentry = 3;
    
    options = strdup(br.mode->pp_options);
    p = strtok(options, " \t");
    while (p) {
      nentry += 1;
      p = strtok(0, " \t");
    }
    
    filter_cmd = (char **) malloc(nentry * sizeof(char *));
    filter_cmd[0] = br.mode->pp_path;
    
    strcpy(options, br.mode->pp_options);
    p = strtok(options, " \t");
    nentry = 1;
    
    while (p) {
      filter_cmd[nentry] = strdup(p);
      p = strtok(0, " \t");
      nentry += 1;
    }
    filter_cmd[nentry] = fn;
    filter_cmd[nentry + 1] = BR_Null;
  
    BR_parse_file(fn, browser_handler, filter_cmd);
    
    while (--nentry) {
      free(filter_cmd[nentry]);
    }
    free(filter_cmd);
    free(options);
  }
  
  if (yaerror && OpVerbose())
    fputc('\n', stderr);

  return 0;		/* yaerror */
}

int yywrap() { return 1; }

/*-------------------------------------------------------------------------
   fonctions Smac
*/

void browser_set_pp(modename, path)
    char * modename;
    char * path;
{
  Mode * mode = GetMode(modename);
  
  if (! mode)
    return;
  
  if (mode->pp_path)
    free(mode->pp_path);
  
  mode->pp_path = strdup(path);
}

void browser_set_pp_options(modename, options)
    char * modename;
    char * options;
{
  Mode * mode = GetMode(modename);
  
  if (! mode)
    return;
  
  if (mode->pp_options)
    free(mode->pp_options);
  if (options) {
    mode->pp_options = strdup(options);
    if (! mode->pp_flag) {
      mode->pp_flag = 1;
      RefreshPpButton();
    }
  }
  else {
    mode->pp_options = 0;
    if (mode->pp_flag) {
      mode->pp_flag = 0;
      RefreshPpButton();
    }
  }
}

void browser_add(s, rec)
    char * s;
    int rec;
{
  if ((s != 0) && (strlen (s) != 0 )) {
    struct stat st;
    int old_rec = br.parse_flag;
    char pathname [MAXPATHLEN+2];
    char * here;
    extern char  *getcwd();
    
    br.parse_flag = (rec) ? RECURSIVE : NO_RECURSIVE;
/*    
#ifdef linux
    if ( getwd ( pathname ) == 0 ) {
      (void) fprintf ( stderr, "Can't getwd\n" );
      return;
    }
#else	
*/
    (void) getcwd ( pathname, MAXPATHLEN );
/* #endif */
    here = (char *) malloc(strlen(pathname) + 1);
    if (! here) return;
    (void) strcpy (here, pathname);
    
    (void) stat ( s, &st );
    if ( S_ISDIR(st.st_mode) != 0 ) {
      (void) chdir(s);
/*      
#ifdef linux
      if ( getwd ( pathname ) == 0 ) {
	(void) fprintf ( stderr, "Can't getwd\n" );
	(void) chdir(here);
	free(here);
	return;
      }
#else	
*/
      (void) getcwd ( pathname, MAXPATHLEN );
/* #endif */
      LoadDir ( pathname );
    }
    else if (S_ISREG(st.st_mode) && GoodSuffix (s)) {
      char * p = strrchr(s, '/');
      
      if (! p)
	sprintf(pathname + strlen(pathname), "/%s", s);
      else if (p == pathname)
	sprintf(pathname, "/%s", s);
      else {
	*p = 0;
	(void) chdir(s);
	sprintf(pathname + strlen(pathname), "/%s", p+1);
      }
      parse_file (pathname); 
    }
    br.parse_flag = old_rec;
    (void) RefreshBrowserInfos ();
    
    (void) chdir(here);
    free(here);
  }  
}

char * browser_class_file(class_name, pline)
    char * class_name;
    int * pline;
{
  ClassRec* class = find_class(class_name);
  
  if (class) {
    *pline = class->_decl_line;
    return class->_decl_file->_name;
  }
  else
    return 0;
}


char * browser_class_flags(class_name)
    char * class_name;
{
  ClassRec* class = find_class(class_name);
  
  if (class) {
    return class->_name - CLASS_PLENGTH;
  }
  else
    return 0;
}

static int browser_select_x(prefix, t, w_x, x_plength)
    char * prefix;
    char **t;
    unsigned w_x;
    unsigned x_plength;
{
  if (t) {
    unsigned plen = strlen(prefix);
    int i = 1;
    
    DisplayBrowserIfNeverMap();
    
    while (*t != 0) {
      if (! HIDED(*t)) {
	if (! strncmp(prefix, *t, plen)) {
	  BWin *b_x = &br.tbw[w_x];
	  
	  UpdateTitle (*t - x_plength);
	  /* i = no ligne dans la liste des classes */
	  UpdateTextItem ( b_x -> text, b_x -> select );
	  GotoLineNumber ( b_x -> text, i );
	  CurrentLineToTop ( b_x -> text );
	  UpdateTextItem ( b_x -> text, b_x -> select );
	  SelectItem ( b_x, 5, 5, w_x );
	  
	  return 1;
	}
	i += 1;
      }
      t += 1;
    }
  }
  
  return 0;
}

int browser_select_class(prefix)
    char * prefix;
{
  return browser_select_x(prefix, br.class_save, W_CLASS, CLASS_PLENGTH);
}

int browser_select_method(prefix)
    char * prefix;
{
  return browser_select_x(prefix, br.methods_save, W_METHOD, METHOD_PLENGTH);
}

int browser_select_attribute(prefix)
    char * prefix;
{
  return browser_select_x(prefix, br.attributes_save,
			  W_ATTRIBUTES, ATTRIBUTE_PLENGTH);
}

int browser_select_proc(prefix)
    char * prefix;
{
  return browser_select_x(prefix, br.procs_save, W_PROC, PROC_PLENGTH);
}

int browser_select_global(prefix)
    char * prefix;
{
  return browser_select_x(prefix, br.globals_save, W_GLOBALS, GLOBAL_PLENGTH);
}

static int browser_x_entry(prefix, save)
    char * prefix;
    char ** save;
{
  unsigned plen = strlen(prefix);
  unsigned rang = 0;
 
  if (save)
    for (rang = 0; *save; save += 1, rang += 1)
      if (! strncmp(*save, prefix, plen))
	return rang;
  
  return -1;
}

int browser_class_entry(prefix)
    char * prefix;
{
  return browser_x_entry(prefix, br.class_save);
}

int browser_function_entry(prefix)
    char * prefix;
{
  return browser_x_entry(prefix, br.procs_save);
}

int browser_global_entry(prefix)
    char * prefix;
{
  return browser_x_entry(prefix, br.globals_save);
}

int browser_file_entry(prefix)
    char * prefix;
{
  return browser_x_entry(prefix, br.files_save);
}

static char * browser_x(rang, save)
    int rang;
    char ** save;
{
  if (save)
    for (; *save; save += 1, rang -= 1)
      if (! rang)
	return *save;
  
  return 0;
}

char * browser_class(n)
    int n;
{
  return browser_x(n, br.class_save);
}

char * browser_function(n)
    int n;
{
  return browser_x(n, br.procs_save);
}

char * browser_global(n)
    int n;
{
  return browser_x(n, br.globals_save);
}

char * browser_file(n)
    int n;
{
  return browser_x(n, br.files_save);
}

#define DUMP_VERSION 1
#define TYPE_FIN		0
#define TYPE_CLASS_DECL		1
#define TYPE_PARENT_DECL	2
#define TYPE_METHOD_DECL	3
#define TYPE_METHOD_IMPL	4
#define TYPE_ATTRIBUT		5
#define TYPE_INFO_PROC		6
#define TYPE_INFO_DATA		7

static void dump_nbr(nbr, fp)
    unsigned nbr;
    FILE * fp;
{
  fputc(nbr >> 24, fp);
  fputc((nbr >> 16) & 255, fp);
  fputc((nbr >> 8) & 255, fp);
  fputc(nbr & 255, fp);
}

static void dump_str(str, fp)
    char * str;
    FILE * fp;
{
  dump_nbr(strlen(str) + 1, fp);
  while (*str)
    fputc(*str++, fp);
  fputc(0, fp);
}

extern FCT(void, DisplayWMessage ,(char *, char *, int));

int browser_dump(fn)
    char * fn;
{
  FILE * fp = fopen(fn, "w");
  unsigned nfile = file_count;
  unsigned ifile;
  
  if (! fp) {
    DisplayWMessage("cannot open ", "browser dump", 1);
    DisplayWMessage(fn, 0, 0);
    DisplayWMessage("\n", "browser dump", 0);
    return 0;
  }
  
  fputc(DUMP_VERSION, fp);
  dump_nbr(Hide_Bits, fp);
  
  for (ifile = 0; nfile; ifile += 1) {
    FileRec * current_file;
    
    for (current_file = file_dict[ifile];
	 current_file;
	 current_file = current_file->_next) {
      unsigned index;
      
      nfile -= 1;
      dump_str(current_file->_name, fp);
	
      for (index = 0; index != PROC_DICT_SIZE; index +=1) {
	ProcRec * current_proc = proc_dict[index];
	
	while (current_proc) {
	  if (current_proc->_impl_file == current_file) {
	    fputc(TYPE_INFO_PROC, fp);
	    dump_str(current_proc->_name, fp);
	    dump_nbr(current_proc->_impl_line, fp);
	    fputc(current_proc->_decl, fp);
	    dump_nbr(current_proc->_hide, fp);
	  }
	  current_proc = current_proc->_next;
	}
      }
      
      for (index = 0; index != CLASS_DICT_SIZE; index += 1) {
	ClassRec * current_class = class_dict[index];
	
	while (current_class) {
	  if (current_class->_decl_file == current_file) {
	    fputc(TYPE_CLASS_DECL, fp);
	    dump_str(current_class->_name, fp);
	    dump_nbr(current_class->_decl_line, fp);
	    fputc(current_class->_decl, fp);
	    dump_nbr(current_class->_hide, fp);
	    {
	      ParentRec * current_parent = current_class->_parents_list;
	      
	      while (current_parent) {
		fputc(TYPE_PARENT_DECL, fp);
		dump_str(current_parent->_name, fp);
		dump_nbr(current_parent->_decl_line, fp);
		fputc(current_parent->_scope, fp);
		current_parent = current_parent->_next;
	      }
	    }
	    {
	      MethodRec * current_method = current_class->_methods_list;
	      
	      while (current_method) {
		if (current_method->_decl_file == current_file) {
		  /* si ce n'est pas le cas il y a une double definition
		     de la classe */
		  fputc(TYPE_METHOD_DECL, fp);
		  dump_str(current_method->_name, fp);
		  fputc(current_method->_scope, fp);
		  fputc(current_method->_decl, fp);
		  dump_nbr(current_method->_decl_line, fp);
		  dump_nbr(current_method->_hide, fp);
		  if (current_method->_impl_file == current_file) {
		    fputc(TYPE_METHOD_IMPL, fp);
		    dump_nbr(current_method->_impl_line, fp);
		  }
		  else
		    fputc(TYPE_FIN, fp);
		}
		current_method = current_method->_next;
	      }
	    }
	    {
	      AttributeRec * current_attr = current_class->_attributes_list;
	      
	      while (current_attr) {
		if (current_attr->_decl_file == current_file) {
		  /* si ce n'est pas le cas il y a une double definition
		     de la classe */
		  fputc(TYPE_ATTRIBUT, fp);
		  dump_str(current_attr->_name, fp);
		  fputc(current_attr->_scope, fp);
		  fputc(current_attr->_decl, fp);
		  dump_nbr(current_attr->_decl_line, fp);
		  dump_nbr(current_attr->_hide, fp);
		}
		current_attr = current_attr->_next;
	      }
	    }
	
	    fputc(TYPE_FIN, fp);
	  }
	  else {
	    MethodRec * current_method = current_class->_methods_list;
	    
	    while (current_method) {
	      if (current_method->_impl_file == current_file) {
		fputc(TYPE_METHOD_IMPL, fp);
		dump_str(current_class->_name, fp);
		dump_str(current_method->_name, fp);
		dump_nbr(current_method->_impl_line, fp);
		dump_nbr(current_method->_hide, fp);
	      }
	      current_method = current_method->_next;
	    }
	  }
	  current_class = current_class->_next;
	}
      }
      
      for (index = 0; index != GLOBAL_DICT_SIZE; index += 1) {
	GlobalRec * current_global = global_dict[index];
	
	while (current_global) {
	  if (current_global->_impl_file == current_file) {
	    fputc(TYPE_INFO_DATA, fp);
	    dump_str(current_global->_name, fp);
	    fputc(current_global->_static, fp);
	    dump_nbr(current_global->_impl_line, fp);
	    dump_nbr(current_global->_hide, fp);
	  }
	  current_global = current_global->_next;
	}
      }
      
      fputc(TYPE_FIN, fp);
    }
  }
  
  fclose(fp);
  return 1;
}


static int restore_nbr(fp)
    FILE * fp;
{
  unsigned nbr = fgetc(fp);
  
  nbr = (nbr << 8) + fgetc(fp);
  nbr = (nbr << 8) + fgetc(fp);
  
  return ((nbr << 8) + fgetc(fp));
}

static char * restore_str(fp)
    FILE * fp;
{
  char * str = (char *) malloc(restore_nbr(fp));
  
  if (str) {
    char * s = str - 1;
    
    while ((*++s = fgetc(fp)) != 0) ;
  }
  
  return str;
}

static int restore_abort(fp)
    FILE * fp;
{
  fclose(fp);
  parsed_file = Null;
  class_cache = Null;
  
  RefreshBrowserInfos ();
  DisplayWMessage("wrong dump file or not enough place\n", "browser dump", 1);
  
  return 0;
}

static int garbage_abort(fp)
    FILE * fp;
{
  fclose(fp);
  garbage_global();
  garbage_proc();
  garbage_class();
  parsed_file = Null;
  class_cache = Null;
  
  RefreshBrowserInfos ();
  DisplayWMessage("wrong dump file or not enough place\n", "browser restore", 1);
  
  return 0;
}

int browser_restore(fn)
    char * fn;
{
  FILE * fp = fopen(fn, "r");
  int c;
  
  if ((! fp) || (fgetc(fp) != DUMP_VERSION)) {
    DisplayWMessage("wrong dump file format\n", "browser restore", 1);    
    return 0;
  }

  browser_show_all();
  Hide_Bits = restore_nbr(fp);
  
  while ((c = fgetc(fp)) != EOF) {
    char * file_name;
    int infotype;
    
    ungetc(c, fp);
    
    if ((file_name = restore_str(fp)) == 0)
      return restore_abort(fp);
    
    parsed_file = find_file(file_name);
    if (parsed_file != Null) {
      proc_eraze_file(file_name);
      global_eraze_file(file_name);
      class_eraze_file(file_name);
    }
    else if ((parsed_file = create_file(file_name)) == 0) {
      free(file_name);
      return restore_abort(fp);
    }
    
    free(file_name);
    
    while ((infotype = fgetc(fp)) != TYPE_FIN) {
      switch (infotype) {
      case TYPE_INFO_PROC :
	{
	  char * name = restore_str(fp);
	  int impl_line = restore_nbr(fp);
	  int decl = fgetc(fp);
	  
	  if (! name) {
	    return garbage_abort(fp);
	  }
	  add_proc_impl(name, C_PROC, impl_line, decl, restore_nbr(fp));
	  free(name);
	}
	break;
      case TYPE_CLASS_DECL :
	{
	  char * class_name = restore_str(fp);
	  
	  if (! class_name) {
	    return garbage_abort(fp);
	  }
	  {
	    int decl_line = restore_nbr(fp);
	    int decl = fgetc(fp);
	    
	    add_class_decl(class_name, decl_line, decl, restore_nbr(fp));
	  }
	  
	  while ((infotype = fgetc(fp)) != TYPE_FIN) {
	    switch (infotype) {
	    case TYPE_PARENT_DECL :
	      {
		char * parent = restore_str(fp);
		int decl_line = restore_nbr(fp);
		
		if (! parent) {
		  free(class_name);
		  return garbage_abort(fp);
		}
		add_parent(class_name, parent, decl_line, fgetc(fp));
		free(parent);
	      }
	      break;
	    case TYPE_METHOD_DECL :
	      {
		char * meth = restore_str(fp);
		int scope = fgetc(fp);
		int decl = fgetc(fp);
		int decl_line = restore_nbr(fp);
		int hide = restore_nbr(fp);
		
		if (! meth) {
		  free(class_name);
		  return garbage_abort(fp);
		}
		add_method_decl(class_name, meth, scope, decl, decl_line, hide);
		if (fgetc(fp) == TYPE_METHOD_IMPL)
		  add_method_impl(class_name, meth, restore_nbr(fp), hide);
		free(meth);
	      }
	      break;
	    case TYPE_ATTRIBUT :
	      {
		char * attr = restore_str(fp);
		int scope = fgetc(fp);
		int decl = fgetc(fp);
		int decl_line = restore_nbr(fp);
		
		if (! attr) {
		  free(class_name);
		  return garbage_abort(fp);
		}
		add_attribute_decl(class_name, attr, scope, decl,
				   decl_line, restore_nbr(fp));
		free(attr);
	      }
              break;
	    default:
  	      free(class_name);
	      return garbage_abort(fp);
	    }
	  }
	  free(class_name);
	}
        break;
      case TYPE_METHOD_IMPL :
	{
	  char * class_name = restore_str(fp);
	  char * meth = restore_str(fp);
	  int impl_line = restore_nbr(fp);
	  
	  if (! class_name) {
	    return garbage_abort(fp);
	  }
	  if (! meth) {
	    free(class_name);
	    return garbage_abort(fp);
	  }
	  add_method_impl(class_name, meth, impl_line, restore_nbr(fp));
	  free(class_name);
	  free(meth);
	}
	break;
      case TYPE_INFO_DATA :
	{
	  char * name = restore_str(fp);
	  int staticp = fgetc(fp);
	  int impl_line = restore_nbr(fp);
	  
	  if (! name) {
	    return garbage_abort(fp);
	  }
	  add_global_impl(name, staticp, impl_line, restore_nbr(fp));
	  free(name);
	}
        break;
      default:
        return garbage_abort();
      }
    }
  }

  garbage_global();
  garbage_proc();
  garbage_class();
  parsed_file = Null;
  class_cache = Null;
  fclose(fp);
  
  RefreshBrowserInfos ();
  
  return 1;
}
