/*
 * This file Copyright (C) Mnemosyne LLC
 *
 * This file is licensed by the GPL version 2. Works owned by the
 * Transmission project are granted a special exemption to clause 2(b)
 * so that the bulk of its code can remain under the MIT license.
 * This exemption does not extend to derived works not owned by
 * the Transmission project.
 *
 * $Id: torrent-cell-renderer.c 13388 2012-07-14 19:26:55Z jordan $
 */

/* This cell renderer has been modified heavily to work with the
 * TrgTorrentModel instead of libtransmission. */

#include "config.h"

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

#include "hig.h"
#include "icons.h"
#include "torrent-cell-renderer.h"
#include "torrent.h"
#include "trg-client.h"
#include "util.h"

enum {
    P_STATUS = 1,
    P_CLIENT,
    P_RATIO,
    P_SEEDRATIOLIMIT,
    P_SEEDRATIOMODE,
    P_DOWNLOADED,
    P_HAVEVALID,
    P_HAVEUNCHECKED,
    P_ERROR,
    P_SIZEWHENDONE,
    P_TOTALSIZE,
    P_UPLOADED,
    P_PERCENTCOMPLETE,
    P_METADATAPERCENTCOMPLETE,
    P_UPSPEED,
    P_DOWNSPEED,
    P_PEERSGETTINGFROMUS,
    P_WEBSEEDSTOUS,
    P_PEERSTOUS,
    P_ETA,
    P_JSON,
    P_CONNECTED,
    P_FILECOUNT,
    P_BAR_HEIGHT,
    P_OWNER,
    P_COMPACT
};

#define DEFAULT_BAR_HEIGHT 12
#define SMALL_SCALE        0.9
#define COMPACT_ICON_SIZE  GTK_ICON_SIZE_MENU
#define FULL_ICON_SIZE     GTK_ICON_SIZE_DND

#define FOREGROUND_COLOR_KEY "foreground-rgba"
typedef GdkRGBA GtrColor;
typedef cairo_t GtrDrawable;
typedef GtkRequisition GtrRequisition;

/***
****
***/

static void render_compact(TorrentCellRenderer *cell, GtrDrawable *window, GtkWidget *widget,
                           const GdkRectangle *background_area, const GdkRectangle *cell_area,
                           GtkCellRendererState flags);

static void render_full(TorrentCellRenderer *cell, GtrDrawable *window, GtkWidget *widget,
                        const GdkRectangle *background_area, const GdkRectangle *cell_area,
                        GtkCellRendererState flags);

struct _TorrentCellRenderer {
    GtkCellRenderer parent;

    GtkCellRenderer *text_renderer;
    GtkCellRenderer *progress_renderer;
    GtkCellRenderer *icon_renderer;
    GString *gstr1;
    GString *gstr2;
    int bar_height;

    guint flags;
    guint fileCount;
    gint64 uploadedEver;
    gint64 sizeWhenDone;
    gint64 totalSize;
    gint64 downloaded;
    gint64 haveValid;
    gint64 haveUnchecked;
    gint64 upSpeed;
    gint64 downSpeed;
    gint64 peersFromUs;
    gint64 webSeedsToUs;
    gint64 peersToUs;
    gint64 connected;
    gint64 eta;
    gint64 error;
    gint64 seedRatioMode;
    gdouble done;
    gdouble metadataPercentComplete;
    gdouble ratio;
    gdouble seedRatioLimit;
    gpointer json;
    TrgClient *client;
    GtkTreeView *owner;
    gboolean compact;
};

G_DEFINE_TYPE(TorrentCellRenderer, torrent_cell_renderer, GTK_TYPE_CELL_RENDERER)

static gboolean getSeedRatio(TorrentCellRenderer *r, gdouble *ratio)
{
    if ((r->seedRatioMode == 0) && (trg_client_get_seed_ratio_limited(r->client) == TRUE)) {
        *ratio = trg_client_get_seed_ratio_limit(r->client);
        return TRUE;
    } else if (r->seedRatioMode == 1) {
        *ratio = r->seedRatioLimit;
        return TRUE;
    }

    return FALSE;
}

