/*
 * Copyright (C) 2007 OpenedHand Ltd
 *
 * This program 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 of the License, or (at your option) any later
 * version.
 *
 * This program 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
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <config.h>
#include <string.h>
#include <libecal/e-cal.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include <libkoto/ical-util.h>
#include <libkoto/koto-group-store.h>
#include <libkoto/koto-hint-entry.h>
#include <libkoto/koto-task.h>
#include <libkoto/koto-task-store.h>
#include <libkoto/koto-task-view.h>
#include <libkoto/koto-task-editor-dialog.h>
#include <libkoto/koto-group-filter-model.h>
#include <libkoto/koto-group-combo.h>

#include "window-util.h"

static GtkWidget *window, *combo, *treeview, *new_entry;
static ECal *cal;
static GtkTreeModel *task_store, *filter, *group_filter_store, *group_selector_store;

static gboolean
select_uid (char *uid)
{
  GtkTreeSelection *selection;
  GtkTreeIter iter, real_iter;
  GtkTreePath *path;

  g_assert (uid);
  
  if (koto_task_store_get_iter_for_uid (KOTO_TASK_STORE (task_store), uid, &iter)) {
    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
    
    gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (filter),
                                                      &real_iter, &iter);
    
    path = gtk_tree_model_get_path (filter, &real_iter);
    gtk_tree_view_set_cursor (GTK_TREE_VIEW (treeview), path, NULL, FALSE);
    gtk_tree_path_free (path);
  }
  
  g_free (uid);
  return FALSE;
}

static void
on_new_clicked (GtkButton *button)
{
  GError *error = NULL;
  const char *text;
  char *uid = NULL;
  char *group = NULL;
  icalcomponent *comp;

  text = gtk_entry_get_text (GTK_ENTRY (new_entry));
  
  if (!text || text[0] == '\0') {
    g_warning ("Got clicked with empty text");
    return;
  }

  comp = icalcomponent_new (ICAL_VTODO_COMPONENT);
  icalcomponent_add_property (comp, icalproperty_new_summary (text));
  icalcomponent_add_property (comp, icalproperty_new_class (ICAL_CLASS_PUBLIC));

  group = koto_group_combo_get_active_group (KOTO_GROUP_COMBO (combo));
  if (group) {
    icalproperty *group_prop;

    group_prop = icalproperty_new (ICAL_CATEGORIES_PROPERTY);
    icalproperty_set_categories (group_prop, group);
    icalcomponent_add_property (comp, group_prop);
    
    g_free (group);
  }

  koto_hint_entry_clear (KOTO_HINT_ENTRY (new_entry));

  if (!e_cal_create_object (cal, comp, &uid, &error)) {
    g_warning (G_STRLOC ": cannot create task: %s", error->message);
    g_error_free (error);
  }

  /*
   * Select the new task in an idle function so that the store can process the
   * signals that are waiting for it (as we did a blocking call to add the
   * task).
   */
  if (uid) {
    g_idle_add ((GSourceFunc)select_uid, uid);
  }
}


/* 
 * Used to enable/disable the new task button depending on the contents of the
 * entry.
 */
static void
on_new_entry_changed (GtkEntry *entry, GtkWidget *button)
{
  gtk_widget_set_sensitive (button, ! koto_hint_entry_is_empty (KOTO_HINT_ENTRY (entry)));
}

static void
edit_task (KotoTask *task)
{
  GtkWidget *dialog;

  g_assert (task);
  
  /* TODO: create once and show/hide it as required */
  dialog = koto_task_editor_dialog_new ();
  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
  g_object_set (dialog,
                "groups", group_selector_store,
                "task", task,
                NULL);
  koto_task_editor_dialog_run (KOTO_TASK_EDITOR_DIALOG (dialog), cal);
  gtk_widget_destroy (dialog);
}

/*
 * Callback when a row in the tree view is activated, which edits the task.
 */
static void
on_row_activated  (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  KotoTask *task;

  model = gtk_tree_view_get_model (tree_view);
  if (!gtk_tree_model_get_iter (model, &iter, path)) {
    g_warning (G_STRLOC ": cannot get iterator for path");
    return;
  }
  gtk_tree_model_get (model, &iter, COLUMN_ICAL, &task, -1);

  edit_task (task);

  koto_task_unref (task);
}

