/*******************************************************************\
 * lot-viewer.c -- a basic lot viewer for GnuCash                   *
 * Copyright (C) 2003 Linas Vepstas <linas@linas.org>               *
 *                                                                  *
 * 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, contact:                        *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
 *                                                                  *
\********************************************************************/

/* XXX todo: The button "view lot in register" is not implemented.
 *   it needs to open register window showing only the splits in the 
 *     given lot ... 
 *
 * XXX clist should be probably be removed and replaced by the gnc_query_list
 */

#include "config.h"

#include <gtk/gtk.h>
#include <glib/gi18n.h>

#include "Account.h"
#include "cap-gains.h"
#include "gnc-commodity.h"
#include "qof.h"
#include "gnc-lot.h"
#include "Scrub3.h"
#include "Transaction.h"

#include "dialog-utils.h"
#include "lot-viewer.h"
#include "gnc-component-manager.h"
#include "gnc-ui-util.h"
#include "gnc-gconf-utils.h"
#include "misc-gnome-utils.h"

#define LOT_VIEWER_CM_CLASS "lot-viewer"

enum lot_cols {
  LOT_COL_OPEN = 0,
  LOT_COL_CLOSE,
  LOT_COL_TITLE,
  LOT_COL_BALN,
  LOT_COL_GAINS,
  LOT_COL_PNTR,
  NUM_LOT_COLS
};

#define RESPONSE_VIEW          1
#define RESPONSE_DELETE        2
#define RESPONSE_SCRUB_LOT     3
#define RESPONSE_SCRUB_ACCOUNT 4

#define GCONF_SECTION "dialogs/lot_viewer"
#define GCONF_KEY_HPOSITION "hpane_position"
#define GCONF_KEY_VPOSITION "vpane_position"

struct _GNCLotViewer 
{
   GtkWidget     * window;
#ifdef LOTS_READY_FOR_SHOWTIME
   GtkButton     * regview_button;
#endif
   GtkButton     * delete_button;
   GtkButton     * scrub_lot_button;
   GtkPaned      * lot_hpaned;
   GtkPaned      * lot_vpaned;
   GtkTreeView   * lot_view;
   GtkListStore  * lot_store;
   GtkTextView   * lot_notes;
   GtkEntry      * title_entry;
   GtkCList      * mini_clist;

   Account       * account;
   GNCLot        * selected_lot;
};

static void gnc_lot_viewer_fill (GNCLotViewer *lv);

/* ======================================================================== */
/* Callback prototypes */

void lv_title_entry_changed_cb (GtkEntry *ent, gpointer user_data);
void lv_response_cb (GtkDialog *dialog, gint response, gpointer data);
void lv_window_destroy_cb (GtkObject *object, gpointer user_data);
void lv_paned_notify_cb (GObject *gobject,
			 GParamSpec *pspec,
			 gpointer user_data);

/* ======================================================================== */
/* Put the splits into the split clist */

#define MINI_DATE_COL 0 
#define MINI_NUM_COL  1
#define MINI_DESC_COL 2 
#define MINI_AMNT_COL 3 
#define MINI_VALU_COL 4 
#define MINI_GAIN_COL 5
#define MINI_BALN_COL 6 
#define MINI_NUM_COLS 7

