#ifndef GTKLIFE_H
#define GTKLIFE_H

#include <gtk/gtk.h>

#include "life.h"
#include "util.h"

/* Defines */

#define PROG      "gtklife"
#define TITLE     "GtkLife"
#define USER_DIR  ".gtklife"

#define HELP_BROWSER_PARAM        "$f"
#define INIT_X_CENTER             (WORLD_SIZE / 2)
#define INIT_Y_CENTER             (WORLD_SIZE / 2)
#define MAX_RECENT_FILES          5
#define MAX_SKIPPED_FRAMES        10
#define MAX_SPEED                 10000
#define MAX_ZOOM                  16
#define MIN_ZOOM                  1
#define RECENT_FILES_INSERT_POS   11
#define STATUS_MESSAGE_COLOR      0x007000
#define TABLE_OPTS                (GTK_SHRINK | GTK_FILL)
#define TEMP_MESSAGE_INTERVAL     5000      /* in milliseconds */

#define FILE_LOADED_MESSAGE       "File loaded successfully"
#define FILE_SAVED_MESSAGE        "File saved successfully"
#define FULLSCREEN_MESSAGE        "Type 'F' to exit fullscreen mode"
#define PASTING_MESSAGE           "Pasting, Ctrl-G to cancel"
#define MOVING_MESSAGE            "Moving, Ctrl-G to cancel"

#define SCROLL_STEP  -1
#define SCROLL_PAGE  -2

/* Configuration Defaults */

#define DEFAULT_COLLECTION        COLL_LPA
#ifdef GTK2
#define DEFAULT_WINDOW_WIDTH      1000
#define DEFAULT_WINDOW_HEIGHT     700
#else
#define DEFAULT_WINDOW_WIDTH      800
#define DEFAULT_WINDOW_HEIGHT     600
#endif
#define DEFAULT_ZOOM              1
#define DEFAULT_SHOW_TOOLBAR      TRUE
#define DEFAULT_SHOW_SIDEBAR      TRUE
#define DEFAULT_SHOW_SCROLLBARS   TRUE
#define DEFAULT_SHOW_STATUSBAR    TRUE
#define DEFAULT_FULLSCREEN        FALSE
#define DEFAULT_BG_COLOR          0xC0C0C0
#define DEFAULT_CELL_COLOR        0x000080
#define DEFAULT_GRID_COLOR        0x808080
#define DEFAULT_SELECT_COLOR      0xFF0000
#define DEFAULT_SPEED             20
#define DEFAULT_SPEED_SLIDER_MIN  1
#define DEFAULT_SPEED_SLIDER_MAX  1000
#define DEFAULT_SPEED_INCREMENT   10
#define DEFAULT_SKIP_FRAMES       TRUE

/* Enumerations */

enum color_index_enum  {BG_COLOR_INDEX, CELL_COLOR_INDEX, GRID_COLOR_INDEX, SELECT_COLOR_INDEX,
                        NUM_COLORS};

typedef enum tracking_mouse_enum {NORMAL, DRAWING, SELECTING, PASTING} tracking_mouse_type;

typedef enum coll_status_enum {COLL_OKAY, COLL_BAD, COLL_DEFAULT_BAD} coll_status_type;

typedef enum item_enum {
    ITEM_NORMAL,
    ITEM_CHECK,
    ITEM_RADIO
} item_type;

