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

#include <poppler/glib/poppler.h>
#include <cairo.h>

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

/* macros */
#define LENGTH(x) sizeof(x)/sizeof((x)[0])

/* enums */
enum { UP, DOWN, LEFT, RIGHT, ZOOM_IN, ZOOM_OUT, ZOOM_ORIGINAL, 
      NEXT, PREVIOUS, HIDE, ERROR, WARNING, DEFAULT, TOP, BOTTOM, 
      ADJUST_BESTFIT, ADJUST_WIDTH, FORWARD, BACKWARD };

struct
{
  GtkWindow         *window;
  GtkBox            *box;
  GtkScrolledWindow *view;
  GtkWidget         *drawing_area;
  GtkBox            *notification;
  GtkEntry          *inputbar;

  struct
  {
    GdkColor              default_fg;
    GdkColor              default_bg;
    GdkColor              inputbar_fg;
    GdkColor              inputbar_bg;
    GdkColor              notification_fg;
    GdkColor              notification_bg;
    GdkColor              notification_wn_fg;
    GdkColor              notification_wn_bg;
    GdkColor              notification_er_fg;
    GdkColor              notification_er_bg;
    GdkColor              completion_fg;
    GdkColor              completion_bg;
    GdkColor              completion_hl_fg;
    GdkColor              completion_hl_bg;
    PangoFontDescription *font;
  } Settings;

  struct
  {
    PopplerDocument *document;
    PopplerPage     *page;
    char            *file;
    cairo_surface_t *surface;
    int              page_number;
    int              number_of_pages;
    double           scale;
    int              rotate;
  } PDF;

} Zathura;

typedef struct
{
  int   n;
  void *data;
} Argument;

typedef struct
{
  int mask;
  int key;
  void (*function)(Argument*);
  Argument argument;
} Shortcut;

typedef struct
{
  char *command;
  void (*function)(int, char**);
} Command;

/* function declarations */
void init();
void update_title();
void update_status();
void set_page(int);
void draw();
void hilight_result(PopplerRectangle*);

gboolean complete(Argument*);
gboolean delete_widget(gpointer);
GtkLabel* notify(int, char*);

/* shortcut declarations */
void sc_focus_inputbar(Argument*);
void sc_scroll(Argument*);
void sc_navigate(Argument*);
void sc_zoom(Argument*);
void sc_adjust_window(Argument*);
void sc_rotate(Argument*);
void sc_search(Argument*);
void sc_print(Argument*);
void sc_quit(Argument*);

/* command declarations */
void cmd_goto(int, char**);
void cmd_open(int, char**);
void cmd_rotate(int, char**);
void cmd_quit(int, char**);
void cmd_zoom(int, char**);

/* callbacks declarations */
void cb_draw(GtkWidget*, gpointer);
void cb_destroy(GtkWidget*, gpointer);
void cb_inputbar_activate(GtkEntry*, gpointer);
void cb_inputbar_button_pressed(GtkWidget*, GdkEventButton*, gpointer);

gboolean cb_inputbar_key_pressed(GtkEntry*,  GdkEventKey*, gpointer);
gboolean cb_inputbar_key_released(GtkEntry*, GdkEventKey*, gpointer);
gboolean cb_view_key_pressed(GtkWidget*,     GdkEventKey*, gpointer);

/* configuration */
#include "config.h"