static void
lv_show_splits (GNCLotViewer *lv)
{
   GNCLot *lot = lv->selected_lot;
   SplitList *split_list, *node;
   gnc_numeric baln = gnc_numeric_zero();

   if (NULL == lot) return;

/* qof_event_suspend();  XXX remove when xaccSplitGetCapGains() fixed */
   gtk_clist_freeze (lv->mini_clist);
   gtk_clist_clear (lv->mini_clist);
   split_list = gnc_lot_get_split_list (lot);
   for (node = split_list; node; node=node->next)
   {
      Split *split = node->data;
      char dbuff[MAX_DATE_LENGTH];
      char amtbuff[200];
      char valbuff[200];
      char gainbuff[200];
      char balnbuff[200];
      gnc_commodity *currency;
      Transaction *trans = xaccSplitGetParent (split);
      time_t date = xaccTransGetDate (trans);
      gnc_numeric amnt, valu, gains;
      const char *row_vals[MINI_NUM_COLS];
      int row;

      /* Do not show gains splits */
      if (gnc_numeric_zero_p (xaccSplitGetAmount(split))) continue;

      /* Opening date */
      qof_print_date_buff (dbuff, MAX_DATE_LENGTH, date);
      row_vals[MINI_DATE_COL] = dbuff;

      row_vals[MINI_NUM_COL]  = xaccTransGetNum (trans);
      row_vals[MINI_DESC_COL] = xaccTransGetDescription (trans);

      /* Amount */
      amnt = xaccSplitGetAmount (split);
      xaccSPrintAmount (amtbuff, amnt,
                 gnc_account_print_info (lv->account, TRUE));
      row_vals[MINI_AMNT_COL] = amtbuff;

      /* Value. Invert the sign on the first, opening entry. */
      currency = xaccTransGetCurrency (trans);
      valu = xaccSplitGetValue (split);
      if (node != split_list)
      {
         valu = gnc_numeric_neg (valu);
      }
      xaccSPrintAmount (valbuff, valu,
                 gnc_commodity_print_info (currency, TRUE));
      row_vals[MINI_VALU_COL] = valbuff;

      /* Gains. Blank if none. */
      gains = xaccSplitGetCapGains (split);
      if (gnc_numeric_zero_p(gains))
      {
         gainbuff[0] = 0;
      }
      else
      {
         xaccSPrintAmount (gainbuff, gains,
                 gnc_commodity_print_info (currency, TRUE));
      }
      row_vals[MINI_GAIN_COL] = gainbuff;

      /* Balance of Gains */
      baln = gnc_numeric_add_fixed (baln, amnt);
      if (gnc_numeric_zero_p(baln))
      {
         balnbuff[0] = 0;
      }
      else
      {
         xaccSPrintAmount (balnbuff, baln,
                 gnc_account_print_info (lv->account, TRUE));
      }
      row_vals[MINI_BALN_COL] = balnbuff;

      /* Self-reference */
      row = gtk_clist_append (lv->mini_clist, (char **)row_vals);
      gtk_clist_set_selectable (lv->mini_clist, row, FALSE);
   }
   gtk_clist_thaw (lv->mini_clist);
/* qof_event_resume();  XXX remove when xaccSplitGetCapGains() fixed */
}

/* ======================================================================== */

static void
lv_clear_splits (GNCLotViewer *lv)
{
   gtk_clist_clear (lv->mini_clist);
}

/* ======================================================================== */
/* Callback for selecting a row the the list-of-list clist */

static void 
lv_select_row (GNCLotViewer *lv,
	       GNCLot       *lot)
{
   const char * str;

   str = gnc_lot_get_title (lot);
   if (!str) str = "";
   gtk_entry_set_text (lv->title_entry, str);
   gtk_editable_set_editable (GTK_EDITABLE(lv->title_entry), TRUE);
   
   /* Set the notes field */
   str = gnc_lot_get_notes (lot);
   if (!str) str = "";
   xxxgtk_textview_set_text (lv->lot_notes, str);
   gtk_text_view_set_editable (lv->lot_notes, TRUE);

   /* Don't set until end, to avoid recursion in gtkentry "changed" cb. */
   lv->selected_lot = lot;
   lv_show_splits (lv);

#ifdef LOTS_READY_FOR_SHOWTIME
   gtk_widget_set_sensitive(GTK_WIDGET(lv->regview_button), TRUE);
#endif
   gtk_widget_set_sensitive(GTK_WIDGET(lv->delete_button), TRUE);
   gtk_widget_set_sensitive(GTK_WIDGET(lv->scrub_lot_button), TRUE);
}

/* ======================================================================== */

