/*
   Copyright (C) 1998,1999,2000,2001
   T. Scott Dattalo and Ralf Forsberg

This file is part of gpsim.

gpsim is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

gpsim is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with gpsim; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

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

#include "../config.h"
#ifdef HAVE_GUI

#include <unistd.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <glib.h>
#include <string.h>

#include <assert.h>

#include "../src/sim_context.h"
#include "../src/interface.h"

#include "gui.h"
#include "gui_regwin.h"
#include "gui_symbols.h"
#include "gui_watch.h"
#include "gui_processor.h"
#include "gui_src.h"

struct symbol_entry {
    unsigned int value; // symbol value
    struct cross_reference_to_gui *xref;
};

typedef enum {
    MENU_ADD_WATCH,
} menu_id;


typedef struct _menu_item {
    char *name;
    menu_id id;
    GtkWidget *item;
} menu_item;

static menu_item menu_items[] = {
    {"Add to watch window", MENU_ADD_WATCH},
};


class GUISymbol
{
public:
  symbol *s;

  virtual void select(void);

};

// Used only in popup menus
Symbol_Window *popup_sw;

static void update_menus(Symbol_Window *sw)
{
    GtkWidget *item;
    unsigned int i;

    for (i=0; i < (sizeof(menu_items)/sizeof(menu_items[0])) ; i++){
	item=menu_items[i].item;
	if(sw)
	{
            Value *entry;
	    entry = (Value*) gtk_clist_get_row_data(GTK_CLIST(sw->symbol_clist),sw->current_row);
	    if(entry==0)
		gtk_widget_set_sensitive (item, FALSE);
	    else
		gtk_widget_set_sensitive (item, TRUE);
	}
	else
	{
	    gtk_widget_set_sensitive (item, FALSE);
	}
    }
}

// called when user has selected a menu item
static void
popup_activated(GtkWidget *widget, gpointer data)
{
  menu_item *item;

  if(widget==0 || data==0)
    {
      printf("Warning popup_activated(%p,%p)\n",widget,data);
      return;
    }
    
  item = (menu_item *)data;

  Value *entry = 
    (Value*) gtk_clist_get_row_data(GTK_CLIST(popup_sw->symbol_clist),popup_sw->current_row);

  if(!entry)
    return;

  switch(item->id) {

  case MENU_ADD_WATCH:
    {
      //GUIRegister *reg = (*popup_sw->gp->regwin_ram)[entry->value];
      //popup_sw->gp->watch_window->Add(popup_sw->gp->regwin_ram->type, reg);
      popup_sw->gp->watch_window->Add(entry);
    }
    break;
  default:
    puts("Unhandled menuitem?");
    break;
  }
}

// helper function, called from do_popup
static GtkWidget *
build_menu(GtkWidget *sheet, Symbol_Window *sw)
{
  GtkWidget *menu;
  GtkWidget *item;
  unsigned int i;

  if(sheet==0 || sw==0)
  {
      printf("Warning build_menu(%p,%p)\n",sheet,sw);
      return 0;
  }
    
  popup_sw = sw;
  
  menu=gtk_menu_new();

  item = gtk_tearoff_menu_item_new ();
  gtk_menu_append (GTK_MENU (menu), item);
  gtk_widget_show (item);
  
  for (i=0; i < (sizeof(menu_items)/sizeof(menu_items[0])) ; i++){
      menu_items[i].item=item=gtk_menu_item_new_with_label(menu_items[i].name);

      gtk_signal_connect(GTK_OBJECT(item),"activate",
			 (GtkSignalFunc) popup_activated,
			 &menu_items[i]);

      gtk_widget_show(item);
      gtk_menu_append(GTK_MENU(menu),item);
  }

  update_menus(sw);
  
  return menu;
}

// button press handler
static gint
do_popup(GtkWidget *widget, GdkEventButton *event, Symbol_Window *sw)
{

    GtkWidget *popup;
//	GdkModifierType mods;

  if(widget==0 || event==0 || sw==0)
  {
      printf("Warning do_popup(%p,%p,%p)\n",widget,event,sw);
      return 0;
  }
  popup=sw->popup_menu;
    if( (event->type == GDK_BUTTON_PRESS) &&  (event->button == 3) )
    {

      gtk_menu_popup(GTK_MENU(popup), 0, 0, 0, 0,
		     3, event->time);
    }
    return FALSE;
}

static void unselect_row(GtkCList *clist,
			 gint row,
			 gint column,
			 GdkEvent *event,
			 Symbol_Window *sw)
{
    update_menus(sw);
}


void Symbol_Window::Update(void)
{



  load_symbols=1;
    
  if(!enabled)
    return;
    

  gtk_clist_freeze(GTK_CLIST(symbol_clist));
    
  gtk_clist_clear(GTK_CLIST(symbol_clist));

  // free all old allocations
  /*
  GList *iter;
  for(iter=symbols;iter!=0;)
    {
      GList *next=iter->next;
      g_list_remove(iter,iter->data);
      iter=next;
    }
  */
  while (symbols)
    symbols = g_list_remove(symbols,symbols->data);

  Symbol_Table &st = CSimulationContext::GetContext()->GetSymbolTable();
  Symbol_Table::iterator symIt;
  Symbol_Table::iterator symItEnd = st.end();

  for(symIt=st.begin(); symIt != symItEnd; symIt++) {
    Value *sym = *symIt;
    // ignore line numbers
    if((typeid(*sym) == typeid(line_number_symbol) )            ||
       (filter_addresses && (typeid(*sym) == typeid(address_symbol)))  ||
       (filter_constants && (typeid(*sym) == typeid(Integer))) ||
       (filter_registers && (typeid(*sym) == typeid(register_symbol)))) {

      continue;
    }

    
    char **entry = (char**)malloc(3*sizeof(char*));
    const int cMaxLength = 32;

    entry[0] = g_strndup(sym->name().c_str(), cMaxLength);
    entry[1] = g_strndup(sym->showType().c_str(), cMaxLength);
    entry[2] = (char*)malloc(cMaxLength);
    if (typeid(*sym) == typeid(register_symbol)) {
      Register * pReg = ((register_symbol*)sym)->getReg();
      int iValue;
      sym->get(iValue);
      snprintf(entry[2], cMaxLength, "%02x / %d (0x%02x)", pReg->address, iValue, iValue);
    }
    else {
      sym->get(entry[2],cMaxLength);
    }
    char *pLF = strchr(entry[2], '\n');
    if(pLF != 0) {
      *pLF = 0;
    }

    symbols=g_list_append(symbols,sym);

    int row = gtk_clist_append(GTK_CLIST(symbol_clist),entry);
    gtk_clist_set_row_data(GTK_CLIST(symbol_clist),row,sym);

  }

  gtk_clist_thaw(GTK_CLIST(symbol_clist));

}

