/*
 * grn_msgwin.c: message window functions
 *
 * $Id: grn_msgwin.c,v 1.27 2000/06/27 13:05:13 sc Exp $
 */
/* Copyright (C) 1999-2000  Sergey Chernikov (sc@ivvs.ul.ru)
 *
 * Authors: Sergey Chernikov <sc@ivvs.ul.ru>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
 */

#include <math.h>
#include "grn_consts.h"
#include <gnome.h>
#include "grn_vars.h"
#include "grn_msgwin.h"
#include "grn_util.h"
#include "grn_misc.h"
#include "grn_commands.h"
#include "grn_menu.h"
#include "grn_config.h"
#include "grn_msgview.h"
#include "grn_threadlist.h"
#include "grn_perl.h"
#include "grn_mhdrwin.h"
#include "queue.h"
#include "grn_mime.h"


static gint evt_mw_event(GtkWidget *, GdkEvent *, GtkText *);
static gint evt_mw_keypress(GtkWidget *, GdkEventKey *, gpointer);
static gint evt_mw_btnpress(GtkWidget *, GdkEventButton *, gpointer);
static void evt_mw_hide(GtkWidget *, gpointer);
static void evt_mw_show(GtkWidget *, gpointer);


GtkWidget *msgwin_create()
{
  GtkWidget *vbox=NULL, *table, *text, *label, *entry;
  GtkAdjustment *adj;
  
  grn_lock();
  vbox = gtk_vbox_new(FALSE, 0);
  
  table = gtk_table_new(2, 2, FALSE);
  gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);

  label = gtk_label_new(_("From:"));
  gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
  gtk_table_attach(GTK_TABLE(table), label, 0,1,0,1, GTK_FILL, GTK_FILL, 3, 1);
  entry = gtk_entry_new();
  gtk_entry_set_editable(GTK_ENTRY(entry), FALSE);
  gtk_table_attach(GTK_TABLE(table), entry, 1,3,0,1,
                   GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 1);
  gtk_object_set_data(GTK_OBJECT(vbox), KEY_MW_FROM, entry);
  
  label = gtk_label_new(_("To:"));
  gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
  gtk_table_attach(GTK_TABLE(table), label, 0,1,1,2, GTK_FILL, GTK_FILL, 3, 1);
  entry = gtk_entry_new();
  gtk_entry_set_editable(GTK_ENTRY(entry), FALSE);
  gtk_table_attach(GTK_TABLE(table), entry, 1,2,1,2,
                   GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 1);
  gtk_object_set_data(GTK_OBJECT(vbox), KEY_MW_TO, entry);
  label = gtk_label_new("");
  gtk_table_attach(GTK_TABLE(table), label, 2,3,1,2, GTK_FILL, GTK_FILL, 3, 1);
  gtk_object_set_data(GTK_OBJECT(vbox), KEY_MW_DATE, label);

  label = gtk_label_new(_("Subject:"));
  gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
  gtk_table_attach(GTK_TABLE(table), label, 0,1,2,3, GTK_FILL, GTK_FILL, 3, 1);
  entry = gtk_entry_new();
  gtk_entry_set_editable(GTK_ENTRY(entry), FALSE);
  gtk_table_attach(GTK_TABLE(table), entry, 1,3,2,3,
                   GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 1);
  gtk_object_set_data(GTK_OBJECT(vbox), KEY_MW_SUBJ, entry);
  
  table = gtk_table_new(2, 1, FALSE);
  gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 0);
  
  adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 0));
  text = gtk_text_new(NULL, adj);
  gtk_text_set_line_wrap(GTK_TEXT(text), TRUE);
  gtk_text_set_word_wrap(GTK_TEXT(text), TRUE);


  gtk_table_attach(GTK_TABLE(table), text, 0,1,0,1,
    GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0,0);

  gtk_table_attach(GTK_TABLE(table), gtk_vscrollbar_new(adj), 1,2,0,1,
    GTK_FILL, GTK_FILL, 0,0);
  
  gtk_object_set_data(GTK_OBJECT(vbox), KEY_TXT_BODY, text);
  gtk_object_set_data(GTK_OBJECT(vbox), KEY_FOCUS, text);
  gtk_object_set_data(GTK_OBJECT(text), KEY_WND, vbox);
  
  gtk_signal_connect(GTK_OBJECT(text), "key_press_event",
                     GTK_SIGNAL_FUNC(evt_mw_keypress), NULL);
  gtk_signal_connect(GTK_OBJECT(text), "button_press_event",
                     GTK_SIGNAL_FUNC(evt_mw_btnpress), NULL);
  gtk_signal_connect(GTK_OBJECT(text), "event",
                     GTK_SIGNAL_FUNC(evt_mw_event), text);
  gtk_signal_connect(GTK_OBJECT(vbox), "hide",
                     GTK_SIGNAL_FUNC(evt_mw_hide), NULL);
  gtk_signal_connect(GTK_OBJECT(vbox), "show",
                     GTK_SIGNAL_FUNC(evt_mw_show), NULL);
  
  gtk_widget_show_all(vbox);
  grn_unlock();
  return vbox;
}


