/*-
 * Copyright (c) 2003-2004 Seth Kingsley.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *    This product includes software developed by Seth Kingsley and
 *    contributors.
 * 4. Neither the author's name, nor any of the contributors names may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include    <sys/types.h>
#include    <sys/errno.h>

#include    <stdio.h>
#include    <string.h>
#include    <ctype.h>
#include    <assert.h>
#ifdef DEBUG
#include    <err.h>
#endif /* DEBUG */

#include    <xmk-gtk.h>

#include    "support.h"
#include    "util.h"
#include    "portsman.h"
#include    "refresh.h"
#include    "catlist.h"
#include    "portlist.h"
#include    "descview.h"

__RCSID("$Id: descview.c,v 1.19 2004/07/22 11:03:38 sethk Exp $");

#define TITLE_SCALE	1.1
#define CAT_SEP		", "
#define KEY_IND		12
#define VALUE_IND	88
#define WWW_STR		"WWW:"
#define MAILTO_SCHEMA	"mailto:"
#define BROWSER_PROGS	{"firefox", "mozilla", "konquerer", NULL}

struct descview_line
{
    const gchar	*dl_tag,
		*dl_value;
    enum
    {
	DLINE_TEXT,
	DLINE_CAT,
	DLINE_EMAIL,
	DLINE_URL,
	DESC_NLINE
    }		dl_type;
};

struct descview_data
{
    struct refresh_data	    *dd_rdatap;
    struct catlist_data	    *dd_cdatap;
    struct portlist_data    *dd_pldatap;
    struct port_desc	    *dd_curport;
    GtkTextView		    *dd_descview;
    GtkTextBuffer	    *dd_buffer;
    GtkTextTag		    *dd_hidetag,
			    *dd_ttltag,
			    *dd_keytag,
			    *dd_valtag,
			    *dd_linktag,
			    *dd_linetags[DESC_NLINE],
			    *dd_dettag;
    GdkCursor		    *dd_ibarcur,
			    *dd_handcur;
    GdkCursor		    *dd_curcur;	    /* Current cursor. */
};

struct descview_data	*ddatap;

static void
_descview_insert_lines(struct descview_line *dlp,
	guint ndl,
	GtkTextIter *iterp,
	struct descview_data *ddp)
{
    guint   i;
    gchar   line[512 + 1];

    for (i = 0; i < ndl; ++i)
    {
	if (!*dlp[i].dl_value)
	    continue;
	gtk_text_buffer_insert_with_tags(ddp->dd_buffer,
		iterp,
		line,
		snprintf(line, sizeof(line), "\n%s:\t", dlp[i].dl_tag),
		ddp->dd_keytag,
		NULL);

	gtk_text_buffer_insert_with_tags(ddp->dd_buffer,
		iterp,
		dlp[i].dl_value,
		strlen(dlp[i].dl_value),
		ddp->dd_valtag,
		(dlp[i].dl_type != DLINE_TEXT) ? ddp->dd_linktag : NULL,
		ddp->dd_linetags[dlp[i].dl_type],
		NULL);
    }
}

static void
_descview_cursor_set(struct descview_data *ddp,
	GdkWindow *win,
	GdkCursor *cur)
{
    if (ddp->dd_curcur != cur)
    {
	gdk_window_set_cursor(win, cur);
	ddp->dd_curcur = cur;
    }
}