static void 
lv_unset_lot (GNCLotViewer *lv)
{
   /* Set immediately, to avoid recursion in gtkentry "changed" cb. */
   lv->selected_lot = NULL;

   /* Blank the title widget */
   gtk_entry_set_text (lv->title_entry, "");
   gtk_editable_set_editable (GTK_EDITABLE(lv->title_entry), FALSE);

   /* Blank the notes area */
   xxxgtk_textview_set_text (lv->lot_notes, "");
   gtk_text_view_set_editable (lv->lot_notes, FALSE);

   /* Erase the mini-view area */
   lv_clear_splits (lv);

#ifdef LOTS_READY_FOR_SHOWTIME
   gtk_widget_set_sensitive(GTK_WIDGET(lv->regview_button), FALSE);
#endif
   gtk_widget_set_sensitive(GTK_WIDGET(lv->delete_button), FALSE);
   gtk_widget_set_sensitive(GTK_WIDGET(lv->scrub_lot_button), FALSE);
}

/* ======================================================================== */
/* Callback for un-selecting a row the the list-of-list clist */

static void 
lv_unselect_row (GNCLotViewer *lv)
{
   GNCLot *lot = lv->selected_lot;
   const char * str;
   char * notes;

   if (lot)
   {
      /* Get the title, save_the_title */
      str = gtk_entry_get_text (lv->title_entry);
      gnc_lot_set_title (lot, str);

      /* Get the notes, save the notes */
      notes = xxxgtk_textview_get_text (lv->lot_notes);
      gnc_lot_set_notes (lot, notes);
      g_free(notes);
   }

   lv_unset_lot (lv);
}

static void
lv_selection_changed_cb (GtkTreeSelection *selection,
			 GNCLotViewer *lv)  
{
   GNCLot *lot;
   GtkTreeModel *model;
   GtkTreeIter iter;

   if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
     gtk_tree_model_get(model, &iter, LOT_COL_PNTR, &lot, -1);
     lv_select_row(lv, lot);
   } else {
     lv_unselect_row(lv);
   }
}


/* ======================================================================== */
/* Callback when user types a new lot title into the entry widget */

void
lv_title_entry_changed_cb (GtkEntry *ent, gpointer user_data)
{
   GNCLotViewer *lv = user_data;
   GtkTreeModel *model;
   GtkTreeIter iter;
   GtkTreeSelection *selection;
   const char * title;
   title = gtk_entry_get_text (lv->title_entry);

   selection = gtk_tree_view_get_selection(lv->lot_view);
   if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
     gtk_list_store_set(GTK_LIST_STORE(model), &iter, LOT_COL_TITLE, title, -1);
   }
}

/* ======================================================================== */
/* Get the realized gains for this lot.  This routine or a varient of it
 * should probably be moved to gnc-lot.c. 
 * The conceptual difficulty here is that this works only if all of the 
 * realized gains in the lot are of the 
 */

static gnc_commodity *
find_first_currency (GNCLot *lot)
{
   SplitList *split_list, *node;

   split_list = gnc_lot_get_split_list(lot); 
   for (node = split_list; node; node=node->next)
   {
      Split *s = node->data;
      Transaction *trans;
      if (FALSE == gnc_numeric_zero_p(xaccSplitGetAmount(s))) continue;
      trans = xaccSplitGetParent (s);
      return xaccTransGetCurrency (trans);
   }
   return NULL;
}

static gnc_numeric
get_realized_gains (GNCLot *lot, gnc_commodity *currency)
{
   gnc_numeric zero = gnc_numeric_zero();
   gnc_numeric gains = zero;
   SplitList *split_list, *node;

   if (!currency) return zero;

   split_list = gnc_lot_get_split_list(lot); 
   for (node = split_list; node; node=node->next)
   {
      Split *s = node->data;
      Transaction *trans;

      if (FALSE == gnc_numeric_zero_p(xaccSplitGetAmount(s))) continue;
      trans = xaccSplitGetParent (s);
      if (FALSE == gnc_commodity_equal (xaccTransGetCurrency(trans), currency)) continue;

      gains = gnc_numeric_add (gains, xaccSplitGetValue (s), GNC_DENOM_AUTO, GNC_DENOM_FIXED);
   }
   return gains;
}

