/***************************************************************************
      KwaveMimeData.cpp  -  mime data container for Kwave's audio data
                             -------------------
    begin                : Oct 04 2008
    copyright            : (C) 2008 by Thomas Eschenbacher
    email                : Thomas Eschenbacher <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 <limits>
#include <new>

#include <QApplication>
#include <QBuffer>
#include <QMutableListIterator>
#include <QVariant>
#include <QWidget>

#include "libkwave/memcpy.h"
#include "libkwave/CodecManager.h"
#include "libkwave/Compression.h"
#include "libkwave/Connect.h"
#include "libkwave/Decoder.h"
#include "libkwave/Encoder.h"
#include "libkwave/MimeData.h"
#include "libkwave/MultiStreamWriter.h"
#include "libkwave/MultiTrackReader.h"
#include "libkwave/MultiTrackWriter.h"
#include "libkwave/Sample.h"
#include "libkwave/SampleReader.h"
#include "libkwave/Signal.h"
#include "libkwave/SignalManager.h"
#include "libkwave/String.h"
#include "libkwave/Utils.h"
#include "libkwave/Writer.h"

#include "libkwave/modules/ChannelMixer.h"
#include "libkwave/modules/RateConverter.h"

// RFC 2361:
#define WAVE_FORMAT_PCM "audio/vnd.wave" // ; codec=001"

/** block size in bytes used as increment when resizing the raw buffer */
#define BUFFER_BLOCK_SIZE (4 * 1024 * 1024) /* 4 MB */

//***************************************************************************
Kwave::MimeData::Buffer::Buffer()
    :QIODevice(), m_block(nullptr), m_size(0), m_data()
{
}

//***************************************************************************
Kwave::MimeData::Buffer::~Buffer()
{
    close();
}

//***************************************************************************
qint64 Kwave::MimeData::Buffer::readData(char *data, qint64 maxlen)
{
    if (atEnd() || (pos() >= size())) return -1;
    if (pos() + maxlen > size())
        maxlen = size() - pos();

    MEMCPY(data, m_block->constData() + Kwave::toUint(pos()),
           Kwave::toUint(maxlen));
    return maxlen;
}

//***************************************************************************
qint64 Kwave::MimeData::Buffer::writeData(const char *data, qint64 len)
{

    // clip the mime data buffer at the "int" border
    if ((pos() + len) > std::numeric_limits<int>::max())
        return -1;

    // round up the block size if it can no longer be considered to be a
    // small block (~ half of block size), avoid wasting too much memory
    // if the needed size is very small.
    int new_size = Kwave::toInt(pos() + len);
    if (new_size > (BUFFER_BLOCK_SIZE / 2))
        new_size = Kwave::round_up<int>(new_size, BUFFER_BLOCK_SIZE);

    if (!m_block) {
        // first call: allocate a new memory object
        m_block = new(std::nothrow) QByteArray(new_size, Qt::Uninitialized);
        if (!m_block) return -1; // allocation failed
    }

    if ((pos() + len) > static_cast<qint64>(m_block->size())) {
        m_block->resize(static_cast<int>(new_size));
        if ((m_block->size()) != qint64(new_size))
            return -1; // resize failed
    }

    // write to the memory block
    MEMCPY(m_block->data() + pos(), data, len);

    if (pos() + len > m_size)
        m_size = pos() + len ; // push the "m_size"

    return len; // write operation was successful
}

//***************************************************************************
bool Kwave::MimeData::Buffer::mapToByteArray()
{
    // reset our QByteArray
    m_data.setRawData(nullptr, 0);
    m_data.clear();

    const char *raw = (m_block) ? ((m_block->data())) : nullptr;
    if (!raw) {
        // mapping failed: free the block here to avoid trouble
        // in close()
        delete m_block;
        m_block = nullptr;
        qWarning("Kwave::MimeData::Buffer::mapToByteArray() failed");
        return false; // mmap failed
    }

    // attach the mapped memory to our QByteArray
    const unsigned int len = Kwave::toUint(m_size);
//    qDebug("Kwave::MimeData::Buffer::mapToByteArray() - %p [%u]", raw, len);
    m_data.setRawData(raw, len);
    return true;
}