void msgwin_set_hdr(GtkWidget *mw, gchar *id, gchar *text)
{
  GtkWidget *hw;
  g_return_if_fail(mw != NULL);
  g_return_if_fail(id != NULL);
  
  grn_lock();
  hw = gtk_object_get_data(GTK_OBJECT(mw), id);
  grn_unlock();
  if (! hw)  return;
  
  grn_widget_set_style(hw, grn_prefs.fonts[2], NULL, NULL, NULL);
  grn_lock();
  if (GTK_IS_ENTRY(hw))
  {
    if (text)  gtk_entry_set_text(GTK_ENTRY(hw), text);
    else  gtk_entry_set_text(GTK_ENTRY(hw), "");
  }
  else if (GTK_IS_LABEL(hw))
  {
    if (text)  gtk_label_set_text(GTK_LABEL(hw), text);
    else  gtk_label_set_text(GTK_LABEL(hw), "");
  }
  grn_unlock();
}

static void show_mw_stats(grn_msgwin_stats *mws)
{
  if (! mws)  return;
  grn_appbar_set_status(GRN->appbar, _("Size: %lu bytes"), mws->msg_size);
}

void msgwin_refresh(GtkWidget *mw, t_message *msg)
{
  GtkText *text;
  grn_msgwin_stats *mws, *mws1;
  grn_html_buf *gxb;
  glong ph_res = -1;
  t_message *m = NULL;
  g_return_if_fail(mw != NULL);
  g_return_if_fail(msg != NULL);
  
  grn_lock();
  gtk_object_remove_data(GTK_OBJECT(mw), KEY_MSG);
  gtk_object_set_data(GTK_OBJECT(mw), KEY_MSG, msg);
  if (gtk_object_get_data(GTK_OBJECT(GRN->window), KEY_MHWIN))
    evt_mhwin_show(mw);
  mws1 = gtk_object_get_data(GTK_OBJECT(mw), KEY_MW_STATS);
  if (mws1)
  {
    g_free(mws1);
    gtk_object_remove_data(GTK_OBJECT(mw), KEY_MW_STATS);
  }

  text = GTK_TEXT(gtk_object_get_data(GTK_OBJECT(mw), KEY_TXT_BODY));
  grn_unlock();

  gxb = html_init_doc();
  gxb->data = text;
  m = t_message_copy(msg);
  grn_mime_parse_message(m);
  if (m->filename)
  {
    GSList *mps = NULL;
    FILE *fp;
    
#ifdef USE_PERL
    ph_res = perl_hook_run("PreShowHeader", GRN, m, NULL);
    if ((ph_res >= 1) && m->body)  gxb->html = g_strdup(m->body);
#endif
    fp = fopen(m->filename, "r");
    if (fp)
    {
      mps = body_to_mime_parts((BODY *)m->data, NULL, fp);
      fclose(fp);
    }
    if (mps)
    {
      gchar *buf;
      GSList *l;

      for (l=mps; l; l=l->next)
      {
        grn_mime_part *mp = (grn_mime_part *) l->data;
	if (! mp)  continue;
	
#ifdef USE_PERL
	ph_res = perl_hook_run("PreShowPart", GRN, mp, NULL);
        if ((ph_res >= 1) && mp->data)  str_add(&(gxb->html), mp->data);
#endif
	if ((! (ph_res >= 1)) && (strcmp(mp->type, "multipart")) &&
	    (strcmp(mp->type, "message")))
	{
	  buf = g_strdup_printf("<BR>_____ MIME part: %s/%s; Size = %ld; Encoding: %s _____<BR>",
	        mp->type, mp->subtype, mp->size, mp->enc);
	  str_add(&(gxb->html), buf);
	  str_free(&buf);
	  if (! g_strcasecmp(mp->type, "text"))
	  { /* libxml's strictness workaround, waiting for gtkhtml */
/*	    if (! strcmp(mp->subtype, "html"))  buf = g_strdup(mp->data);
	    else*/  buf = html_escape(mp->data);
	    str_add(&(gxb->html), buf);
	    str_free(&buf);
	  }
	}
        grn_mime_part_free(&mp);
      }
      g_slist_free(mps);
    }
#ifdef USE_PERL
    ph_res = perl_hook_run("PreShowFooter", GRN, m, NULL);
    if ((ph_res >= 1) && m->body)  str_add(&(gxb->html), m->body);
#endif
  }
  else {
#ifdef USE_PERL
    ph_res = perl_hook_run("PreShow", GRN, m, NULL);
    if ((ph_res >= 1) && m->body)  gxb->html = g_strdup(m->body);
#endif
  }
  if (! (ph_res >= 1) && (! gxb->html))
  {
    str_add(&(gxb->html), "<HTML>\n");
    if (m->mh && str_check(m->mh->subject))
    {
      gchar *buf1 = html_escape(m->mh->subject);
      gchar *buf = g_strdup_printf("<HEAD>\n<TITLE>%s</TITLE>\n</HEAD>\n", buf1);
      str_free(&buf1);
      str_add(&(gxb->html), buf);
      str_free(&buf);
    }
    str_add(&(gxb->html), "<BODY>");
    html_parse_msg_body(gxb, msg->body);
    str_add(&(gxb->html), "</BODY>\n</HTML>\n");
  }

  if (m->mh)
  {
    msgwin_set_hdr(mw, KEY_MW_FROM, m->mh->from);
    msgwin_set_hdr(mw, KEY_MW_DATE, date_get_local(m->mh->parsed_date));
    msgwin_set_hdr(mw, KEY_MW_TO, t_msgheaders_get_extra_hdr(m->mh, "X-Comment-To"));
    msgwin_set_hdr(mw, KEY_MW_SUBJ, m->mh->subject);
  }
  if (text && m->body)
  {
    gchar *fnt_name;

    if (grn_prefs.mw_prop)  fnt_name = grn_prefs.fonts[0];
    else  fnt_name = grn_prefs.fonts[1];
    grn_widget_set_style(GTK_WIDGET(text), fnt_name, NULL,
      &(grn_prefs.bg_colors[0]), grn_prefs.pixmaps[0]);

    grn_lock();
    gtk_editable_delete_text(GTK_EDITABLE(text), 0, -1);
    gtk_text_freeze(text);
    grn_unlock();

    html_draw_body(gxb);
    grn_lock();
    gtk_text_set_point(text, 0);
    gtk_text_thaw(text);
    grn_unlock();
    html_free_doc(&gxb);

    mws = g_new(grn_msgwin_stats, 1);
    mws->msg_size = strlen(msg->body);
    grn_lock();
    gtk_object_set_data(GTK_OBJECT(mw), KEY_MW_STATS, mws);
    grn_unlock();
    show_mw_stats(mws);
  }
  if (m)  t_message_free(&m);
}


