/******************************************************************************
 * $Id$
 *
 * Copyright (c) 2005-2008 Transmission authors and contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *****************************************************************************/

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include <libtransmission/transmission.h>

#include "file-list.h"
#include "hig.h"

enum
{
  FC_STOCK,
  FC_LABEL,
  FC_PROG,
  FC_KEY,
  FC_INDEX,
  FC_SIZE,
  FC_PRIORITY,
  FC_ENABLED,
  N_FILE_COLS
};

typedef struct
{
  TrTorrent * gtor;
  GtkWidget * top;
  GtkWidget * view;
  GtkTreeModel * model; /* same object as store, but recast */
  GtkTreeStore * store; /* same object as model, but recast */
  guint timeout_tag;
}
FileData;

static const char*
priorityToString( const int priority )
{
    switch( priority ) {
        /* this refers to priority */
        case TR_PRI_HIGH:   return _("High");
        /* this refers to priority */
        case TR_PRI_NORMAL: return _("Normal");
        /* this refers to priority */
        case TR_PRI_LOW:    return _("Low");
        default:            return "BUG!";
    }
}

static tr_priority_t
stringToPriority( const char* str )
{
    if( !strcmp( str, priorityToString( TR_PRI_HIGH ) ) ) return TR_PRI_HIGH;
    if( !strcmp( str, priorityToString( TR_PRI_LOW ) ) ) return TR_PRI_LOW;
    return TR_PRI_NORMAL;
}

static void
parsepath( const tr_torrent  * tor,
           GtkTreeStore      * store,
           GtkTreeIter       * ret,
           const char        * path,
           int                 index,
           uint64_t            size )
{
    GtkTreeModel * model;
    GtkTreeIter  * parent, start, iter;
    char         * file, * lower, * mykey;
    const char   * stock;
    int            priority = 0;
    gboolean       enabled = TRUE;

    model  = GTK_TREE_MODEL( store );
    parent = NULL;
    file   = g_path_get_basename( path );
    if( 0 != strcmp( file, path ) )
    {
        char * dir = g_path_get_dirname( path );
        parsepath( tor, store, &start, dir, index, size );
        parent = &start;
        g_free( dir );
    }

    lower = g_utf8_casefold( file, -1 );
    mykey = g_utf8_collate_key( lower, -1 );
    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
    {
        gboolean stop;
        char * modelkey;
        gtk_tree_model_get( model, &iter, FC_KEY, &modelkey, -1 );
        stop = (modelkey!=NULL) && !strcmp(mykey,modelkey);
        g_free (modelkey);
        if (stop) goto done;
    }
    while( gtk_tree_model_iter_next( model, &iter ) );

    gtk_tree_store_append( store, &iter, parent );
    if( NULL == ret )
    {
        stock = GTK_STOCK_FILE;
    }
    else
    {
        stock = GTK_STOCK_DIRECTORY;
        size  = 0;
        index = -1;
    }

    if (index != -1) {
        priority = tr_torrentGetFilePriority( tor, index );
        enabled  = tr_torrentGetFileDL( tor, index );
    }

    gtk_tree_store_set( store, &iter, FC_INDEX, index,
                                      FC_LABEL, file,
                                      FC_KEY, mykey,
                                      FC_STOCK, stock,
                                      FC_PRIORITY, priorityToString(priority),
                                      FC_ENABLED, enabled,
                                      FC_SIZE, size, -1 );
  done:
    g_free( mykey );
    g_free( lower );
    g_free( file );
    if( NULL != ret )
      *ret = iter;
}

static uint64_t
getdirtotals( GtkTreeStore * store, GtkTreeIter * parent )
{
    GtkTreeModel * model;
    GtkTreeIter    iter;
    uint64_t       mysize, subsize;
    char         * name, * label;

    model  = GTK_TREE_MODEL( store );
    mysize = 0;
    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
    {
         char sizeStr[64];
        if( gtk_tree_model_iter_has_child( model, &iter ) )
        {
            subsize = getdirtotals( store, &iter );
            gtk_tree_store_set( store, &iter, FC_SIZE, subsize, -1 );
        }
        else
        {
            gtk_tree_model_get( model, &iter, FC_SIZE, &subsize, -1 );
        }
        gtk_tree_model_get( model, &iter, FC_LABEL, &name, -1 );
        tr_strlsize( sizeStr, subsize, sizeof( sizeStr ) );
        label = g_markup_printf_escaped( "<small>%s (%s)</small>",
                                          name, sizeStr );
        g_free( name );
        gtk_tree_store_set( store, &iter, FC_LABEL, label, -1 );
        g_free( label );
        mysize += subsize;
    }
    while( gtk_tree_model_iter_next( model, &iter ) );

    return mysize;
}

