#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <linux/types.h>
#include <X11/xpm.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "../wmgeneral/wmgeneral.h"
#include "bbrun.xpm"
#include "ctype.h"

#define MAXCMDLEN 256
#define MAXPATHLEN 256
#define DEFAULT_HIST_FILENAME "/.bbrun_history"

#define SIMPLE_WINDOW_ROWS 2
#define SIMPLE_WINDOW_COLS 10
#define ADVANCED_WINDOW_ROWS 3
#define ADVANCED_WINDOW_COLS 10

#define u32 __u32
#define __DEBUG__ 0
#define VERSION "1.6"

int wminet_mask_width = 16;
int wminet_mask_height = 16;
char wminet_mask_bits[16 * 16];
GtkWidget *combo;
GtkWidget *workingDirectoryEntry;

int withdrawnMode = 0;                   // 0 if normal launch mode, 1 if -w is specified
int advancedMode = 0;                    // 0 if the window should be the normal simple one, or 1 for advanced.
int historyLength = 0;                   // The number of items in history, or -1 if history file could not be created.
int histIndex = 0;
char historyFilename[MAXPATHLEN + 1];    // The path to the history file, +1 is to allow room for '\0'

// TOC = Table Of Contents, it is a dynamically allocated array of pointers to dynamically
// allocated history items. 
u32 *histTOC;				 // Always keep track of the beginning, this one is NEVER incremented.
u32 *histTOC2;				 // We increment this one for each item

void parseArguments(int, char **);
void execDialogInformation();
void readHistory(void);
int runwin();
void updateHistory(char *);
void writeHistory(void);

void callback(GtkWidget *, gpointer);
void usage(void);

/********************************
 *           Main               *
 ********************************/
int main(int argc, char *argv[]) 
{
  int xfd;
  int buttonStatus = -1;
  int i;
  fd_set rfds;
  XEvent Event;

  gtk_init(&argc, &argv);
  parseArguments(argc, argv);
  readHistory();
  
  if (withdrawnMode) {
    runwin();
    exit(0);
  }

  createXBMfromXPM(wminet_mask_bits, bbrun, wminet_mask_width, wminet_mask_height);
  openXwindow(argc, argv, bbrun, wminet_mask_bits, wminet_mask_width, wminet_mask_height);
  AddMouseRegion(0, 0, 0, 16, 16);   // whole area
  xfd = ConnectionNumber(display);

  while (1) {
    RedrawWindow();
    FD_ZERO(&rfds);
    FD_SET(xfd, &rfds);
    select(xfd + 1, &rfds, NULL, NULL, NULL);

    // X Events
    while (XPending(display)) {
      XNextEvent(display, &Event);
      switch (Event.type) {
        case Expose:
          RedrawWindow();
          break;
        case DestroyNotify:
          XCloseDisplay(display);
          exit(0);
          break;
        case ButtonPress:
          buttonStatus = CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
          break;
        case ButtonRelease:
          i = CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
          if ((buttonStatus == i) && (buttonStatus == 0)) {
            runwin();
            break;
          }
          buttonStatus = -1;
          RedrawWindow();
          break;
      }
    }
  }
  
  return 0;
}

void parseArguments(int argc, char *argv[]) 
{
  int curOpt;
  int optionIndex = 0;
  static struct option validOptions[] = {
    {"help", no_argument, 0, 'h'},
    {"history-file", required_argument, 0, 'i'},
    {"version", no_argument, 0, 'v'},
    {"withdraw", no_argument, 0, 'w'},
    {"advanced", no_argument, 0, 'a'},
    {0, 0, 0, 0}
  };

  bzero(historyFilename, MAXPATHLEN + 1);

  while ((curOpt = getopt_long(argc, argv, "hi:vwa", validOptions, &optionIndex)) > -1) {
    switch (curOpt) {
      case 'h':
        usage();
        exit(0);
      case 'i':
        strncpy(historyFilename, optarg, MAXPATHLEN);
        break;
      case 'v':
        printf("BBrun Version %s\n", VERSION);
        exit(0);
      case 'w':
        withdrawnMode = 1;
        break;
      case 'a':
        advancedMode = 1;
        break;
      case '?': 	// getopt_long has already printed an error message, so just exit out.  
        exit( -1);
      default:
	// getopt_long thought the option was fine, but we haven't coded a proper handler for it yet.
        fprintf(stderr, "Invalid parameter '%c'.\n", curOpt);
        exit( -1);
    }
  }

  if (historyFilename[0] == '\0') {
    strncat(historyFilename, getenv("HOME"), MAXPATHLEN - strlen(DEFAULT_HIST_FILENAME) - 1);
    strncat(historyFilename, DEFAULT_HIST_FILENAME, strlen(DEFAULT_HIST_FILENAME));
  }

  if (__DEBUG__) {
    fprintf(stderr, "parseArguments() is complete.\n");
    fprintf(stderr, "historyFilename is '%s'.\n", historyFilename);
  }
}


