#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <pthread.h>
#include <stdio.h>
#include "internal.h"

#include "support.h"
#include "interface.h"

// Used to update the state of the simulator
extern Display* display;
extern Window win;
extern int screen;
extern int width;
extern int height;
extern void simStop();
extern bool simGetPaused();
extern void simPause(bool state);

// Threading variables
static pthread_t gtk_thread;
static pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t init_finished = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t run_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t is_running = PTHREAD_COND_INITIALIZER;

// Called by the menu entry as well as callback for the destruction of the main window
// This blocks until the OpenGL window is destroyed
static void shutdown_callback()
{
    pthread_mutex_lock(&run_lock);
    simStop();
    gdk_threads_leave();
    pthread_cond_wait(&is_running, &run_lock);
    gdk_threads_enter();
    pthread_mutex_unlock(&run_lock);
}

// This passes along resize information to the captured OpenGL window
static bool configure_event(GtkWidget *w, GdkEventConfigure *e)
{
    XWindowChanges changes;
    changes.width = w->allocation.width;
    changes.height = w->allocation.height;
    if (win) { 
        XConfigureWindow(display, win, CWWidth | CWHeight, &changes);
        width = w->allocation.width;
        height = w->allocation.height;
    }
    else {
        width = w->allocation.width;
        height = w->allocation.height;
    }
    
    return TRUE;
}

extern void handleEvent(XEvent &event);

static bool key_press_event(GtkWidget *w, GdkEventKey *e, gpointer data)
{
#if 0
    bool ctrl;
    KeySym key;

    ctrl = e->state&GDK_CONTROL_MASK;
    key = e->keyval;
    
    return handleKeyPress(key, ctrl);
#else
    XEvent event;
    Status status;

    memset(&event, 0x00, sizeof(XEvent));
    event.type = KeyPress;
    event.xkey.keycode = XKeysymToKeycode(display, e->keyval);
    event.xkey.state |= (e->state&GDK_CONTROL_MASK) ? ControlMask : 0;

    if (event.xkey.keycode) {
        status = XSendEvent(display, win, False, 0, &event);
        if (status) {
	  //printf("XSendEvent failed in GTK key handler\n");
        } 
    }

    return false;
#endif
}   

// GTK state variables
static GtkWidget *gtk_window;
static GtkWidget *gl_target;
static guint idle_handler;

// Idle function used to flush the necessary events and synchronize 
// to make the Window object of gl_target sane 
static int gtk_idle(void *arg)
{
    pthread_mutex_lock(&init_lock);
    gtk_idle_remove(idle_handler);
    gdk_flush();
    pthread_cond_signal(&init_finished);
    pthread_mutex_unlock(&init_lock);
    return 0;
}

// This thread enters gtk_main and exits when it is told to quit
// After that it signals the is_running condition variable
static void *thread_work(void *arg)
{
    char *(argv2[2]) = {"DPRSim", NULL};
    char **argv = &argv2[0];
    int argc = 1; 

    //these calls are necessary for multithreading (so the simulator can use update_widget)
    g_thread_init(NULL);
    gdk_threads_init();
    //end threading calls
  
    gtk_set_locale ();
    gtk_init (&argc, &argv);
      
    gtk_window = create_main_window();

    //figure out whether the simulator is in sim or builder mode
    //and display only the appropriate tab (by deleting the others)
    GtkNotebook* nbook = (GtkNotebook*)lookup_widget(gtk_window, "notebook1");
    gtk_notebook_remove_page(nbook, building()?0:1);
        
    gtk_widget_show(gtk_window);

    gl_target = lookup_widget(gtk_window, "gl_target");
    g_signal_connect(GTK_OBJECT(gtk_window), "unrealize", GTK_SIGNAL_FUNC(shutdown_callback), NULL);
    g_signal_connect(GTK_OBJECT(gtk_window), "key_press_event", GTK_SIGNAL_FUNC(key_press_event), NULL);
    g_signal_connect(GTK_OBJECT(gl_target), "configure_event", GTK_SIGNAL_FUNC(configure_event), NULL);
      
    idle_handler = gtk_idle_add((GtkFunction)gtk_idle, NULL);
    
    //get gdk lock
    gdk_threads_enter();
    gtk_main();
    //release lock
    gdk_threads_leave();
    
    pthread_mutex_lock(&run_lock);
    pthread_cond_signal(&is_running);
    pthread_mutex_unlock(&run_lock);
    return NULL;
}