typedef enum command_enum {
    CMD_FILE_NEW,
    CMD_FILE_OPEN,
    CMD_FILE_REOPEN,
    CMD_FILE_SAVE,
    CMD_FILE_SAVE_AS,
    CMD_FILE_DESCRIPTION,
    CMD_FILE_CHANGE_COLLECTION,
    CMD_FILE_QUIT,

    CMD_VIEW_ZOOM_IN,
    CMD_VIEW_ZOOM_OUT,
    CMD_VIEW_ZOOM_1,
    CMD_VIEW_ZOOM_2,
    CMD_VIEW_ZOOM_4,
    CMD_VIEW_ZOOM_8,
    CMD_VIEW_ZOOM_16,
    CMD_VIEW_SCROLL_LEFT,
    CMD_VIEW_SCROLL_RIGHT,
    CMD_VIEW_SCROLL_UP,
    CMD_VIEW_SCROLL_DOWN,
    CMD_VIEW_SCROLL_NW,
    CMD_VIEW_SCROLL_NE,
    CMD_VIEW_SCROLL_SW,
    CMD_VIEW_SCROLL_SE,
    CMD_VIEW_SCROLL_PAGE_LEFT,
    CMD_VIEW_SCROLL_PAGE_RIGHT,
    CMD_VIEW_SCROLL_PAGE_UP,
    CMD_VIEW_SCROLL_PAGE_DOWN,
    CMD_VIEW_SCROLL_PAGE_NW,
    CMD_VIEW_SCROLL_PAGE_NE,
    CMD_VIEW_SCROLL_PAGE_SW,
    CMD_VIEW_SCROLL_PAGE_SE,
    CMD_VIEW_RECENTER,
    CMD_VIEW_GOTO,
    CMD_VIEW_FIND_ACTIVE_CELLS,
    CMD_VIEW_SHOW_TOOLBAR,
    CMD_VIEW_SHOW_SIDEBAR,
    CMD_VIEW_SHOW_SCROLLBARS,
    CMD_VIEW_SHOW_STATUSBAR,
    CMD_VIEW_FULLSCREEN,

    CMD_EDIT_DRAWING_TOOL,
    CMD_EDIT_SELECTION_TOOL,
    CMD_EDIT_CUT,
    CMD_EDIT_COPY,
    CMD_EDIT_CLEAR,
    CMD_EDIT_PASTE,
    CMD_EDIT_MOVE,
    CMD_EDIT_CANCEL_PASTE,
    CMD_EDIT_PREFERENCES,

    CMD_RUN_START_STOP,
    CMD_RUN_STEP,
    CMD_RUN_JUMP,
    CMD_RUN_FASTER,
    CMD_RUN_SLOWER,
    CMD_RUN_SPEED,

    CMD_HELP_HELP,
    CMD_HELP_PATTERN_ARCHIVE,
    CMD_HELP_GLF_FILE_FORMAT,
    CMD_HELP_ABOUT,

    NUM_COMMANDS
} command_type;

typedef enum component_enum {
    COMPONENT_TOOLBAR,
    COMPONENT_SIDEBAR,
    COMPONENT_SCROLLBARS,
    COMPONENT_STATUSBAR,

    NUM_COMPONENTS
} component_type;

typedef enum pattern_coll_enum {
    COLL_LPA,
    COLL_JSLIFE,

    NUM_COLLECTIONS
} pattern_coll_id;

typedef enum help_browser_enum {
    HB_FIREFOX,
    HB_MOZILLA,

    NUM_HELP_BROWSERS
} help_browser_id;

/* Macros */

#define DRAW_GRID(zoom_level)  ((zoom_level) > 2)
#define SELECTION_ACTIVE()     (!rects_identical(&state.selection, &null_rect))
#define COPY_BUFFER_ACTIVE()   (!rects_identical(&state.copy_rect, &null_rect))
#define SWAP(a,b)              {temp=a; a=b; b=temp;}
#define ZOOM_CMD(z)            ((z == 1) ? CMD_VIEW_ZOOM_1 :   \
                                (z == 2) ? CMD_VIEW_ZOOM_2 :   \
                                (z == 4) ? CMD_VIEW_ZOOM_4 :   \
                                (z == 8) ? CMD_VIEW_ZOOM_8 :   \
                                           CMD_VIEW_ZOOM_16)

#define LEFT_ALIGN(w)   alignment = gtk_alignment_new(0, 0.5, 0, 0); \
                        gtk_container_add(GTK_CONTAINER(alignment), (w))
#define RIGHT_ALIGN(w)  alignment = gtk_alignment_new(1, 0.5, 0, 0); \
                        gtk_container_add(GTK_CONTAINER(alignment), (w))
#define CENTER_ALIGN(w) alignment = gtk_alignment_new(0.5, 0.5, 0, 0); \
                        gtk_container_add(GTK_CONTAINER(alignment), (w))