static void
_descview_populate_descr(struct descview_data *ddp,
	struct port_desc *pdescp,
	GtkTextIter *iterp)
{
    gchar line[256 + 1];
    gsize size;
    gchar *desc, *desc_utf8;
    gsize uout;
    GError *errp = NULL;
    char *dline;
    size_t dlen;

    gtk_text_buffer_insert_with_tags(ddp->dd_buffer,
	    iterp,
	    line,
	    snprintf(line, sizeof(line), "\n%s:\t", _("Details")),
	    ddp->dd_keytag,
	    NULL);

    if (g_file_get_contents(pdescp->pd_descpath, &desc, &size, &errp) ==
	    FALSE)
    {
	char estr[256 + 1];

	gtk_text_buffer_insert_with_tags_by_name(ddp->dd_buffer,
		iterp,
		estr,
		snprintf(estr, sizeof(estr),
		    _("Cannot read desc file %s: %s"),
		    pdescp->pd_descpath,
		    strerror(errno)),
		"error",
		NULL);

	return;
    }

    /* Strip maintainer and website from descr text. */
    while ((dline = strrchr(desc, '\n')))
    {
	char	*cp = dline + 1;

	while (*cp == ' ' || *cp == '\t')
	    ++cp;

	/* Check for blank line. */
	if (!*cp || *cp == '\n')
	{
	    *dline = '\0';
	    continue;
	}

	/* Check for signature. */
	if (strchr(cp, '@') && dline > desc)
	{
	    char    *sline = dline - 1,
		    *sp;

	    /* Find the previous line. */
	    while (sline > desc && *sline != '\n')
		--sline;

	    sp = sline + 1;

	    while (*sp == ' ' || *sp == '\t')
		++sp;

	    if (*sp == '-')
	    {
		*sline = '\0';
		continue;
	    }
	}

	/* Check for URL. */
	if (!strncmp(cp, WWW_STR, sizeof(WWW_STR) - 1))
	{
	    *dline = '\0';
	    continue;
	}

	/* Regular description line found. */
	break;
    }
    dlen = strlen(desc);

    /* Add a newline to the end of the desc if it's missing. */
    if (desc[dlen - 1] != '\n')
    {
	if (dlen == size)
	    desc = g_realloc(desc, dlen);

	desc[dlen++] = '\n';
	desc[dlen] = '\0';
    }
	
    desc_utf8 = g_convert(desc, -1,
	    "UTF-8", "ISO-8859-1",
	    NULL, &uout,
	    &errp);
    gtk_text_buffer_insert_with_tags(ddp->dd_buffer,
	    iterp,
	    desc_utf8,
	    uout,
	    ddp->dd_valtag,
	    ddp->dd_dettag,
	    NULL);
    g_free(desc_utf8);
    g_free(desc);
}

struct descview_data *
descview_init(GtkWindow *app,
	struct refresh_data *rdp,
	struct catlist_data *cdp,
	struct portlist_data *pldp)
{
    static struct descview_data	ddata;
    PangoTabArray		*tabs;

    ddata.dd_rdatap = rdp;
    ddata.dd_cdatap = cdp;
    ddata.dd_pldatap = pldp;

    ddata.dd_curport = NULL;

    ddata.dd_descview = GTK_TEXT_VIEW(lookup_widget(GTK_WIDGET(app),
		"descview"));
    ddata.dd_buffer = gtk_text_view_get_buffer(ddata.dd_descview);

    gtk_text_buffer_create_tag(ddata.dd_buffer,
	    "error",
	    "foreground", "Red",
	    "weight", PANGO_WEIGHT_BOLD,
	    NULL);

    ddata.dd_ttltag = gtk_text_buffer_create_tag(ddata.dd_buffer,
	    "title",
	    "scale", TITLE_SCALE,
	    "weight", PANGO_WEIGHT_BOLD,
	    "stretch", PANGO_STRETCH_CONDENSED,
	    "pixels-below-lines", 0,
	    NULL);
    tabs = pango_tab_array_new(3, TRUE);
    pango_tab_array_set_tab(tabs, 0, PANGO_TAB_LEFT, 1);
    pango_tab_array_set_tab(tabs, 1, PANGO_TAB_LEFT, VALUE_IND - KEY_IND);

    ddata.dd_keytag = gtk_text_buffer_create_tag(ddata.dd_buffer,
	    "key",
	    "left-margin", KEY_IND,
	    "tabs", tabs,
	    NULL);

    ddata.dd_valtag = gtk_text_buffer_create_tag(ddata.dd_buffer,
	    "value",
	    "left-margin", VALUE_IND,
	    "left-margin-set", TRUE,
	    NULL);

    ddata.dd_dettag = gtk_text_buffer_create_tag(ddata.dd_buffer,
	    "detail",
	    "family", "Monospace",
	    NULL);

    ddata.dd_linktag = gtk_text_buffer_create_tag(ddata.dd_buffer,
	    "link",
	    "foreground", "Blue",
	    "underline", PANGO_UNDERLINE_SINGLE,
	    NULL);