static void do_symbol_select(Symbol_Window *sw, Value *e)
{

  if(!sw || !sw->gp)
    return;

  // Do what is to be done when a symbol is selected.
  // Except for selecting the symbol row in the symbol_clist

  if(typeid(*e) == typeid(line_number_symbol) ||
     typeid(*e) == typeid(address_symbol)) {
    if(sw->gp->source_browser)
      sw->gp->source_browser->SelectAddress(e);
    if(sw->gp->program_memory)
      sw->gp->program_memory->SelectAddress(e);
  } else 
    if(typeid(*e) == typeid(register_symbol))
      if(sw->gp->regwin_ram)
	sw->gp->regwin_ram->SelectRegister(e);
}

static gint symbol_list_row_selected(GtkCList *symlist,gint row, gint column,GdkEvent *event, Symbol_Window *sw)
{
  if(!symlist || !sw)
    return 0;

  Value *e=(Value*)gtk_clist_get_row_data(symlist,row);
  sw->current_row=row;
  do_symbol_select(sw,e);
  update_menus(sw);
  return 0;
}

/*
 pop up symbol window and select row with regnumber if it exists
 */
void SymbolWindow_select_symbol_regnumber(Symbol_Window *sw, int regnumber)
{
    GList *p;
    if(!sw)
      return;
    if(!sw->enabled)
	return;
    
    p=sw->symbols;
    while(p)
    {
      Value *e = (Value*)p->data;

      if(typeid(*e) == typeid(register_symbol)) {

	int i;

	e->get(i);

	if(i == regnumber) {
	  int row;
	  row=gtk_clist_find_row_from_data(GTK_CLIST(sw->symbol_clist),e);

	  if(row!=-1) {
	    gtk_clist_select_row(GTK_CLIST(sw->symbol_clist),row,0);
	    gtk_clist_moveto(GTK_CLIST(sw->symbol_clist),row,0,0.5,0.5);

	    do_symbol_select(sw,e);
	  }
	  break;
	}
	p=p->next;
      }
    }
       
}