#define FRAME_ALIGN(w)  alignment = gtk_alignment_new(0, 0, 1, 0); \
                        gtk_container_add(GTK_CONTAINER(alignment), (w))

/* Types */

typedef struct dimension_struct {
    int32 width;
    int32 height;
} dimension;

typedef struct point_struct {
    int32 x;
    int32 y;
} point;

typedef struct rect_struct {
    point start;
    point end;
} rect;

typedef void (*bound_function)(void);

typedef struct command_info_struct {
    item_type      type;
    bound_function handler;
} command_info_type;

typedef struct command_widgets_struct {
    GtkWidget*  menu_item;
    GtkWidget*  toolbar_button;
} command_widgets_type;

typedef struct pattern_file_struct {
    char*    filename;
    char*    path;
    char*    title;
    boolean  is_directory;
} pattern_file;

typedef struct recent_file_struct {
    char*       filename;
    char*       full_path;
    GtkWidget*  menu_item;
    GtkWidget*  label;
} recent_file;

typedef struct pattern_coll_struct {
    const char*  dir;
    const char*  title;
} pattern_coll;

typedef struct help_browser_struct {
    const char*  name;
    const char*  command_line;
} help_browser;

typedef struct toolbar_button_struct {
    int32        cmd;
    const char*  description;
    uint8*       icon;
} toolbar_button;

typedef struct collection_dialog_info_struct {
    GtkWidget*  dialog;
    GtkWidget*  radio_buttons[NUM_COLLECTIONS+1];
    GtkWidget*  custom_path_entry;
    GtkWidget*  custom_path_button;
} collection_dialog_info;

typedef struct goto_dialog_info_struct {
    GtkWidget*  dialog;
    GtkWidget*  x_entry;
    GtkWidget*  y_entry;
    GtkWidget*  ul_corner_radio;
    GtkWidget*  center_radio;
} goto_dialog_info;

typedef struct speed_dialog_info_struct {
    GtkWidget*  dialog;
    GtkWidget*  speed_spinbox;
    GtkWidget*  skip_toggle;
} speed_dialog_info;

typedef struct color_dialog_info_struct {
    const char*  dialog_title;
    GtkWidget*   prefs_entry;
    uint32       cur_value;
    uint32       default_value;
} color_dialog_info;

typedef struct pick_browser_dialog_info_struct {
    const char*  helpfile;
    GtkWidget*   dialog;
    GtkWidget*   radio_buttons[NUM_HELP_BROWSERS+2];
    GtkWidget*   custom_browser_entry;
} pick_browser_dialog_info;

typedef struct prefs_dialog_info_struct {
    GtkWidget*  dialog;
    GtkWidget*  collection_radio_buttons[NUM_COLLECTIONS+1];
    GtkWidget*  custom_collection_path_entry;
    GtkWidget*  custom_collection_path_button;
    GtkWidget*  patterns_dir_entry;
    GtkWidget*  default_window_width_spinbox;
    GtkWidget*  default_window_height_spinbox;
    GtkWidget*  default_zoom_combo;
    GtkWidget*  visible_component_toggles[NUM_COMPONENTS];
    GtkWidget*  default_fullscreen_toggle;
    GtkWidget*  color_entries[NUM_COLORS];
    color_dialog_info*  color_dialog_infos[NUM_COLORS];
    GtkWidget*  default_speed_spinbox;
    GtkWidget*  speed_slider_min_spinbox;
    GtkWidget*  speed_slider_max_spinbox;
    GtkWidget*  speed_increment_spinbox;
    GtkWidget*  default_skip_toggle;
    GtkWidget*  browser_radio_buttons[NUM_HELP_BROWSERS+2];
    GtkWidget*  custom_browser_entry;
} prefs_dialog_info;

/* Prototypes */

void  usage_abort(void);

/* Main loop functions */

void  main_loop(void);
void  tick_and_update(void);

/* Functions for reading/saving user settings */

void  create_user_dir(void);
void  load_preferences(void);
void  apply_defaults(void);
void  load_keybindings(void);
void  load_recent_files_list(void);
void  save_preferences(void);
void  save_keybindings(void);
void  save_recent_files_list(void);