    ddata.dd_linetags[DLINE_TEXT] = NULL;
    ddata.dd_linetags[DLINE_CAT] = gtk_text_buffer_create_tag(ddata.dd_buffer,
	    "category",
	    NULL);
    ddata.dd_linetags[DLINE_EMAIL] =
	gtk_text_buffer_create_tag(ddata.dd_buffer, "email", NULL);
    ddata.dd_linetags[DLINE_URL] = gtk_text_buffer_create_tag(ddata.dd_buffer,
	    "website",
	    NULL);

    ddata.dd_ibarcur = gdk_cursor_new(GDK_XTERM);
    ddata.dd_handcur = gdk_cursor_new(GDK_HAND2);
    ddata.dd_curcur = ddata.dd_ibarcur;

    return (ddatap = &ddata);
}

void
descview_fini(struct descview_data *ddp)
{
}

void
descview_populate(struct descview_data *ddp,
	struct cat_desc *cdescp,
	struct port_desc *pdescp)
{
    gchar		    line[256 + 1];
    struct descview_line    lines1[] =
    {
	{_("Description"), pdescp->pd_comm, DLINE_TEXT},
	{_("Version"), pdescp->pd_version, DLINE_TEXT},
	{_("State"), portsman_state_desc(pdescp->pd_state), DLINE_TEXT},
    },			    lines2[] =
    {
	{_("Maintainer"), pdescp->pd_maint, DLINE_EMAIL},
	{_("Directory"), pdescp->pd_origin, DLINE_TEXT},
    },
			    lines3[] =
    {
	{_("Website"), pdescp->pd_website, DLINE_URL}
    };
    GtkTextIter		    iter;
    guint		    i;

#ifdef DEBUG_VIEWS
    warnx("%s(%p)", __func__, pdescp);
#endif /* DEBUG_VIEWS */

    assert(!ddp->dd_curport);
    ddp->dd_curport = pdescp;

    if (!pdescp->pd_descpath)
    {
#ifdef DEBUG
	warnx("no desc path!");
#endif /* !DEBUG */
	return;
    }

    gtk_text_buffer_get_end_iter(ddp->dd_buffer, &iter);

    gtk_text_buffer_insert_with_tags(ddp->dd_buffer,
	    &iter,
	    line,
	    snprintf(line, sizeof(line), "%s:", pdescp->pd_name),
	    ddp->dd_ttltag,
	    NULL);
    gtk_text_buffer_insert(ddp->dd_buffer, &iter, "\n", 1);

    _descview_insert_lines(lines1,
	    sizeof(lines1) / sizeof(lines1[0]),
	    &iter,
	    ddp);

    gtk_text_buffer_insert_with_tags(ddp->dd_buffer,
	    &iter,
	    line,
	    snprintf(line,
		sizeof(line),
		"\n%s:\t", _("Categories")),
	    ddp->dd_keytag,
	    NULL);
    for (i = 0; i < pdescp->pd_ncat; ++i)
    {
	if (i)
	    gtk_text_buffer_insert_with_tags(ddp->dd_buffer,
		    &iter,
		    CAT_SEP,
		    sizeof(CAT_SEP) - 1,
		    ddp->dd_valtag,
		    NULL);
	gtk_text_buffer_insert_with_tags(ddp->dd_buffer,
		&iter,
		pdescp->pd_cats[i]->cd_name,
		strlen(pdescp->pd_cats[i]->cd_name),
		ddp->dd_valtag,
		(pdescp->pd_cats[i] != cdescp) ? ddp->dd_linktag : NULL,
		ddp->dd_linetags[DLINE_CAT],
		NULL);
    }

    _descview_insert_lines(lines2,
	    sizeof(lines2) / sizeof(lines2[0]),
	    &iter,
	    ddp);

    _descview_populate_descr(ddp, pdescp, &iter);

    _descview_insert_lines(lines3,
	    sizeof(lines3) / sizeof(lines3[0]),
	    &iter,
	    ddp);

    gtk_text_buffer_insert(ddp->dd_buffer,
	    &iter,
	    "\n",
	    1);

    gtk_widget_set_sensitive(GTK_WIDGET(ddp->dd_descview), TRUE);
}

