/*
 *  Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */


#include <stdio.h>
#include "kis_tile_data.h"
#include "kis_tile_data_store.h"
#include "kis_tile_data_store_iterators.h"
#include "kis_debug.h"
#include "kis_tile_data_pooler.h"

const qint32 KisTileDataPooler::MAX_NUM_CLONES = 16;
const qint32 KisTileDataPooler::MAX_TIMEOUT = 60000; // 01m00s
const qint32 KisTileDataPooler::MIN_TIMEOUT = 100; // 00m00.100s
const qint32 KisTileDataPooler::TIMEOUT_FACTOR = 2;

//#define DEBUG_POOLER

#ifdef DEBUG_POOLER
#define DEBUG_CLONE_ACTION(td, numClones)                               \
    printf("Cloned (%d):\t\t\t\t0x%X (clones: %d, users: %d, refs: %d)\n", \
           numClones, td, td->m_clonesStack.size(),                      \
           (int)td->m_usersCount, (int)td->m_refCount)
#define DEBUG_SIMPLE_ACTION(action)     \
    printf("pooler: %s\n", action)

#define RUNTIME_SANITY_CHECK(td) do {                                   \
        if(td->m_usersCount < td->m_refCount) {                         \
            qDebug("**** Suspicious tiledata: 0x%X (clones: %d, users: %d, refs: %d) ****", \
                   td, td->m_clonesStack.size(),                         \
                   (int)td->m_usersCount, (int)td->m_refCount);         \
        }                                                               \
        if(td->m_usersCount <= 0) {                                     \
            qFatal("pooler: Tiledata 0x%X has zero users counter. Crashing...", td); \
        }                                                               \
    } while(0)                                                          \

#define DEBUG_TILE_STATISTICS() debugTileStatistics()
#else
#define DEBUG_CLONE_ACTION(td, numClones)
#define DEBUG_SIMPLE_ACTION(action)
#define RUNTIME_SANITY_CHECK(td)
#define DEBUG_TILE_STATISTICS()
#endif


KisTileDataPooler::KisTileDataPooler(KisTileDataStore *store)
        : QThread()
{
    m_shouldExitFlag = 0;
    m_store = store;
    m_timeout = MIN_TIMEOUT;
    m_lastCycleHadWork = false;
}

KisTileDataPooler::~KisTileDataPooler()
{
}

void KisTileDataPooler::kick()
{
    m_semaphore.release();
}

void KisTileDataPooler::terminatePooler()
{
    m_shouldExitFlag = 1;
    kick();
    wait();
}

qint32 KisTileDataPooler::numClonesNeeded(KisTileData *td) const
{
    RUNTIME_SANITY_CHECK(td);
    qint32 numUsers = td->m_usersCount;
    qint32 numPresentClones = td->m_clonesStack.size();
    qint32 totalClones = qMin(numUsers - 1, MAX_NUM_CLONES);

    return totalClones - numPresentClones;
}

void KisTileDataPooler::cloneTileData(KisTileData *td, qint32 numClones) const
{
    if (numClones > 0) {
        td->blockSwapping();
        for (qint32 i = 0; i < numClones; i++) {
            td->m_clonesStack.push(new KisTileData(*td));
        }
        td->unblockSwapping();
    } else {
        qint32 numUnnededClones = qAbs(numClones);
        for (qint32 i = 0; i < numUnnededClones; i++) {
            KisTileData *clone = 0;

            bool result = td->m_clonesStack.pop(clone);
            if(!result) break;

            delete clone;
        }
    }

    DEBUG_CLONE_ACTION(td, numClones);
}

void KisTileDataPooler::waitForWork()
{
    bool success;

    if (m_lastCycleHadWork)
        success = m_semaphore.tryAcquire(1, m_timeout);
    else {
        m_semaphore.acquire();
        success = true;
    }

    m_lastCycleHadWork = false;
    if (success) {
        m_timeout = MIN_TIMEOUT;
    } else {
        m_timeout *= TIMEOUT_FACTOR;
        m_timeout = qMin(m_timeout, MAX_TIMEOUT);
    }
}

inline bool KisTileDataPooler::interestingTileData(KisTileData* td)
{
    /**
     * We have to look after all clones we created.
     * That is why we recheck all tiles with non-empty clones lists
     */

    return td->m_state == KisTileData::NORMAL &&
           (td->m_usersCount > 1 || !td->m_clonesStack.isEmpty());
}

void KisTileDataPooler::run()
{
    while (1) {
        DEBUG_SIMPLE_ACTION("went to bed... Zzz...");

        waitForWork();

        if (m_shouldExitFlag)
            return;

        QThread::msleep(0);
        DEBUG_SIMPLE_ACTION("cycle started");


        KisTileDataStoreReverseIterator *iter = m_store->beginReverseIteration();
        KisTileData *item;

        while(iter->hasNext()) {
            item = iter->next();
            if (interestingTileData(item)) {

                qint32 clonesNeeded = numClonesNeeded(item);
                if (clonesNeeded) {
                    m_lastCycleHadWork = true;
                    cloneTileData(item, clonesNeeded);
                }
            }
        }

        m_store->endIteration(iter);


        DEBUG_TILE_STATISTICS();
        DEBUG_SIMPLE_ACTION("cycle finished");
    }
}

void KisTileDataPooler::debugTileStatistics()
{
    /**
     * Assume we are called from the inside of the loop.
     * This means m_store is already locked
     */

    qint64 preallocatedTiles=0;

    KisTileDataStoreIterator *iter = m_store->beginIteration();
    KisTileData *item;

    while(iter->hasNext()) {
        item = iter->next();
        preallocatedTiles += item->m_clonesStack.size();
    }

    m_store->endIteration(iter);

    qDebug() << "Tiles statistics:\t total:" << m_store->numTiles() << "\t preallocated:"<< preallocatedTiles;
}