static void return_to_threadlist(GtkWidget *w)
{
  if (! GRN->threadlist)
  {
    evt_grouplist(w);
    return;
  }
  grn_set_contents(GRN->threadlist);
}

static void scroll_line(GtkText *text, gint offset)
{
  gfloat new_val = text->vadj->value + offset;
  if (new_val < text->vadj->lower)  new_val = text->vadj->lower;
  if (new_val > (text->vadj->upper - text->vadj->page_size))
    new_val = text->vadj->upper - text->vadj->page_size;
  gtk_adjustment_set_value(text->vadj, new_val);
}

static void scroll_smooth(GtkText *text, gint n)
{
  gint i=0;

  if (n > 0)
    while (i <= n)
    {
      gint step = sin(M_PI/n*i) * 4 + 1;
      if (step > (n - i))  step = n - i;
      i += step;
      scroll_line(text, step);
      if (n == i)  break;
    }
  else
    while (i >= n)
    {
      gint step = -sin(M_PI/n*i) * 4 - 1;
      if (step < (n - i))  step = n - i;
      i += step;
      scroll_line(text, step);
      if (n == i)  break;
    }
}

static gint evt_mw_keypress(GtkWidget *w, GdkEventKey *event, gpointer data)
{
  gint offset = 0;
  GtkText *text;
  GtkWidget *tmp;
  g_return_val_if_fail(w != NULL, FALSE);
  g_return_val_if_fail(event != NULL, FALSE);
  
  tmp = gtk_object_get_data(GTK_OBJECT(w), KEY_TXT_BODY);
  if ((! tmp) && (GRN->msgwin))
    tmp = gtk_object_get_data(GTK_OBJECT(GRN->msgwin), KEY_TXT_BODY);
  if (! tmp)  return FALSE;
  text = GTK_TEXT(tmp);

  switch (event->keyval)
  {
    case GDK_Left:
    case GDK_KP_Left:
      return_to_threadlist(w);
      break;
    case GDK_Right:
    case GDK_KP_Right:
      if (GRN->threadlist)
      {
        evt_tl_urnext_msg(GRN->threadlist);
	evt_tl_open_article(GRN->threadlist);
      }
      break;
    case GDK_Up:
    case GDK_KP_Up:
      if (event->state & GDK_CONTROL_MASK)  offset = -1;
      else if (event->state & GDK_SHIFT_MASK)  offset = -2;
      else  offset = -5;
      break;
    case GDK_Down:
    case GDK_KP_Down:
      if (event->state & GDK_CONTROL_MASK)  offset = 1;
      else if (event->state & GDK_SHIFT_MASK)  offset = 2;
      else  offset = 5;
      break;
    case GDK_Page_Up:
    case GDK_KP_Page_Up:
      if (grn_prefs.smooth_scrolling && (event->state == 0))  offset = -4;
      else  return FALSE;
      break;
    case GDK_Page_Down:
    case GDK_KP_Page_Down:
      if (grn_prefs.smooth_scrolling && (event->state == 0))  offset = 4;
      else  return FALSE;
      break;
    default:  return FALSE;
  }
  gtk_signal_emit_stop_by_name(GTK_OBJECT(w), "key_press_event");
  if (offset && text)
  {
    gint n, lh = gdk_char_height(GTK_WIDGET(text)->style->font, 'X');

    if (offset == 5)  n = lh;
    else if (offset == -5)  n = -lh;
    else  n = offset * text->vadj->page_increment / 4;

    if (grn_prefs.smooth_scrolling)  scroll_smooth(text, n);
    else  scroll_line(text, n);
  }
  return TRUE;
}

