/*
 * 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: completion.c 12918 2011-09-26 06:18:48Z jordan $
 */

#include <assert.h>

#include "transmission.h"
#include "completion.h"
#include "torrent.h"
#include "utils.h"

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

static void
tr_cpReset( tr_completion * cp )
{
    cp->sizeNow = 0;
    cp->sizeWhenDoneIsDirty = true;
    cp->haveValidIsDirty = true;
    tr_bitfieldSetHasNone( &cp->blockBitfield );
}

void
tr_cpConstruct( tr_completion * cp, tr_torrent * tor )
{
    cp->tor = tor;
    tr_bitfieldConstruct( &cp->blockBitfield, tor->blockCount );
    tr_cpReset( cp );
}

void
tr_cpBlockInit( tr_completion * cp, const tr_bitfield * b )
{
    tr_cpReset( cp );

    /* set blockBitfield */
    tr_bitfieldSetFromBitfield( &cp->blockBitfield, b );

    /* set sizeNow */
    cp->sizeNow = tr_bitfieldCountTrueBits( &cp->blockBitfield );
    assert( cp->sizeNow <= cp->tor->blockCount );
    cp->sizeNow *= cp->tor->blockSize;
    if( tr_bitfieldHas( b, cp->tor->blockCount-1 ) )
        cp->sizeNow -= ( cp->tor->blockSize - cp->tor->lastBlockSize );
    assert( cp->sizeNow <= cp->tor->info.totalSize );
}

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

tr_completeness
tr_cpGetStatus( const tr_completion * cp )
{
    if( tr_cpHasAll( cp ) ) return TR_SEED;
    if( !tr_torrentHasMetadata( cp->tor ) ) return TR_LEECH;
    if( cp->sizeNow == tr_cpSizeWhenDone( cp ) ) return TR_PARTIAL_SEED;
    return TR_LEECH;
}

void
tr_cpPieceRem( tr_completion *  cp, tr_piece_index_t piece )
{
    tr_block_index_t i, f, l;
    const tr_torrent * tor = cp->tor;

    tr_torGetPieceBlockRange( cp->tor, piece, &f, &l );

    for( i=f; i<=l; ++i )
        if( tr_cpBlockIsComplete( cp, i ) )
            cp->sizeNow -= tr_torBlockCountBytes( tor, i );

    cp->haveValidIsDirty = true;
    cp->sizeWhenDoneIsDirty = true;
    tr_bitfieldRemRange( &cp->blockBitfield, f, l+1 );
}

void
tr_cpPieceAdd( tr_completion * cp, tr_piece_index_t piece )
{
    tr_block_index_t i, f, l;
    tr_torGetPieceBlockRange( cp->tor, piece, &f, &l );

    for( i=f; i<=l; ++i )
        tr_cpBlockAdd( cp, i );
}

void
tr_cpBlockAdd( tr_completion * cp, tr_block_index_t block )
{
    const tr_torrent * tor = cp->tor;

    if( !tr_cpBlockIsComplete( cp, block ) )
    {
        tr_bitfieldAdd( &cp->blockBitfield, block );
        cp->sizeNow += tr_torBlockCountBytes( tor, block );

        cp->haveValidIsDirty = true;
        cp->sizeWhenDoneIsDirty = true;
    }
}

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

uint64_t
tr_cpHaveValid( const tr_completion * ccp )
{
    if( ccp->haveValidIsDirty )
    {
        tr_piece_index_t i;
        uint64_t size = 0;
        tr_completion * cp = (tr_completion *) ccp; /* mutable */
        const tr_torrent * tor = ccp->tor;
        const tr_info * info = &tor->info;

        for( i=0; i<info->pieceCount; ++i )
            if( tr_cpPieceIsComplete( ccp, i ) )
                size += tr_torPieceCountBytes( tor, i );

        cp->haveValidLazy = size;
        cp->haveValidIsDirty = false;
    }

    return ccp->haveValidLazy;
}

uint64_t
tr_cpSizeWhenDone( const tr_completion * ccp )
{
    if( ccp->sizeWhenDoneIsDirty )
    {
        uint64_t size = 0;
        const tr_torrent * tor = ccp->tor;
        const tr_info * inf = tr_torrentInfo( tor );
        tr_completion * cp = (tr_completion *) ccp; /* mutable */

        if( tr_cpHasAll( ccp ) )
        {
            size = inf->totalSize;
        }
        else
        {
            tr_piece_index_t p;

            for( p=0; p<inf->pieceCount; ++p )
            {
                uint64_t n = 0;
                const uint64_t pieceSize = tr_torPieceCountBytes( tor, p );

                if( !inf->pieces[p].dnd )
                {
                    n = pieceSize;
                }
                else
                {
                    uint64_t o = 0;
                    tr_block_index_t b, f, l;
                    tr_torGetPieceBlockRange( cp->tor, p, &f, &l );
                    for( b=f; b<=l; ++b )
                        if( tr_cpBlockIsComplete( cp, b ) )
                            n += tr_torBlockCountBytes( tor, b );

                    o = tr_bitfieldCountRange( &cp->blockBitfield, f, l+1 );
                    o *= cp->tor->blockSize;
                    if( l == ( cp->tor->blockCount - 1 )  && tr_bitfieldHas( &cp->blockBitfield, l ) )
                        o -= ( cp->tor->blockSize - cp->tor->lastBlockSize );

                    assert( n == o );
                }

                assert( n <= tr_torPieceCountBytes( tor, p ) );
                size += n;
            }
        }

        assert( size <= inf->totalSize );
        assert( size >= cp->sizeNow );

        cp->sizeWhenDoneLazy = size;
        cp->sizeWhenDoneIsDirty = false;
    }

    return ccp->sizeWhenDoneLazy;
}