void readHistory(void) {
  char buf[MAXCMDLEN];
  char *item;
  FILE *fp;

  histTOC = malloc(sizeof(u32) * 1);
  histTOC2 = histTOC;

  if ((fp = fopen(historyFilename, "r")) == 0) {
    if ((fp = fopen(historyFilename, "w")) == 0) {
      fprintf(stderr, "Error creating history file '%s'.\n", historyFilename);
      historyLength = -1;
      return;
    }

    if (__DEBUG__) 
      fprintf(stderr, "Just finished creating a new history file '%s'.\n", historyFilename);

    fclose(fp);

    return;
  }

  while (fgets(buf, MAXCMDLEN, fp) != 0) {
    if (buf != NULL) {
      historyLength++;

      histTOC = realloc(histTOC, sizeof(u32) * historyLength);
      histTOC2 = histTOC + historyLength - 1;

      item = malloc(strlen(buf));
      strncpy(item, buf, strlen(buf));
      item[strlen(buf)- 1] = 0x0;			// Remove the newline char

      *histTOC2 = (u32) item;
    } else {
      // scott@furt.com, This is a NULL line, which should NEVER happen.  Stop any further processing, 
      // because chances are very good that the rest of the file is corrupt too.
      if (__DEBUG__)
	fprintf(stderr, "Null line encountered in history file.  Stopping at %d history items.\n", historyLength);
      break;
    }
  }

  fclose(fp);

  if (__DEBUG__) {
    histTOC2 = histTOC;

    fprintf(stderr, "Finished reading old history file. History items are: \n");
    for (histIndex = 0; histIndex < historyLength; histIndex++) {
      fprintf(stderr, "\t[%02d] '%s'\n", histIndex, (char *) *histTOC2);
      histTOC2++;
    }
  }

//  for (histTOC2 = histTOC; *histTOC2; histTOC2++)
//    free(*histTOC2);
//  free(histTOC);

}

void updateHistory(char *newHistoryItem) 
{
  int duplicate = -1;
  int historyIndex;
  char *item;
  u32 *histTransit;		// Before, we would copy the data around, now we play around
 				// with the pointers, which should be more efficient. 

  if (__DEBUG__)
    fprintf(stderr, "Adding '%s' to history... ", newHistoryItem);

  if (historyLength == -1)
    return;

  // See if the command is in history already
  histTOC2 = histTOC;
  for (historyIndex = 0; historyIndex < historyLength; historyIndex++) {
    if (strncmp((char *) *histTOC2, newHistoryItem, MAXCMDLEN) == 0) {
      duplicate = historyIndex;
      break;
    }
    histTOC2++;
  }

  if (duplicate != -1) {
    if (__DEBUG__)
      fprintf(stderr, " duplicate of item [%02d].\n", duplicate);

    if (duplicate != (historyLength - 1)) {	// If the duplicate entry is not at the end 
      histTransit = (u32 *) (histTOC + duplicate);

      // Shift each entry forward
      for (historyIndex = duplicate; historyIndex < historyLength - 1; historyIndex++) {
	*histTOC2 = *(histTOC2+1); 
	histTOC2++;
      }

      // put duplicate at the end
      histTOC2 = histTOC + historyLength - 1;
      *histTOC2 = (u32) histTransit; 
    }
  } else {
    // The command is NOT in the history already, so add it
    if (__DEBUG__)
      fprintf(stderr, " new history item.\n");

    historyLength++;

    // Set the last item of the history to be the new command
    histTOC = realloc(histTOC, sizeof(u32) * historyLength);
    histTOC2 = histTOC + historyLength - 1;

    item = malloc(MAXCMDLEN + 1);

    strncpy(item, newHistoryItem, MAXCMDLEN + 1);
    item[strlen(item)] = 0x0;
    *histTOC2 = (u32) item;
  }

  if (__DEBUG__) {
    histTOC2 = histTOC;
    fprintf(stderr, "History shuffling complete.  New history is: \n");
    for (histIndex = 0; histIndex < historyLength; histIndex++) {
      fprintf(stderr, "\t[%02d] '%s'\n", histIndex, (char *) *histTOC2);
      histTOC2++;
    }
  }

  writeHistory();
}

