/**
 * Graphical .deb file viewer
 *
 * 	(C) 1998 Lalo Martins <lalo@debian.org>
 * 	    2002, 2003 Filip Van Raemdonck <mechanix@debian.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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 * 	$Id$
 *
 **/

#include "gdeb.h"

#include <ctype.h>
#include <gtk/gtktext.h>

#ifndef _
#if ENABLE_NLS
#define _(x) gettext(x)
#else
#define _(x) (x)
#endif
#endif

#define GDEB_PAD 6

using namespace std;

// we keep an instance of this for when we don't have any
//  package
class NoPackage : public GDeb::Package
{
public:
  // Stuff to display
  virtual string Name() { return _("No package selected"); }
  virtual string Version() { return ""; }
  virtual string Description_Short()  { return ""; }
  virtual string Description_Long()  { return ""; }
  virtual string Section()  { return ""; }
  virtual string Priority()  { return ""; }
  virtual string Size()  { return ""; }
  virtual string Installed_Size()  { return ""; }
  virtual string Maintainer()  { return ""; }
  virtual string Status()  { return ""; }
  virtual string Current_Version()  { return ""; }
  virtual string Architecture()  { return ""; }
  virtual string Source()  { return ""; }
  virtual string Filename()  { return ""; }
	virtual string Filelist (void) { return ""; }
	virtual string Author (void) { return ""; }
	virtual string Homepage (void) { return ""; }
  virtual bool   Essential() { return false; }
  virtual bool   Downloadable() { return false; }
  virtual bool   Automatic() { return false; }
  virtual vector<string> Provides() { return vector<string>(0); }
  virtual vector<string> PreDepends()  { return vector<string>(0); }
  virtual vector<string> Depends()  { return vector<string>(0); }
  virtual vector<string> Recommends()  { return vector<string>(0); }
  virtual vector<string> Suggests()  { return vector<string>(0); }
  virtual vector<string> Conflicts()  { return vector<string>(0); }
  virtual vector<string> Replaces()  { return vector<string>(0); }
  
  // Actions - should not be called on a nopackage
  virtual void Install() { g_warning(__FUNCTION__); }
  virtual void Delete() { g_warning(__FUNCTION__); }
  virtual void Keep() { g_warning(__FUNCTION__); }

};

static NoPackage* no_package = 0;