//***************************************************************************
void Kwave::MimeData::Buffer::close()
{
    QIODevice::close();

    // reset the byte array and it's connection to the block of memory
    m_data.setRawData(nullptr, 0);
    m_data.clear();

    // unmap and discard the mapped memory
    if (m_block) {
        delete m_block;
        m_block = nullptr;
    }
    m_size = 0;
}

//***************************************************************************
//***************************************************************************
Kwave::MimeData::MimeData()
    :QMimeData(), m_buffer()
{
}

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

//***************************************************************************
bool Kwave::MimeData::encode(QWidget *widget,
                             Kwave::MultiTrackReader &src,
                             const Kwave::MetaDataList &meta_data)
{
    // use our default encoder
    Kwave::Encoder *encoder = Kwave::CodecManager::encoder(_(WAVE_FORMAT_PCM));
    Q_ASSERT(encoder);
    if (!encoder) return false;

    Q_ASSERT(src.tracks());
    if (!src.tracks()) return false;

    // set hourglass cursor
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

    sample_index_t first = src.first();
    sample_index_t last  = src.last();
    Kwave::MetaDataList new_meta_data = meta_data.selectByRange(first, last);

    // move all meta data left, to start at the beginning of the selection
    new_meta_data.shiftLeft(first, first);

    // fix the length information in the new file info
    // and change to uncompressed mode
    Kwave::FileInfo info(meta_data);
    info.set(Kwave::INF_COMPRESSION, QVariant(Kwave::Compression::NONE));
    info.setLength(last - first + 1);
    info.setTracks(src.tracks());
    new_meta_data.replace(Kwave::MetaDataList(info));

    // encode into the buffer
    m_buffer.close(); // discard old stuff
    encoder->encode(widget, src, m_buffer, new_meta_data);

    delete encoder;

    // set the mime data into this mime data container
    bool succeeded = m_buffer.mapToByteArray();
    if (succeeded) {
        // mmap succeeded
        setData(_(WAVE_FORMAT_PCM), m_buffer.byteArray());
    } else {
        // failed to map memory
        m_buffer.close();
    }

    // remove hourglass
    QApplication::restoreOverrideCursor();

    return succeeded;
}

