/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** 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 The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/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 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qsoundinstance_p.h"
#include "qsoundsource_p.h"
#include "qsoundbuffer_p.h"
#include "qdeclarative_sound_p.h"
#include "qdeclarative_audiocategory_p.h"
#include "qdeclarative_audiosample_p.h"
#include "qdeclarative_attenuationmodel_p.h"
#include "qdeclarative_playvariation_p.h"
#include "qdeclarative_audioengine_p.h"
#include "qdeclarative_audiolistener_p.h"

#include "qdebug.h"

#define DEBUG_AUDIOENGINE

QT_BEGIN_NAMESPACE

QSoundInstance::QSoundInstance(QObject *parent)
    : QObject(parent)
    , m_soundSource(0)
    , m_bindBuffer(0)
    , m_sound(0)
    , m_variationIndex(-1)
    , m_isReady(false)
    , m_gain(1)
    , m_attenuationGain(1)
    , m_varGain(1)
    , m_pitch(1)
    , m_varPitch(1)
    , m_state(QSoundInstance::StoppedState)
    , m_coneOuterGain(0)
    , m_engine(0)
{
#ifdef DEBUG_AUDIOENGINE
    qDebug() << "creating new QSoundInstance";
#endif
    m_engine = qobject_cast<QDeclarativeAudioEngine*>(parent);
}

void QSoundInstance::bindSoundDescription(QDeclarativeSound *sound)
{
#ifdef DEBUG_AUDIOENGINE
    qDebug() << "QSoundInstance::bindSoundDescription" << sound;
#endif
    if (m_sound == sound)
        return;

    if (m_sound && m_sound->categoryObject()) {
        disconnect(m_sound->categoryObject(), SIGNAL(volumeChanged(qreal)), this, SLOT(categoryVolumeChanged()));
        disconnect(m_sound->categoryObject(), SIGNAL(paused()), this, SLOT(pause()));
        disconnect(m_sound->categoryObject(), SIGNAL(stopped()), this, SLOT(stop()));
        disconnect(m_sound->categoryObject(), SIGNAL(resumed()), this, SLOT(resume()));
    }
    m_attenuationGain = 1;
    m_gain = 1;

    m_sound = sound;

    if (sound) {
        if (!m_soundSource) {
            m_soundSource = m_engine->engine()->createSoundSource();
            connect(m_soundSource, SIGNAL(stateChanged(QSoundSource::State)),
                    this, SLOT(handleSourceStateChanged(QSoundSource::State)));
        }
    } else {
        if (m_soundSource) {
            detach();
            m_engine->engine()->releaseSoundSource(m_soundSource);
            m_soundSource = 0;
        }
    }

    if (m_sound) {
        if (m_sound->categoryObject()) {
            connect(m_sound->categoryObject(), SIGNAL(volumeChanged(qreal)), this, SLOT(categoryVolumeChanged()));
            connect(m_sound->categoryObject(), SIGNAL(paused()), this, SLOT(pause()));
            connect(m_sound->categoryObject(), SIGNAL(stopped()), this, SLOT(stop()));
            connect(m_sound->categoryObject(), SIGNAL(resumed()), this, SLOT(resume()));
        }
        prepareNewVariation();
    } else {
        m_variationIndex = -1;
    }
}

QSoundInstance::~QSoundInstance()
{
#ifdef DEBUG_AUDIOENGINE
    qDebug() << "QSoundInstance::dtor()";
#endif
    if (m_soundSource) {
        detach();
        m_engine->engine()->releaseSoundSource(m_soundSource);
    }
}

void QSoundInstance::prepareNewVariation()
{
#ifdef DEBUG_AUDIOENGINE
    qDebug() << "QSoundInstance::prepareNewVariation()";
#endif
    int newVariationIndex = m_sound->genVariationIndex(m_variationIndex);
    if (newVariationIndex == m_variationIndex)
        return;
    QDeclarativePlayVariation *playVar = m_sound->getVariation(newVariationIndex);
#ifdef DEBUG_AUDIOENGINE
    qDebug() << "QSoundInstance: generate new play variation [old:" << m_variationIndex << ", new:" << newVariationIndex << "-" << playVar->sample() << "]";
#endif
    m_variationIndex = newVariationIndex;
    playVar->applyParameters(this);
    detach();

    m_bindBuffer = playVar->sampleObject()->soundBuffer();
    if (m_bindBuffer->state() == QSoundBuffer::Ready) {
        Q_ASSERT(m_soundSource);
        m_soundSource->bindBuffer(m_bindBuffer);
        m_isReady = true;
    } else {
        m_bindBuffer->load();
        connect(m_bindBuffer, SIGNAL(ready()), this, SLOT(bufferReady()));
    }
}

void QSoundInstance::bufferReady()
{
#ifdef DEBUG_AUDIOENGINE
    qDebug() << "QSoundInstance::bufferReady()";
#endif
    if (!m_soundSource)
        return;
    m_soundSource->bindBuffer(m_bindBuffer);
    disconnect(m_bindBuffer, SIGNAL(ready()), this, SLOT(bufferReady()));
    m_isReady = true;

    if (m_state == QSoundInstance::PlayingState) {
        sourcePlay();
    } else if (m_state == QSoundInstance::PausedState) {
        sourcePause();
    }
}

