/*************************************************************************
      ReversePlugin.cpp  -  reverses the current selection
                             -------------------
    begin                : Tue Jun 09 2009
    copyright            : (C) 2009 by Thomas Eschenbacher
    email                : Thomas.Eschenbacher@gmx.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "config.h"
#include <math.h>
#include <new>

#include <KLocalizedString> // for the i18n macro

#include <QFutureSynchronizer>
#include <QList>
#include <QSharedPointer>
#include <QStringList>
#include <QThread>
#include <QtConcurrentRun>

#include "libkwave/MultiTrackReader.h"
#include "libkwave/PluginManager.h"
#include "libkwave/SignalManager.h"
#include "libkwave/String.h"
#include "libkwave/Utils.h"
#include "libkwave/Writer.h"
#include "libkwave/undo/UndoTransactionGuard.h"

#include "libgui/SelectTimeWidget.h" // for selection mode

#include "ReversePlugin.h"
#include "UndoReverseAction.h"

KWAVE_PLUGIN(reverse, ReversePlugin)

//***************************************************************************
Kwave::ReversePlugin::ReversePlugin(QObject *parent,
                                    const QVariantList &args)
    :Kwave::Plugin(parent, args)
{
}

//***************************************************************************
Kwave::ReversePlugin::~ReversePlugin()
{
}

//***************************************************************************
void Kwave::ReversePlugin::run(QStringList params)
{
    Kwave::SignalManager &signal_manager = signalManager();

    QSharedPointer<Kwave::UndoTransactionGuard> undo_guard;

    // get the current selection and the list of affected tracks
    QVector<unsigned int> tracks;
    sample_index_t first = 0;
    sample_index_t last  = 0;
    sample_index_t length = selection(&tracks, &first, &last, true);
    if (!length || tracks.isEmpty())
        return;

    if ((params.count() != 1) || (params.first() != _("noundo"))) {
        // undo is enabled, create a undo guard
        undo_guard = QSharedPointer<Kwave::UndoTransactionGuard>(
            new(std::nothrow) Kwave::UndoTransactionGuard(
                *this, i18n("Reverse")));
        if (!undo_guard) return;

        // try to save undo information
        Kwave::UndoAction *undo =
            new(std::nothrow) Kwave::UndoReverseAction(manager());
        if (!undo_guard->registerUndoAction(undo))
            return;
        undo->store(signal_manager);
    }

    Kwave::MultiTrackReader source_a(Kwave::SinglePassForward,
        signalManager(), tracks, first, last);
    Kwave::MultiTrackReader source_b(Kwave::SinglePassReverse,
        signalManager(), tracks, first, last);

    // break if aborted
    if (!source_a.tracks() || !source_b.tracks())
        return;

    // connect the progress dialog
    connect(&source_a, SIGNAL(progress(qreal)),
            this,      SLOT(updateProgress(qreal)),
            Qt::BlockingQueuedConnection);

    // use a reasonably big buffer size
    const unsigned int block_size = 5 * source_a.blockSize();

    // loop over the sample range
    while ((first < last) && !shouldStop()) {
        QFutureSynchronizer<void> synchronizer;

        Kwave::ReversePlugin::SliceParams slice_params;
        slice_params.m_first      = first;
        slice_params.m_last       = last;
        slice_params.m_block_size = block_size;

        // loop over all tracks
        for (int i = 0; i < tracks.count(); i++) {
            synchronizer.addFuture(QtConcurrent::run(
                &Kwave::ReversePlugin::reverseSlice,
                this,
                tracks[i], source_a[i], source_b[i],
                slice_params)
            );
        }

        // next positions
        first += block_size;
        last  = (last > block_size) ? (last - block_size) : 0;

        synchronizer.waitForFinished();
    }

}

//***************************************************************************
void Kwave::ReversePlugin::reverseSlice(unsigned int track,
    Kwave::SampleReader *src_a, Kwave::SampleReader *src_b,
    const Kwave::ReversePlugin::SliceParams &params)
{
    Kwave::SignalManager &signal_manager = signalManager();
    const sample_index_t first      = params.m_first;
    const sample_index_t last       = params.m_last;
    const unsigned int   block_size = params.m_block_size;
    const sample_index_t start_a    = first;
    const sample_index_t start_b    = (last >= block_size) ?
                                      (last - block_size) : 0;
    bool ok = true;

    if (start_a + block_size < start_b) {
        // read from start
        Kwave::SampleArray buffer_a;
        ok &= buffer_a.resize(block_size);
        Q_ASSERT(ok);
        *src_a >> buffer_a;

        // read from end
        Kwave::SampleArray buffer_b;
        ok &= buffer_b.resize(block_size);
        Q_ASSERT(ok);
        src_b->seek(start_b);
        *src_b >> buffer_b;

        // swap the contents
        reverse(buffer_a);
        reverse(buffer_b);

        // write back buffer from the end at the start
        Kwave::Writer *dst_a = signal_manager.openWriter(
            Kwave::Overwrite, track,
            start_a, start_a + block_size - 1);
        Q_ASSERT(dst_a);
        if (!dst_a) return;
        *dst_a << buffer_b;
        dst_a->flush();
        delete dst_a;

        // write back buffer from the start at the end
        Kwave::Writer *dst_b = signal_manager.openWriter(
            Kwave::Overwrite, track,
            start_b, start_b + block_size - 1);
        Q_ASSERT(dst_b);
        if (!dst_b) return;
        *dst_b << buffer_a << flush;
        delete dst_b;
    } else {
        // single buffer with last block
        Kwave::SampleArray buffer;
        ok &= buffer.resize(Kwave::toUint(last - first + 1));
        Q_ASSERT(ok);

        // read from start
        *src_a >> buffer;

        // swap content
        reverse(buffer);

        // write back
        Kwave::Writer *dst = signal_manager.openWriter(
            Kwave::Overwrite, track, first, last);
        if (!dst) return;
        (*dst) << buffer << flush;
        delete dst;
    }
}

//***************************************************************************
void Kwave::ReversePlugin::reverse(Kwave::SampleArray &buffer)
{
    unsigned int count = buffer.size() >> 1;
    if (count <= 1) return;

    sample_t *a = buffer.data();
    sample_t *b = buffer.data() + (buffer.size() - 1);
    for (; count; count--) {
        sample_t h = *a;
        *a++ = *b;
        *b-- = h;
    }
}

//***************************************************************************
void Kwave::ReversePlugin::updateProgress(qreal progress)
{
    Kwave::Plugin::updateProgress(progress + progress);
}

//***************************************************************************
#include "ReversePlugin.moc"
//***************************************************************************
//***************************************************************************

#include "moc_ReversePlugin.cpp"