/* function implementations */ 
void
init()
{
  /* settings */
  gdk_color_parse(default_fgcolor,              &(Zathura.Settings.default_fg));
  gdk_color_parse(default_bgcolor,              &(Zathura.Settings.default_bg));
  gdk_color_parse(inputbar_fgcolor,             &(Zathura.Settings.inputbar_fg));
  gdk_color_parse(inputbar_bgcolor,             &(Zathura.Settings.inputbar_bg));
  gdk_color_parse(notification_fgcolor,         &(Zathura.Settings.notification_fg));
  gdk_color_parse(notification_bgcolor,         &(Zathura.Settings.notification_bg));
  gdk_color_parse(notification_warning_fgcolor, &(Zathura.Settings.notification_wn_fg));
  gdk_color_parse(notification_warning_bgcolor, &(Zathura.Settings.notification_wn_bg));
  gdk_color_parse(notification_error_fgcolor,   &(Zathura.Settings.notification_er_fg));
  gdk_color_parse(notification_error_bgcolor,   &(Zathura.Settings.notification_er_bg));
  gdk_color_parse(completion_fgcolor,           &(Zathura.Settings.completion_fg));
  gdk_color_parse(completion_bgcolor,           &(Zathura.Settings.completion_bg));
  gdk_color_parse(completion_hl_fgcolor,        &(Zathura.Settings.completion_hl_fg));
  gdk_color_parse(completion_hl_bgcolor,        &(Zathura.Settings.completion_hl_bg));
  Zathura.Settings.font = pango_font_description_from_string(font);

  /* variables */
  Zathura.window       = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
  Zathura.box          = GTK_BOX(gtk_vbox_new(FALSE, 0));
  Zathura.view         = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
  Zathura.drawing_area = gtk_drawing_area_new();
  Zathura.notification = GTK_BOX(gtk_vbox_new(FALSE, 0));
  Zathura.inputbar     = GTK_ENTRY(gtk_entry_new());
  Zathura.PDF.scale    = 1.0;
  Zathura.PDF.rotate   = 0;

  /* window */
  gtk_window_set_title(Zathura.window, "zathura");
  gtk_window_set_default_size(Zathura.window, 800, 600);
  g_signal_connect(G_OBJECT(Zathura.window), "destroy",         G_CALLBACK(cb_destroy),     NULL);

  /* box */
  gtk_box_set_spacing(Zathura.box, 0);
  gtk_container_add(GTK_CONTAINER(Zathura.window), GTK_WIDGET(Zathura.box));

  /* view */
  gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(Zathura.view), Zathura.drawing_area);
  g_signal_connect(G_OBJECT(Zathura.view), "key-press-event", G_CALLBACK(cb_view_key_pressed), NULL);
  
  #ifdef SHOW_SCROLLBARS
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(Zathura.view), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  #else
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(Zathura.view), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
  #endif

  /* drawing area */
  g_signal_connect(G_OBJECT(Zathura.drawing_area), "expose-event", G_CALLBACK(cb_draw), NULL);

  /* inputbar */
  gtk_entry_set_inner_border(Zathura.inputbar, NULL);
  gtk_entry_set_has_frame(Zathura.inputbar, FALSE);
  gtk_entry_set_editable(Zathura.inputbar,TRUE);
  g_signal_connect(G_OBJECT(Zathura.inputbar), "activate",           G_CALLBACK(cb_inputbar_activate),       NULL);
  g_signal_connect(G_OBJECT(Zathura.inputbar), "button-press-event", G_CALLBACK(cb_inputbar_button_pressed), NULL);
  g_signal_connect(G_OBJECT(Zathura.inputbar), "key-press-event",    G_CALLBACK(cb_inputbar_key_pressed),    NULL);
  g_signal_connect(G_OBJECT(Zathura.inputbar), "key-release-event",  G_CALLBACK(cb_inputbar_key_released),   NULL);

  /* packing */
  gtk_box_pack_start(Zathura.box, GTK_WIDGET(Zathura.view),         TRUE,  TRUE,  0);
  gtk_box_pack_start(Zathura.box, GTK_WIDGET(Zathura.notification), FALSE, FALSE, 0);
  gtk_box_pack_end(Zathura.box,   GTK_WIDGET(Zathura.inputbar),     FALSE, FALSE, 0);

  /* visuals */
  gtk_widget_modify_base(GTK_WIDGET(Zathura.inputbar), GTK_STATE_NORMAL, &(Zathura.Settings.inputbar_bg));
  gtk_widget_modify_text(GTK_WIDGET(Zathura.inputbar), GTK_STATE_NORMAL, &(Zathura.Settings.inputbar_fg));
  gtk_widget_modify_font(GTK_WIDGET(Zathura.inputbar), Zathura.Settings.font);
}

void
update_title()
{
  if(Zathura.PDF.file != NULL)
    gtk_window_set_title(GTK_WINDOW(Zathura.window), Zathura.PDF.file);
  else
    gtk_window_set_title(GTK_WINDOW(Zathura.window), "zathura");
}

