/*  This file is part of the KDE project
    Copyright (C) 2007 Matthias Kretz <kretz@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License version 2 as published by the Free Software Foundation.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

*/

#include "packetpool.h"

namespace Phonon
{

struct PacketPrivate
{
    inline PacketPrivate(char *_data, PacketPoolPrivate *_pool)
        : ref(0), m_size(0), m_data(_data), m_pool(_pool) {}
    inline PacketPrivate() : ref(1), m_size(0), m_data(0), m_pool(0) {}

    QAtomicInt ref;

    int m_size;
    char *const m_data;
    PacketPoolPrivate *const m_pool;
};

class PacketPoolPrivate
{
    friend class PacketPool;
    friend class Packet;
    public:
        ~PacketPoolPrivate();

        Packet acquirePacket();
        void releasePacket(const Packet &);

    protected:
        QAtomicInt ref;

    private:
        PacketPoolPrivate(int packetSize, int _poolSize);

        // C-array of PacketPrivate*
        PacketPrivate **freePackets;
        char *const packetMemory;

        QAtomicInt readPosition;
        QAtomicInt writePosition;
        QAtomicInt ringBufferSize;

        const int packetSize;
        const int poolSize;
};

//PacketData Packet::shared_null = { 0, 0, 0 };//, 0, 0, Q_ATOMIC_INIT(1) }; //, 0, 0, 0 };

int PacketPool::packetSize() const { return d_ptr->packetSize; }
int PacketPool::poolSize() const { return d_ptr->poolSize; }
int PacketPool::unusedPackets() const { return d_ptr->ringBufferSize; }

PacketPoolPrivate::PacketPoolPrivate(int _packetSize, int _poolSize)
    : freePackets(new PacketPrivate *[_poolSize]),
    packetMemory(new char[packetSize * _poolSize]),
    readPosition(0), writePosition(0),
    ringBufferSize(_poolSize),
    packetSize(_packetSize),
    poolSize(_poolSize)
{
    for (int i = 0; i < _poolSize; ++i) {
        freePackets[i] = new PacketPrivate(&packetMemory[i * packetSize], this);
    }
}

PacketPoolPrivate::~PacketPoolPrivate()
{
    Q_ASSERT(poolSize == ringBufferSize);
    for (int i = 0; i < poolSize; ++i) {
        delete freePackets[i];
    }
    delete[] freePackets;
    delete[] packetMemory;
}

void PacketPoolPrivate::releasePacket(const Packet &packet)
{
    const int _writePos = writePosition.fetchAndAddAcquire(1);
    int pos = _writePos;
    while (pos >= poolSize) {
        pos -= poolSize;
    }
    writePosition.testAndSetRelease(_writePos, pos);
    freePackets[pos] = packet.d_ptr;
    ringBufferSize.ref();
}

Packet PacketPoolPrivate::acquirePacket()
{
    const int s = ringBufferSize.fetchAndAddRelaxed(-1);
    if (s <= 0) {
        ringBufferSize.fetchAndAddRelaxed(1);
        return Packet();
    }
    const int _readPos = readPosition.fetchAndAddRelaxed(1);
    int pos = _readPos;
    while (pos >= poolSize) {
        pos -= poolSize;
    }
    readPosition.testAndSetRelease(_readPos, pos);
    return Packet(*freePackets[pos]);
}

PacketPool::PacketPool(int packetSize, int _poolSize)
    : d_ptr(new PacketPoolPrivate(packetSize, _poolSize))
{
    d_ptr->ref.ref();
}

PacketPool::PacketPool(const PacketPool &rhs)
    : d_ptr(rhs.d_ptr)
{
    d_ptr->ref.ref();
}

PacketPool &PacketPool::operator=(const PacketPool &rhs)
{
    if (d_ptr != rhs.d_ptr) {
        if (!d_ptr->ref.deref()) {
            delete d_ptr;
        }
        d_ptr = rhs.d_ptr;
        d_ptr->ref.ref();
    }
    return *this;
}

PacketPool::~PacketPool()
{
    if (!d_ptr->ref.deref()) {
        delete d_ptr;
    }
}

Q_GLOBAL_STATIC(PacketPrivate, shared_null_packet)

Packet::Packet() : d_ptr(shared_null_packet()) { d_ptr->ref.ref(); }
Packet::Packet(PacketPool &pool) : d_ptr(pool.d_ptr->acquirePacket().d_ptr) { d_ptr->ref.ref(); }
Packet::Packet(const Packet &rhs) : d_ptr(rhs.d_ptr) { d_ptr->ref.ref(); }
Packet::Packet(PacketPrivate &dd) : d_ptr(&dd) { d_ptr->ref.ref(); }
Packet &Packet::operator=(const Packet &rhs)
{
    if (!d_ptr->ref.deref()) {
        Q_ASSERT(d_ptr->m_pool);
        d_ptr->m_pool->releasePacket(*this);
    }
    d_ptr = rhs.d_ptr;
    d_ptr->ref.ref();
    return *this;
}
Packet::~Packet()
{
    if (!d_ptr->ref.deref()) {
        Q_ASSERT(d_ptr->m_pool);
        d_ptr->m_pool->releasePacket(*this);
    }
}
bool Packet::operator==(const Packet &rhs) const { return d_ptr == rhs.d_ptr; }
bool Packet::operator!=(const Packet &rhs) const { return d_ptr != rhs.d_ptr; }
bool Packet::isNull() const { return d_ptr->m_pool == 0; }
void *Packet::data() const { return d_ptr->m_data; }
int Packet::size() const { return d_ptr->m_size; }
void Packet::setSize(int size) { d_ptr->m_size = size; }
int Packet::capacity() const { return d_ptr->m_pool ? d_ptr->m_pool->packetSize : 0; }

} // namespace Phonon