// rebuilds the Containers (for example, if configuration changed)
void 
GDeb::rebuild ()
{
  if (toplevel_ == 0)
    {
      toplevel_ = gtk_vbox_new(FALSE, 5);
    }


  if (conf_.show_title) 
    {
      if (title_ == 0)
        {
          title_ = gtk_hbox_new(FALSE, 10);
          gtk_box_pack_start(GTK_BOX(toplevel_), title_, FALSE, FALSE, 0);
        }
    }
  else 
    {
      if (title_) gtk_widget_destroy(title_);
      title_ = 0;
      // child widgets
      short_desc_ = 0;
      package_ = 0;
    }


  if (title_ != 0 && package_ == 0 && short_desc_ == 0)
    {
		GtkWidget* tbl = gtk_table_new (7, 2, FALSE);
		gtk_table_set_row_spacings (GTK_TABLE (tbl), 2);
		gtk_table_set_col_spacings (GTK_TABLE (tbl), 8);
		gtk_box_pack_end (GTK_BOX (title_), tbl, TRUE, TRUE, 0);

      package_ = gtk_label_new("");
      short_desc_ = gtk_label_new("");

		gtk_table_attach_defaults (GTK_TABLE (tbl), package_, 0, 2, 0, 1);
		gtk_table_attach_defaults (GTK_TABLE (tbl), short_desc_, 0, 2, 1, 2);

      // Status
      status_ = gtk_label_new("");
		GtkWidget* lbl = gtk_label_new (_("<b>Status:</b>"));
		gtk_label_set_use_markup (GTK_LABEL (lbl), TRUE);
		gtk_misc_set_alignment (GTK_MISC (lbl), 1.0, 0.5);
		gtk_misc_set_alignment (GTK_MISC (status_), 0.0, 0.5);
		gtk_table_attach (GTK_TABLE (tbl), lbl, 0, 1, 2, 3, GTK_FILL, (GtkAttachOptions) 0, GDEB_PAD, 0);
		gtk_table_attach_defaults (GTK_TABLE (tbl), status_, 1, 2, 2, 3);

      // Installed size
      installed_size_ = gtk_label_new("");
		lbl = gtk_label_new (_("<b>Installed Size:</b>"));
		gtk_label_set_use_markup (GTK_LABEL (lbl), TRUE);
		gtk_misc_set_alignment (GTK_MISC (lbl), 1.0, 0.5);
		gtk_misc_set_alignment (GTK_MISC (installed_size_), 0.0, 0.5);
		gtk_table_attach (GTK_TABLE (tbl), lbl, 0, 1, 3, 4, GTK_FILL, (GtkAttachOptions) 0, GDEB_PAD, 0);
		gtk_table_attach_defaults (GTK_TABLE (tbl), installed_size_, 1, 2, 3, 4);

      // Section
      section_ = gtk_label_new("");
		lbl = gtk_label_new (_("<b>Section:</b>"));
		gtk_label_set_use_markup (GTK_LABEL (lbl), TRUE);
		gtk_misc_set_alignment (GTK_MISC (lbl), 1.0, 0.5);
		gtk_misc_set_alignment (GTK_MISC (section_), 0.0, 0.5);
		gtk_table_attach (GTK_TABLE (tbl), lbl, 0, 1, 4, 5, GTK_FILL, (GtkAttachOptions) 0, GDEB_PAD, 0);
		gtk_table_attach_defaults (GTK_TABLE (tbl), section_, 1, 2, 4, 5);

      // Priority
      priority_ = gtk_label_new("");
		lbl = gtk_label_new (_("<b>Priority:</b>"));
		gtk_label_set_use_markup (GTK_LABEL (lbl), TRUE);
		gtk_misc_set_alignment (GTK_MISC (lbl), 1.0, 0.5);
		gtk_misc_set_alignment (GTK_MISC (priority_), 0.0, 0.5);
		gtk_table_attach (GTK_TABLE (tbl), lbl, 0, 1, 5, 6, GTK_FILL, (GtkAttachOptions) 0, GDEB_PAD, 0);
		gtk_table_attach_defaults (GTK_TABLE (tbl), priority_, 1, 2, 5, 6);

      // Download Size
      size_ = gtk_label_new("");
		lbl = gtk_label_new (_("<b>Package file size:</b>"));
		gtk_label_set_use_markup (GTK_LABEL (lbl), TRUE);
		gtk_misc_set_alignment (GTK_MISC (lbl), 1.0, 0.5);
		gtk_misc_set_alignment (GTK_MISC (size_), 0.0, 0.5);
		gtk_table_attach (GTK_TABLE (tbl), lbl, 0, 1, 6, 7, GTK_FILL, (GtkAttachOptions) 0, GDEB_PAD, 0);
		gtk_table_attach_defaults (GTK_TABLE (tbl), size_, 1, 2, 6, 7);
    }

  // We always create the notebook; the config option 
  //  determines whether it has tabs. Good idea from Lalo
  if (notebook_ == 0)
    {
      notebook_ = gtk_notebook_new();

      gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook_));

      gtk_box_pack_end(GTK_BOX(toplevel_), notebook_, TRUE, TRUE, 0);
    }

  gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook_), conf_.use_notebook);
  
  // Eventually, we will do this differently for advanced/normal mode
  //  and consider vertical/horizontal
  if (pages_[PageDescription] == 0)
    {
      GtkWidget* desc = gtk_vbox_new(FALSE, 2);
      GtkWidget* label = gtk_label_new(_("Description"));

      GtkWidget* sw = gtk_scrolled_window_new(0,0);
		gtk_container_set_border_width (GTK_CONTAINER (sw), 1);

      gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), 
                                     GTK_POLICY_AUTOMATIC,
                                     GTK_POLICY_AUTOMATIC);

		long_desc_ = gtk_text_view_new ();
		gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (long_desc_), GTK_WRAP_WORD);
		gtk_text_view_set_editable (GTK_TEXT_VIEW (long_desc_), FALSE);
		gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (long_desc_), FALSE);
		gtk_container_add (GTK_CONTAINER (sw), long_desc_);

      gtk_box_pack_start(GTK_BOX(desc), sw, TRUE, TRUE, 0);

      gtk_notebook_append_page(GTK_NOTEBOOK(notebook_), desc, label);

      pages_[PageDescription] = desc;
    }

	if (pages_[PageContents] == 0) {
		GtkWidget* sw = gtk_scrolled_window_new (0, 0);
		gtk_container_set_border_width (GTK_CONTAINER (sw), 1);
		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

		GtkWidget* label = gtk_label_new (_("File list"));
		gtk_notebook_append_page (GTK_NOTEBOOK (notebook_), sw, label);
		pages_[PageContents] = sw;
	}

	if (pages_[PageLinks] == 0) {
		GtkWidget* sw = gtk_scrolled_window_new (0, 0);
		gtk_container_set_border_width (GTK_CONTAINER (sw), 1);
		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

		GtkWidget* label = gtk_label_new(_("Links"));
		gtk_notebook_append_page (GTK_NOTEBOOK (notebook_), sw, label);
		pages_[PageLinks] = sw;
    }

  refresh();
}