static void getProgressString(GString *gstr, TorrentCellRenderer *r)
{
    const gint64 haveTotal = r->haveUnchecked + r->haveValid;
    const int isSeed = r->haveValid >= r->totalSize;
    char buf1[32], buf2[32], buf3[32], buf4[32], buf5[32], buf6[32];
    double seedRatio;
    const gboolean hasSeedRatio = getSeedRatio(r, &seedRatio);

    if (r->flags & TORRENT_FLAG_DOWNLOADING) { /* downloading */
        g_string_append_printf(gstr,
                               /* %1$s is how much we've got,
                                  %2$s is how much we'll have when done,
                                  %3$s%% is a percentage of the two */
                               _("%1$s of %2$s (%3$s)"), tr_strlsize(buf1, haveTotal, sizeof(buf1)),
                               tr_strlsize(buf2, r->sizeWhenDone, sizeof(buf2)),
                               tr_strlpercent(buf3, r->done, sizeof(buf3)));
    } else if (!isSeed) { /* Partial seed */
        if (hasSeedRatio) {
            g_string_append_printf(gstr,
                                   _("%1$s of %2$s (%3$s), uploaded %4$s (Ratio: %5$s Goal: %6$s)"),
                                   tr_strlsize(buf1, haveTotal, sizeof(buf1)),
                                   tr_strlsize(buf2, r->totalSize, sizeof(buf2)),
                                   tr_strlpercent(buf3, r->done, sizeof(buf3)),
                                   tr_strlsize(buf4, r->uploadedEver, sizeof(buf4)),
                                   tr_strlratio(buf5, r->ratio, sizeof(buf5)),
                                   tr_strlratio(buf6, seedRatio, sizeof(buf6)));
        } else {
            g_string_append_printf(gstr, _("%1$s of %2$s (%3$s), uploaded %4$s (Ratio: %5$s)"),
                                   tr_strlsize(buf1, haveTotal, sizeof(buf1)),
                                   tr_strlsize(buf2, r->totalSize, sizeof(buf2)),
                                   tr_strlpercent(buf3, r->done, sizeof(buf3)),
                                   tr_strlsize(buf4, r->uploadedEver, sizeof(buf4)),
                                   tr_strlratio(buf5, r->ratio, sizeof(buf5)));
        }
    } else { /* seeding */

        if (hasSeedRatio) {
            g_string_append_printf(gstr, _("%1$s, uploaded %2$s (Ratio: %3$s Goal: %4$s)"),
                                   tr_strlsize(buf1, r->totalSize, sizeof(buf1)),
                                   tr_strlsize(buf2, r->uploadedEver, sizeof(buf2)),
                                   tr_strlratio(buf3, r->ratio, sizeof(buf3)),
                                   tr_strlratio(buf4, seedRatio, sizeof(buf4)));
        } else {
            g_string_append_printf(gstr,
                                   /* %1$s is the torrent's total size,
                                      %2$s is how much we've uploaded,
                                      %3$s is our upload-to-download ratio */
                                   _("%1$s, uploaded %2$s (Ratio: %3$s)"),
                                   tr_strlsize(buf1, r->sizeWhenDone, sizeof(buf1)),
                                   tr_strlsize(buf2, r->uploadedEver, sizeof(buf2)),
                                   tr_strlratio(buf3, r->ratio, sizeof(buf3)));
        }
    }

    /* add time when downloading */
    if ((r->flags & TORRENT_FLAG_DOWNLOADING)
        || (hasSeedRatio && (r->flags & TORRENT_FLAG_SEEDING))) {
        gint64 eta = r->eta;
        g_string_append(gstr, " - ");
        if (eta < 0)
            g_string_append(gstr, _("Remaining time unknown"));
        else {
            char timestr[128];
            tr_strltime_long(timestr, eta, sizeof(timestr));
            /* time remaining */
            g_string_append_printf(gstr, _("%s remaining"), timestr);
        }
    }
}

static char *getShortTransferString(TorrentCellRenderer *r, char *buf, size_t buflen)
{
    char downStr[32], upStr[32];
    const gboolean haveMeta = r->fileCount > 0;
    const gboolean haveUp = haveMeta && r->peersFromUs > 0;
    const gboolean haveDown = haveMeta && r->peersToUs > 0;

    if (haveDown)
        tr_formatter_speed_KBps(downStr, r->downSpeed / speed_K, sizeof(downStr));
    if (haveUp)
        tr_formatter_speed_KBps(upStr, r->upSpeed / speed_K, sizeof(upStr));

    if (haveDown && haveUp)
        /* 1==down arrow, 2==down speed, 3==up arrow, 4==down speed */
        g_snprintf(buf, buflen, _("%1$s %2$s, %3$s %4$s"), GTR_UNICODE_DOWN, downStr,
                   GTR_UNICODE_UP, upStr);
    else if (haveDown)
        /* bandwidth speed + unicode arrow */
        g_snprintf(buf, buflen, _("%1$s %2$s"), GTR_UNICODE_DOWN, downStr);
    else if (haveUp)
        /* bandwidth speed + unicode arrow */
        g_snprintf(buf, buflen, _("%1$s %2$s"), GTR_UNICODE_UP, upStr);
    /*else if( st->isStalled )
       g_strlcpy( buf, _( "Stalled" ), buflen ); */
    else if (haveMeta)
        g_strlcpy(buf, _("Idle"), buflen);
    else
        *buf = '\0';

    return buf;
}