/* Bound Functions */

void  file_new(void);
void  file_open(void);
void  file_reopen(void);
void  file_save(void);
void  file_save_as(void);
void  file_description(void);
void  file_change_collection(void);
void  file_recent(GtkMenuItem* menu_item, gpointer user_data);
void  file_quit(void);

void  view_zoom_in(void);
void  view_zoom_out(void);
void  view_zoom_1(void);
void  view_zoom_2(void);
void  view_zoom_4(void);
void  view_zoom_8(void);
void  view_zoom_16(void);
void  view_scroll_left(void);
void  view_scroll_right(void);
void  view_scroll_up(void);
void  view_scroll_down(void);
void  view_scroll_nw(void);
void  view_scroll_ne(void);
void  view_scroll_sw(void);
void  view_scroll_se(void);
void  view_scroll_page_left(void);
void  view_scroll_page_right(void);
void  view_scroll_page_up(void);
void  view_scroll_page_down(void);
void  view_scroll_page_nw(void);
void  view_scroll_page_ne(void);
void  view_scroll_page_sw(void);
void  view_scroll_page_se(void);
void  view_recenter(void);
void  view_goto(void);
void  view_find_active_cells(void);
void  view_show_toolbar(void);
void  view_show_sidebar(void);
void  view_show_scrollbars(void);
void  view_show_statusbar(void);
void  view_fullscreen(void);

void  edit_drawing_tool(void);
void  edit_selection_tool(void);
void  edit_cut(void);
void  edit_copy(void);
void  edit_clear(void);
void  edit_paste(void);
void  edit_move(void);
void  edit_cancel_paste(void);
void  edit_preferences(void);

void  run_start_stop(void);
void  run_step(void);
void  run_jump(void);
void  run_faster(void);
void  run_slower(void);
void  run_speed(void);

void  help_help(void);
void  help_pattern_archive(void);
void  help_glf_file_format(void);
void  help_about(void);

/* Bound Function Helpers */

void       file_save_as_confirm_overwrite(GtkWidget* file_selector);
void       file_description_perform(void);
boolean    file_change_collection_perform(collection_dialog_info* info);
void       view_zoom(int32 new_zoom);
void       view_scroll_generic(int32 xdir, int32 ydir, int32 how_far);
void       view_show(component_type component);
void       edit_paste_win_style(const point* pt);
void       edit_paste_unix_style(const point* pt);
boolean    edit_paste_verify_target(const point* pt);
void       edit_paste_perform(const point* pt);
boolean    edit_preferences_perform(prefs_dialog_info* info);
void       help_view_page(const char* filename);
void       pick_help_browser(const char* helpfile);
GtkWidget* create_help_browser_table(GtkWidget* radio_buttons[], GtkWidget** custom_browser_entry);

/* GUI initialization functions */

void  init_gui(void);
void  init_rgb(void);
void  init_window(void);
void  init_menubar(GtkWidget* containing_box);
void  init_toolbar(GtkWidget* containing_box);
void  init_sensitivities(void);
void  init_speed_box(GtkWidget* containing_box);
void  init_panes(GtkWidget* containing_box);
void  init_sidebar(GtkWidget* containing_box);
void  init_canvas(GtkWidget* containing_box);
void  init_statusbar(GtkWidget* containing_box);
void  init_tick_display(GtkWidget* containing_box);
void  init_population_display(GtkWidget* containing_box);

/* Event handlers */

void      handle_menu_item_activate(gpointer callback_data, guint callback_action,
                                    GtkWidget *widget);
void      handle_toolbar_click(GtkButton* button, gpointer user_data);

gboolean  handle_canvas_expose(GtkWidget* widget, GdkEventExpose* event, gpointer user_data);
gboolean  handle_canvas_resize(GtkWidget* widget, GdkEventConfigure* event, gpointer user_data);
void      handle_child_window_realize(GtkWidget* child, gpointer user_data);
#ifdef GTK2
gboolean  handle_desc_box_mouse_scroll(GtkWidget* widget, GdkEventScroll* event,
                                       gpointer user_data);