#define ga_debug(format, args...)
static string
normalize (string desc) {
	string::size_type pos = desc.find ('\n');
	if (pos == string::npos) return string ("");

	desc = string (desc, pos + 2);
	ga_debug ("pos %d size %d desc %s", pos, desc.size(), desc.c_str());
	while (desc.size() && (pos = desc.find ('\n')) == 0) {
		desc = string (desc, pos + 1);
		ga_debug ("pos %d size %d desc %s", pos, desc.size(), desc.c_str());
	}

	gboolean newpar = FALSE;
	pos = 0;
	while ((pos = desc.find ('\n', pos)) != string::npos) {
		int i = pos + 2;	/* move past initial space */

		if (desc[i] == ' ') {
/* FIXME: GtkTextView _will_ wrap these, and not hard, contrary to what policy dictates */
			/* must display verbatim - including newlines ... */
			if ((pos = desc.find ('\n', ++pos)) == string::npos) {
				/* ... but do bail out when that newline is the end of the description */
				break;
			}
			/* don't forget to insert a newline or we'll eat the next one */
			desc.insert (pos, "\n");
		} else if (desc[i] == '.') {
			if (desc[i+1] == '\n') {
				desc[i-1] = '\n';	/* replace ' ' by newline */
				desc.erase (i, 1);	/* remove '.' */
				pos = i;	/* reposition */
				newpar = TRUE;
			} else {
				/* Trailing garbage - we're supposed to ignore such lines */
				string::size_type npos = desc.find ('\n', pos + 1);
				desc.erase (pos, npos - pos);
			}
		}

		desc.erase (pos, 1);
		if (newpar) {
			newpar = FALSE;
			desc.erase (pos, 1);
		}
	}

	return desc;
}

#define BROWSER "sensible-browser"
static void
launch_uri (const gchar* uri) {
	GError* err = NULL;

	gchar* cmd = g_strdup_printf (BROWSER " \"%s\"", uri);
	g_warning ("launching URI: %s", cmd);
	if (!g_spawn_command_line_async (cmd, &err)) {
		g_warning ("Browser launch failed: %s", err->message);
	}

	g_free (cmd);
}

static gboolean
text_clicked (GtkWidget* wdg, GdkEventButton* ev, gpointer data) {
	static GtkTextTag* tag = NULL;

	g_return_val_if_fail (GTK_IS_TEXT_VIEW (wdg), FALSE);

	if (!tag) {
		tag = gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (
		          gtk_text_view_get_buffer (GTK_TEXT_VIEW (wdg))), "url");
	}
	if (!tag) return FALSE;

	if (ev->button == 1 || ev->button == 2) {
		gint x, y;
		GtkTextIter pos;

		gtk_text_view_window_to_buffer_coords (
		      GTK_TEXT_VIEW (wdg), GTK_TEXT_WINDOW_TEXT, (gint) ev->x, (gint) ev->y, &x, &y);
		gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (wdg), &pos, x, y);
		if (!gtk_text_iter_has_tag (&pos, tag)) return FALSE;

		GtkTextIter to = pos;
		if (!gtk_text_iter_backward_to_tag_toggle (&pos, NULL)) return FALSE;
		if (!gtk_text_iter_forward_to_tag_toggle (&to, NULL)) return FALSE;
		launch_uri (gtk_text_iter_get_text (&pos, &to));
	}

	return FALSE;
}