void writeHistory() 
{
  FILE *fp;

  if ((fp = fopen(historyFilename, "w")) == 0) {
    fprintf(stderr, "Could not open history file '%s' for writing.  History will not be saved.\n", historyFilename);
    return;
  }

  histTOC2 = histTOC;
  for (histIndex = 0; histIndex < historyLength; histIndex++) {
    fprintf(fp, "%s\n", (char *) *histTOC2);
    histTOC2++;
  }

  fclose(fp);

  if (__DEBUG__)
    fprintf(stderr, "Finished writing new history file.\n");
}

// Event handler for Ok and Cancel buttons
void callback (GtkWidget * widget, gpointer data) 
{
  if ((char *) data == "ok") 
    execDialogInformation();

  gtk_main_quit();
}


// This handles executing the information in the dialog box when the user hits 'ok'
void execDialogInformation() 
{
  gchar *workingPath = '\0';
  gchar *command = '\0';
  char backgroundCommand[MAXCMDLEN + 3];
  char *originalWorkingDirectory = '\0';

  command = (gchar *) gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(combo)->entry));
  if (command[0] != '\0') {
    bzero(backgroundCommand, MAXPATHLEN + 3);
    if (__DEBUG__)
      fprintf(stderr, "Command retrieved from input box as '%s'.\n", command);
    command = strtok(command, "\n");
    updateHistory(command);
    strncpy(backgroundCommand, command, MAXCMDLEN);
    strcat(backgroundCommand, " &");
    if (advancedMode) {
      originalWorkingDirectory = getcwd(NULL, 0);
      workingPath = (gchar *) gtk_entry_get_text(GTK_ENTRY(workingDirectoryEntry));
      if (__DEBUG__) {
        fprintf(stderr, "Original working directory is '%s'\n", originalWorkingDirectory);
        fprintf(stderr, "Execution working path is '%s'\n", workingPath);
        fprintf(stderr, "Command to execute is '%s'\n", backgroundCommand);
      }
      if (workingPath[0] == '\0')
        system(backgroundCommand);
      else {
        if (chdir(workingPath) == 0) {
          system(backgroundCommand);
          if (chdir(originalWorkingDirectory) != 0)
            perror(originalWorkingDirectory);
        } else
          perror(workingPath);
      }
      if (originalWorkingDirectory) 
	free(originalWorkingDirectory);
    } else 
      system(backgroundCommand);
  }
}


gboolean key_pressed(GtkWidget *widget, GdkEventKey *event) {
  if(widget && event && event->keyval == GDK_Escape) {
    gtk_widget_destroy(widget);
    gtk_main_quit();
  }
  
  return FALSE;
}