#else
gboolean  handle_desc_box_mouse_scroll(GtkWidget* widget, GdkEventButton* event,
                                       gpointer user_data);
#endif
void      handle_main_window_destroy(GtkObject* object, gpointer user_data);
void      handle_mouse_movement(void);
gboolean  handle_mouse_press(GtkWidget* widget, GdkEventButton* event, gpointer user_data);
gboolean  handle_mouse_release(GtkWidget* widget, GdkEventButton* event, gpointer user_data);
gboolean  handle_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
gboolean  handle_key_release(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
#ifdef GTK2
gboolean  handle_mouse_scroll(GtkWidget* widget, GdkEventScroll* event, gpointer user_data);
#endif
void      handle_hscrollbar_change(GtkAdjustment* adjustment, gpointer user_data);
void      handle_vscrollbar_change(GtkAdjustment* adjustment, gpointer user_data);
void      handle_sidebar_select(GtkCList* clist, gint row, gint column, GdkEventButton* event,
                                gpointer user_data);
void      handle_sidebar_header_click(GtkCList* clist, gint column, gpointer user_data);
void      handle_speed_slider_change(GtkAdjustment* adjustment, gpointer user_data);

void      file_open_ok(GtkButton* button, gpointer user_data);
void      file_save_as_ok(GtkButton* button, gpointer user_data);
void      file_save_as_confirm_ok(GtkButton* button, gpointer user_data);
void      file_description_ok(GtkButton* button, gpointer user_data);
void      file_description_apply(GtkButton* button, gpointer user_data);
void      file_description_destroy(GtkObject* object, gpointer user_data);
void      file_change_collection_ok(GtkButton* button, gpointer user_data);
void      file_change_collection_enter(GtkEditable* editable, gpointer user_data);
void      file_change_collection_apply(GtkButton* button, gpointer user_data);
void      file_change_collection_destroy(GtkObject* object, gpointer user_data);
void      file_change_coll_toggle_custom(GtkToggleButton* toggle, gpointer user_data);
void      file_change_coll_select_custom(GtkButton* button, gpointer user_data);
void      file_change_coll_select_custom_ok(GtkButton* button, gpointer user_data);
void      view_goto_ok(GtkButton* button, gpointer user_data);
void      view_goto_enter(GtkEditable* editable, gpointer user_data);
void      view_goto_apply(GtkButton* button, gpointer user_data);
void      view_goto_destroy(GtkObject* object, gpointer user_data);
void      edit_preferences_ok(GtkButton* button, gpointer user_data);
void      edit_preferences_apply(GtkButton* button, gpointer user_data);
void      edit_preferences_destroy(GtkObject* object, gpointer user_data);
void      prefs_toggle_custom_collection(GtkToggleButton* toggle, gpointer user_data);
void      prefs_select_custom_collection(GtkButton* button, gpointer user_data);
void      prefs_select_custom_collection_ok(GtkButton* button, gpointer user_data);
void      prefs_select_color(GtkButton* button, gpointer user_data);
void      prefs_select_color_ok(GtkButton* button, gpointer user_data);
void      prefs_default_color(GtkButton* button, gpointer user_data);
void      prefs_toggle_custom_browser(GtkToggleButton* toggle, gpointer user_data);
void      run_jump_ok(GtkButton* button, gpointer user_data);
void      run_jump_enter(GtkEditable* editable, gpointer user_data);
void      run_jump_stop(GtkObject* object, gpointer user_data);
void      run_speed_ok(GtkButton* button, gpointer user_data);
void      run_speed_enter(GtkEditable* editable, gpointer user_data);
void      run_speed_apply(GtkButton* button, gpointer user_data);
void      run_speed_destroy(GtkObject* object, gpointer user_data);
void      pick_browser_ok(GtkButton* button, gpointer user_data);
void      pick_browser_destroy(GtkObject* object, gpointer user_data);
void      pick_browser_toggle_custom(GtkToggleButton* toggle, gpointer user_data);

/* Functions for drawing the offscreen pixmap */

void  draw_life_pixmap(void);
void  draw_grid_and_boxes(void);
void  draw_screen_box(const rect* box);
void  draw_life_block(uint16 block, int32 xstart, int32 ystart, boolean full_update);
void  user_draw(const point* pt);
void  draw_from_to(const point* start, const point* end);

/* Misc. GUI functions */

void       adjust_scrollbars(void);
void       adjust_scrollbar_values(void);
void       setup_child_window(GtkWidget* child);
GtkWidget* create_dialog(const char* title, gpointer info, GtkSignalFunc destroy_handler,
                         boolean first_button_default, int32 num_buttons, ...);
void       error_dialog(const char* msg, ...);
void       force_sidebar_resize(void);
void       full_canvas_redraw(void);
void       put_filename_in_window_title(void);
void       restore_status_message(void);
void       set_command_sensitivity(command_type cmd, boolean sensitive);
void       set_cursor(GdkCursorType type);
void       set_effective_canvas_size(void);
void       set_speed_label_size(int32 max_speed);
void       set_status_message(char* msg, boolean is_temporary);
void       set_viewport_position(const point* pt, boolean center_around);
void       set_zoom_sensitivities(void);
void       trigger_canvas_update(void);
void       update_description_textbox(boolean first_time);
void       update_hover_point_label(const point* mouse_pointer);
void       update_population_label(void);
void       update_sidebar_contents(void);
void       update_sub_sidebar_contents(int32 sidebar_selection);
void       update_sidebar_generic(boolean is_sub_sidebar, const char* new_dir);
void       update_tick_label(void);
GtkWidget* create_spinbox(int32 init_value, int32 min, int32 max);

/* Miscellanous Functions */

void     add_to_recent_files(void);
boolean  attempt_load_pattern(const char* path);
boolean  attempt_save_pattern(const char* path, file_format_type format);
void     activate_selection(const point* pt);
void     clear_pattern_list(pattern_file** patterns, int32* num_patterns);
void     deactivate_selection(boolean redraw);
void     deactivate_paste(boolean redraw);
char*    find_life_file(const char* path);
int32    get_component_by_short_name(const char* name);
void     get_component_widgets(component_type component, GtkWidget** widget1,
                               GtkWidget** widget2);
void     get_logical_coords(const point* p, point* logical_p);
void     get_screen_coords(const point* p, point* screen_p);
void     get_screen_rectangle(const rect* r, rect* screen_r);
void     load_pattern_directory(const char* dir, boolean include_subdirs, pattern_file** patterns,
                                int32* num_patterns);
void     reset_fps_measure(void);
void     screen_box_update(rect* oldr, const rect* newr, boolean redraw);
void     selection_copy_clear(boolean copy, boolean clear);
void     set_collection_dir(const char* path, boolean have_gui);
void     set_current_dir_from_file(const char* filepath);
void     set_pattern_title(pattern_file* file);
void     set_speed(int32 new_speed);
void     sidebar_unselect(void);
void     start_stop(void);
boolean  last_version_atleast(double req_version);
char*    validate_collection_dir(const char* path, boolean have_gui);
boolean  validate_and_set_collection_dir(const char* path, boolean have_gui);

/* Utility Functions */

void       alpha_blend(uint8* rgb1, const uint8* rgb2, int32 opacity);
char*      append_trailing_slash(const char* path);
char*      get_canonical_path(const char* path);
char*      get_home_directory(void);
void       get_rect_dimensions(const rect* r, dimension* dim);
uint64     get_time_milliseconds(void);
char*      join_path(const char* dir, const char* file);
GtkWidget* load_pixmap_from_xpm_file(const char* path);
GtkWidget* load_pixmap_from_rgba_array(uint8* pixels, int32 width, int32 height, GdkColor* bg);
void       normalize_rectangle(rect* r);
boolean    points_identical(const point* p1, const point* p2);
int32      qsort_pattern_files(const void* pat1, const void* pat2);
boolean    rects_identical(const rect* r1, const rect* r2);
char*      remove_underscore(const char* str);
void       split_path(const char* path, char** dir, char** file);

#endif
