/*
 * This file Copyright (C) Mnemosyne LLC
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 * $Id: torrent-model.cc 13982 2013-02-07 21:07:16Z jordan $
 */

#include <cassert>
#include <iostream>

#include <libtransmission/transmission.h>
#include <libtransmission/variant.h>

#include "torrent-delegate.h"
#include "torrent-model.h"

void
TorrentModel :: clear( )
{
    myIdToRow.clear( );
    myIdToTorrent.clear( );
    foreach( Torrent * tor, myTorrents ) delete tor;
    myTorrents.clear( );
    reset( );
}

int
TorrentModel :: rowCount( const QModelIndex& parent ) const
{
    Q_UNUSED( parent );

    return myTorrents.size( );
}

QVariant
TorrentModel :: data (const QModelIndex& index, int role) const
{
  QVariant var;

  const Torrent * t = myTorrents.value (index.row(), 0);
  if (t != 0)
    {
      switch (role)
        {
          case Qt::DisplayRole:
            var.setValue (t->name());
            break;

          case Qt::DecorationRole:
            var.setValue (t->getMimeTypeIcon());
            break;

          case TorrentRole:
            var = qVariantFromValue(t);
            break;

          default:
            //std::cerr << "Unhandled role: " << role << std::endl;
            break;
        }
    }

  return var;
}

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

void
TorrentModel :: addTorrent( Torrent * t )
{
    myIdToTorrent.insert( t->id( ), t );
    myIdToRow.insert( t->id( ), myTorrents.size( ) );
    myTorrents.append( t );
}

TorrentModel :: TorrentModel( Prefs& prefs ):
    myPrefs( prefs )
{
}

TorrentModel :: ~TorrentModel( )
{
    clear( );
}

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

Torrent*
TorrentModel :: getTorrentFromId( int id )
{
    id_to_torrent_t::iterator it( myIdToTorrent.find( id ) );
    return it == myIdToTorrent.end() ? 0 : it.value( );
}

const Torrent*
TorrentModel :: getTorrentFromId( int id ) const
{
    id_to_torrent_t::const_iterator it( myIdToTorrent.find( id ) );
    return it == myIdToTorrent.end() ? 0 : it.value( );
}

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

void
TorrentModel :: onTorrentChanged( int torrentId )
{
    const int row( myIdToRow.value( torrentId, -1 ) );
    if( row >= 0 ) {
        QModelIndex qmi( index( row, 0 ) );
        emit dataChanged( qmi, qmi );
    }
}

void
TorrentModel :: removeTorrents( tr_variant * torrents )
{
    int i = 0;
    tr_variant * child;
    while(( child = tr_variantListChild( torrents, i++ ))) {
        int64_t intVal;
        if( tr_variantGetInt( child, &intVal ) )
            removeTorrent( intVal );
    }
}

void
TorrentModel :: updateTorrents( tr_variant * torrents, bool isCompleteList )
{
    QList<Torrent*> newTorrents;
    QSet<int> oldIds;
    QSet<int> addIds;
    QSet<int> newIds;
    int updatedCount = 0;

    if ( isCompleteList )
      oldIds = getIds( );

    if( tr_variantIsList( torrents ) )
    {
        size_t i( 0 );
        tr_variant * child;
        while(( child = tr_variantListChild( torrents, i++ )))
        {
            int64_t id;
            if( tr_variantDictFindInt( child, TR_KEY_id, &id ) )
            {
                newIds.insert( id );

                Torrent * tor = getTorrentFromId( id );
                if( tor == 0 )
                {
                    tor = new Torrent( myPrefs, id );
                    tor->update( child );
                    if( !tor->hasMetadata() )
                        tor->setMagnet( true );
                    newTorrents.append( tor );
                    connect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged(int)));
                }
                else
                {
                    tor->update( child );
                    ++updatedCount;
                    if( tor->isMagnet() && tor->hasMetadata() )
                    {
                        addIds.insert( tor->id() );
                        tor->setMagnet( false );
                    }
                }
            }
        }
    }

    if( !newTorrents.isEmpty( ) )
    {
        const int oldCount( rowCount( ) );
        const int newCount( oldCount + newTorrents.size( ) );
        QSet<int> ids;

        beginInsertRows( QModelIndex(), oldCount, newCount - 1 );

        foreach( Torrent * tor, newTorrents ) {
            addTorrent( tor );
            addIds.insert( tor->id( ) );
        }
        endInsertRows( );
    }

    if( !addIds.isEmpty() )
        emit torrentsAdded( addIds );

    if( isCompleteList )
    {
        QSet<int> removedIds( oldIds );
        removedIds -= newIds;
        foreach( int id, removedIds )
            removeTorrent( id );
    }
}

void
TorrentModel :: removeTorrent( int id )
{
    const int row = myIdToRow.value( id, -1 );
    if( row >= 0 )
    {
        Torrent * tor = myIdToTorrent.value( id, 0 );

        beginRemoveRows( QModelIndex(), row, row );
        // make the myIdToRow map consistent with list view/model
        for( QMap<int,int>::iterator i = myIdToRow.begin(); i != myIdToRow.end(); ++i )
            if( i.value() > row )
                --i.value();
        myIdToRow.remove( id );
        myIdToTorrent.remove( id );
        myTorrents.remove( myTorrents.indexOf( tor ) );
        endRemoveRows( );

        delete tor;
    }
}

void
TorrentModel :: getTransferSpeed (Speed   & uploadSpeed,
                                  size_t  & uploadPeerCount,
                                  Speed   & downloadSpeed,
                                  size_t  & downloadPeerCount)
{
  Speed upSpeed, downSpeed;
  size_t upCount=0, downCount=0;

  foreach (const Torrent * const tor, myTorrents)
    {
      upSpeed += tor->uploadSpeed ();
      upCount += tor->peersWeAreUploadingTo ();
      downSpeed += tor->downloadSpeed ();
      downCount += tor->webseedsWeAreDownloadingFrom();
      downCount += tor->peersWeAreDownloadingFrom();
    }

  uploadSpeed = upSpeed;
  uploadPeerCount = upCount;
  downloadSpeed = downSpeed;
  downloadPeerCount = downCount;
}

QSet<int>
TorrentModel :: getIds () const
{
  QSet<int> ids;

  ids.reserve (myTorrents.size());
  foreach (const Torrent * tor, myTorrents)
    ids.insert (tor->id());

  return ids;
}

bool
TorrentModel :: hasTorrent( const QString& hashString ) const
{
    foreach( const Torrent * tor, myTorrents )
        if( tor->hashString( ) == hashString )
            return true;
    return false;
}