/*
 * Foreach function called from update_title() that simply counts the number of
 * uncompleted tasks.  @userdata is an int* to the count.
 */
static gboolean
count_pending (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
  int *count = data;
  gboolean done;
  
  gtk_tree_model_get (model, iter, COLUMN_DONE, &done, -1);

  if (!done)
    (*count)++;

  /*
   * Hopefully one KotoTaskStore will always be sorted, but at the moment new
   * tasks are appended and then moved to the correct location. When this is
   * fixed we can return @done here and not bother counting completed tasks.
   */
  return FALSE;
}

/*
 * Update the window title, generally as the number of tasks has changed.
 */
static void
update_title (GtkWindow *window, GtkTreeModel *model)
{
  int count = 0;
  char *title;

  g_assert (GTK_IS_WINDOW (window));
  g_assert (GTK_IS_TREE_MODEL (model));

  gtk_tree_model_foreach (model, count_pending, &count);
  title = g_strdup_printf (_("Tasks (%d)"), count);
  gtk_window_set_title (window, title);
  g_free (title);
}

/*
 * Callback when rows are inserted into the task store, to update the window
 * title.
 */
static void
on_row_inserted (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
{
  update_title (GTK_WINDOW (window), task_store);
}

/*
 * Callback when rows are removed from the task store, to update the window
 * title.
 */
static void
on_row_deleted (GtkTreeModel *model, GtkTreePath *path, gpointer user_data)
{
  update_title (GTK_WINDOW (window), task_store);
}

/*
 * Callback from the New Task action.
 */
static void
on_new_task_action (GtkAction *action, gpointer user_data)
{
  gtk_widget_grab_focus (new_entry);
}

/*
 * Callback from the Edit Task action.
 */
static void
on_edit_task_action (GtkAction *action, gpointer user_data)
{
  KotoTask *task;

  task = koto_task_view_get_selected_task (KOTO_TASK_VIEW (treeview));
  if (!task) {
    g_warning ("TODO: No task selected, EditTask should be disabled");
    return;
  }
  
  edit_task (task);

  koto_task_unref (task);
}

static void
on_complete_task_action (GtkAction *action, gpointer user_data)
{
  GtkTreeIter real_iter, iter;

  if (!koto_task_view_get_selected_iter (KOTO_TASK_VIEW (treeview), &iter)) {
    g_warning ("TODO: No task selected, CompleteTask should be disabled");
    return;
  }

  gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (filter), &real_iter, &iter);
  
  koto_task_store_set_done (KOTO_TASK_STORE (task_store), &real_iter, TRUE);
}

/*
 * Callback from the Delete Task action.
 */
static void
on_delete_task_action (GtkAction *action, gpointer user_data)
{
  GError *error = NULL;
  GtkWidget *dialog;
  KotoTask *task;

  task = koto_task_view_get_selected_task (KOTO_TASK_VIEW (treeview));
  if (!task) {
    g_warning ("TODO: No task selected, DeleteTask should be disabled");
    return;
  }
  
  dialog = gtk_message_dialog_new (GTK_WINDOW (window),
                                   GTK_DIALOG_DESTROY_WITH_PARENT,
                                   GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
                                   _("Are you sure you want to delete \"%s\"?"),
                                   icalcomponent_get_summary (task->comp));
  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
                                            _("If you delete an item, it is permanently lost."));
  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                          GTK_STOCK_DELETE, GTK_RESPONSE_ACCEPT,
                          NULL);
  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
  
  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
    if (!e_cal_remove_object (cal, icalcomponent_get_uid (task->comp), &error)) {
      g_warning ("Cannot remove object: %s", error->message);
      g_error_free (error);
    }
  }
  
  gtk_widget_destroy (dialog);

  koto_task_unref (task);
}

/*
 * Foreach handler for the Purge Complete action, which removes a task if it is
 * completed.
 */
static gboolean
purge_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
  GError *error = NULL;
  KotoTask *task;
  gboolean done;

  gtk_tree_model_get (model, iter,
                      COLUMN_DONE, &done,
                      COLUMN_ICAL, &task,
                      -1);
  
  if (done) {
    if (!e_cal_remove_object (cal, icalcomponent_get_uid (task->comp), &error)) {
      g_warning ("Cannot remove object: %s", error->message);
      g_error_free (error);
      error = NULL;
    }
  }

  koto_task_unref (task);
  
  return FALSE;
}