/* ======================================================================== */

static void
gnc_lot_viewer_fill (GNCLotViewer *lv)
{
   LotList *lot_list, *node;
   GNCLot *this_lot, *selected_lot = NULL;
   GtkListStore *store;
   GtkTreeModel *model;
   GtkTreeIter iter;
   GtkTreeSelection *selection;
   gboolean found = FALSE;

   lot_list = xaccAccountGetLotList (lv->account);

   selection = gtk_tree_view_get_selection(lv->lot_view);
   if (gtk_tree_selection_get_selected (selection, &model, &iter))
     gtk_tree_model_get(model, &iter, LOT_COL_PNTR, &selected_lot, -1);

   /* Crazy. Should update in place if possible. */
   gtk_list_store_clear (lv->lot_store);

   for (node = lot_list; node; node=node->next)
   {
      char obuff[MAX_DATE_LENGTH];
      char cbuff[MAX_DATE_LENGTH];
      char baln_buff[200];
      char gain_buff[200];
      GNCLot *lot = node->data;
      Split *esplit = gnc_lot_get_earliest_split (lot);
      Transaction *etrans = xaccSplitGetParent (esplit);
      time_t open_date = xaccTransGetDate (etrans);
      gnc_numeric amt_baln = gnc_lot_get_balance (lot);
      gnc_commodity *currency = find_first_currency (lot);
      gnc_numeric gains_baln = get_realized_gains (lot, currency);

      store = lv->lot_store;
      gtk_list_store_append(store, &iter);

      /* Opening date */
      qof_print_date_buff (obuff, MAX_DATE_LENGTH, open_date);
      gtk_list_store_set(store, &iter, LOT_COL_OPEN, obuff, -1);

      /* Closing date */
      if (gnc_lot_is_closed (lot))
      {
         Split *fsplit = gnc_lot_get_latest_split (lot);
         Transaction *ftrans = xaccSplitGetParent (fsplit);
         time_t close_date = xaccTransGetDate (ftrans);
   
         qof_print_date_buff (cbuff, MAX_DATE_LENGTH, close_date);
	 gtk_list_store_set(store, &iter, LOT_COL_CLOSE, cbuff, -1);
      }
      else
      {
	gtk_list_store_set(store, &iter, LOT_COL_CLOSE, cbuff, _("Open"), -1);
      }

      /* Title */
      gtk_list_store_set(store, &iter, LOT_COL_TITLE, gnc_lot_get_title(lot), -1);
      
      /* Amount */
      xaccSPrintAmount (baln_buff, amt_baln, 
                 gnc_account_print_info (lv->account, TRUE));
      gtk_list_store_set(store, &iter, LOT_COL_BALN, baln_buff, -1);

      /* Capital Gains/Losses Appreciation/Depreciation */
      xaccSPrintAmount (gain_buff, gains_baln, 
                 gnc_commodity_print_info (currency, TRUE));
      gtk_list_store_set(store, &iter, LOT_COL_GAINS, gain_buff, -1);

      /* Self-reference */
      gtk_list_store_set(store, &iter, LOT_COL_PNTR, lot, -1);
   }

   /* re-select the row that the user had previously selected, 
    * if possible. */
   if (selected_lot) {
     model = GTK_TREE_MODEL(lv->lot_store);
     if (gtk_tree_model_get_iter_first(model, &iter)) {
       do {
	 gtk_tree_model_get(model, &iter, LOT_COL_PNTR, &this_lot, -1);
	 if (this_lot == selected_lot) {
	   gtk_tree_selection_select_iter(selection, &iter);
	   found = TRUE;
	   break;
	 }
       } while (gtk_tree_model_iter_next(model, &iter));
     }
   }

   if (!found)
     gtk_tree_selection_unselect_all(selection);
}

/* ======================================================================== */