static void getShortStatusString(GString *gstr, TorrentCellRenderer *r)
{
    guint flags = r->flags;

    if (flags & TORRENT_FLAG_PAUSED) {
        g_string_append(gstr, (flags & TORRENT_FLAG_COMPLETE) ? _("Finished") : _("Paused"));
    } else if (flags & TORRENT_FLAG_WAITING_CHECK) {
        g_string_append(gstr, _("Queued for verification"));
    } else if (flags & TORRENT_FLAG_DOWNLOADING_WAIT) {
        g_string_append(gstr, _("Queued for download"));
    } else if (flags & TORRENT_FLAG_SEEDING_WAIT) {
        g_string_append(gstr, _("Queued for seeding"));
    } else if (flags & TORRENT_FLAG_CHECKING) {
        char buf1[32];
        g_string_append_printf(gstr, _("Verifying data (%1$s tested)"),
                               tr_strlpercent(buf1, r->done, sizeof(buf1)));
    } else if ((flags & TORRENT_FLAG_DOWNLOADING) || (flags & TORRENT_FLAG_SEEDING)) {
        char buf[512];
        if (flags & ~TORRENT_FLAG_DOWNLOADING) {
            tr_strlratio(buf, r->ratio, sizeof(buf));
            g_string_append_printf(gstr, _("Ratio %s"), buf);
            g_string_append(gstr, ", ");
        }
        getShortTransferString(r, buf, sizeof(buf));
        g_string_append(gstr, buf);
    }
}

static void getStatusString(GString *gstr, TorrentCellRenderer *r)
{
    char buf[256];
    const gchar *errstr;

    if (r->error) {
        errstr = torrent_get_errorstr(r->json);
        switch (r->error) {
        case 0: /* OK */
            break;
        case 1: /* Tracking Warning */
            g_string_append_printf(gstr, _("Tracker gave a warning: \"%s\""), errstr);
            break;
        case 2: /* Tracker Error */
            g_string_append_printf(gstr, _("Tracker gave an error: \"%s\""), errstr);
            break;
        case 3: /* Local Error */
            g_string_append_printf(gstr, _("Error: \"%s\""), errstr);
            break;
        }
    } else if ((r->flags & TORRENT_FLAG_PAUSED) || (r->flags & TORRENT_FLAG_WAITING_CHECK)
               || (r->flags & TORRENT_FLAG_CHECKING) || (r->flags & TORRENT_FLAG_DOWNLOADING_WAIT)
               || (r->flags & TORRENT_FLAG_SEEDING_WAIT)) {
        getShortStatusString(gstr, r);
    } else if (r->flags & TORRENT_FLAG_DOWNLOADING) {
        if (r->fileCount > 0) {
            g_string_append_printf(gstr,
                                   ngettext("Downloading from %1$" G_GUINT64_FORMAT
                                            " of %2$" G_GUINT64_FORMAT " connected peer",
                                            "Downloading from %1$" G_GUINT64_FORMAT
                                            " of %2$" G_GUINT64_FORMAT " connected peers",
                                            r->webSeedsToUs + r->peersToUs),
                                   r->webSeedsToUs + r->peersToUs, r->webSeedsToUs + r->connected);
        } else {
            g_string_append_printf(
                gstr,
                ngettext("Downloading metadata from %1$" G_GUINT64_FORMAT " peer (%2$s done)",
                         "Downloading metadata from %1$" G_GUINT64_FORMAT " peers (%2$s done)",
                         r->connected + r->webSeedsToUs),
                r->connected + r->webSeedsToUs,
                tr_strlpercent(buf, r->metadataPercentComplete, sizeof(buf)));
        }
    } else if (r->flags & TORRENT_FLAG_SEEDING) {
        g_string_append_printf(gstr,
                               ngettext("Seeding to %1$" G_GUINT64_FORMAT " of %2$" G_GUINT64_FORMAT
                                        " connected peer",
                                        "Seeding to %1$" G_GUINT64_FORMAT " of %2$" G_GUINT64_FORMAT
                                        " connected peers",
                                        r->connected),
                               r->peersFromUs, r->connected);
    }

    if ((r->flags & ~TORRENT_FLAG_WAITING_CHECK) && (r->flags & ~TORRENT_FLAG_CHECKING)
        && (r->flags & ~TORRENT_FLAG_DOWNLOADING_WAIT) && (r->flags & ~TORRENT_FLAG_SEEDING_WAIT)
        && (r->flags & ~TORRENT_FLAG_PAUSED)) {
        getShortTransferString(r, buf, sizeof(buf));
        if (*buf)
            g_string_append_printf(gstr, " - %s", buf);
    }
}

/***
****
***/