void Symbol_Window::SelectSymbolName(char *symbol_name)
{
  cout << "SelectSymbolName is broken\n";
#if 0
  GList *p;
  //sym *s;
    
  if(!symbol_name)
    return;

  // If window is not displayed, then display it.
  if(!enabled)
    ChangeView(VIEW_SHOW);

  // See if the type of symbol selected is currently filtered out, and
  // if so we unfilter it.
  Value *sym=0;
  Symbol_Table_Iterator sti;


  for(sym=sti.begin(); sym != sti.end(); sym = sti.next()) {

    // ignore line numbers
    if(typeid(*sym) == typeid(line_number_symbol))
		  continue;
    
    if(!strcasecmp((*sti)->name().data(),symbol_name)) {

       if(filter_addresses && (typeid(*sym) == typeid(address_symbol)))
         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (addressesbutton), TRUE);
       else if (filter_constants && (typeid(*sym) == typeid(Integer)))
         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (constantsbutton), TRUE);
       else if (filter_registers && (typeid(*sym) == typeid(register_symbol)))
         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (registersbutton), TRUE);
    }

    
/*
  list <symbol *>::iterator sti = st.begin();

  for(sti = st.begin(); sti != st.end(); sti++) {
    
    if(!strcasecmp((*sti)->name().data(),symbol_name)) {

      switch((*sti)->isa()) {
	    
      case SYMBOL_ADDRESS:
	if(filter_addresses) {
	  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (addressesbutton), TRUE);
	  while(gtk_events_pending()) // FIXME. Not so nice...
	    gtk_main_iteration();
	}
	break;
      case SYMBOL_CONSTANT:
	if(filter_constants) {
	  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (constantsbutton), TRUE);
	  while(gtk_events_pending()) // FIXME. Not so nice...
	    gtk_main_iteration();
	}
	break;
      case SYMBOL_REGISTER:
	if(filter_registers) {
	  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (registersbutton), TRUE);
	  while(gtk_events_pending()) // FIXME. Not so nice...
	    gtk_main_iteration();
	}
	break;
      default:
	break;
      }
      break;
    }
  }
*/

  // Find the symbol and select it from the clist
  p=symbols;
  while(p) {
    
    Value *e;
    e=(sym*)p->data;
    if(!strcasecmp(e->name,symbol_name)) {
      
      int row;
      row=gtk_clist_find_row_from_data(GTK_CLIST(symbol_clist),e);
      if(row!=-1) {
        
				gtk_clist_select_row(GTK_CLIST(symbol_clist),row,0);
        gtk_clist_moveto(GTK_CLIST(symbol_clist),row,0,0.5,0.5);
        
				do_symbol_select(this,e);
		
      }
    }
    p=p->next;
  }

#endif //0
}

void Symbol_Window::NewSymbols(void)
{
  Update();
}

/*
 the function comparing rows of symbol list for sorting
 FIXME this can be improved. When we have equal cells in sort_column
 of the two rows, compare another column instead of returning 'match'.
 */
