/***************************************************************************
    Writer.cpp - base class for writers, providing a C++ stream interface
                             -------------------
    begin                : Sun Aug 23 2009
    copyright            : (C) 2009 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 <QObject>

#include "libkwave/SampleReader.h"
#include "libkwave/Utils.h"
#include "libkwave/Writer.h"
#include "libkwave/memcpy.h"

/** size of m_buffer in samples */
#define BUFFER_SIZE (1024 * 1024)

/** minimum block size used for direct block i/o */
#define MIN_DIRECT_IO_BLOCK_SIZE (BUFFER_SIZE / 2)

//***************************************************************************
Kwave::Writer::Writer()
    :Kwave::SampleSink(nullptr),
     m_first(0), m_last(0), m_mode(Kwave::Insert), m_position(0),
     m_buffer(BUFFER_SIZE), m_buffer_size(BUFFER_SIZE), m_buffer_used(0)
{
}

//***************************************************************************
Kwave::Writer::Writer(Kwave::InsertMode mode,
                      sample_index_t left, sample_index_t right)
    :Kwave::SampleSink(nullptr),
     m_first(left), m_last(right), m_mode(mode), m_position(left),
     m_buffer(BUFFER_SIZE), m_buffer_size(BUFFER_SIZE), m_buffer_used(0)
{
}

//***************************************************************************
Kwave::Writer::~Writer()
{
    // If this assert gets hit, you deleted a writer without calling
    // flush() before. Flushing in the destructor is problematic and
    // might be too late when the derived classes' destructor was
    // already done and signal/slot connections were already released!
    Q_ASSERT(!m_buffer_used);
    if (m_buffer_used) qWarning("Writer was not flushed!?");

    Q_ASSERT((m_mode != Kwave::Overwrite) || (m_position <= m_last + 1));

    // inform others that we proceeded
    emit sigSamplesWritten(m_position - m_first);
}

//***************************************************************************
Kwave::Writer &Kwave::Writer::operator << (const Kwave::SampleArray &samples)
{
    unsigned int count = samples.size();

    if ( (m_buffer_used || (count < MIN_DIRECT_IO_BLOCK_SIZE)) &&
         (m_buffer_used + count <= m_buffer_size) )
    {
        // append to the internal buffer if it is already in use
        // and if there is still some room,
        // or if the buffer so small that it would make too much overhead
        // to process it by block operation
        MEMCPY(&(m_buffer[m_buffer_used]), &(samples[0]),
               count * sizeof(sample_t));
        m_buffer_used += count;
        if (m_buffer_used >= m_buffer_size) flush();
    } else {
        // first flush the single-sample buffer before doing block operation
        if (m_buffer_used) flush();

        // now flush the block that we received as parameter (pass-through)
        write(samples, count);
        Q_ASSERT(!count);
    }

    return *this;
}

//***************************************************************************
Kwave::Writer &Kwave::Writer::operator << (const sample_t &sample)
{
    m_buffer[m_buffer_used++] = sample;
    if (m_buffer_used >= m_buffer_size) flush();
    return *this;
}

//***************************************************************************
Kwave::Writer &Kwave::Writer::operator << (Kwave::SampleReader &reader)
{
    if (m_buffer_used) flush();

    // transfer data, using our internal buffer
    unsigned int buflen = m_buffer_size;
    while (!reader.eof() && !eof()) {

        // overwrite mode -> clip at right border
        if (m_mode == Kwave::Overwrite) {
            if (m_position + buflen - 1 > m_last)
                buflen = Kwave::toUint(m_last - m_position + 1);
        }

        m_buffer_used = reader.read(m_buffer, 0, buflen);
        Q_ASSERT(m_buffer_used);
        if (!m_buffer_used) break;

        if (!flush()) return *this; // out of memory
    }

    // pad the rest with zeroes
    if (m_mode == Kwave::Overwrite) {
        Q_ASSERT(m_position <= m_last + 1);
        while (m_buffer_used + m_position <= m_last) {
            *this << static_cast<sample_t>(0);
            m_position++;
        }
        Q_ASSERT(m_position <= m_last + 1);
    }

    return *this;
}

//***************************************************************************
bool Kwave::Writer::eof() const
{
    return (m_mode == Kwave::Overwrite) ? (m_position > m_last) : false;
}

//***************************************************************************
Kwave::Writer &flush(Kwave::Writer &s)
{
    s.flush();
    return s;
}

//***************************************************************************
void Kwave::Writer::input(Kwave::SampleArray data)
{
    if (data.size()) (*this) << data;
}

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

#include "moc_Writer.cpp"