void QSoundInstance::categoryVolumeChanged()
{
    updateGain();
}

void QSoundInstance::handleSourceStateChanged(QSoundSource::State newState)
{
    State ns = State(newState);
    if (ns == m_state)
        return;
    if (ns == QSoundInstance::StoppedState) {
        prepareNewVariation();
    }
    setState(ns);
}

void QSoundInstance::setState(State state)
{
    if (state == m_state)
        return;
    m_state = state;
    emit stateChanged(m_state);
}

qreal QSoundInstance::categoryVolume() const
{
    if (!m_sound)
        return 1;
    if (!m_sound->categoryObject())
        return 1;
    return m_sound->categoryObject()->volume();
}

void QSoundInstance::sourceStop()
{
    Q_ASSERT(m_soundSource);
    m_soundSource->stop();
    setState(QSoundInstance::StoppedState);
}

void QSoundInstance::detach()
{
    sourceStop();
    m_isReady = false;
    if (m_soundSource)
        m_soundSource->unbindBuffer();
    if (m_bindBuffer) {
        disconnect(m_bindBuffer, SIGNAL(ready()), this, SLOT(bufferReady()));
        m_engine->engine()->releaseSoundBuffer(m_bindBuffer);
        m_bindBuffer = 0;
    }
}

void QSoundInstance::play()
{
#ifdef DEBUG_AUDIOENGINE
    qDebug() << "QSoundInstancePrivate::play()";
#endif
    if (!m_soundSource || m_state == QSoundInstance::PlayingState)
        return;
    if (!m_isReady) {
        setState(QSoundInstance::PlayingState);
        return;
    }
    sourcePlay();
    setState(QSoundInstance::PlayingState);
}

void QSoundInstance::sourcePlay()
{
    update3DVolume(m_engine->listener()->position());
    Q_ASSERT(m_soundSource);
    m_soundSource->play();
}

void QSoundInstance::resume()
{
#ifdef DEBUG_AUDIOENGINE
    qDebug() << "QSoundInstancePrivate::resume()";
#endif
    if (m_state != QSoundInstance::PausedState)
        return;
    play();
}

void QSoundInstance::pause()
{
    if (!m_soundSource || m_state == QSoundInstance::PausedState)
        return;
    if (!m_isReady) {
        setState(QSoundInstance::PausedState);
        return;
    }
    sourcePause();
    setState(QSoundInstance::PausedState);
}

void QSoundInstance::sourcePause()
{
    Q_ASSERT(m_soundSource);
    m_soundSource->pause();
}

void QSoundInstance::stop()
{
    if (!m_isReady || !m_soundSource || m_state == QSoundInstance::StoppedState) {
        setState(QSoundInstance::StoppedState);
        return;
    }
    sourceStop();
    prepareNewVariation();
}

QSoundInstance::State QSoundInstance::state() const
{
    return m_state;
}

void QSoundInstance::setPosition(const QVector3D& position)
{
    if (!m_soundSource)
        return;
    m_soundSource->setPosition(position);
}

void QSoundInstance::setDirection(const QVector3D& direction)
{
    if (!m_soundSource)
        return;
    m_soundSource->setDirection(direction);
}

void QSoundInstance::setVelocity(const QVector3D& velocity)
{
    if (!m_soundSource)
        return;
    m_soundSource->setVelocity(velocity);
}

void QSoundInstance::setGain(qreal gain)
{
    if (!m_soundSource)
        return;
    m_gain = gain;
    updateGain();
}

void QSoundInstance::setPitch(qreal pitch)
{
    if (!m_soundSource)
        return;
    m_pitch = pitch;
    updatePitch();
}

void QSoundInstance::setCone(qreal innerAngle, qreal outerAngle, qreal outerGain)
{
    if (!m_soundSource)
        return;
    m_soundSource->setCone(innerAngle, outerAngle, outerGain);
}

bool QSoundInstance::attenuationEnabled() const
{
    if (!m_sound || !m_sound->attenuationModelObject())
        return false;
    return true;
}

void QSoundInstance::update3DVolume(const QVector3D& listenerPosition)
{
    if (!m_sound || !m_soundSource)
        return;
    QDeclarativeAttenuationModel *attenModel = m_sound->attenuationModelObject();
    if (!attenModel)
        return;
    m_attenuationGain = attenModel->calculateGain(listenerPosition, m_soundSource->position());
    updateGain();
}

void QSoundInstance::updateVariationParameters(qreal varPitch, qreal varGain, bool looping)
{
    if (!m_soundSource)
        return;
    m_soundSource->setLooping(looping);
#ifdef DEBUG_AUDIOENGINE
    qDebug() << "QSoundInstance::updateVariationParameters" << varPitch << varGain << looping;
#endif
    m_varPitch = varPitch;
    m_varGain = varGain;
    updatePitch();
    updateGain();
}

void QSoundInstance::updatePitch()
{
    m_soundSource->setPitch(m_pitch * m_varPitch);
}

void QSoundInstance::updateGain()
{
    m_soundSource->setGain(m_gain * m_varGain * m_attenuationGain * categoryVolume());
}

QT_END_NAMESPACE