static gint
symbol_compare_func(GtkCList *clist, gconstpointer ptr1,gconstpointer ptr2)
{
    char *text1, *text2;
    long val1, val2;
    GtkCListRow *row1 = (GtkCListRow *) ptr1;
    GtkCListRow *row2 = (GtkCListRow *) ptr2;

    switch (row1->cell[clist->sort_column].type)
    {
    case GTK_CELL_TEXT:
	text1 = GTK_CELL_TEXT (row1->cell[clist->sort_column])->text;
	break;
    case GTK_CELL_PIXTEXT:
	text1 = GTK_CELL_PIXTEXT (row1->cell[clist->sort_column])->text;
	break;
    default:
	assert(0);
	break;
    }

    switch (row2->cell[clist->sort_column].type)
    {
    case GTK_CELL_TEXT:
	text2 = GTK_CELL_TEXT (row2->cell[clist->sort_column])->text;
	break;
    case GTK_CELL_PIXTEXT:
	text2 = GTK_CELL_PIXTEXT (row2->cell[clist->sort_column])->text;
	break;
    default:
	assert(0);
	break;
    }

    if (!text2)
	assert(0);

    if (!text1)
	assert(0);

    if(1==sscanf(text1,"%li",&val1))
    {
      if(1==sscanf(text2,"%li",&val2))
	return val1-val2;

    }
    return strcmp(text1,text2);
}

static void symbol_click_column(GtkCList *clist, int column)
{
    static int last_col=-1;
    static GtkSortType last_sort_type=GTK_SORT_DESCENDING;
    
    if(last_col==-1)
	last_col=column;

    if(last_col == column)
    {
	if(last_sort_type==GTK_SORT_DESCENDING)
	{
	    gtk_clist_set_sort_type(clist,GTK_SORT_ASCENDING);
	    last_sort_type=GTK_SORT_ASCENDING;
	}
	else
	{
	    gtk_clist_set_sort_type(clist,GTK_SORT_DESCENDING);
	    last_sort_type=GTK_SORT_DESCENDING;
	}
    }

    gtk_clist_set_sort_column(clist,column);
    gtk_clist_sort(clist);
    last_col=column;
}

static int delete_event(GtkWidget *widget,
			GdkEvent  *event,
                        Symbol_Window *sw)
{
    sw->ChangeView(VIEW_HIDE);
    return TRUE;
}

static void
toggle_addresses (GtkToggleButton *button, Symbol_Window *sw)
{
    sw->filter_addresses = !sw->filter_addresses;
    config_set_variable(sw->name(), "filter_addresses", sw->filter_addresses);
    sw->Update();
}

static void
toggle_constants (GtkToggleButton *button, Symbol_Window *sw)
{
    sw->filter_constants = !sw->filter_constants;
    config_set_variable(sw->name(), "filter_constants", sw->filter_constants);
    sw->Update();
}

static void
toggle_registers (GtkToggleButton *button, Symbol_Window *sw)
{
    sw->filter_registers = !sw->filter_registers;
    config_set_variable(sw->name(), "filter_registers", sw->filter_registers);
    sw->Update();
}

static char *symbol_titles[3]={"Name","Type","Address/Value"};

//------------------------------------------------------------------------
// Build
//