GtkLabel*
notify(int level, char* text)
{
  GtkLabel *message = GTK_LABEL(gtk_label_new(text));
  GtkEventBox *view = GTK_EVENT_BOX(gtk_event_box_new());
  
  gtk_misc_set_alignment(GTK_MISC(message), 0, 0);
  gtk_widget_modify_font(GTK_WIDGET(message), Zathura.Settings.font);

  if(level == WARNING)
  {
    gtk_widget_modify_fg(GTK_WIDGET(message), GTK_STATE_NORMAL, &(Zathura.Settings.notification_wn_fg));
    gtk_widget_modify_bg(GTK_WIDGET(view),    GTK_STATE_NORMAL, &(Zathura.Settings.notification_wn_bg));
  }
  else if(level == ERROR)
  {
    gtk_widget_modify_fg(GTK_WIDGET(message), GTK_STATE_NORMAL, &(Zathura.Settings.notification_er_fg));
    gtk_widget_modify_bg(GTK_WIDGET(view),    GTK_STATE_NORMAL, &(Zathura.Settings.notification_er_bg));
  }
  else
  {
    gtk_widget_modify_fg(GTK_WIDGET(message), GTK_STATE_NORMAL, &(Zathura.Settings.notification_fg));
    gtk_widget_modify_bg(GTK_WIDGET(view),    GTK_STATE_NORMAL, &(Zathura.Settings.notification_bg));

  }

  gtk_container_add(GTK_CONTAINER(view), GTK_WIDGET(message));
  gtk_container_add(GTK_CONTAINER(Zathura.notification), GTK_WIDGET(view));

  gtk_widget_show_all(GTK_WIDGET(Zathura.window));
  g_timeout_add_seconds(SHOW_NOTIFICATION, delete_widget, GTK_WIDGET(GTK_WIDGET(view)));

  return message;
}

void
update_status()
{
  char* text = "";

  if(Zathura.PDF.document && Zathura.PDF.page)
  {
    char* show_page = g_strdup_printf("[%i/%i]", Zathura.PDF.page_number + 1, 
      Zathura.PDF.number_of_pages);
    text = g_strdup_printf("%s %s", show_page, Zathura.PDF.file);
  }

  gtk_entry_set_text(Zathura.inputbar, text);
}

void set_page(int page_number)
{
  if(page_number > Zathura.PDF.number_of_pages) 
  {
    notify(WARNING, "Could not open page");
    return;
  }
  
  Zathura.PDF.page_number = page_number;
  Zathura.PDF.page = poppler_document_get_page(Zathura.PDF.document, page_number);
}

void
draw()
{
  if(!Zathura.PDF.document || !Zathura.PDF.page)
    return;

  double page_width, page_height;
  double width, height;

  if(Zathura.PDF.surface)
    cairo_surface_destroy(Zathura.PDF.surface);
  Zathura.PDF.surface = NULL;

  poppler_page_get_size(Zathura.PDF.page, &page_width, &page_height);
 
  if(Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180)
  {
    width  = page_width  * Zathura.PDF.scale;
    height = page_height * Zathura.PDF.scale;
  }
  else
  {
    width  = page_height * Zathura.PDF.scale;
    height = page_width  * Zathura.PDF.scale;
  }

  cairo_t *cairo;
  Zathura.PDF.surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
  cairo = cairo_create(Zathura.PDF.surface);

  cairo_save(cairo);
  cairo_set_source_rgb(cairo, 1, 1, 1);
  cairo_rectangle(cairo, 0, 0, width, height);
  cairo_fill(cairo);
  cairo_restore(cairo);
  cairo_save(cairo);

  switch(Zathura.PDF.rotate)
  {
    case 90:
      cairo_translate(cairo, width, 0);
      break;
    case 180:
      cairo_translate(cairo, width, height);
      break;
    case 270:
      cairo_translate(cairo, 0, height);
      break;
    default:
      cairo_translate(cairo, 0, 0);
  }

  if(Zathura.PDF.scale != 1.0)
    cairo_scale(cairo, Zathura.PDF.scale, Zathura.PDF.scale);

  if(Zathura.PDF.rotate != 0)
    cairo_rotate(cairo, Zathura.PDF.rotate * G_PI / 180.0);

  poppler_page_render(Zathura.PDF.page, cairo);
  cairo_restore(cairo);
  cairo_destroy(cairo);

  gtk_widget_set_size_request(Zathura.drawing_area, width, height);
  gtk_widget_queue_draw(Zathura.drawing_area);
}

