/*  This file is part of the KDE project.

    Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).

    This library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2.1 or 3 of the License.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "audioeffects.h"

QT_BEGIN_NAMESPACE

namespace Phonon
{
namespace QT7
{

AudioEffectAudioNode::AudioEffectAudioNode(int effectType)
    : AudioNode(1, 1), m_effectType(effectType)
{
}

ComponentDescription AudioEffectAudioNode::getAudioNodeDescription() const
{
	ComponentDescription d;
	d.componentType = kAudioUnitType_Effect;
	d.componentSubType = m_effectType;
	d.componentManufacturer = kAudioUnitManufacturer_Apple;
	d.componentFlags = 0;
	d.componentFlagsMask = 0;
    return d;
}

void AudioEffectAudioNode::initializeAudioUnit()
{
    if (!m_audioUnit)
        return;
    foreach(int id, m_alteredParameters.keys()){
        Float32 value = m_alteredParameters.value(id);
        ComponentResult res = AudioUnitSetParameter(m_audioUnit, id, kAudioUnitScope_Global, 0, value, 0);
        BACKEND_ASSERT2(res == noErr, "Could not initialize audio effect.", NORMAL_ERROR)
    }
}

QVariant AudioEffectAudioNode::parameterValue(const Phonon::EffectParameter &parameter) const
{
    if (m_audioUnit){
        Float32 value = 0;
        AudioUnitGetParameter(m_audioUnit, parameter.id(), kAudioUnitScope_Global, 0, &value);
        return QVariant(value);
    } else if (m_alteredParameters.contains(parameter.id())){
        return QVariant(m_alteredParameters.value(parameter.id()));
    } else {
        // Use default value:
        AudioUnit tmpAudioUnit;
        ComponentDescription description = getAudioNodeDescription();
        Component component = FindNextComponent(0, &description);
        BACKEND_ASSERT3(component, "Could not get parameters of audio effect.", NORMAL_ERROR, QVariant())
        OSErr err = OpenAComponent(component, &tmpAudioUnit);
        BACKEND_ASSERT3(err == noErr, "Could not get parameters of audio effect.", NORMAL_ERROR, QVariant())
        AudioUnitParameterInfo info;
        UInt32 size = sizeof(info);
        ComponentResult res = AudioUnitGetProperty(tmpAudioUnit,
            kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, parameter.id(), &info, &size);
        BACKEND_ASSERT3(res == noErr, "Could not get parameter info from audio effect.", NORMAL_ERROR, QVariant())
        return QVariant(info.defaultValue);
    }
}

void AudioEffectAudioNode::setParameterValue(const Phonon::EffectParameter &parameter, const QVariant &newValue)
{
    Float32 value = 0;
    if (newValue.isValid()){
        value = newValue.toDouble();
        m_alteredParameters.insert(parameter.id(), value);
    } else {
        // Use default value:
        m_alteredParameters.remove(parameter.id());
        if (m_audioUnit){
            AudioUnit tmpAudioUnit;
            ComponentDescription description = getAudioNodeDescription();
            Component component = FindNextComponent(0, &description);
            BACKEND_ASSERT2(component, "Could not get parameters of audio effect.", NORMAL_ERROR)
            OSErr err = OpenAComponent(component, &tmpAudioUnit);
            BACKEND_ASSERT2(err == noErr, "Could not get parameters of audio effect.", NORMAL_ERROR)
            AudioUnitParameterInfo info;
            UInt32 size = sizeof(info);
            ComponentResult res = AudioUnitGetProperty(tmpAudioUnit,
                kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, parameter.id(), &info, &size);
            BACKEND_ASSERT2(res == noErr, "Could not get parameter info from audio effect.", NORMAL_ERROR)
            value = info.defaultValue;
        }
    }

    if (m_audioUnit){
        ComponentResult res = AudioUnitSetParameter(m_audioUnit, parameter.id(), kAudioUnitScope_Global, 0, value, 0);
        BACKEND_ASSERT2(res == noErr, "Could not set effect parameter value.", NORMAL_ERROR)
    }
}

///////////////////////////////////////////////////////////////////////

AudioEffect::AudioEffect(int effectType, QObject *parent)
    : MediaNode(AudioSink | AudioSource, 0, parent)
{
    m_audioNode = new AudioEffectAudioNode(effectType);
    setAudioNode(m_audioNode);
}

QList<Phonon::EffectParameter> AudioEffect::parameters() const
{
    QList<Phonon::EffectParameter> effectList;
    // Create a temporary audio unit:
    AudioUnit audioUnit;
    ComponentDescription description = m_audioNode->getAudioNodeDescription();
    Component component = FindNextComponent(0, &description);
    BACKEND_ASSERT3(component, "Could not get parameters of audio effect.", NORMAL_ERROR, effectList)
    OSErr err = OpenAComponent(component, &audioUnit);
    BACKEND_ASSERT3(err == noErr, "Could not get parameters of audio effect.", NORMAL_ERROR, effectList)

    UInt32 size = 0;
    // Get parameter count:
    ComponentResult res = AudioUnitGetProperty(audioUnit, 
        kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, 0, &size);
    BACKEND_ASSERT3(res == noErr, "Could not get parameter count from audio effect.", NORMAL_ERROR, effectList)
    int paramCount = size / sizeof(AudioUnitParameterID);

    // Get parameters:
    AudioUnitParameterID parameters[paramCount];
    res = AudioUnitGetProperty(audioUnit, 
        kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, &parameters, &size);
    BACKEND_ASSERT3(res == noErr, "Could not get parameter list from audio effect.", NORMAL_ERROR, effectList)

    for (int i=0; i<paramCount; ++i)
        effectList << createParameter(audioUnit, parameters[i]);
    
    CloseComponent(audioUnit);
    return effectList;
}

QString AudioEffect::name()
{
    ComponentDescription description = m_audioNode->getAudioNodeDescription();
    Component component = FindNextComponent(0, &description);
    BACKEND_ASSERT3(component, "Could not get audio effect name.", NORMAL_ERROR, QLatin1String("<unknown effect>"))

    ComponentDescription cDesc;
    Handle nameH = NewHandle(0);
    GetComponentInfo(component, &cDesc, nameH, 0, 0); 
    HLock(nameH);
    char *namePtr = *nameH;
    int len = *namePtr++;
    namePtr[len] = 0;
    QString qsName = QString::fromUtf8(namePtr);
    DisposeHandle(nameH);
    return qsName;
}

QString AudioEffect::description()
{
    ComponentDescription description = m_audioNode->getAudioNodeDescription();
    Component component = FindNextComponent(0, &description);
    BACKEND_ASSERT3(component, "Could not get audio effect description.", NORMAL_ERROR, QLatin1String("<unknown effect>"))

    ComponentDescription cDesc;
    Handle descH = NewHandle(0);
    GetComponentInfo(component, &cDesc, 0, descH, 0); 
    HLock(descH);
    char *descPtr = *descH;
    int len = *descPtr++;
    descPtr[len] = 0;
    QString qsDesc = QString::fromUtf8(descPtr);
    DisposeHandle(descH);
    return qsDesc;
}

QList<int> AudioEffect::effectList()
{
    QList<int> effects;

	ComponentDescription d;
	d.componentType = kAudioUnitType_Effect;
	d.componentSubType = 0;
	d.componentManufacturer = 0;
	d.componentFlags = 0;
	d.componentFlagsMask = 0;
    Component component = FindNextComponent(0, &d);
    
    while (component) {
        ComponentDescription cDesc;
        GetComponentInfo(component, &cDesc, 0, 0, 0); 
        effects << cDesc.componentSubType;
        component = FindNextComponent(component, &d);
    }
    return effects;
}

Phonon::EffectParameter AudioEffect::createParameter(const AudioUnit &audioUnit, const AudioUnitParameterID &id) const
{
    AudioUnitParameterInfo info;
    UInt32 size = sizeof(info);
    ComponentResult res = AudioUnitGetProperty(audioUnit,
        kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, id, &info, &size);
    BACKEND_ASSERT3(res == noErr, "Could not get parameter info from audio effect.", NORMAL_ERROR, Phonon::EffectParameter())
    
    QString name = info.flags & kAudioUnitParameterFlag_HasCFNameString
        ? PhononCFString::toQString(info.cfNameString) : QLatin1String("<unknown parameter>");
        
    Phonon::EffectParameter::Hint hint;
    switch(info.unit){
    case (kAudioUnitParameterUnit_Indexed):
    case (kAudioUnitParameterUnit_Seconds):
    case (kAudioUnitParameterUnit_SampleFrames):
    case (kAudioUnitParameterUnit_Milliseconds):
        hint = Phonon::EffectParameter::IntegerHint;
        break;
    case (kAudioUnitParameterUnit_Boolean):
        hint = Phonon::EffectParameter::ToggledHint;
        break;
    default:
        hint = Phonon::EffectParameter::LogarithmicHint;
        break;
    }
    
    QVariant def(info.defaultValue);    
    QVariant min(info.minValue);
    QVariant max(info.maxValue);
    return Phonon::EffectParameter(id, name, hint, def, min, max, QVariantList(), name);
}

QVariant AudioEffect::parameterValue(const Phonon::EffectParameter &value) const
{
    return m_audioNode->parameterValue(value);
}

void AudioEffect::setParameterValue(const Phonon::EffectParameter &parameter, const QVariant &newValue)
{
    m_audioNode->setParameterValue(parameter, newValue);
}

}} //namespace Phonon::QT7

QT_END_NAMESPACE

#include "moc_audioeffects.cpp"