void
descview_clear(struct descview_data *ddp)
{
    GtkTextBuffer   *buffer = gtk_text_view_get_buffer(ddp->dd_descview);
    GtkTextIter	    start;

#ifdef DEBUG_VIEWS
    warnx("%s", __func__);
#endif /* DEBUG_VIEWS */

    ddp->dd_curport = NULL;

    gtk_text_buffer_set_text(buffer, "", 0);
    gtk_text_buffer_get_start_iter(buffer, &start);
    gtk_text_view_scroll_to_iter(ddp->dd_descview, &start, 0, FALSE, 0, 0);
    gtk_widget_set_sensitive(GTK_WIDGET(ddp->dd_descview), FALSE);
}

void
descview_motion(struct descview_data *ddp, GdkEventMotion *gemp)
{
    GtkTextIter	iter;
    gint	x, y,
		bufx, bufy;
    
    if (gtk_text_view_get_window_type(ddp->dd_descview, gemp->window) !=
	    GTK_TEXT_WINDOW_TEXT)
	return;

    gdk_window_get_pointer(gemp->window, &x, &y, NULL);
    gtk_text_view_window_to_buffer_coords(ddp->dd_descview,
	    GTK_TEXT_WINDOW_TEXT,
	    x, y,
	    &bufx, &bufy);
    gtk_text_view_get_iter_at_location(ddp->dd_descview, &iter, bufx, bufy);

    if (gtk_text_iter_has_tag(&iter, ddp->dd_linktag))
	_descview_cursor_set(ddp, gemp->window, ddp->dd_handcur);
    else
	_descview_cursor_set(ddp, gemp->window, ddp->dd_ibarcur);
}

gboolean
descview_button(struct descview_data *ddp, GdkEventButton *gebp)
{
    GtkTextIter	iter;
    guint	bufx, bufy;

    if (gtk_text_view_get_window_type(ddp->dd_descview, gebp->window) !=
	    GTK_TEXT_WINDOW_TEXT)
	return FALSE;

    gtk_text_view_window_to_buffer_coords(ddp->dd_descview,
	    GTK_TEXT_WINDOW_TEXT,
	    gebp->x, gebp->y,
	    &bufx, &bufy);
    gtk_text_view_get_iter_at_location(ddp->dd_descview, &iter, bufx, bufy);

    if (gtk_text_iter_has_tag(&iter, ddp->dd_linktag))
    {
	GtkTextIter *siter, *eiter;
	gchar	    *link;

	siter = gtk_text_iter_copy(&iter);
	eiter = gtk_text_iter_copy(&iter);
	if (!gtk_text_iter_begins_tag(siter, ddp->dd_linktag))
	    gtk_text_iter_backward_to_tag_toggle(siter, ddp->dd_linktag);
	if (!gtk_text_iter_ends_tag(eiter, ddp->dd_linktag))
	    gtk_text_iter_forward_to_tag_toggle(eiter, ddp->dd_linktag);

	link = gtk_text_buffer_get_text(ddp->dd_buffer, siter, eiter, TRUE);

	if (gtk_text_iter_has_tag(&iter, ddp->dd_linetags[DLINE_CAT]))
	{
	    struct cat_desc	*cdescp = refresh_cat_find(ddp->dd_rdatap,
		    link,
		    TRUE,
		    FALSE);
	    struct port_desc	*pdescp = ddp->dd_curport;

	    assert(cdescp);
	    catlist_select(ddp->dd_cdatap, cdescp);
	    portlist_select(ddp->dd_pldatap, pdescp);
	}
	else if (gtk_text_iter_has_tag(&iter,
		    ddp->dd_linetags[DLINE_EMAIL]) ||
		gtk_text_iter_has_tag(&iter, ddp->dd_linetags[DLINE_URL]))
	{
	    char    *bprogs[] = BROWSER_PROGS,
		    *av[] = {NULL, "-remote", NULL, NULL};

	    av[2] = g_strdup_printf("openURL(%s%s)",
		    (gtk_text_iter_has_tag(&iter,
					   ddp->dd_linetags[DLINE_EMAIL])) ?
		    MAILTO_SCHEMA : "",
		    link);
	    util_spawn("browser", NULL, bprogs, av);
	    g_free(av[2]);
	}

	gtk_text_iter_free(siter);
	gtk_text_iter_free(eiter);

	g_free(link);

	return TRUE;
    }

    return FALSE;
}