static GdkPixbuf *get_icon(TorrentCellRenderer *r, GtkIconSize icon_size, GtkWidget *for_widget)
{
    const char *mime_type;

    if (r->fileCount == 0)
        mime_type = UNKNOWN_MIME_TYPE;
    else if (r->fileCount > 1)
        mime_type = DIRECTORY_MIME_TYPE;
    /*else if( strchr( info->files[0].name, '/' ) != NULL )
       mime_type = DIRECTORY_MIME_TYPE;
       else
       mime_type = gtr_get_mime_type_from_filename( info->files[0].name ); */
    else
        mime_type = FILE_MIME_TYPE;

    // return NULL;
    return gtr_get_mime_type_icon(mime_type, icon_size, for_widget);
}

/***
****
***/

static void gtr_cell_renderer_get_preferred_size(GtkCellRenderer *renderer, GtkWidget *widget,
                                                 GtkRequisition *minimum_size,
                                                 GtkRequisition *natural_size)
{
    gtk_cell_renderer_get_preferred_size(renderer, widget, minimum_size, natural_size);
}

static void get_size_compact(TorrentCellRenderer *cell, GtkWidget *widget, gint *width,
                             gint *height)
{
    int xpad, ypad;
    GtkRequisition icon_size;
    GtkRequisition name_size;
    GtkRequisition stat_size;
    GdkPixbuf *icon;

    GString *gstr_stat = cell->gstr1;

    icon = get_icon(cell, COMPACT_ICON_SIZE, widget);
    g_string_truncate(gstr_stat, 0);
    getShortStatusString(gstr_stat, cell);
    gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);

    /* get the idealized cell dimensions */
    g_object_set(cell->icon_renderer, "pixbuf", icon, NULL);
    gtr_cell_renderer_get_preferred_size(cell->icon_renderer, widget, NULL, &icon_size);
    g_object_set(cell->text_renderer, "text", torrent_get_name(cell->json), "ellipsize",
                 PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &name_size);
    g_object_set(cell->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &stat_size);

    /**
    *** LAYOUT
    **/

#define BAR_WIDTH 50
    if (width != NULL)
        *width = xpad * 2 + icon_size.width + GUI_PAD + name_size.width + GUI_PAD + BAR_WIDTH
            + GUI_PAD + stat_size.width;
    if (height != NULL)
        *height = ypad * 2 + MAX(name_size.height, cell->bar_height);

    /* cleanup */
    g_object_unref(icon);
}

static void get_size_full(TorrentCellRenderer *cell, GtkWidget *widget, gint *width, gint *height)
{
    int xpad, ypad;
    GtkRequisition icon_size;
    GtkRequisition name_size;
    GtkRequisition stat_size;
    GtkRequisition prog_size;
    GdkPixbuf *icon;

    GString *gstr_prog = cell->gstr1;
    GString *gstr_stat = cell->gstr2;

    icon = get_icon(cell, FULL_ICON_SIZE, widget);

    g_string_truncate(gstr_stat, 0);
    getStatusString(gstr_stat, cell);
    g_string_truncate(gstr_prog, 0);
    getProgressString(gstr_prog, cell);
    gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);

    /* get the idealized cell dimensions */
    g_object_set(cell->icon_renderer, "pixbuf", icon, NULL);
    gtr_cell_renderer_get_preferred_size(cell->icon_renderer, widget, NULL, &icon_size);
    g_object_set(cell->text_renderer, "text", torrent_get_name(cell->json), "weight",
                 PANGO_WEIGHT_BOLD, "scale", 1.0, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &name_size);
    g_object_set(cell->text_renderer, "text", gstr_prog->str, "weight", PANGO_WEIGHT_NORMAL,
                 "scale", SMALL_SCALE, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &prog_size);
    g_object_set(cell->text_renderer, "text", gstr_stat->str, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &stat_size);

    /**
    *** LAYOUT
    **/

    if (width != NULL)
        *width = xpad * 2 + icon_size.width + GUI_PAD
            + MAX3(name_size.width, prog_size.width, stat_size.width);
    if (height != NULL)
        *height = ypad * 2 + name_size.height + prog_size.height + GUI_PAD_SMALL + cell->bar_height
            + GUI_PAD_SMALL + stat_size.height;

    /* cleanup */
    g_object_unref(icon);
}

static void torrent_cell_renderer_get_size(GtkCellRenderer *cell, GtkWidget *widget,
                                           const GdkRectangle *cell_area, gint *x_offset,
                                           gint *y_offset, gint *width, gint *height)
{
    TorrentCellRenderer *self = TORRENT_CELL_RENDERER(cell);

    if (self) {
        int w, h;

        if (self->compact)
            get_size_compact(TORRENT_CELL_RENDERER(cell), widget, &w, &h);
        else
            get_size_full(TORRENT_CELL_RENDERER(cell), widget, &w, &h);

        if (width)
            *width = w;

        if (height)
            *height = h;

        if (x_offset)
            *x_offset = cell_area ? cell_area->x : 0;

        if (y_offset) {
            int xpad, ypad;
            gtk_cell_renderer_get_padding(cell, &xpad, &ypad);
            *y_offset = cell_area ? (int)((cell_area->height - (ypad * 2 + h)) / 2.0) : 0;
        }
    }
}