void 
hilight_result(PopplerRectangle* rectangle)
{

}

gboolean
delete_widget(gpointer data)
{
  gtk_widget_destroy(GTK_WIDGET(data));

  return FALSE;
}

gboolean
complete(Argument* argument)
{
  static GtkBox     *results;
  static GtkWidget **result_rows;
  static char      **command_names;
  static int         n = 0;
  static int         current = -1; 

  char *command = (char*) gtk_entry_get_text(Zathura.inputbar);
  int   command_length  = strlen(command);
  int   length = 0;
  int   i = 0;

  if( (command_length == 0 || command[0] != ':') && argument->n != HIDE )
    return TRUE;

  if(argument->n != HIDE && n > 0 && results && current != -1 && !strcmp(&command[1], command_names[current]) )
  {
    GtkEventBox *current_box   = (GtkEventBox*) g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(results)),     current);
    GtkLabel    *current_label = (GtkLabel*)    g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(current_box)), 0);
   
    gtk_widget_modify_fg(GTK_WIDGET(current_label), GTK_STATE_NORMAL, &(Zathura.Settings.completion_fg));
    gtk_widget_modify_bg(GTK_WIDGET(current_box),   GTK_STATE_NORMAL, &(Zathura.Settings.completion_bg));

    if(argument->n == NEXT)
      current = (current + n + 1) % n;
    else if (argument->n == PREVIOUS)
      current = (current + n - 1) % n;

    current_box   = (GtkEventBox*) g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(results)),     current);
    current_label = (GtkLabel*)    g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(current_box)), 0);

    gtk_widget_modify_fg(GTK_WIDGET(current_label), GTK_STATE_NORMAL, &(Zathura.Settings.completion_hl_fg));
    gtk_widget_modify_bg(GTK_WIDGET(current_box),   GTK_STATE_NORMAL, &(Zathura.Settings.completion_hl_bg));

    char* temp_string = g_strconcat(":", command_names[current], NULL);
    gtk_entry_set_text(GTK_ENTRY(Zathura.inputbar), temp_string);
    gtk_editable_set_position(GTK_EDITABLE(Zathura.inputbar), -1);
    g_free(temp_string);
  }
  else
  {
    if(results != NULL)
      gtk_widget_destroy(GTK_WIDGET(results));
 
    free(result_rows);
    free(command_names);

    n             = 0;
    current       = -1;
    results       = NULL;
    result_rows   = NULL;
    command_names = NULL;

    if(argument->n == HIDE)
      return TRUE;
  }

  if(!results)
  {
    results       = GTK_BOX(gtk_vbox_new(FALSE, 0));
    result_rows   = malloc(LENGTH(commands) * sizeof(GtkWidget*));
    command_names = malloc(LENGTH(commands) * sizeof(char*));

    for(i = 0; i < LENGTH(commands); i++)
    {
      length = strlen(commands[i].command);
      
      if( (command_length - 1 <= length) && !strncmp(&command[1], commands[i].command, command_length - 1) )
      {
        GtkLabel *show_command = GTK_LABEL(gtk_label_new(commands[i].command));
        GtkEventBox *event_box = GTK_EVENT_BOX(gtk_event_box_new());

        gtk_misc_set_alignment(GTK_MISC(show_command), 0, 0);
        gtk_widget_modify_fg(  GTK_WIDGET(show_command), GTK_STATE_NORMAL, &(Zathura.Settings.completion_fg));
        gtk_widget_modify_bg(  GTK_WIDGET(event_box),    GTK_STATE_NORMAL, &(Zathura.Settings.completion_bg));
        gtk_widget_modify_font(GTK_WIDGET(show_command), Zathura.Settings.font);

        gtk_container_add(GTK_CONTAINER(event_box), GTK_WIDGET(show_command));
        gtk_box_pack_start(results, GTK_WIDGET(event_box), FALSE, FALSE, 0);
        
        command_names[n] = commands[i].command;
        result_rows[n++] = GTK_WIDGET(event_box);
      }
    }

    command_names = realloc(command_names, n * sizeof(char*));
    result_rows   = realloc(result_rows,   n * sizeof(GtkWidget*));
    
    gtk_box_pack_start(Zathura.box, GTK_WIDGET(results), FALSE, FALSE, 0);
    gtk_widget_show_all(GTK_WIDGET(Zathura.window));

    current = (argument->n == NEXT) ? 0 : (n - 1);
  }

  if(current != -1 && n > 0)
  {
    GtkEventBox *current_box   = (GtkEventBox*) g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(results)),     current);
    GtkLabel    *current_label = (GtkLabel*)    g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(current_box)), 0);
   
    gtk_widget_modify_fg(GTK_WIDGET(current_label), GTK_STATE_NORMAL, &(Zathura.Settings.completion_hl_fg));
    gtk_widget_modify_bg(GTK_WIDGET(current_box),   GTK_STATE_NORMAL, &(Zathura.Settings.completion_hl_bg));

    char* temp_string = g_strconcat(":", command_names[current], NULL);
    gtk_entry_set_text(GTK_ENTRY(Zathura.inputbar), temp_string);
    gtk_editable_set_position(GTK_EDITABLE(Zathura.inputbar), -1);
    g_free(temp_string); 
  }

  return TRUE;
}

