/*
 * anim.cpp - class for handling animations
 * Copyright (C) 2003-2006  Michail Pishchagin
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "anim.h"

#include "iconset.h"

// #include <QApplication>
#include <QBuffer>
#include <QImage>
#include <QImageReader>
#include <QObject>
#include <QThread>
#include <QTimer>

/**
 * \class Anim
 * \brief Class for handling animations
 *
 * Anim is a class that can load animations. Generally, it looks like
 * QMovie but it loads animations in one pass and stores it in memory.
 *
 * Each frame of Anim is stored as Impix.
 */

static QThread *animMainThread = nullptr;

//! \if _hide_doc_
class Anim::Private : public QObject, public QSharedData {
    Q_OBJECT
public:
    QTimer *frametimer;

    bool empty;
    bool paused;

    int speed;
    int lasttimerinterval;

    int looping, loop;

    class Frame {
    public:
        Impix impix;
        int   period = 100;
    };

    QList<Frame> frames;
    int          frame;

public:
    void init()
    {
        frametimer = new QTimer();
        if (animMainThread && animMainThread != QThread::currentThread()) {
            frametimer->moveToThread(animMainThread);
            moveToThread(animMainThread);
        }
        QObject::connect(frametimer, SIGNAL(timeout()), this, SLOT(refresh()));

        speed             = 120;
        lasttimerinterval = -1;

        looping = 0; // MNG movies doesn't have loop flag?
        loop    = 0;
        frame   = 0;
        paused  = true;
    }

    Private() { init(); }

    Private(const Private &from) : QObject(), QSharedData()
    {
        init();

        speed             = from.speed;
        lasttimerinterval = from.lasttimerinterval;
        looping           = from.looping;
        loop              = from.loop;
        frame             = from.frame;
        paused            = from.paused;
        frames            = from.frames;

        if (!paused)
            unpause();
    }

    Private(const QByteArray *ba)
    {
        init();

        QBuffer buffer(const_cast<QByteArray *>(ba));
        buffer.open(QBuffer::ReadOnly);
        QImageReader reader(&buffer);

        while (reader.canRead()) {
            QImage image = reader.read();
            if (!image.isNull()) {
                Frame newFrame;
                newFrame.impix  = Impix(image);
                newFrame.period = reader.nextImageDelay();
                frames.append(newFrame);
            } else {
                break;
            }
        }

        looping = reader.loopCount();

        if (!reader.supportsAnimation() && (frames.count() == 1)) {
            QImage frame = frames[0].impix.image();

            // we're gonna slice the single image we've got if we're absolutely sure
            // that it's can be cut into multiple frames
            if ((frame.width() / frame.height() > 0) && !(frame.width() % frame.height())) {
                int          h = frame.height();
                QList<Frame> newFrames;

                for (int i = 0; i < frame.width() / frame.height(); i++) {
                    Frame newFrame;
                    newFrame.impix  = Impix(frame.copy(i * h, 0, h, h));
                    newFrame.period = 120;
                    newFrames.append(newFrame);
                }

                frames  = newFrames;
                looping = 0;
            }
        }
    }

    ~Private()
    {
        if (frametimer)
            delete frametimer;
    }

    void pause()
    {
        paused = true;
        frametimer->stop();
    }

    void unpause()
    {
        paused = false;
        restartTimer();
    }

    void restart()
    {
        frame = 0;
        if (!paused)
            restartTimer();
    }

    int numFrames() const { return frames.count(); }

    void restartTimer()
    {
        if (!paused && speed > 0) {
            int frameperiod = frames[frame].period;
            int i           = frameperiod >= 0 ? frameperiod * 100 / speed : 0;
            if (i != lasttimerinterval || !frametimer->isActive()) {
                lasttimerinterval = i;
                frametimer->start(i);
            }
        } else {
            frametimer->stop();
        }
    }

signals:
    void areaChanged();

public slots:
    void refresh()
    {
        frame++;
        if (frame >= numFrames()) {
            frame = 0;

            loop++;
            if (looping > 0 && loop >= looping) {
                frame = numFrames() - 1;
                pause();
                restart();
            }
        }

        emit areaChanged();
        restartTimer();
    }
};
//! \endif