static void get_text_color(TorrentCellRenderer *r, GtkWidget *widget, GtrColor *setme)
{
    static const GdkRGBA red = { 1.0, 0, 0, 0 };

    if (r->error)
        *setme = red;
    else if (r->flags & TORRENT_FLAG_PAUSED)
        gtk_style_context_get_color(gtk_widget_get_style_context(widget),
                                    GTK_STATE_FLAG_INSENSITIVE, setme);
    else
        gtk_style_context_get_color(gtk_widget_get_style_context(widget), GTK_STATE_FLAG_NORMAL,
                                    setme);
}

static double get_percent_done(TorrentCellRenderer *r, gboolean *seed)
{
    gdouble d;

    if ((r->flags & TORRENT_FLAG_SEEDING) && getSeedRatio(r, &d)) {
        *seed = TRUE;
        const gint64 baseline = r->downloaded ? r->downloaded : r->sizeWhenDone;
        const gint64 goal = baseline * r->seedRatioLimit;
        const gint64 bytesLeft = goal > r->uploadedEver ? goal - r->uploadedEver : 0;
        float seedRatioPercentDone = (gdouble)(goal - bytesLeft) / goal;

        d = MAX(0.0, seedRatioPercentDone * 100.0);
    } else {
        *seed = FALSE;
        d = MAX(0.0, r->done);
    }

    return d;
}

static void gtr_cell_renderer_render(GtkCellRenderer *renderer, GtrDrawable *drawable,
                                     GtkWidget *widget, const GdkRectangle *area,
                                     GtkCellRendererState flags)
{
    gtk_cell_renderer_render(renderer, drawable, widget, area, area, flags);
}

static void torrent_cell_renderer_render(GtkCellRenderer *cell, GtrDrawable *window,
                                         GtkWidget *widget, const GdkRectangle *background_area,
                                         const GdkRectangle *cell_area, GtkCellRendererState flags)
{
    TorrentCellRenderer *self = TORRENT_CELL_RENDERER(cell);

    if (self) {
        if (self->compact)
            render_compact(self, window, widget, background_area, cell_area, flags);
        else
            render_full(self, window, widget, background_area, cell_area, flags);
    }
}

static void torrent_cell_renderer_set_property(GObject *object, guint property_id, const GValue *v,
                                               GParamSpec *pspec)
{
    TorrentCellRenderer *self = TORRENT_CELL_RENDERER(object);

    switch (property_id) {
    case P_JSON:
        self->json = g_value_get_pointer(v);
        break;
    case P_STATUS:
        self->flags = g_value_get_uint(v);
        break;
    case P_TOTALSIZE:
        self->totalSize = g_value_get_int64(v);
        break;
    case P_SIZEWHENDONE:
        self->sizeWhenDone = g_value_get_int64(v);
        break;
    case P_DOWNLOADED:
        self->downloaded = g_value_get_int64(v);
        break;
    case P_HAVEVALID:
        self->haveValid = g_value_get_int64(v);
        break;
    case P_HAVEUNCHECKED:
        self->haveUnchecked = g_value_get_int64(v);
        break;
    case P_UPLOADED:
        self->uploadedEver = g_value_get_int64(v);
        break;
    case P_UPSPEED:
        self->upSpeed = g_value_get_int64(v);
        break;
    case P_DOWNSPEED:
        self->downSpeed = g_value_get_int64(v);
        break;
    case P_PEERSGETTINGFROMUS:
        self->peersFromUs = g_value_get_int64(v);
        break;
    case P_WEBSEEDSTOUS:
        self->webSeedsToUs = g_value_get_int64(v);
        break;
    case P_CONNECTED:
        self->connected = g_value_get_int64(v);
        break;
    case P_FILECOUNT:
        self->fileCount = g_value_get_uint(v);
        break;
    case P_ETA:
        self->eta = g_value_get_int64(v);
        break;
    case P_PEERSTOUS:
        self->peersToUs = g_value_get_int64(v);
        break;
    case P_ERROR:
        self->error = g_value_get_int64(v);
        break;
    case P_RATIO:
        self->ratio = g_value_get_double(v);
        break;
    case P_PERCENTCOMPLETE:
        self->done = g_value_get_double(v);
        break;
    case P_METADATAPERCENTCOMPLETE:
        self->metadataPercentComplete = g_value_get_double(v);
        break;
    case P_BAR_HEIGHT:
        self->bar_height = g_value_get_int(v);
        break;
    case P_COMPACT:
        self->compact = g_value_get_boolean(v);
        break;
    case P_SEEDRATIOMODE:
        self->seedRatioMode = g_value_get_int64(v);
        break;
    case P_SEEDRATIOLIMIT:
        self->seedRatioLimit = g_value_get_double(v);
        break;
    case P_CLIENT:
        self->client = g_value_get_pointer(v);
        break;
    case P_OWNER:
        self->owner = g_value_get_pointer(v);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
        break;
    }
}