/*
 * Callback from the Purge Tasks action.
 */
static void
on_purge_action (GtkAction *action, gpointer user_data)
{
  GtkWidget *dialog;

  dialog = gtk_message_dialog_new (GTK_WINDOW (window),
                                   GTK_DIALOG_DESTROY_WITH_PARENT,
                                   GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
                                   _("Are you sure you want to delete all completed tasks?"));
  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
                                            _("Deleting completed tasks means they are permanently lost."));
  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                          GTK_STOCK_DELETE, GTK_RESPONSE_ACCEPT,
                          NULL);
  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
  
  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
    gtk_tree_model_foreach (task_store, purge_foreach, NULL);
  }
  
  gtk_widget_destroy (dialog);
}

/*
 * Callback from the About action.
 */
static void
on_about_action (GtkAction *action, gpointer user_data)
{
  const char* authors[] = {
    "Ross Burton <ross@openedhand.com>", NULL,
  };
  const char* artists[] = {
    "Andreas Nilsson <andreas@andreasn.se>",
    "Jakub Steiner <jimmac@ximian.com>",
    NULL,
  };
  const char *license = {
    N_(
       "Tasks 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 of the License, or "
       "(at your option) any later version.\n\n"
       "Tasks 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.\n\n"
       "You should have received a copy of the GNU General Public License "
       "along with Tasks; if not, write to the Free Software Foundation, Inc., "
       "51 Franklin St, Fifth Floor, Boston, MA 0110-1301, USA"
       )
  };

  gtk_show_about_dialog (GTK_WINDOW (window),
                         "name", _("Tasks"),
                         "version", VERSION,
                         "logo-icon-name", "tasks",
                         "copyright", "Copyright \302\251 2007 OpenedHand Ltd",
                         "authors", authors,
                         "artists", artists,
                         "translator-credits", _("translator-credits"),
                         "license", license,
                         "wrap-license", TRUE,
                         "website", "http://pimlico-project.org",
                         "website-label", _("The Pimlico Project"),
                         NULL);
}

/*
 * Callback from the UI manager with the GtkMenu widget. Pack and add this to
 * the container.
 */
static void
ui_add_widget (GtkUIManager *ui, GtkWidget *widget, GtkContainer *container)
{
  gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
  gtk_widget_show (widget);
}

/* TODO: split into global actions and actions that require a task to be selected */
static const GtkActionEntry actions[] = 
{
  /* Action name, stock ID, label, accelerator, tooltip, callback */
  { "TasksMenu", NULL, N_("_Task") },
  { "NewTask", GTK_STOCK_NEW, NULL, NULL, NULL, G_CALLBACK (on_new_task_action) },
  { "EditTask", GTK_STOCK_EDIT, NULL, "<control>e", NULL, G_CALLBACK (on_edit_task_action) },
  /* TODO: turn this action into a toggle action */
  { "CompleteTask", NULL, N_("Mark Complete"), "<control>d", NULL, G_CALLBACK (on_complete_task_action) },
  { "DeleteTask", GTK_STOCK_DELETE, NULL, NULL, NULL, G_CALLBACK (on_delete_task_action) },
  { "PurgeTasks", NULL, N_("_Remove Completed"), NULL, NULL, G_CALLBACK (on_purge_action) },
  { "Quit", GTK_STOCK_QUIT, NULL, NULL, NULL, G_CALLBACK (gtk_main_quit) },
  
  { "HelpMenu", NULL, N_("_Help") },
  { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK (on_about_action) },
};