/**
 * Creates an empty animation.
 */
Anim::Anim() { d = new Private(); }

/**
 * Creates animation from QByteArray \a data.
 */
Anim::Anim(const QByteArray &data) { d = new Private(&data); }

/**
 * Creates shared copy of Anim \a anim.
 */
Anim::Anim(const Anim &anim) { d = anim.d; }

/**
 * Deletes animation.
 */
Anim::~Anim() { }

/**
 * Returns QPixmap of current frame.
 */
const QPixmap &Anim::framePixmap() const { return d->frames[d->frame].impix.pixmap(); }

/**
 * Returns QImage of current frame.
 */
const QImage &Anim::frameImage() const { return d->frames[d->frame].impix.image(); }

/**
 * Returns Impix of current frame.
 */
const Impix &Anim::frameImpix() const { return d->frames[d->frame].impix; }

/**
 * Returns total number of frames in animation.
 */
int Anim::numFrames() const { return d->numFrames(); }

/**
 * Returns the number of current animation frame.
 */
int Anim::frameNumber() const { return d->frame; }

/**
 * Returns Impix of animation frame number \a n.
 */
const Impix &Anim::frame(int n) const { return d->frames[n].impix; }

/**
 * Returns \c true if numFrames() == 0 and \c false otherwise.
 */
bool Anim::isNull() const { return !numFrames(); }

/**
 * Returns \c true when animation is paused.
 */
bool Anim::paused() const { return d->paused; }

/**
 * Continues the animation playback.
 */
void Anim::unpause()
{
    if (!isNull() && paused())
        d->unpause();
}

/**
 * Pauses the animation.
 */
void Anim::pause()
{
    if (!isNull() && !paused())
        d->pause();
}

/**
 * Starts animation from the very beginning.
 */
void Anim::restart()
{
    if (!isNull())
        d->restart();
}

/**
 * Connects internal signal with specified slot \a member of object \a receiver, which
 * is emitted when animation changes its frame.
 *
 * \code
 * class MyObject : public QObject {
 *    Q_OBJECT
 *    // ...
 *    Anim *anim;
 *    void initAnim() {
 *       anim = new Anim( QByteArray(someData()) );
 *       anim->connectUpdate( this, SLOT(animUpdated()) );
 *    }
 * public slots:
 *    void animUpdated();
 *    // ...
 * }
 * \endcode
 *
 * \sa disconnectUpdate()
 */
void Anim::connectUpdate(QObject *receiver, const char *member)
{
    QObject::connect(d, SIGNAL(areaChanged()), receiver, member);
}

/**
 * Disconnects slot \a member, which was prevously connected with connectUpdate().
 * \sa connectUpdate()
 */
void Anim::disconnectUpdate(QObject *receiver, const char *member)
{
    QObject::disconnect(d, SIGNAL(areaChanged()), receiver, member);
}

Anim &Anim::operator=(const Anim &from)
{
    d = from.d;

    return *this;
}

Anim Anim::copy() const
{
    Anim anim(*this);
    anim.d = new Private(*this->d.data());

    return anim;
}

void Anim::detach() { d.detach(); }

/**
 * Strips the first animation frame, if there is more than one frame.
 */
void Anim::stripFirstFrame()
{
    detach();
    if (numFrames() > 1) {
        d->frames.takeFirst();

        if (!paused())
            restart();
    }
}

/**
 * Sets the main thread that will be used to create objects. Useful if you want
 * to create Anim in non-main thread.
 */
void Anim::setMainThread(QThread *thread) { animMainThread = thread; }

/**
 * Get the Anim's main thread.
 */
QThread *Anim::mainThread() { return animMainThread; }

#include "anim.moc"