static void
lv_refresh_handler (GHashTable *changes, gpointer user_data)
{
   GNCLotViewer *lv = user_data;
   gnc_lot_viewer_fill (lv);
   lv_show_splits (lv);
}

static void
lv_close_handler (gpointer user_data)
{
   GNCLotViewer *lv = user_data;
   GNCLot *lot = lv->selected_lot;

   if (lot)
   {
      const char * str;
      char *notes;

      /* Get the title, save the title */
      str = gtk_entry_get_text (lv->title_entry);
      gnc_lot_set_title (lot, str);

      /* Get the notes, save the notes */
      notes = xxxgtk_textview_get_text (lv->lot_notes);
      gnc_lot_set_notes (lot, notes);
      g_free(notes);
   }

   gnc_save_window_size(GCONF_SECTION, GTK_WINDOW(lv->window));
   gtk_widget_destroy (lv->window);
}

void
lv_window_destroy_cb (GtkObject *object, gpointer user_data)
{
   GNCLotViewer *lv = user_data;
   gnc_close_gui_component_by_data (LOT_VIEWER_CM_CLASS, lv);
   gnc_unregister_gui_component_by_data (LOT_VIEWER_CM_CLASS, lv);
   g_free (lv);
}
                                                                                

/* ======================================================================== */
/* Divider moved */

void
lv_paned_notify_cb (GObject *gobject,
		     GParamSpec *pspec,
		     gpointer user_data)
{
   const gchar *param_name;
   gint value;

   param_name = g_param_spec_get_name(pspec);
   if (strcmp(param_name, "position") != 0)
     return;
   g_object_get(gobject, "position", &value, NULL);

   if (GTK_IS_HPANED(gobject)) {
     gnc_gconf_set_int(GCONF_SECTION, GCONF_KEY_HPOSITION, value, NULL);
   } else {
     gnc_gconf_set_int(GCONF_SECTION, GCONF_KEY_VPOSITION, value, NULL);
   }
}

/* ======================================================================== */
/* Any button was pressed */

void
lv_response_cb (GtkDialog *dialog, gint response, gpointer data)
{
   GNCLotViewer *lv = data;
   GNCLot *lot = lv->selected_lot;

   switch (response) {
   case GTK_RESPONSE_CLOSE:
     lv_close_handler(lv);
     return;

   case RESPONSE_VIEW:
     if (NULL == lot)
       return; 
     printf ("duude UNIMPLEMENTED: need to disply register showing only this one lot \n");
     break;

   case RESPONSE_DELETE:
     if (NULL == lot)
       return; 
     xaccAccountRemoveLot (gnc_lot_get_account(lot), lot);
     gnc_lot_destroy (lot);
     lv_unset_lot (lv);
     gnc_lot_viewer_fill (lv);
     break;

   case RESPONSE_SCRUB_LOT:
     if (NULL == lot)
       return; 
     xaccScrubLot (lot);
     gnc_lot_viewer_fill (lv);
     lv_show_splits (lv);
     break;

   case RESPONSE_SCRUB_ACCOUNT:
     xaccAccountScrubLots (lv->account);
     gnc_lot_viewer_fill (lv);
     lv_show_splits (lv);
     break;
   }
}

/* ======================================================================== */