static void torrent_cell_renderer_get_property(GObject *object, guint property_id, GValue *v,
                                               GParamSpec *pspec)
{
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
}

static void torrent_cell_renderer_dispose(GObject *o)
{
    TorrentCellRenderer *r = TORRENT_CELL_RENDERER(o);

    if (r) {
        g_string_free(r->gstr1, TRUE);
        g_string_free(r->gstr2, TRUE);
        g_object_unref(G_OBJECT(r->text_renderer));
        g_object_unref(G_OBJECT(r->progress_renderer));
        g_object_unref(G_OBJECT(r->icon_renderer));
    }

    G_OBJECT_CLASS(torrent_cell_renderer_parent_class)->dispose(o);
}

static void torrent_cell_renderer_class_init(TorrentCellRendererClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass);

    cell_class->render = torrent_cell_renderer_render;
    cell_class->get_size = torrent_cell_renderer_get_size;
    gobject_class->set_property = torrent_cell_renderer_set_property;
    gobject_class->get_property = torrent_cell_renderer_get_property;
    gobject_class->dispose = torrent_cell_renderer_dispose;

    g_object_class_install_property(gobject_class, P_JSON,
                                    g_param_spec_pointer("json", NULL, "json", G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_CLIENT, g_param_spec_pointer("client", NULL, "client", G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_OWNER, g_param_spec_pointer("owner", NULL, "owner", G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_RATIO,
        g_param_spec_double("ratio", NULL, "ratio", 0, G_MAXDOUBLE, 0, G_PARAM_READWRITE));

    g_object_class_install_property(gobject_class, P_SEEDRATIOLIMIT,
                                    g_param_spec_double("seedRatioLimit", NULL, "seedRatioLimit", 0,
                                                        G_MAXDOUBLE, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_SEEDRATIOMODE,
        g_param_spec_int64("seedRatioMode", NULL, "seedRatioMode", 0, 2, 0, G_PARAM_READWRITE));

    g_object_class_install_property(gobject_class, P_PERCENTCOMPLETE,
                                    g_param_spec_double("percentComplete", NULL, "percentComplete",
                                                        0, 100.00, 0, G_PARAM_READWRITE));

    g_object_class_install_property(gobject_class, P_METADATAPERCENTCOMPLETE,
                                    g_param_spec_double("metadataPercentComplete", NULL,
                                                        "metadataPercentComplete", 0, 100.00, 0,
                                                        G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_TOTALSIZE,
        g_param_spec_int64("totalSize", NULL, "totalSize", 0, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(gobject_class, P_SIZEWHENDONE,
                                    g_param_spec_int64("sizeWhenDone", NULL, "sizeWhenDone", 0,
                                                       G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_STATUS,
        g_param_spec_uint("status", NULL, "status", 0, G_MAXUINT, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_FILECOUNT,
        g_param_spec_uint("fileCount", NULL, "fileCount", 0, G_MAXUINT, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_ERROR,
        g_param_spec_int64("error", NULL, "error", 0, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_UPSPEED,
        g_param_spec_int64("upSpeed", NULL, "upSpeed", 0, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_DOWNSPEED,
        g_param_spec_int64("downSpeed", NULL, "downSpeed", 0, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_DOWNLOADED,
        g_param_spec_int64("downloaded", NULL, "downloaded", 0, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_HAVEVALID,
        g_param_spec_int64("haveValid", NULL, "haveValid", 0, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(gobject_class, P_HAVEUNCHECKED,
                                    g_param_spec_int64("haveUnchecked", NULL, "haveUnchecked", 0,
                                                       G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_UPLOADED,
        g_param_spec_int64("uploaded", NULL, "uploaded", 0, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(gobject_class, P_PEERSGETTINGFROMUS,
                                    g_param_spec_int64("peersGettingFromUs", NULL,
                                                       "peersGettingFromUs", -1, G_MAXINT64, 0,
                                                       G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_PEERSTOUS,
        g_param_spec_int64("peersToUs", NULL, "peersToUs", -1, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(gobject_class, P_WEBSEEDSTOUS,
                                    g_param_spec_int64("webSeedsToUs", NULL, "webSeedsToUs", 0,
                                                       G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_ETA,
        g_param_spec_int64("eta", NULL, "eta", -2, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_CONNECTED,
        g_param_spec_int64("connected", NULL, "connected", 0, G_MAXINT64, 0, G_PARAM_READWRITE));

    g_object_class_install_property(gobject_class, P_BAR_HEIGHT,
                                    g_param_spec_int("bar-height", NULL, "Bar Height", 1, INT_MAX,
                                                     DEFAULT_BAR_HEIGHT, G_PARAM_READWRITE));

    g_object_class_install_property(
        gobject_class, P_COMPACT,
        g_param_spec_boolean("compact", NULL, "Compact Mode", FALSE, G_PARAM_READWRITE));
}

static void torrent_cell_renderer_init(TorrentCellRenderer *self)
{
    self->gstr1 = g_string_new(NULL);
    self->gstr2 = g_string_new(NULL);
    self->text_renderer = gtk_cell_renderer_text_new();
    g_object_set(self->text_renderer, "xpad", 0, "ypad", 0, NULL);
    self->progress_renderer = gtk_cell_renderer_progress_new();
    self->icon_renderer = gtk_cell_renderer_pixbuf_new();
    g_object_ref_sink(self->text_renderer);
    g_object_ref_sink(self->progress_renderer);
    g_object_ref_sink(self->icon_renderer);

    self->bar_height = DEFAULT_BAR_HEIGHT;
}

GtkCellRenderer *torrent_cell_renderer_new(void)
{
    return (GtkCellRenderer *)g_object_new(TORRENT_CELL_RENDERER_TYPE, NULL);
}

static void render_compact(TorrentCellRenderer *cell, GtrDrawable *window, GtkWidget *widget,
                           const GdkRectangle *background_area, const GdkRectangle *cell_area,
                           GtkCellRendererState flags)
{
    int xpad, ypad;
    GtkRequisition size;
    GdkRectangle icon_area;
    GdkRectangle name_area;
    GdkRectangle stat_area;
    GdkRectangle prog_area;
    GdkRectangle fill_area;
    GdkPixbuf *icon;
    GtrColor text_color;
    gboolean seed;

    const gboolean active = (cell->flags & ~TORRENT_FLAG_PAUSED)
        && (cell->flags & ~TORRENT_FLAG_DOWNLOADING_WAIT)
        && (cell->flags & ~TORRENT_FLAG_SEEDING_WAIT);
    const double percentDone = get_percent_done(cell, &seed);
    const gboolean sensitive = active || cell->error;
    GString *gstr_stat = cell->gstr1;

    icon = get_icon(cell, COMPACT_ICON_SIZE, widget);

    g_string_truncate(gstr_stat, 0);
    getShortStatusString(gstr_stat, cell);
    gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
    get_text_color(cell, widget, &text_color);

    fill_area = *background_area;
    fill_area.x += xpad;
    fill_area.y += ypad;
    fill_area.width -= xpad * 2;
    fill_area.height -= ypad * 2;
    icon_area = name_area = stat_area = prog_area = fill_area;

    g_object_set(cell->icon_renderer, "pixbuf", icon, NULL);
    gtr_cell_renderer_get_preferred_size(cell->icon_renderer, widget, NULL, &size);
    icon_area.width = size.width;
    g_object_set(cell->text_renderer, "text", torrent_get_name(cell->json), "ellipsize",
                 PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &size);
    name_area.width = size.width;
    g_object_set(cell->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &size);
    stat_area.width = size.width;

    icon_area.x = fill_area.x;
    prog_area.x = fill_area.x + fill_area.width - BAR_WIDTH;
    prog_area.width = BAR_WIDTH;
    stat_area.x = prog_area.x - GUI_PAD - stat_area.width;
    name_area.x = icon_area.x + icon_area.width + GUI_PAD;
    name_area.y = fill_area.y;
    name_area.width = stat_area.x - GUI_PAD - name_area.x;

    /**
    *** RENDER
    **/

    g_object_set(cell->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL);
    gtr_cell_renderer_render(cell->icon_renderer, window, widget, &icon_area, flags);
    g_object_set(cell->progress_renderer, "value", (gint)percentDone, "text", NULL, "sensitive",
                 sensitive, NULL);
    gtr_cell_renderer_render(cell->progress_renderer, window, widget, &prog_area, flags);
    g_object_set(cell->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, "ellipsize",
                 PANGO_ELLIPSIZE_END, FOREGROUND_COLOR_KEY, &text_color, NULL);
    gtr_cell_renderer_render(cell->text_renderer, window, widget, &stat_area, flags);
    g_object_set(cell->text_renderer, "text", torrent_get_name(cell->json), "scale", 1.0,
                 FOREGROUND_COLOR_KEY, &text_color, NULL);
    gtr_cell_renderer_render(cell->text_renderer, window, widget, &name_area, flags);

    /* cleanup */
    g_object_unref(icon);
}

static void render_full(TorrentCellRenderer *cell, GtrDrawable *window, GtkWidget *widget,
                        const GdkRectangle *background_area, const GdkRectangle *cell_area,
                        GtkCellRendererState flags)
{
    int xpad, ypad;
    GtkRequisition size;
    GdkRectangle fill_area;
    GdkRectangle icon_area;
    GdkRectangle name_area;
    GdkRectangle stat_area;
    GdkRectangle prog_area;
    GdkRectangle prct_area;
    GdkPixbuf *icon;
    GtrColor text_color;
    gboolean seed;

    const gboolean active = (cell->flags & ~TORRENT_FLAG_PAUSED)
        && (cell->flags & ~TORRENT_FLAG_DOWNLOADING_WAIT)
        && (cell->flags & ~TORRENT_FLAG_SEEDING_WAIT);
    const gboolean sensitive = active || cell->error;
    const double percentDone = get_percent_done(cell, &seed);
    GString *gstr_prog = cell->gstr1;
    GString *gstr_stat = cell->gstr2;

    icon = get_icon(cell, FULL_ICON_SIZE, widget);
    g_string_truncate(gstr_prog, 0);
    getProgressString(gstr_prog, cell);
    g_string_truncate(gstr_stat, 0);
    getStatusString(gstr_stat, cell);
    gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
    get_text_color(cell, widget, &text_color);

    /* get the idealized cell dimensions */
    g_object_set(cell->icon_renderer, "pixbuf", icon, NULL);
    gtr_cell_renderer_get_preferred_size(cell->icon_renderer, widget, NULL, &size);
    icon_area.width = size.width;
    icon_area.height = size.height;
    g_object_set(cell->text_renderer, "text", torrent_get_name(cell->json), "weight",
                 PANGO_WEIGHT_BOLD, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &size);
    name_area.width = size.width;
    name_area.height = size.height;
    g_object_set(cell->text_renderer, "text", gstr_prog->str, "weight", PANGO_WEIGHT_NORMAL,
                 "scale", SMALL_SCALE, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &size);
    prog_area.width = size.width;
    prog_area.height = size.height;
    g_object_set(cell->text_renderer, "text", gstr_stat->str, NULL);
    gtr_cell_renderer_get_preferred_size(cell->text_renderer, widget, NULL, &size);
    stat_area.width = size.width;
    stat_area.height = size.height;

    /**
    *** LAYOUT
    **/

    fill_area = *background_area;
    fill_area.x += xpad;
    fill_area.y += ypad;
    fill_area.width -= xpad * 2;
    fill_area.height -= ypad * 2;

    /* icon */
    icon_area.x = fill_area.x;
    icon_area.y = fill_area.y + (fill_area.height - icon_area.height) / 2;

    /* name */
    name_area.x = icon_area.x + icon_area.width + GUI_PAD;
    name_area.y = fill_area.y;
    name_area.width = fill_area.width - GUI_PAD - icon_area.width - GUI_PAD_SMALL;

    /* prog */
    prog_area.x = name_area.x;
    prog_area.y = name_area.y + name_area.height;
    prog_area.width = name_area.width;

    /* progressbar */
    prct_area.x = prog_area.x;
    prct_area.y = prog_area.y + prog_area.height + GUI_PAD_SMALL;
    prct_area.width = prog_area.width;
    prct_area.height = cell->bar_height;

    /* status */
    stat_area.x = prct_area.x;
    stat_area.y = prct_area.y + prct_area.height + GUI_PAD_SMALL;
    stat_area.width = prct_area.width;

    /**
    *** RENDER
    **/

    g_object_set(cell->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL);
    gtr_cell_renderer_render(cell->icon_renderer, window, widget, &icon_area, flags);
    g_object_set(cell->text_renderer, "text", torrent_get_name(cell->json), "scale", 1.0,
                 FOREGROUND_COLOR_KEY, &text_color, "ellipsize", PANGO_ELLIPSIZE_END, "weight",
                 PANGO_WEIGHT_BOLD, NULL);
    gtr_cell_renderer_render(cell->text_renderer, window, widget, &name_area, flags);
    g_object_set(cell->text_renderer, "text", gstr_prog->str, "scale", SMALL_SCALE, "weight",
                 PANGO_WEIGHT_NORMAL, NULL);
    gtr_cell_renderer_render(cell->text_renderer, window, widget, &prog_area, flags);
    g_object_set(cell->progress_renderer, "value", (gint)percentDone, "text", "", "sensitive",
                 sensitive, NULL);
    gtr_cell_renderer_render(cell->progress_renderer, window, widget, &prct_area, flags);
    g_object_set(cell->text_renderer, "text", gstr_stat->str, FOREGROUND_COLOR_KEY, &text_color,
                 NULL);
    gtr_cell_renderer_render(cell->text_renderer, window, widget, &stat_area, flags);

    /* cleanup */
    g_object_unref(icon);
}

GtkTreeView *torrent_cell_renderer_get_owner(TorrentCellRenderer *r)
{
    return r->owner;
}