//***************************************************************************
sample_index_t Kwave::MimeData::decode(QWidget *widget, const QMimeData *e,
                                        Kwave::SignalManager &sig,
                                        sample_index_t pos)
{
    // decode, use the first format that matches
    sample_index_t decoded_length = 0;
    unsigned int   decoded_tracks = 0;

    // try to find a suitable decoder
    for (const QString &format : e->formats()) {
        // skip all non-supported formats
        if (!Kwave::CodecManager::canDecode(format)) continue;

        Kwave::Decoder *decoder = Kwave::CodecManager::decoder(format);
        Q_ASSERT(decoder);
        if (!decoder) return 0;

        QByteArray raw_data = e->data(format);
        QBuffer src(&raw_data);

        // open the mime source and get header information
        bool ok = decoder->open(widget, src);
        if (!ok) {
            delete decoder;
            continue;
        }

        decoded_length = Kwave::FileInfo(decoder->metaData()).length();
        decoded_tracks = Kwave::FileInfo(decoder->metaData()).tracks();
        Q_ASSERT(decoded_length);
        Q_ASSERT(decoded_tracks);
        if (!decoded_length || !decoded_tracks) {
            delete decoder;
            continue;
        }

        // get sample rates of source and destination
        double src_rate = Kwave::FileInfo(decoder->metaData()).rate();
        double dst_rate = sig.rate();

        // if the sample rate has to be converted, adjust the length
        // right border
        if ( !qFuzzyCompare(src_rate, dst_rate) && (dst_rate > 1) &&
             sig.tracks() )
            decoded_length = qRound(static_cast<double>(decoded_length) *
                (dst_rate / src_rate));

        sample_index_t left  = pos;
        sample_index_t right = left + decoded_length - 1;
        QVector<unsigned int> tracks = sig.selectedTracks();
        if (tracks.isEmpty()) tracks = sig.allTracks();

        // special case: destination is currently empty
        if (!sig.tracks()) {
            // encode into an empty window -> create tracks
            qDebug("Kwave::MimeData::decode(...) -> new signal");
            dst_rate = src_rate;
            sig.newSignal(0,
                src_rate,
                Kwave::FileInfo(decoder->metaData()).bits(),
                decoded_tracks);
            ok = (sig.tracks() == decoded_tracks);
            if (!ok) {
                delete decoder;
                continue;
            }
        }
        const unsigned int dst_tracks =
            static_cast<unsigned int>(sig.selectedTracks().count());

        // create the final sink
        Kwave::MultiTrackWriter dst(sig, sig.selectedTracks(),
                                    Kwave::Insert, left, right);

        // if the track count does not match, then we need a channel mixer
        Q_ASSERT(ok);
        Kwave::ChannelMixer *mixer = nullptr;
        if (ok && (decoded_tracks != dst_tracks)) {
            qDebug("Kwave::MimeData::decode(...) -> mixing channels: %u -> %u",
                   decoded_tracks, dst_tracks);
            mixer = new(std::nothrow)
                Kwave::ChannelMixer(decoded_tracks, dst_tracks);
            Q_ASSERT(mixer);
            ok &= (mixer) && mixer->init();
            Q_ASSERT(ok);
        }
        Q_ASSERT(ok);

        // if the sample rates do not match, then we need a rate converter
        Kwave::StreamObject *rate_converter = nullptr;
        if (ok && !qFuzzyCompare(src_rate, dst_rate)) {
            // create a sample rate converter
            qDebug("Kwave::MimeData::decode(...) -> rate conversion: "\
                   "%0.1f -> %0.1f", src_rate, dst_rate);
            rate_converter = new(std::nothrow)
                Kwave::MultiTrackSource<Kwave::RateConverter, true>(
                    dst_tracks, widget);
            Q_ASSERT(rate_converter);
            if (rate_converter)
                rate_converter->setAttribute(SLOT(setRatio(QVariant)),
                                             QVariant(dst_rate / src_rate));
            else
                ok = false;
        }
        Q_ASSERT(ok);

        // set hourglass cursor
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

        if (ok && (rate_converter || mixer)) {
            // pass all data through a filter chain
            Kwave::MultiStreamWriter adapter(decoded_tracks);

            // pass the data through a sample rate converter
            // decoder -> adapter -> [mixer] -> [converter] -> dst

            Kwave::StreamObject *last_output = &adapter;

            if (mixer) {
                // connect the channel mixer
                ok = Kwave::connect(
                    *last_output, SIGNAL(output(Kwave::SampleArray)),
                    *mixer,       SLOT(input(Kwave::SampleArray))
                );
                last_output = mixer;
            }

            if (ok && rate_converter) {
                // connect the rate converter
                ok = Kwave::connect(
                    *last_output,    SIGNAL(output(Kwave::SampleArray)),
                    *rate_converter, SLOT(input(Kwave::SampleArray))
                );
                last_output = rate_converter;
            }

            // connect the sink
            if (ok) {
                ok = Kwave::connect(
                    *last_output, SIGNAL(output(Kwave::SampleArray)),
                    dst,          SLOT(input(Kwave::SampleArray))
                );
            }

            // this also starts the conversion automatically
            if (ok)
                ok = decoder->decode(widget, adapter);

            // flush all samples that are still in the adapter
            adapter.flush();

        } else if (ok) {
            // decode directly without any filter
            ok = decoder->decode(widget, dst);
        }

        dst.flush();

        // clean up the filter chain
        delete mixer;
        delete rate_converter;

        // remove hourglass
        QApplication::restoreOverrideCursor();

        // failed :-(
        Q_ASSERT(ok);
        if (!ok) {
            delete decoder;
            decoded_length = 0;
            continue;
        }

        // take care of the meta data, shift all of it by "left" and
        // add it to the signal
        Kwave::MetaDataList meta_data = decoder->metaData();

        // adjust meta data position in case of different sample rate
        if (!qFuzzyCompare(src_rate, dst_rate))
            meta_data.scalePositions(dst_rate / src_rate);

        meta_data.shiftRight(0, left);

        // remove the file info, this must not be handled here, otherwise
        // this would overwrite the file info of the destination
        meta_data.remove(meta_data.selectByType(
            Kwave::FileInfo::metaDataType()));

        // add the remaining meta data (e.g. labels etc)
        sig.metaData().add(meta_data);

        delete decoder;
        break;
    }

//     qDebug("Kwave::MimeData::decode -> decoded_length=%u", decoded_length);
    return decoded_length;
}

//***************************************************************************
void Kwave::MimeData::clear()
{
    m_buffer.close();
}

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

#include "moc_MimeData.cpp"