static void
lv_init_lot_view (GNCLotViewer *lv)
{
  GtkTreeView *view;
  GtkListStore *store;
  GtkTreeViewColumn *column;
  GtkTreeSelection *selection;
  GtkCellRenderer *renderer;

  g_return_if_fail(GTK_IS_TREE_VIEW(lv->lot_view));

  view = lv->lot_view;
  store = gtk_list_store_new(NUM_LOT_COLS, G_TYPE_STRING, G_TYPE_STRING,
			     G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
			     G_TYPE_POINTER);
  gtk_tree_view_set_model(view, GTK_TREE_MODEL(store));
  g_object_unref(store);
  lv->lot_store = store;

  /* Set up the columns */
  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes(_("Opened"), renderer,
						    "text", LOT_COL_OPEN, NULL);
  gtk_tree_view_append_column(view, column);

  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes(_("Closed"), renderer,
						    "text", LOT_COL_CLOSE, NULL);
  gtk_tree_view_append_column(view, column);

  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes(_("Title"), renderer,
						    "text", LOT_COL_TITLE, NULL);
  gtk_tree_view_append_column(view, column);

  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes(_("Balance"), renderer,
						    "text", LOT_COL_BALN, NULL);
  gtk_tree_view_append_column(view, column);

  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes(_("Gains"), renderer,
						    "text", LOT_COL_GAINS, NULL);
  gtk_tree_view_append_column(view, column);

  /* Set up the selection callbacks */
  selection =  gtk_tree_view_get_selection(view);
  g_signal_connect(selection, "changed",
		   G_CALLBACK(lv_selection_changed_cb), lv);
}

/* ======================================================================== */

static void
lv_create (GNCLotViewer *lv)
{
   GladeXML *xml;
   char win_title[251];
   gint position;

   xml = gnc_glade_xml_new ("lots.glade", "Lot Viewer Window");
   lv->window = glade_xml_get_widget (xml, "Lot Viewer Window");

   snprintf (win_title, 250, _("Lots in Account %s"),
                  xaccAccountGetName(lv->account));
   gtk_window_set_title (GTK_WINDOW (lv->window), win_title);

#ifdef LOTS_READY_FOR_SHOWTIME
   lv->regview_button = GTK_BUTTON(glade_xml_get_widget (xml, "regview button"));
#endif
   lv->delete_button = GTK_BUTTON(glade_xml_get_widget (xml, "delete button"));
   lv->scrub_lot_button = GTK_BUTTON(glade_xml_get_widget (xml, "scrub lot button"));

   lv->lot_view = GTK_TREE_VIEW(glade_xml_get_widget (xml, "lot view"));
   lv_init_lot_view(lv);
   lv->lot_notes = GTK_TEXT_VIEW(glade_xml_get_widget (xml, "lot notes text"));
   lv->title_entry = GTK_ENTRY (glade_xml_get_widget (xml, "lot title entry"));

   lv->lot_vpaned = GTK_PANED (glade_xml_get_widget (xml, "lot vpaned"));
   position = gnc_gconf_get_int(GCONF_SECTION, GCONF_KEY_VPOSITION, NULL);
   if (position)
     gtk_paned_set_position (lv->lot_vpaned, position);

   lv->lot_hpaned = GTK_PANED (glade_xml_get_widget (xml, "lot hpaned"));
   position = gnc_gconf_get_int(GCONF_SECTION, GCONF_KEY_HPOSITION, NULL);
   if (position)
     gtk_paned_set_position (lv->lot_hpaned, position);

   lv->mini_clist = GTK_CLIST(glade_xml_get_widget (xml, "mini clist"));

   lv->selected_lot = NULL;
    
   /* Setup signals */
   glade_xml_signal_autoconnect_full( xml,
                                     gnc_glade_autoconnect_full_func,
                                     lv);

   gnc_restore_window_size(GCONF_SECTION, GTK_WINDOW(lv->window));
}

/* ======================================================================== */

GNCLotViewer * 
gnc_lot_viewer_dialog (Account *account)
{
   GNCLotViewer *lv;
   gint component_id;

   if (!account) return NULL;

   lv = g_new0 (GNCLotViewer, 1);
   lv->account = account;
   lv_create (lv);
   gnc_lot_viewer_fill (lv);

   component_id = gnc_register_gui_component (LOT_VIEWER_CM_CLASS,
                                              lv_refresh_handler,
                                              lv_close_handler,
                                              lv);

   gnc_gui_component_watch_entity_type (component_id,
               GNC_ID_LOT,
               QOF_EVENT_CREATE | QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);

   gtk_widget_show_all (lv->window);
   gnc_window_adjust_for_screen (GTK_WINDOW(lv->window));

   return lv;
}

/* ============================ END OF FILE =============================== */