static void
updateprogress( GtkTreeModel   * model,
                GtkTreeStore   * store,
                GtkTreeIter    * parent,
                tr_file_stat   * fileStats,
                guint64        * setmeGotSize,
                guint64        * setmeTotalSize)
{
    GtkTreeIter iter;
    guint64 gotSize=0, totalSize=0;

    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
    {
        int oldProg, newProg;
        guint64 subGot, subTotal;

        if (gtk_tree_model_iter_has_child( model, &iter ) )
        {
            updateprogress( model, store, &iter, fileStats, &subGot, &subTotal);
        }
        else
        {
            int index, percent;
            gtk_tree_model_get( model, &iter, FC_SIZE, &subTotal,
                                              FC_INDEX, &index,
                                              -1 );
            g_assert( 0 <= index );
            percent = (int)(fileStats[index].progress * 100.0); /* [0...100] */
            subGot = (guint64)(subTotal * percent/100.0);
        }

        if (!subTotal) subTotal = 1; /* avoid div by zero */
        g_assert (subGot <= subTotal);

        /* why not just set it every time?
           because that causes the "priorities" combobox to pop down */
        gtk_tree_model_get (model, &iter, FC_PROG, &oldProg, -1);
        newProg = (int)(100.0*subGot/subTotal);
        if (oldProg != newProg)
          gtk_tree_store_set (store, &iter,
                              FC_PROG, (int)(100.0*subGot/subTotal), -1);

        gotSize += subGot;
        totalSize += subTotal;
    }
    while( gtk_tree_model_iter_next( model, &iter ) );

    *setmeGotSize = gotSize;
    *setmeTotalSize = totalSize;
}

static GtkTreeModel*
priority_model_new (void)
{
  GtkTreeIter iter;
  GtkListStore * store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
  gtk_list_store_append (store, &iter);
  gtk_list_store_set (store, &iter, 0, priorityToString( TR_PRI_HIGH ), 1, TR_PRI_HIGH, -1);
  gtk_list_store_append (store, &iter);
  gtk_list_store_set (store, &iter, 0, priorityToString( TR_PRI_NORMAL ), 1, TR_PRI_NORMAL, -1);
  gtk_list_store_append (store, &iter);
  gtk_list_store_set (store, &iter, 0, priorityToString( TR_PRI_LOW ), 1, TR_PRI_LOW, -1);
  return GTK_TREE_MODEL (store);
}

static void
subtree_walk_dnd( GtkTreeStore   * store,
                  GtkTreeIter    * iter,
                  tr_torrent     * tor,
                  gboolean         enabled,
                  GArray         * indices )
{
    int index;
    GtkTreeIter child;
 
    /* update this node */ 
    gtk_tree_model_get( GTK_TREE_MODEL(store), iter, FC_INDEX, &index, -1  );
    if (index >= 0) {
      tr_file_index_t fi = index;
      g_array_append_val( indices, fi );
    }
    gtk_tree_store_set( store, iter, FC_ENABLED, enabled, -1 );

    /* visit the children */
    if( gtk_tree_model_iter_children( GTK_TREE_MODEL(store), &child, iter ) ) do
      subtree_walk_dnd( store, &child, tor, enabled, indices );
    while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &child ) );
}

static void
set_subtree_dnd( GtkTreeStore   * store,
                 GtkTreeIter    * iter,
                 tr_torrent     * tor,
                 gboolean         enabled )
{
    GArray * indices = g_array_new( FALSE, FALSE, sizeof(tr_file_index_t) );
    subtree_walk_dnd( store, iter, tor, enabled, indices );
    tr_torrentSetFileDLs( tor, (tr_file_index_t*)indices->data, (tr_file_index_t)indices->len, enabled );
    g_array_free( indices, TRUE );
}

static void
subtree_walk_priority( GtkTreeStore   * store,
                       GtkTreeIter    * iter,
                       tr_torrent     * tor,
                       int              priority,
                       GArray         * indices )
{
    int index;
    GtkTreeIter child;

    /* update this node */ 
    gtk_tree_model_get( GTK_TREE_MODEL(store), iter, FC_INDEX, &index, -1  );
    if( index >= 0 )
        g_array_append_val( indices, index );
    gtk_tree_store_set( store, iter, FC_PRIORITY, priorityToString(priority), -1 );

    /* visit the children */
    if( gtk_tree_model_iter_children( GTK_TREE_MODEL(store), &child, iter ) ) do
        subtree_walk_priority( store, &child, tor, priority, indices );
    while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &child ) );

}