static gint evt_mw_event(GtkWidget *w, GdkEvent *event, GtkText *text)
{
  static gboolean dragging = FALSE;
  static gint oy = 0;
  gint y = 0;
  GdkCursor *cursor;
  g_return_val_if_fail(event != NULL, FALSE);
  

  switch (event->type)
  {
    case GDK_BUTTON_PRESS:
      dragging = FALSE;
      if (event->button.button == 2)
      {
        dragging = TRUE;
        cursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW);
	gdk_window_set_cursor(w->window, cursor);
	gdk_cursor_destroy(cursor);
	gtk_grab_add(w);
	oy = event->button.y;
      }
      break;
    case GDK_BUTTON_RELEASE:
      dragging = FALSE;
      gtk_grab_remove(w);
      cursor = gdk_cursor_new(GDK_LEFT_PTR);
      gdk_window_set_cursor(w->window, cursor);
      gdk_cursor_destroy(cursor);
      break;
    case GDK_MOTION_NOTIFY:
      if (! dragging)  break;
      y = event->motion.y;
      if (y != oy)
      {
        gfloat val = text->vadj->value - (y - oy) /** text->vadj->step_increment*/;
        if (val < text->vadj->lower)  val = text->vadj->lower;
        if (val > (text->vadj->upper - text->vadj->page_size))
          val = text->vadj->upper - text->vadj->page_size;
        gtk_adjustment_set_value(text->vadj, val);
        oy = y;
      }
      break;
    default: break;
  }
  return FALSE;
}