// This creates the gtk window and also spawns the thread for gtk_main
// Then it returns the X Window object for OpenGL to attach to
int create_gtk_win()
{
  
    if (!(worldPtr->search_key_value_list("USE_GTK")=="false")) {
   
      pthread_mutex_lock(&init_lock);
      pthread_create(&gtk_thread, NULL, thread_work, NULL);
      pthread_cond_wait(&init_finished, &init_lock);
      pthread_mutex_unlock(&init_lock);
      
      width = gl_target->allocation.width;
      height = gl_target->allocation.height;
      
      return GDK_WINDOW_XWINDOW(gl_target->window);
    }
    else {
      return 0;
    }
}       

// This takes care of shutting down the GTK thread
// It calls gtk_main_quit and also schedules an immediate event
// so that the gtk_main_quit gets processed immediately
int destroy_gtk_win()
{
    if (worldPtr->search_key_value_list("USE_GTK")=="YES" ||
        worldPtr->search_key_value_list("USE_GTK")=="Yes" ||
        worldPtr->search_key_value_list("USE_GTK")=="yes") {
        pthread_mutex_lock(&run_lock);
        pthread_cond_signal(&is_running);
        gtk_main_quit();
        gtk_timeout_add(0, (gint (*)(void *))gtk_false, NULL);
        pthread_cond_wait(&is_running, &run_lock);
        pthread_mutex_unlock(&run_lock);
    }
    else {
    }

    return 0;
}


//the update_widget functions are for use by the simulator to update 
//the values of widgets (in order to display information to the user)
void update_widget(string widgetName, string widgetType, float val){
  //get gdk lock
  gdk_threads_enter();
  
  GtkWidget* widget = lookup_widget(gtk_window, widgetName.c_str());
  
  if(widgetType == "GtkProgressBar"){
    gtk_progress_bar_set_fraction((GtkProgressBar*)widget, val);
  }
  else {cout<<"Widget update error: "<<widgetType<<" not supported."<<endl;}

  gdk_flush();
  XSync(display, False);
  //release lock
  gdk_threads_leave();
}


void update_widget(string widgetName, string widgetType, string val){
  //get lock
  gdk_threads_enter();
  
  GtkWidget* widget = lookup_widget(gtk_window, widgetName.c_str());
  
  if(widgetType == "GtkProgressBar"){
    gtk_progress_bar_set_text((GtkProgressBar*)widget, val.c_str());
  }
  else if (widgetType == "GtkEntry"){
    gtk_entry_set_text((GtkEntry*)widget, val.c_str());
  }
  else if (widgetType == "GtkLabel"){
    gtk_label_set_text((GtkLabel*)widget, val.c_str());
  }
  else {cout<<"Widget update error: "<<widgetType<<" not supported."<<endl;}
  
  
  gdk_flush();
  XSync(display, False);
  //release lock
  gdk_threads_leave();
}


void display_gtk_message(string message){
  //get lock
  gdk_threads_enter();

  GtkWidget* dialog = gtk_message_dialog_new ((GtkWindow*)gtk_window,
				   GTK_DIALOG_DESTROY_WITH_PARENT,
				   GTK_MESSAGE_INFO,
				   GTK_BUTTONS_CLOSE,
				   message.c_str());
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
  
  gdk_flush();
  XSync(display, False);
  //release lock
  gdk_threads_leave();
}


bool display_gtk_yes_no_box(string message){
  //get lock
  gdk_threads_enter();
  GtkWidget * dialog = gtk_message_dialog_new ((GtkWindow*)gtk_window,
				   GTK_DIALOG_DESTROY_WITH_PARENT,
				   GTK_MESSAGE_WARNING,
				   GTK_BUTTONS_YES_NO,
				   message.c_str());
  gint response = gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);

  gdk_flush();
  XSync(display, False);
  //release lock
  gdk_threads_leave();

  return(response == GTK_RESPONSE_YES);
}