static void
set_subtree_priority( GtkTreeStore * store,
                      GtkTreeIter * iter,
                      tr_torrent * tor,
                      int priority )
{
    GArray * indices = g_array_new( FALSE, FALSE, sizeof(int) );
    subtree_walk_priority( store, iter, tor, priority, indices );
    tr_torrentSetFilePriorities( tor, (tr_file_index_t*)indices->data, (tr_file_index_t)indices->len, priority );
    g_array_free( indices, TRUE );
}

static void
priority_changed_cb (GtkCellRendererText * cell UNUSED,
                     const gchar         * path,
		     const gchar         * value,
		     void                * file_data)
{
    GtkTreeIter iter;
    FileData * d = file_data;
    if (gtk_tree_model_get_iter_from_string (d->model, &iter, path))
    {
        tr_torrent  * tor = tr_torrent_handle( d->gtor );
        const tr_priority_t priority = stringToPriority( value );
        set_subtree_priority( d->store, &iter, tor, priority );
    }
}

static void
enabled_toggled (GtkCellRendererToggle  * cell UNUSED,
	         const gchar            * path_str,
	         gpointer                 data_gpointer)
{
  FileData * data = data_gpointer;
  GtkTreePath * path = gtk_tree_path_new_from_string( path_str );
  GtkTreeModel * model = data->model;
  GtkTreeIter iter;
  gboolean enabled;

  gtk_tree_model_get_iter( model, &iter, path );
  gtk_tree_model_get( model, &iter, FC_ENABLED, &enabled, -1 );
  enabled = !enabled;
  set_subtree_dnd( GTK_TREE_STORE(model),
                   &iter,
                   tr_torrent_handle( data->gtor ),
                   enabled );

  gtk_tree_path_free( path );
}

static gboolean
refreshModel( gpointer gdata )
{
    FileData * data  = gdata;

    g_assert( data != NULL );

    if( data->gtor )
    {
        guint64 foo, bar;
        tr_file_index_t fileCount;
        tr_torrent * tor;
        tr_file_stat * fileStats;

        tor = tr_torrent_handle( data->gtor );
        fileCount = 0;
        fileStats = tr_torrentFiles( tor, &fileCount );
        updateprogress (data->model, data->store, NULL, fileStats, &foo, &bar);
        tr_torrentFilesFree( fileStats, fileCount );
    }

    return TRUE;
}

static void
clearData( FileData * data )
{
    data->gtor = NULL;

    if( data->timeout_tag ) {
        g_source_remove( data->timeout_tag );
        data->timeout_tag = 0;
    }
}

void
file_list_set_torrent( GtkWidget * w, TrTorrent * gtor )
{
    GtkTreeStore        * store;
    FileData            * data;

    data = g_object_get_data( G_OBJECT( w ), "file-data" );

    /* unset the old fields */
    clearData( data );

    /* instantiate the model */
    store = gtk_tree_store_new ( N_FILE_COLS,
                                 G_TYPE_STRING,    /* stock */
                                 G_TYPE_STRING,    /* label */
                                 G_TYPE_INT,       /* prog [0..100] */
                                 G_TYPE_STRING,    /* key */
                                 G_TYPE_INT,       /* index */
                                 G_TYPE_UINT64,    /* size */
                                 G_TYPE_STRING,    /* priority */
                                 G_TYPE_BOOLEAN ); /* dl enabled */
    data->store = store;
    data->model = GTK_TREE_MODEL( store );
    data->gtor = gtor;


    /* populate the model */
    if( gtor )
    {
        tr_file_index_t i;
        const tr_info * inf = tr_torrent_info( gtor );
        tr_torrent * tor = tr_torrent_handle( gtor );

        for( i=0; inf && i<inf->fileCount; ++i )
        {
            const char * path = inf->files[i].name;
            const char * base = g_path_is_absolute( path ) ? g_path_skip_root( path ) : path;
            parsepath( tor, store, NULL, base, i, inf->files[i].length );
        }

        getdirtotals( store, NULL );

        data->timeout_tag = g_timeout_add( 1000, refreshModel, data );
    }

    gtk_tree_view_set_model( GTK_TREE_VIEW( data->view ), GTK_TREE_MODEL( store ) );
    gtk_tree_view_expand_all( GTK_TREE_VIEW( data->view ) );
}