static gint evt_mw_btnpress(GtkWidget *w, GdkEventButton *event, gpointer data)
{
  GtkText *text;
  GtkWidget *tmp;
  gint offset = 0;
  gboolean processed = FALSE;
  g_return_val_if_fail(w != NULL, FALSE);
  g_return_val_if_fail(event != NULL, FALSE);

  tmp = gtk_object_get_data(GTK_OBJECT(w), KEY_TXT_BODY);
  if ((! tmp) && (GRN->msgwin))
    tmp = gtk_object_get_data(GTK_OBJECT(GRN->msgwin), KEY_TXT_BODY);
  if (! tmp)  return FALSE;
  text = GTK_TEXT(tmp);

  if (event->button == 3)
  {
    GtkWidget *mpopup;
    
    mpopup = gnome_popup_menu_new(mw_context_menu);
    grn_menuitem_check(mpopup, _("Proportional font"), grn_prefs.mw_prop);
    grn_menuitem_check(mpopup, _("Monospaced font"), !grn_prefs.mw_prop);
    gnome_popup_menu_do_popup(mpopup, NULL, NULL, event, NULL);
    processed = TRUE;
  }
  else if (event->button == 4)
  {
    if (event->state & GDK_CONTROL_MASK)  offset = -4;
    else if (event->state & GDK_SHIFT_MASK)  offset = -2;
    else  offset = -1;
  }
  else if (event->button == 5)
  {
    if (event->state & GDK_CONTROL_MASK)  offset = 4;
    else if (event->state & GDK_SHIFT_MASK)  offset = 2;
    else  offset = 1;
  }

  if (offset && text)
  {
    gint n = offset * text->vadj->page_increment / 4;

    if (grn_prefs.smooth_scrolling)  scroll_smooth(text, n);
    else  scroll_line(text, n);
    processed = TRUE;
  }
  if (processed)
  {
    gtk_signal_emit_stop_by_name(GTK_OBJECT(w), "button_press_event");
    return TRUE;
  }
  return FALSE;
}

static void evt_mw_hide(GtkWidget *w, gpointer data)
{
  GtkText *text  = GTK_TEXT(gtk_object_get_data(GTK_OBJECT(w), KEY_TXT_BODY));
  show_message_menu(FALSE);
  if (text)  gtk_editable_delete_text(GTK_EDITABLE(text), 0, -1);
}

static void evt_mw_show(GtkWidget *w, gpointer data)
{
  grn_msgwin_stats *mws;

  show_message_menu(TRUE);
  mws = gtk_object_get_data(GTK_OBJECT(GRN->window), KEY_MW_STATS);
  show_mw_stats(mws);
}