int
main (int argc, char **argv)
{
  GError *error = NULL;
  ECalView *cal_view;
  GtkWidget *top_box, *box, *scrolled, *hbox, *new_button;
  GtkActionGroup *action_group;
  GtkUIManager *ui_manager;

#ifdef ENABLE_NLS
  /* Initialise i18n*/
  bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
  bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
  textdomain(GETTEXT_PACKAGE);
#endif

  g_set_application_name (_("Tasks"));
  gtk_init (&argc, &argv);

  cal = e_cal_new_system_tasks ();
  if (!cal)
    g_error ("Cannot get system tasks");
  
  if (!e_cal_open (cal, FALSE, &error))
    g_error("Cannot open calendar: %s", error->message);

  if (!e_cal_get_query (cal, "#t", &cal_view, &error))
    g_error("Cannot get calendar view: %s", error->message);
  /* TODO: nasty, should pass cal to the stores or add e_cal_view_get_cal() */
  g_object_set_data_full (G_OBJECT (cal_view), "koto-ecal", g_object_ref (cal), g_object_unref);

  /* Create the data stores */
  group_filter_store = koto_group_store_new (KOTO_GROUP_STORE_TYPE_FILTER, cal_view);
  group_selector_store = koto_group_store_new (KOTO_GROUP_STORE_TYPE_SELECTOR, cal_view);
  task_store = koto_task_store_new (cal_view);

  /* Create the UI */
  gtk_window_set_default_icon_name ("tasks");
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  update_title (GTK_WINDOW (window), task_store);
  gtk_window_set_default_size (GTK_WINDOW (window), 240, 320);
  window_bind_state (GTK_WINDOW (window));
  g_signal_connect (window, "destroy", gtk_main_quit, NULL);

  /* Connect to the task store change events to update the title bar */
  /* TODO: pull this into a utility function */
  g_signal_connect (task_store, "row-inserted", G_CALLBACK (on_row_inserted), NULL);
  g_signal_connect (task_store, "row-changed", G_CALLBACK (on_row_inserted), NULL);
  g_signal_connect (task_store, "row-deleted", G_CALLBACK (on_row_deleted), NULL);

  top_box = gtk_vbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (window), top_box); 

  action_group = gtk_action_group_new ("Actions");
  gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
  gtk_action_group_add_actions (action_group, actions, G_N_ELEMENTS (actions), NULL);

  ui_manager = gtk_ui_manager_new ();
  gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);

  gtk_ui_manager_add_ui_from_file (ui_manager, PKGDATADIR "/tasks-ui.xml", &error);
  if (error) {
    g_warning ("Cannot load UI: %s", error->message);
    g_error_free (error);
    error = NULL;
  }
  /* Bind the accelerators */
  gtk_window_add_accel_group (GTK_WINDOW (window), gtk_ui_manager_get_accel_group (ui_manager));
  g_signal_connect (ui_manager, "add-widget", G_CALLBACK (ui_add_widget), top_box);

  /* Do this so that the menu is packed now instead of in the idle loop */
  gtk_ui_manager_ensure_update (ui_manager);

  box = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (box), 4);
  gtk_container_add (GTK_CONTAINER (top_box), box);

  combo = koto_group_combo_new (KOTO_GROUP_STORE (group_filter_store));
  gtk_box_pack_start (GTK_BOX (box), combo, FALSE, FALSE, 0);

  scrolled = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  gtk_box_pack_start (GTK_BOX (box), scrolled, TRUE, TRUE, 0);

  filter = koto_group_model_filter_new (KOTO_TASK_STORE (task_store));
  treeview = koto_task_view_new (KOTO_TASK_STORE (task_store), KOTO_GROUP_MODEL_FILTER (filter));
  g_signal_connect (treeview, "row-activated", G_CALLBACK (on_row_activated), NULL);
  gtk_container_add (GTK_CONTAINER (scrolled), treeview);

  hbox = gtk_hbox_new (FALSE, 4);
  new_entry = koto_hint_entry_new (_("New task..."));
  gtk_entry_set_activates_default (GTK_ENTRY (new_entry), TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), new_entry, TRUE, TRUE, 0);

  new_button = gtk_button_new_from_stock (GTK_STOCK_ADD);
  gtk_widget_set_sensitive (new_button, FALSE);
  GTK_WIDGET_SET_FLAGS (new_button, GTK_CAN_DEFAULT);
  gtk_window_set_default (GTK_WINDOW (window), new_button);
  g_signal_connect (new_button, "clicked", G_CALLBACK (on_new_clicked), NULL);
  g_signal_connect (new_entry, "changed", G_CALLBACK (on_new_entry_changed), new_button);
  gtk_box_pack_start (GTK_BOX (hbox), new_button, FALSE, FALSE, 0);

  gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0);

  gtk_widget_grab_focus (treeview);

  /* Select the first row, the All group. */
  gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
  koto_group_combo_connect_filter (KOTO_GROUP_COMBO (combo),
                                   KOTO_GROUP_MODEL_FILTER (filter));

  e_cal_view_start (cal_view);

  gtk_widget_show_all (window);  
  gtk_main ();

  return 0;
}