uint64_t
tr_cpLeftUntilDone( const tr_completion * cp )
{
    const uint64_t sizeWhenDone = tr_cpSizeWhenDone( cp );

    assert( sizeWhenDone >= cp->sizeNow );

    return sizeWhenDone - cp->sizeNow;
}

void
tr_cpGetAmountDone( const tr_completion * cp, float * tab, int tabCount )
{
    int i;
    const bool seed = tr_cpHasAll( cp );
    const float interval = cp->tor->info.pieceCount / (float)tabCount;

    for( i=0; i<tabCount; ++i ) {
        if( seed )
            tab[i] = 1.0f;
        else {
            tr_block_index_t f, l;
            const tr_piece_index_t piece = (tr_piece_index_t)i * interval;
            tr_torGetPieceBlockRange( cp->tor, piece, &f, &l );
            tab[i] = tr_bitfieldCountRange( &cp->blockBitfield, f, l+1 )
                                                            / (float)(l+1-f);
        }
    }
}

size_t
tr_cpMissingBlocksInPiece( const tr_completion * cp, tr_piece_index_t piece )
{
    if( tr_cpHasAll( cp ) )
        return 0;
    else {
        tr_block_index_t f, l;
        tr_torGetPieceBlockRange( cp->tor, piece, &f, &l );
        return (l+1-f) - tr_bitfieldCountRange( &cp->blockBitfield, f, l+1 );
    }
}

size_t
tr_cpMissingBytesInPiece( const tr_completion * cp, tr_piece_index_t piece )
{
    if( tr_cpHasAll( cp ) )
        return 0;
    else {
        size_t haveBytes = 0;
        tr_block_index_t f, l;
        const size_t pieceByteSize = tr_torPieceCountBytes( cp->tor, piece );
        tr_torGetPieceBlockRange( cp->tor, piece, &f, &l );
        if( f != l ) {
            /* nb: we don't pass the usual l+1 here to tr_bitfieldCountRange().
               It's faster to handle the last block separately because its size
               needs to be checked separately. */
            haveBytes = tr_bitfieldCountRange( &cp->blockBitfield, f, l );
            haveBytes *= cp->tor->blockSize;
        }
        if( tr_bitfieldHas( &cp->blockBitfield, l ) ) /* handle the last block */
            haveBytes += tr_torBlockCountBytes( cp->tor, l );
        assert(  haveBytes <= pieceByteSize );
        return pieceByteSize - haveBytes;
    }
}

bool
tr_cpFileIsComplete( const tr_completion * cp, tr_file_index_t i )
{
    if( cp->tor->info.files[i].length == 0 )
        return true;
    else {
        tr_block_index_t f, l;
        tr_torGetFileBlockRange( cp->tor, i, &f, &l );
        return tr_bitfieldCountRange( &cp->blockBitfield, f, l+1 ) == (l+1-f);
    }
}

void *
tr_cpCreatePieceBitfield( const tr_completion * cp, size_t * byte_count )
{
    void * ret;
    tr_bitfield pieces;
    const tr_piece_index_t n = cp->tor->info.pieceCount;
    tr_bitfieldConstruct( &pieces, n );

    if( tr_cpHasAll( cp ) )
        tr_bitfieldSetHasAll( &pieces );
    else if( !tr_cpHasNone( cp ) ) {
        tr_piece_index_t i;
        bool * flags = tr_new( bool, n );
        for( i=0; i<n; ++i )
            flags[i] = tr_cpPieceIsComplete( cp, i );
        tr_bitfieldSetFromFlags( &pieces, flags, n );
        tr_free( flags );
    }

    ret = tr_bitfieldGetRaw( &pieces, byte_count );
    tr_bitfieldDestruct( &pieces );
    return ret;
}

double
tr_cpPercentComplete( const tr_completion * cp )
{
    const double ratio = tr_getRatio( cp->sizeNow, cp->tor->info.totalSize );

    if( (int)ratio == TR_RATIO_NA )
        return 0.0;
    else if( (int)ratio == TR_RATIO_INF )
        return 1.0;
    else
        return ratio;
}

double
tr_cpPercentDone( const tr_completion * cp )
{
    const double ratio = tr_getRatio( cp->sizeNow, tr_cpSizeWhenDone( cp ) );
    const int iratio = (int)ratio;
    return ((iratio == TR_RATIO_NA) || (iratio == TR_RATIO_INF)) ? 0.0 : ratio;
}