static void mw_toggle_font(GtkWidget *w, gboolean is_prop)
{
  GtkCheckMenuItem *cmi;
  GtkWidget *text;
  gchar *fnt_name;
  g_return_if_fail(w != NULL);
  g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(w));
  
  cmi = GTK_CHECK_MENU_ITEM(w);
  if (! cmi->active)  return;
  
  if (! GRN->msgwin)  return;
  text = gtk_object_get_data(GTK_OBJECT(GRN->msgwin), KEY_TXT_BODY);
  if (! text)  return;
  
  if (is_prop)  fnt_name = grn_prefs.fonts[0];
  else  fnt_name = grn_prefs.fonts[1];
  grn_widget_set_style(text, fnt_name, NULL, NULL, NULL);
  grn_prefs.mw_prop = is_prop;
}

void evt_mw_prop_font(GtkWidget *w)
{  mw_toggle_font(w, TRUE);  }
void evt_mw_monosp_font(GtkWidget *w)
{  mw_toggle_font(w, FALSE);  }


/* Returns newly allocated (t_message *) or NULL on fail. */
t_message *get_cur_msg(GtkWidget *w)
{
  GtkCTree *ctree;
  GtkCList *clist;
  t_message *ret=NULL;

  if (w && GRN->threadlist)
  {
    if (! GTK_IS_CTREE(w))
    {
      grn_lock();
      ctree = GTK_CTREE(gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_CT_MSGS));
      grn_unlock();
    }
    else  ctree = GTK_CTREE(w);
    if (ctree)
    {
      clist = GTK_CLIST(ctree);
      ret = t_message_copy(tl_get_selected_message(ctree));
      if (ret)
      {
        if (! ret->cached)
	{
	  if (ret->mh)  t_msgheaders_rm_extra_hdrs(ret->mh);
	  nntp_download_article(ret);
//	  grnq_add_download_article(ret);
	}
        return ret;
      }
    }
  }
  if (GRN->msgwin)
  {
    grn_lock();
    ret = t_message_copy((t_message *) gtk_object_get_data(GTK_OBJECT(GRN->msgwin), KEY_MSG));
    grn_unlock();
  }
  if (ret)  return ret;
  if (GRN->grouplist)
  {
    ret = t_message_alloc();
    ret->mh = t_msgheaders_alloc();
    grn_lock();
    clist = GTK_CLIST(gtk_object_get_data(GTK_OBJECT(GRN->grouplist), KEY_CL_GROUPS));
    if (clist && clist->selection)
    {
      gint row = GPOINTER_TO_INT(clist->selection->data);
      ret->mh->grp = gtk_clist_get_row_data(clist, row);
    }
    grn_unlock();
  }
  return ret;
}

gchar *get_cur_group(GtkWidget *w)
{
  GtkCTree *ctree;
  GtkCList *clist;
  t_message *m = NULL;

  if (GRN->grouplist)
  {
    grn_lock();
    clist = GTK_CLIST(gtk_object_get_data(GTK_OBJECT(GRN->grouplist), KEY_CL_GROUPS));
    if (clist && clist->selection)
    {
      gint row = GPOINTER_TO_INT(clist->selection->data);
      grn_newsgroup *grp = gtk_clist_get_row_data(clist, row);
      if (grp && grp->name)  return g_strdup(grp->name);
    }
    grn_unlock();
  }
  if (GRN->threadlist)
  {
    grn_lock();
    ctree = GTK_CTREE(gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_CT_MSGS));
    grn_unlock();
    if (ctree)
    {
      clist = GTK_CLIST(ctree);
      m = tl_get_selected_message(ctree);
      if (m && m->mh && m->mh->grp && m->mh->grp->name)
        return g_strdup(m->mh->grp->name);
    }
  }
  if (GRN->msgwin)
  {
    grn_lock();
    m = (t_message *) gtk_object_get_data(GTK_OBJECT(GRN->msgwin), KEY_MSG);
    grn_unlock();
    if (m && m->mh && m->mh->grp && m->mh->grp->name)
      return g_strdup(m->mh->grp->name);
  }
  return NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1