/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qwavedecoder_p.h"

#include <QtCore/qtimer.h>
#include <QtCore/qendian.h>

QT_BEGIN_NAMESPACE

QWaveDecoder::QWaveDecoder(QIODevice *s, QObject *parent):
    QIODevice(parent),
    haveFormat(false),
    dataSize(0),
    remaining(0),
    source(s),
    state(QWaveDecoder::InitialState)
{
    open(QIODevice::ReadOnly | QIODevice::Unbuffered);

    if (enoughDataAvailable())
        QTimer::singleShot(0, this, SLOT(handleData()));
    else
        connect(source, SIGNAL(readyRead()), SLOT(handleData()));
}

QWaveDecoder::~QWaveDecoder()
{
}

QAudioFormat QWaveDecoder::audioFormat() const
{
    return format;
}

int QWaveDecoder::duration() const
{
    return size() * 1000 / (format.sampleSize() / 8) / format.channels() / format.frequency();
}

qint64 QWaveDecoder::size() const
{
    return haveFormat ? dataSize : 0;
}

bool QWaveDecoder::isSequential() const
{
    return source->isSequential();
}

qint64 QWaveDecoder::bytesAvailable() const
{
    return haveFormat ? source->bytesAvailable() : 0;
}

qint64 QWaveDecoder::readData(char *data, qint64 maxlen)
{
    return haveFormat ? source->read(data, maxlen) : 0;
}

qint64 QWaveDecoder::writeData(const char *data, qint64 len)
{
    Q_UNUSED(data);
    Q_UNUSED(len);

    return -1;
}

void QWaveDecoder::handleData()
{
    if (state == QWaveDecoder::InitialState) {
        if (source->bytesAvailable() < qint64(sizeof(RIFFHeader)))
            return;

        RIFFHeader riff;
        source->read(reinterpret_cast<char *>(&riff), sizeof(RIFFHeader));

        if (qstrncmp(riff.descriptor.id, "RIFF", 4) != 0 ||
            qstrncmp(riff.type, "WAVE", 4) != 0) {
            source->disconnect(SIGNAL(readyRead()), this, SLOT(handleData()));
            emit invalidFormat();

            return;
        } else {
            state = QWaveDecoder::WaitingForFormatState;
        }
    }

    if (state == QWaveDecoder::WaitingForFormatState) {
        if (findChunk("fmt ")) {
            chunk descriptor;
            source->peek(reinterpret_cast<char *>(&descriptor), sizeof(chunk));

            if (source->bytesAvailable() < qint64(descriptor.size + sizeof(chunk)))
                return;

            WAVEHeader wave;
            source->read(reinterpret_cast<char *>(&wave), sizeof(WAVEHeader));
            if (descriptor.size > sizeof(WAVEHeader))
                discardBytes(descriptor.size - sizeof(WAVEHeader));

            if (wave.audioFormat != 0 && wave.audioFormat != 1) {
                source->disconnect(SIGNAL(readyRead()), this, SLOT(handleData()));
                emit invalidFormat();

                return;
            } else {
                int bps = qFromLittleEndian<quint16>(wave.bitsPerSample);

                format.setCodec(QLatin1String("audio/pcm"));
                format.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt);
                format.setByteOrder(QAudioFormat::LittleEndian);
                format.setFrequency(qFromLittleEndian<quint32>(wave.sampleRate));
                format.setSampleSize(bps);
                format.setChannels(qFromLittleEndian<quint16>(wave.numChannels));

                state = QWaveDecoder::WaitingForDataState;
            }
        }
    }

    if (state == QWaveDecoder::WaitingForDataState) {
        if (findChunk("data")) {
            source->disconnect(SIGNAL(readyRead()), this, SLOT(handleData()));

            chunk descriptor;
            source->read(reinterpret_cast<char *>(&descriptor), sizeof(chunk));
            dataSize = descriptor.size;

            haveFormat = true;
            connect(source, SIGNAL(readyRead()), SIGNAL(readyRead()));
            emit formatKnown();

            return;
        }
    }

    if (source->atEnd()) {
        source->disconnect(SIGNAL(readyRead()), this, SLOT(handleData()));
        emit invalidFormat();

        return;
    }

}

bool QWaveDecoder::enoughDataAvailable()
{
    if (source->bytesAvailable() < qint64(sizeof(chunk)))
        return false;

    chunk descriptor;
    source->peek(reinterpret_cast<char *>(&descriptor), sizeof(chunk));

    if (source->bytesAvailable() < qint64(sizeof(chunk) + descriptor.size))
        return false;

    return true;
}

bool QWaveDecoder::findChunk(const char *chunkId)
{
    if (source->bytesAvailable() < qint64(sizeof(chunk)))
        return false;

    chunk descriptor;
    source->peek(reinterpret_cast<char *>(&descriptor), sizeof(chunk));

    if (qstrncmp(descriptor.id, chunkId, 4) == 0)
        return true;

    while (source->bytesAvailable() >= qint64(sizeof(chunk) + descriptor.size)) {
        discardBytes(sizeof(chunk) + descriptor.size);

        source->peek(reinterpret_cast<char *>(&descriptor), sizeof(chunk));

        if (qstrncmp(descriptor.id, chunkId, 4) == 0)
            return true;
    }

    return false;
}

void QWaveDecoder::discardBytes(qint64 numBytes)
{
    if (source->isSequential())
        source->read(numBytes);
    else
        source->seek(source->pos() + numBytes);
}

QT_END_NAMESPACE

#include "moc_qwavedecoder_p.cpp"