/* More or less stolen from pan */
static gboolean
text_motion (GtkWidget* wdg, GdkEventMotion* ev, gpointer data) {
	static GdkCursor* cursor_current = NULL;
	static GdkCursor* cursor_ibeam = NULL;
	static GdkCursor* cursor_href = NULL;
	static GtkTextTag* tag = NULL;

	g_return_val_if_fail (GTK_IS_TEXT_VIEW (wdg), FALSE);
	if (!ev->window) return FALSE;

	int x, y;
	GdkCursor* cursor_new;
	GdkModifierType state;

	/* initialize static variables */
	if (!cursor_ibeam)
		cursor_ibeam = gdk_cursor_new (GDK_XTERM);
	if (!cursor_href)
		cursor_href = gdk_cursor_new (GDK_HAND2);

	/* pump out x, y, and state */
/* FIXME */
	if (ev->is_hint)
		gdk_window_get_pointer (ev->window, &x, &y, &state);
	else {
		x = (gint) ev->x;
		y = (gint) ev->y;
	}

	/* decide what cursor we should be using */
	if (!tag) {
		tag = gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (
		          gtk_text_view_get_buffer (GTK_TEXT_VIEW (wdg))), "url");
	}
	if (!tag) return FALSE;

	GtkTextIter pos;
	gtk_text_view_window_to_buffer_coords (
	      GTK_TEXT_VIEW (wdg), GTK_TEXT_WINDOW_TEXT, (gint) ev->x, (gint) ev->y, &x, &y);
	gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (wdg), &pos, x, y);

	gboolean url = gtk_text_iter_has_tag (&pos, tag);
	if (!url)
		cursor_new = cursor_ibeam;
	else {
		cursor_new = cursor_href;
	}

	/* change the cursor if needed */
	if (cursor_new != cursor_current)
		gdk_window_set_cursor (ev->window, cursor_current = cursor_new);

	return FALSE;
}

void
GDeb::refresh()
{
  Package* p = 0;
  if (pkg_ == 0)
    { 
      if (no_package == 0)
        no_package = new NoPackage;

      p = no_package;
    }
  else 
    {
      p = pkg_;
    }

  if (package_ != 0) 
    {
		string s = "<b>" + p->Name() + " " + p->Version() + "</b>";
		gtk_label_set_text (GTK_LABEL (package_), s.c_str());
		gtk_label_set_use_markup (GTK_LABEL (package_), TRUE);
    }

  if (short_desc_ != 0) 
    {
      string s = p->Description_Short();
		gtk_label_set_text (GTK_LABEL (short_desc_), s.c_str());
    }

  if (status_ != 0)
    {
      string s = p->Status();
		gtk_label_set_text (GTK_LABEL (status_), s.c_str());
    }

  if (installed_size_ != 0)
    {
      string s = p->Installed_Size();
		gtk_label_set_text (GTK_LABEL (installed_size_), s.c_str());
    }

  if (section_ != 0)
    {
      string s = p->Section();
		gtk_label_set_text (GTK_LABEL (section_), s.c_str());
    }

  if (priority_ != 0)
    {
      string s = p->Priority();
		gtk_label_set_text (GTK_LABEL (priority_), s.c_str());
    }

  if (size_ != 0)
    {
      string s = p->Size();
		gtk_label_set_text (GTK_LABEL (size_), s.c_str());
    }

	if (long_desc_) {
		GtkTextBuffer* buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (long_desc_));
		gtk_text_buffer_set_text (buffer, normalize (p->Description_Long()).c_str(), -1);
	}

	string files = p->Filelist();
	if (p == no_package) {
		GtkWidget* sw = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook_), (gint) PageContents);
		GtkWidget* view = gtk_bin_get_child (GTK_BIN (sw));
		if (view) gtk_widget_destroy (view);
	} else if (files.size() > 0) {
		GtkWidget* sw = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook_), (gint) PageContents);

		GtkWidget* filesview = gtk_bin_get_child (GTK_BIN (sw));
		if (!filesview) {
			filesview = gtk_text_view_new ();
			gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (filesview), GTK_WRAP_WORD);
			gtk_text_view_set_editable (GTK_TEXT_VIEW (filesview), FALSE);
			gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (filesview), FALSE);
			gtk_container_add (GTK_CONTAINER (sw), filesview);
			gtk_widget_show_all (sw);
		}

		GtkTextBuffer* buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (filesview));
		gtk_text_buffer_set_text (buffer, files.c_str(), -1);
	}

	if (p == no_package) {
		GtkWidget* sw = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook_), (gint) PageLinks);
		GtkWidget* view = gtk_bin_get_child (GTK_BIN (sw));
		if (view) gtk_widget_destroy (view);
	} else {
		GtkWidget* sw = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook_), (gint) PageLinks);

		GtkWidget* linkview = gtk_bin_get_child (GTK_BIN (sw));
		if (!linkview) {
			linkview = gtk_text_view_new();
			gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (linkview), GTK_WRAP_WORD);
			gtk_text_view_set_editable (GTK_TEXT_VIEW (linkview), FALSE);
			gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (linkview), FALSE);
			gtk_container_add (GTK_CONTAINER (sw), linkview);
			g_signal_connect (G_OBJECT (linkview), "button_press_event", G_CALLBACK (text_clicked), NULL);
			g_signal_connect (G_OBJECT (linkview), "motion_notify_event", G_CALLBACK (text_motion), NULL);
			gtk_widget_show_all (sw);
		}

		GtkTextBuffer* buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (linkview));
		gtk_text_buffer_set_text (buffer, "", -1);
		GtkTextTag* tag = gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (buffer), "url");
		if (!tag) {
			tag = gtk_text_buffer_create_tag (
			      buffer, "url", "underline", PANGO_UNDERLINE_SINGLE, "foreground", "blue", NULL);
		}

		GtkTextIter it;
		gtk_text_buffer_get_end_iter (buffer, &it);

		gtk_text_buffer_insert (buffer, &it, _("Maintainer: "), -1);
		const gchar* str = p->Maintainer().c_str();
		if (!g_utf8_validate (str, -1, NULL)) {
			str = g_locale_to_utf8 (str, -1, NULL, NULL, NULL);
		}
		gtk_text_buffer_insert (buffer, &it, str, -1);
		gtk_text_buffer_insert (buffer, &it, "\n", -1);

		gtk_text_buffer_insert (buffer, &it, _("Bug list: "), -1);
		gtk_text_buffer_insert_with_tags (buffer, &it, "http://bugs.debian.org/", -1, tag, NULL);
		gtk_text_buffer_insert_with_tags (buffer, &it, p->Name().c_str(), -1, tag, NULL);
		gtk_text_buffer_insert (buffer, &it, "\n", -1);

		gtk_text_buffer_insert (buffer, &it, _("Author: "), -1);
		str = p->Author().c_str();
		if (!g_utf8_validate (str, -1, NULL)) {
			str = g_locale_to_utf8 (str, -1, NULL, NULL, NULL);
		}
		gtk_text_buffer_insert (buffer, &it, str, -1);
		gtk_text_buffer_insert (buffer, &it, "\n", -1);

		gtk_text_buffer_insert (buffer, &it, _("Homepage: "), -1);
		string s = p->Homepage();
		if (s != _("Unknown")) {
			gtk_text_buffer_insert_with_tags (buffer, &it, s.c_str(), -1, tag, NULL);
		} else {
			gtk_text_buffer_insert (buffer, &it, s.c_str(), -1);
		}
		gtk_text_buffer_insert (buffer, &it, "\n", -1);
	}
}