int runwin()
{
  GtkWidget *window;
  GtkWidget *cancelButton;
  GtkWidget *okButton;
  GtkWidget *table;
  GtkWidget *buttonPanel;
  GList *combo_items = NULL;
  int c = 0;
  int windowRows, windowCols;
  char *pwd;

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW (window), "Run");
  gtk_container_set_border_width(GTK_CONTAINER(window), 10); 	// Sets the border width of the window


  // Create a text entry area with drop down history list
  combo = gtk_combo_new();
  gtk_widget_set_usize(combo, 300, 30);
  gtk_combo_set_use_arrows_always(GTK_COMBO(combo), TRUE); 	// scroll through the list with the arrow keys.
  gtk_signal_connect(GTK_OBJECT(GTK_COMBO(combo)->entry), "activate", GTK_SIGNAL_FUNC(callback), "ok");
  gtk_window_set_focus(GTK_WINDOW(window), GTK_COMBO(combo)->entry); // focus the entry area so we can type right away.

  // Instead of appending the items in reverse order, we prepend each item.
  // This fixes a problem with the arrow keys cycling through the list,
  // You would only be able to go up the list from oldest item to newest.
  histTOC2 = histTOC;
  for (c = 0; c < historyLength; c++) {
    combo_items = g_list_prepend(combo_items, (char *) *histTOC2);
    histTOC2++;
  }

  combo_items = g_list_prepend(combo_items, "");
  gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_items);
  g_list_free(combo_items);

  // Create the Cancel button
  cancelButton = gtk_button_new_with_label("Cancel");
  gtk_signal_connect(GTK_OBJECT(cancelButton), "clicked", GTK_SIGNAL_FUNC(callback), (gpointer) "cancel");
  gtk_signal_connect_object(GTK_OBJECT(cancelButton), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(window));

  // Create the Ok button
  okButton = gtk_button_new_with_label("OK");
  gtk_signal_connect(GTK_OBJECT(okButton), "clicked", GTK_SIGNAL_FUNC(callback), (gpointer) "ok");
  gtk_signal_connect_object(GTK_OBJECT(okButton), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(window));

  if (advancedMode) {
    windowRows = ADVANCED_WINDOW_ROWS;
    windowCols = ADVANCED_WINDOW_COLS;
  } else {
    windowRows = SIMPLE_WINDOW_ROWS;
    windowCols = SIMPLE_WINDOW_COLS;
  }

  table = gtk_table_new(windowRows, windowCols, FALSE); // Create a table to pack things in

  // Group the buttons together for ease of placement
  buttonPanel = gtk_hbox_new(FALSE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(buttonPanel), 1);
  gtk_container_add(GTK_CONTAINER(buttonPanel), cancelButton);
  gtk_container_add(GTK_CONTAINER(buttonPanel), okButton);
  gtk_widget_show(okButton);
  gtk_widget_show(cancelButton);

  // Place the buttons and the combo box
  gtk_table_attach_defaults(GTK_TABLE(table), combo, 0, windowCols, 0, 1);
  gtk_widget_show(combo);
  gtk_table_attach_defaults(GTK_TABLE(table), buttonPanel, windowCols - 6, windowCols, windowRows - 1, windowRows);
  gtk_widget_show(buttonPanel);

  if (advancedMode) {
    GtkWidget *workingDirectoryFrame;
    GtkWidget *workingDirectoryBoxLayout;

    // Create and set up the 'working directory' text box
    workingDirectoryFrame = gtk_frame_new(" Working Directory ");
    gtk_table_attach_defaults(GTK_TABLE(table), workingDirectoryFrame, 0, windowCols, 1, 2);
    gtk_widget_show(workingDirectoryFrame);

    workingDirectoryBoxLayout = gtk_hbox_new(FALSE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(workingDirectoryBoxLayout), 2);
    gtk_container_add(GTK_CONTAINER(workingDirectoryFrame), workingDirectoryBoxLayout);
    gtk_widget_show(workingDirectoryBoxLayout);

    workingDirectoryEntry = gtk_entry_new_with_max_length(MAXPATHLEN);

    if ((pwd = getenv("PWD")))	// In case PWD is not availalbe
      gtk_entry_set_text(GTK_ENTRY(workingDirectoryEntry), pwd);

    gtk_entry_set_editable(GTK_ENTRY(workingDirectoryEntry), TRUE);
    gtk_entry_select_region(GTK_ENTRY(workingDirectoryEntry), 0, GTK_ENTRY(workingDirectoryEntry)->text_length);
    gtk_container_add(GTK_CONTAINER(workingDirectoryBoxLayout), workingDirectoryEntry);
    gtk_widget_show(workingDirectoryEntry);
  }

  gtk_container_add(GTK_CONTAINER(window), table);	// Put the table in the main window
  gtk_widget_show(table);
  gtk_widget_show(window);

  g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(key_pressed), NULL);
  g_signal_connect (GTK_OBJECT(window), "destroy", G_CALLBACK (gtk_main_quit), NULL);

  GTK_WIDGET_SET_FLAGS (okButton, GTK_CAN_DEFAULT);
  gtk_widget_grab_default (okButton);

  gtk_main();					// Rest in gtk_main and wait for the fun to begin!

  return 0;
}

void usage (void) 
{
  fprintf(stderr, "bbrun v%s - Josh King <jking@dwave.net>\n", VERSION);
  fprintf(stderr, "Usage: bbrun [GENERAL OPTIONS] [-- [-display <display>]]...\n");
  fprintf(stderr, "\n");
  fprintf(stderr, " -a, --advanced\t\tadvanced mode, shows more options than the normal\n");
  fprintf(stderr, "\t\t\tcommand entry box.  The default is for it to be in\n");
  fprintf(stderr, "\t\t\tnormal (traditional/simple) mode.\n");
  fprintf(stderr, " -h, --help\t\tthis help screen\n");
  fprintf(stderr, " -i, --history-file=<history file>\n");
  fprintf(stderr, "\t\t\tset the history file to use.\n");
  fprintf(stderr, "\t\t\t(default: '~%s')\n", DEFAULT_HIST_FILENAME);
  fprintf(stderr, " -v, --version\t\tprint the version number\n");
  fprintf(stderr, " -w, --withdraw\t\twithdrawn mode, will go straight to command entry box\n\n");
  fprintf(stderr, "Options that must occur after '--' if they are used:\n\n");
  fprintf(stderr, " -display <display>\tset the display that the gearbox should show up on\n\n");

  exit(0);
}