/* shortcut implementations */
void
sc_focus_inputbar(Argument *argument)
{
  gtk_entry_set_text(Zathura.inputbar, argument->data);
  gtk_widget_modify_text(GTK_WIDGET(Zathura.inputbar), GTK_STATE_NORMAL, &(Zathura.Settings.inputbar_fg));
  gtk_widget_modify_base(GTK_WIDGET(Zathura.inputbar), GTK_STATE_NORMAL, &(Zathura.Settings.inputbar_bg));
  
  gtk_widget_grab_focus(GTK_WIDGET(Zathura.inputbar));
  gtk_editable_set_position(GTK_EDITABLE(Zathura.inputbar), -1);
}

void
sc_scroll(Argument *argument)
{
  GtkAdjustment* adjustment;

  if( (argument->n == LEFT) || (argument->n == RIGHT) )
    adjustment = gtk_scrolled_window_get_hadjustment(Zathura.view);
  else
    adjustment = gtk_scrolled_window_get_vadjustment(Zathura.view);

  gdouble view_size  = gtk_adjustment_get_page_size(adjustment);
  gdouble value      = gtk_adjustment_get_value(adjustment);
  gdouble max        = gtk_adjustment_get_upper(adjustment) - view_size;

  if( (argument->n == LEFT) || (argument->n == DOWN))
    gtk_adjustment_set_value(adjustment, (value - SCROLL_STEP) < 0 ? 0 : (value - SCROLL_STEP));
  else if (argument->n == TOP)
    gtk_adjustment_set_value(adjustment, 0);
  else if(argument->n == BOTTOM)
    gtk_adjustment_set_value(adjustment, max);
  else
    gtk_adjustment_set_value(adjustment, (value + SCROLL_STEP) > max ? max : (value + SCROLL_STEP));
}

void
sc_navigate(Argument *argument)
{
  int number_of_pages = poppler_document_get_n_pages(Zathura.PDF.document);
  int new_page = Zathura.PDF.page_number;

  if(argument->n == NEXT)
    new_page = abs( (new_page + number_of_pages + 1) % number_of_pages);
  else if(argument->n == PREVIOUS)
    new_page = abs( (new_page + number_of_pages - 1) % number_of_pages);

  set_page(new_page);

  Argument reset;
  reset.n = TOP;
  sc_scroll(&reset);
  
  draw();

  update_status();
}

void
sc_zoom(Argument *argument)
{
  if(argument->n == ZOOM_IN)
    Zathura.PDF.scale += ZOOM_STEP;
  else if(argument->n == ZOOM_OUT)
    Zathura.PDF.scale -= ZOOM_STEP;
  else if(argument->n == ZOOM_ORIGINAL)
    Zathura.PDF.scale = 1.0;

  draw();
}

void
sc_adjust_window(Argument *argument)
{
  GtkAdjustment* adjustment;
  double view_size;
  double page_width;
  double page_height;

  if(argument->n == ADJUST_WIDTH)
    adjustment = gtk_scrolled_window_get_vadjustment(Zathura.view);
  else
    adjustment = gtk_scrolled_window_get_hadjustment(Zathura.view);

  view_size  = gtk_adjustment_get_page_size(adjustment);
  poppler_page_get_size(Zathura.PDF.page, &page_width, &page_height);

  if(argument->n == ADJUST_WIDTH)
    Zathura.PDF.scale = view_size / page_height;
  else
    Zathura.PDF.scale = view_size / page_width;
  
  draw();
}