// constructor
GDeb::GDeb (const string & app_name)
  : 
     toplevel_(0),
     title_(0),
     package_(0),
     short_desc_(0),
     
     status_(0),
     installed_size_(0),
     section_(0),
     priority_(0),
     size_(0),

     notebook_(0),

     long_desc_(0),

     pkg_(0),
     
     name_(app_name)
{
  int i = 0;
  while (i < static_cast<int>(PageTypeEnd))
    {
      pages_[i] = 0;
      ++i;
    }

  rebuild();
}

// destructor
GDeb::~GDeb()
{
  if (no_package != 0)
    {
      delete no_package;
      no_package = 0;
    }

  // We don't nuke the widgets, since we assume they were
  //  placed in some container.
}

// get the toplevel widget
GtkWidget* 
GDeb::widget()
{
  return toplevel_;
}

// sets the current page
void 
GDeb::set_page (PageType p)
{
	gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook_), p);
}

// changes the package data
void 
GDeb::set_package (Package* pkg)
{
  // OK to set pkg_ to 0
  pkg_ = pkg;
  refresh();
}

void 
GDeb::set_config (const Config & c) 
{
  // If we change configuration such that widgets will need replacing,
  //  we need to destroy the old widgets and set their slots to 0, so 
  //  rebuild will replace them.

  if (conf_.adv_mode != c.adv_mode)
    {
      // Need to destroy all pages and recreate.

      int i = 0;
      while (i < static_cast<int>(PageTypeEnd))
        {
          if (pages_[i]) 
            gtk_widget_destroy(pages_[i]);
          pages_[i] = 0;
          ++i;
        }  
    }
  
  conf_ = c;
  rebuild();
}