static void
freeData( gpointer gdata )
{
    FileData * data = gdata;
    clearData( data );
    g_free( data );
}

GtkWidget *
file_list_new( TrTorrent * gtor )
{
    GtkWidget           * ret;
    FileData            * data;
    GtkWidget           * view, * scroll;
    GtkCellRenderer     * rend;
    GtkCellRenderer     * priority_rend;
    GtkCellRenderer     * enabled_rend;
    GtkTreeViewColumn   * col;
    GtkTreeSelection    * sel;
    GtkTreeModel        * model;

    /* create the view */
    view = gtk_tree_view_new( );
    gtk_container_set_border_width( GTK_CONTAINER( view ), GUI_PAD_BIG );

    /* add file column */
    
    col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
        "expand", TRUE,
    /* Translators: this is a column header in Files tab, Details dialog;
       Don't include the prefix "filedetails|" in the translation. */ 				      
        "title", Q_("filedetails|File"),
        NULL));
    rend = gtk_cell_renderer_pixbuf_new();
    gtk_tree_view_column_pack_start( col, rend, FALSE );
    gtk_tree_view_column_add_attribute( col, rend, "stock-id", FC_STOCK );
    /* add text renderer */
    rend = gtk_cell_renderer_text_new();
    g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
    gtk_tree_view_column_pack_start( col, rend, TRUE );
    gtk_tree_view_column_add_attribute( col, rend, "markup", FC_LABEL );
    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
    /* add progress column */
    rend = gtk_cell_renderer_progress_new();
    /* Translators: this is a column header in Files tab, Details dialog;
       Don't include the prefix "filedetails|" in the translation. */ 
    col = gtk_tree_view_column_new_with_attributes (Q_("filedetails|Progress"),
						    rend,
						    "value", FC_PROG,
						    NULL);
    gtk_tree_view_column_set_sort_column_id( col, FC_PROG );
    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
    /* set up view */
    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
    gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
    gtk_tree_view_set_search_column( GTK_TREE_VIEW( view ), FC_LABEL );

    /* add "download" checkbox column */
    col = gtk_tree_view_column_new ();
    gtk_tree_view_column_set_sort_column_id( col, FC_ENABLED );
    rend = enabled_rend = gtk_cell_renderer_toggle_new  ();
    /* Translators: this is a column header in Files tab, Details dialog;
       Don't include the prefix "filedetails|" in the translation. 
       Please note the items for this column are checkboxes (yes/no) */ 
    col = gtk_tree_view_column_new_with_attributes (Q_("filedetails|Download"),
                                                    rend,
                                                    "active", FC_ENABLED,
                                                    NULL);
    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );

    /* add priority column */
    model = priority_model_new ();
    col = gtk_tree_view_column_new ();
    gtk_tree_view_column_set_sort_column_id( col, FC_PRIORITY );
    /* Translators: this is a column header in Files tab, Details dialog;
       Don't include the prefix "filedetails|" in the translation. */ 
    gtk_tree_view_column_set_title (col, Q_("filedetails|Priority"));
    rend = priority_rend = gtk_cell_renderer_combo_new ();
    gtk_tree_view_column_pack_start (col, rend, TRUE);
    g_object_set (G_OBJECT(rend), "model", model,
                                  "editable", TRUE,
                                  "has-entry", FALSE,
                                  "text-column", 0,
                                  NULL);
    g_object_unref (G_OBJECT(model));
    gtk_tree_view_column_add_attribute (col, rend, "text", FC_PRIORITY);
    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );

    /* create the scrolled window and stick the view in it */
    scroll = gtk_scrolled_window_new( NULL, NULL );
    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ),
                                    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll),
                                         GTK_SHADOW_IN);
    gtk_container_add( GTK_CONTAINER( scroll ), view );
    gtk_widget_set_size_request (scroll, 0u, 200u);

    ret = scroll;
    data = g_new0( FileData, 1 );
    data->view = view;
    data->top = scroll;
    g_signal_connect (G_OBJECT(priority_rend), "edited", G_CALLBACK(priority_changed_cb), data);
    g_signal_connect(enabled_rend, "toggled", G_CALLBACK(enabled_toggled), data );
    g_object_set_data_full( G_OBJECT( ret ), "file-data", data, freeData );
    file_list_set_torrent( ret, gtor );

    return ret;
}