void
sc_rotate(Argument *argument)
{
  if(argument->n == LEFT)
    Zathura.PDF.rotate = abs((Zathura.PDF.rotate - 90) % 360);
  else if(argument->n == RIGHT)
    Zathura.PDF.rotate = abs((Zathura.PDF.rotate + 90) % 360);

  draw();
}

void
sc_search(Argument *argument)
{
  static char* search_item;
  static int direction;
  int page_counter;
  GList* results;
  GList* list;

  if(argument->data)
    search_item = (char*) argument->data;

  if(!Zathura.PDF.document || !Zathura.PDF.page || !search_item)
    return;

  /* search document */
  GtkLabel* search_status = notify(DEFAULT, "Searching...");
  if(argument->n)
    direction = (argument->n == BACKWARD) ? -1 : 1;

  for(page_counter = 0; page_counter < Zathura.PDF.number_of_pages; page_counter++)
  {
    int next_page = (Zathura.PDF.page_number + page_counter * direction) % Zathura.PDF.number_of_pages;
    set_page(next_page);
    
    results = poppler_page_find_text(Zathura.PDF.page, search_item);
    if(results)
      break;
  }

  /* draw results */
  if(results)
  {
    draw();
    
    gtk_label_set_text(search_status, g_strdup_printf("%s found on page %i for %i time(s)", 
          search_item, Zathura.PDF.page_number, g_list_length(results)));
    
    for(list = results; list && list->data; list = g_list_next(list))
    {
      hilight_result((PopplerRectangle*) list->data);
    }
  }
  else
    gtk_label_set_text(search_status, g_strdup_printf("No match for %s", search_item));

}

void
sc_print(Argument *argument)
{

}

void
sc_quit(Argument *argument)
{
  cb_destroy(NULL, NULL);
}

/* command implementations */
void
cmd_open(int argc, char** argv)
{
  if(argc == 0 || (int) strlen(argv[0]) == 0)
    return;

  gchar* file;

  if(!g_file_test(argv[0], G_FILE_TEST_EXISTS))
  {
    notify(ERROR, "File does not exist");
    return;
  }

  if(g_ascii_strncasecmp(argv[0], "file://", strlen("file://")) == 0)
	  file = g_strdup(argv[0]);
  else
    file = g_filename_to_uri(argv[0], NULL, NULL);
  
  Zathura.PDF.document = poppler_document_new_from_file(file, (argc == 2) ? argv[1] : NULL, NULL);
  if(!Zathura.PDF.document)
  {
    notify(WARNING, "Can not open file");
    return;
  }

  Zathura.PDF.number_of_pages = poppler_document_get_n_pages(Zathura.PDF.document);
  Zathura.PDF.file = file + strlen("file://");
  set_page(0);
  
  draw();  
  
  update_status();
  update_title();
}

void
cmd_goto(int argc, char** argv)
{
  if(argc == 0)
    return;

  int page = atoi(argv[0]);;
  set_page(page - 1);
  
  Argument argument;
  argument.n = TOP;
  sc_scroll(&argument);
  
  draw();

  update_status();
}

void
cmd_rotate(int argc, char** argv)
{
  Argument argument;
  
  if(argc == 0)
    argument.n = RIGHT;
  else 
  {
    if(!strncmp(argv[0], "left", strlen(argv[0]) - 1))
      argument.n = LEFT;
    else
      argument.n = RIGHT;
  }

  sc_rotate(&argument);
}

void
cmd_zoom(int argc, char** argv)
{
  if(argc == 0)
    return;

  int zoom = atoi(argv[0]);
  if(zoom < 1 || zoom >= 1000)
    return;

  Zathura.PDF.scale = (float) zoom / 100;
  draw();
}

void
cmd_quit(int argc, char** argv)
{
  cb_destroy(NULL, NULL);
}

/* callback implementations */
void
cb_draw(GtkWidget *widget, gpointer data)
{
  gdk_window_clear(widget->window);

  cairo_t *cairo = gdk_cairo_create(widget->window);
  cairo_set_source_surface(cairo, Zathura.PDF.surface, 0, 0);
  cairo_paint(cairo);
  cairo_destroy(cairo);
}