void Symbol_Window::Build(void)
{
  if(bIsBuilt)
    return;

  GtkWidget *vbox;
  GtkWidget *scrolled_window;
  GtkWidget *hbox;

  window=gtk_window_new(GTK_WINDOW_TOPLEVEL);

  gtk_window_set_title(GTK_WINDOW(window), "Symbol Viewer");
  
  gtk_window_set_default_size(GTK_WINDOW(window), width,height);
  gtk_widget_set_uposition(GTK_WIDGET(window),x,y);
  gtk_window_set_wmclass(GTK_WINDOW(window),name(),"Gpsim");
  
  gtk_signal_connect (GTK_OBJECT (window), "delete_event",
		      GTK_SIGNAL_FUNC(delete_event), (gpointer)this);

  symbol_clist=gtk_clist_new_with_titles(3,symbol_titles);
  gtk_widget_show(symbol_clist);
  gtk_clist_set_column_auto_resize(GTK_CLIST(symbol_clist),0,TRUE);
  gtk_clist_set_column_auto_resize(GTK_CLIST(symbol_clist),1,TRUE);
  gtk_clist_set_column_auto_resize(GTK_CLIST(symbol_clist),2,TRUE);
  gtk_clist_set_auto_sort(GTK_CLIST(symbol_clist),TRUE);
  gtk_clist_set_compare_func(GTK_CLIST(symbol_clist),
			     (GtkCListCompareFunc)symbol_compare_func);

  gtk_signal_connect(GTK_OBJECT(symbol_clist),"click_column",
		     (GtkSignalFunc)symbol_click_column,0);
  gtk_signal_connect(GTK_OBJECT(symbol_clist),"select_row",
		     (GtkSignalFunc)symbol_list_row_selected,this);
  gtk_signal_connect(GTK_OBJECT(symbol_clist),"unselect_row",
		     (GtkSignalFunc)unselect_row,this);
  gtk_signal_connect(GTK_OBJECT(symbol_clist),
		     "button_press_event",
		     (GtkSignalFunc) do_popup,
		     this);

  scrolled_window=gtk_scrolled_window_new(0, 0);
  gtk_widget_show(scrolled_window);

  vbox = gtk_vbox_new(FALSE,1);
  
  gtk_container_add(GTK_CONTAINER(scrolled_window), symbol_clist);
  
  gtk_container_add(GTK_CONTAINER(window),vbox);



  hbox = gtk_hbox_new(FALSE,1);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE,FALSE,0);
  gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);

  addressesbutton = gtk_check_button_new_with_label ("addresses");
  gtk_box_pack_start (GTK_BOX (hbox), addressesbutton, TRUE, TRUE, 5);
  if(filter_addresses)
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (addressesbutton), FALSE);
  else
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (addressesbutton), TRUE);
  gtk_signal_connect (GTK_OBJECT (addressesbutton), "toggled",
		      GTK_SIGNAL_FUNC (toggle_addresses), (gpointer)this);

  
  constantsbutton = gtk_check_button_new_with_label ("constants");
  gtk_box_pack_start (GTK_BOX (hbox), constantsbutton, TRUE, TRUE, 5);
  if(filter_constants)
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (constantsbutton), FALSE);
  else
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (constantsbutton), TRUE);
  gtk_signal_connect (GTK_OBJECT (constantsbutton), "toggled",
		      GTK_SIGNAL_FUNC (toggle_constants), (gpointer)this);

  
  registersbutton = gtk_check_button_new_with_label ("registers");
  gtk_box_pack_start (GTK_BOX (hbox), registersbutton, TRUE, TRUE, 5);
  if(filter_registers)
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (registersbutton), FALSE);
  else
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (registersbutton), TRUE);
  gtk_signal_connect (GTK_OBJECT (registersbutton), "toggled",
		      GTK_SIGNAL_FUNC (toggle_registers), (gpointer)this);

  gtk_signal_connect_after(GTK_OBJECT(window), "configure_event",
			   GTK_SIGNAL_FUNC(gui_object_configure_event),this);

  
  gtk_widget_show_all (window);
  

  bIsBuilt = true;

  if(load_symbols)
    NewSymbols();

  UpdateMenuItem();
  
  popup_menu=build_menu(window,this);
  
}


Symbol_Window::Symbol_Window(GUI_Processor *_gp)
{


#define MAXROWS  (MAX_REGISTERS/REGISTERS_PER_ROW)
#define MAXCOLS  (REGISTERS_PER_ROW+1)


  menu = "<main>/Windows/Symbols";

  gp = _gp;
  set_name("symbol_viewer");
  wc = WC_misc;
  wt = WT_symbol_window;
  window = 0;

  symbols=0;
  filter_addresses=0;
  filter_constants=1;
  filter_registers=0;

  load_symbols=0;
  
  get_config();

  config_get_variable(name(),"filter_addresses",&filter_addresses);
  config_get_variable(name(),"filter_constants",&filter_constants);
  config_get_variable(name(),"filter_registers",&filter_registers);

  if(enabled)
    Build();
  
}

#endif // HAVE_GUI


syntax highlighted by Code2HTML, v. 0.9.1