void
cb_destroy(GtkWidget *widget, gpointer data)
{
  gtk_main_quit();
}

gboolean
cb_view_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
  int i;
  for(i = 0; i < LENGTH(shortcuts); i++)
  {
    if (event->keyval == shortcuts[i].key && 
      (((event->state & shortcuts[i].mask) == shortcuts[i].mask) || shortcuts[i].mask == 0))
    {
      shortcuts[i].function(&(shortcuts[i].argument));
      return TRUE;
    }
  }
  return FALSE;
}

void
cb_inputbar_activate(GtkEntry* entry, gpointer data)
{
  gchar **tokens   = g_strsplit(gtk_entry_get_text(entry), " ", gtk_entry_get_text_length(entry));
  gchar  *command  = tokens[0] + 1;
  int     length   = g_strv_length(tokens);
  int          i   = 0;
  gboolean success = FALSE;
  Argument argument;

  argument.n = HIDE;
  complete(&argument);

  if(length < 1)
    return;

  // special command
  if(*tokens[0] == '/')
  {
    Argument argument;
    argument.data = (char*) gtk_entry_get_text(entry) + 1;
    argument.n = -1;
    sc_search(&argument);
    g_strfreev(tokens);
    update_status();
    gtk_widget_grab_focus(GTK_WIDGET(Zathura.view));
    return;
  }

  // other
  for(i = 0; i < LENGTH(commands); i++)
  {
    if(g_strcmp0(command, commands[i].command) == 0)
    {
      commands[i].function(length - 1, tokens + 1);
      success = TRUE;
      break;
    }
  }

  if(!success)
    notify(ERROR, g_strdup_printf("\"%s\" is not a valid command", command));

  update_status();
  g_strfreev(tokens);
  gtk_widget_grab_focus(GTK_WIDGET(Zathura.view));
}

void 
cb_inputbar_button_pressed(GtkWidget* widget, GdkEventButton* event, gpointer data)
{
  if(event->type == GDK_BUTTON_PRESS)
  {
    gtk_widget_modify_text(GTK_WIDGET(Zathura.inputbar), GTK_STATE_NORMAL, &(Zathura.Settings.inputbar_fg));
    gtk_widget_modify_base(GTK_WIDGET(Zathura.inputbar), GTK_STATE_NORMAL, &(Zathura.Settings.inputbar_bg));
    gtk_widget_grab_focus(GTK_WIDGET(Zathura.inputbar));
    gtk_entry_set_text(Zathura.inputbar, "");
    gtk_editable_set_position(GTK_EDITABLE(Zathura.inputbar), -1);
  }
}

gboolean
cb_inputbar_key_pressed(GtkEntry* entry, GdkEventKey *event, gpointer data)
{
  Argument argument;

  switch(event->keyval)
  {
    case GDK_Escape:
      gtk_widget_grab_focus(GTK_WIDGET(Zathura.view));
      argument.n = HIDE;
      return complete(&argument);
    case GDK_Tab:
      argument.n = NEXT;
      return complete(&argument);
    case GDK_ISO_Left_Tab:
      argument.n = PREVIOUS;
      return complete(&argument);
  }
  
  return FALSE;
}

gboolean
cb_inputbar_key_released(GtkEntry *entry, GdkEventKey *event, gpointer data)
{
  int  length = gtk_entry_get_text_length(entry);
  char*  text = (char*) gtk_entry_get_text(entry);
  Argument argument;

  if(!length)
  {
    argument.n = HIDE;
    complete(&argument);
  }
  else if(length > 1 && text[0] == '/')
  {
    Argument argument;
    argument.data = (char*) text + 1;
    argument.n = -1;
    sc_search(&argument);
    gtk_widget_grab_focus(GTK_WIDGET(Zathura.inputbar));
    gtk_editable_set_position(GTK_EDITABLE(Zathura.inputbar), -1);
  }

  return FALSE;
}

/* main function */
int
main(int argc, char* argv[])
{
  gtk_init(&argc, &argv);

  init();
  
  if(argc >= 2)
    cmd_open(2, &argv[1]);

  gtk_widget_show_all(GTK_WIDGET(Zathura.window));
  gtk_main();
  
  return 0;
}