/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* EffectSend implementation */

#include "sles_allinclusive.h"


/** \brief Maps AUX index to OutputMix interface index */

static const unsigned char AUX_to_MPH[AUX_MAX] = {
    MPH_ENVIRONMENTALREVERB,
    MPH_PRESETREVERB
};


/** \brief This is a private function that validates the effect interface specified by the
 *  application when it calls EnableEffectSend, IsEnabled, SetSendLevel, or GetSendLevel.
 *  For the interface to be valid, it has to satisfy these requirements:
 *   - object is an audio player (MIDI player is not supported yet)
 *   - audio sink is an output mix
 *   - interface was exposed at object creation time or by DynamicInterface::AddInterface
 *   - interface was "gotten" with Object::GetInterface
 */

static struct EnableLevel *getEnableLevel(IEffectSend *this, const void *pAuxEffect)
{
    // Make sure this effect send is on an audio player, not a MIDI player
    CAudioPlayer *audioPlayer = (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) ?
        (CAudioPlayer *) this->mThis : NULL;
    if (NULL == audioPlayer) {
        return NULL;
    }
    // Get the output mix for this player
    COutputMix *outputMix = CAudioPlayer_GetOutputMix(audioPlayer);
    unsigned aux;
    if (pAuxEffect == &outputMix->mEnvironmentalReverb.mItf) {
        aux = AUX_ENVIRONMENTALREVERB;
    } else if (pAuxEffect == &outputMix->mPresetReverb.mItf) {
        aux = AUX_PRESETREVERB;
    } else {
        SL_LOGE("EffectSend on unknown aux effect %p", pAuxEffect);
        return NULL;
    }
    assert(aux < AUX_MAX);
    // Validate that the application has a valid interface for the effect.  The interface must have
    // been exposed at object creation time or by DynamicInterface::AddInterface, and it also must
    // have been "gotten" with Object::GetInterface.
    unsigned MPH = AUX_to_MPH[aux];
    int index = MPH_to_OutputMix[MPH];
    if (0 > index) {
        SL_LOGE("EffectSend aux=%u MPH=%u", aux, MPH);
        return NULL;
    }
    unsigned mask = 1 << index;
    object_lock_shared(&outputMix->mObject);
    SLuint32 state = outputMix->mObject.mInterfaceStates[index];
    mask &= outputMix->mObject.mGottenMask;
    object_unlock_shared(&outputMix->mObject);
    switch (state) {
    case INTERFACE_EXPOSED:
    case INTERFACE_ADDED:
    case INTERFACE_SUSPENDED:
    case INTERFACE_SUSPENDING:
    case INTERFACE_RESUMING_1:
    case INTERFACE_RESUMING_2:
        if (mask) {
            return &this->mEnableLevels[aux];
        }
        SL_LOGE("EffectSend no GetInterface yet");
        break;
    default:
        SL_LOGE("EffectSend invalid interface state %lu", state);
        break;
    }
    return NULL;
}

#if defined(ANDROID) && !defined(USE_BACKPORT)
/** \brief This is a private function that translates an Android effect framework status code
 *  to the SL ES result code used in the EnableEffectSend() function of the SLEffectSendItf
 *  interface.
 */
static SLresult translateEnableFxSendError(android::status_t status) {
    switch (status) {
    case android::NO_ERROR:
        return SL_RESULT_SUCCESS;
    case android::INVALID_OPERATION:
    case android::BAD_VALUE:
    default:
        SL_LOGE("EffectSend status %u", status);
        return SL_RESULT_RESOURCE_ERROR;
    }
}
#endif


static SLresult IEffectSend_EnableEffectSend(SLEffectSendItf self,
    const void *pAuxEffect, SLboolean enable, SLmillibel initialLevel)
{
    SL_ENTER_INTERFACE

    if (!((SL_MILLIBEL_MIN <= initialLevel) && (initialLevel <= 0))) {
        result = SL_RESULT_PARAMETER_INVALID;
    } else {
        IEffectSend *this = (IEffectSend *) self;
        struct EnableLevel *enableLevel = getEnableLevel(this, pAuxEffect);
        if (NULL == enableLevel) {
            result = SL_RESULT_PARAMETER_INVALID;
        } else {
            interface_lock_exclusive(this);
            enableLevel->mEnable = SL_BOOLEAN_FALSE != enable; // normalize
            enableLevel->mSendLevel = initialLevel;
#if !defined(ANDROID) || defined(USE_BACKPORT)
            result = SL_RESULT_SUCCESS;
#else
            // TODO do not repeat querying of CAudioPlayer, done inside getEnableLevel()
            CAudioPlayer *ap = (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) ?
                    (CAudioPlayer *) this->mThis : NULL;
            // note that if this was a MIDI player, getEnableLevel would have returned NULL
            assert(NULL != ap);
            // check which effect the send is attached to, attach and set level
            COutputMix *outputMix = CAudioPlayer_GetOutputMix(ap);
            // the initial send level set here is the total energy on the aux bus,
            //  so it must take into account the player volume level
            if (pAuxEffect == &outputMix->mPresetReverb.mItf) {
                result = translateEnableFxSendError(android_fxSend_attach(ap, (bool) enable,
                    outputMix->mPresetReverb.mPresetReverbEffect,
                    initialLevel + ap->mVolume.mLevel));
            } else if (pAuxEffect == &outputMix->mEnvironmentalReverb.mItf) {
                result = translateEnableFxSendError(android_fxSend_attach(ap, (bool) enable,
                    outputMix->mEnvironmentalReverb.mEnvironmentalReverbEffect,
                    initialLevel + ap->mVolume.mLevel));
            } else {
                SL_LOGE("EffectSend unknown aux effect %p", pAuxEffect);
                result = SL_RESULT_PARAMETER_INVALID;
            }
#endif
            interface_unlock_exclusive(this);
        }
    }

    SL_LEAVE_INTERFACE
}


static SLresult IEffectSend_IsEnabled(SLEffectSendItf self,
    const void *pAuxEffect, SLboolean *pEnable)
{
    SL_ENTER_INTERFACE

    if (NULL == pEnable) {
        result = SL_RESULT_PARAMETER_INVALID;
    } else {
        IEffectSend *this = (IEffectSend *) self;
        struct EnableLevel *enableLevel = getEnableLevel(this, pAuxEffect);
        if (NULL == enableLevel) {
            *pEnable = SL_BOOLEAN_FALSE;
            result = SL_RESULT_PARAMETER_INVALID;
        } else {
            interface_lock_shared(this);
            SLboolean enable = enableLevel->mEnable;
            interface_unlock_shared(this);
            *pEnable = enable;
            result = SL_RESULT_SUCCESS;
        }
    }

    SL_LEAVE_INTERFACE
}


static SLresult IEffectSend_SetDirectLevel(SLEffectSendItf self, SLmillibel directLevel)
{
    SL_ENTER_INTERFACE

    if (!((SL_MILLIBEL_MIN <= directLevel) && (directLevel <= 0))) {
        result = SL_RESULT_PARAMETER_INVALID;
    } else {
        IEffectSend *this = (IEffectSend *) self;
        interface_lock_exclusive(this);
        CAudioPlayer *ap = (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) ?
                (CAudioPlayer *) this->mThis : NULL;
        if (NULL != ap) {
            SLmillibel oldDirectLevel = ap->mDirectLevel;
            if (oldDirectLevel != directLevel) {
                ap->mDirectLevel = directLevel;
#if defined(ANDROID)
                ap->mAmplFromDirectLevel = sles_to_android_amplification(directLevel);
                interface_unlock_exclusive_attributes(this, ATTR_GAIN);
#else
                interface_unlock_exclusive(this);
#endif
            } else {
                interface_unlock_exclusive(this);
            }
        } else {
            // MIDI player is silently not supported
            interface_unlock_exclusive(this);
        }
        result = SL_RESULT_SUCCESS;
    }

    SL_LEAVE_INTERFACE
}


static SLresult IEffectSend_GetDirectLevel(SLEffectSendItf self, SLmillibel *pDirectLevel)
{
    SL_ENTER_INTERFACE

    if (NULL == pDirectLevel) {
        result = SL_RESULT_PARAMETER_INVALID;
    } else {
        IEffectSend *this = (IEffectSend *) self;
        interface_lock_shared(this);
        CAudioPlayer *ap = (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) ?
                (CAudioPlayer *) this->mThis : NULL;
        if (NULL != ap) {
            *pDirectLevel = ap->mDirectLevel;
        } else {
            // MIDI player is silently not supported
            *pDirectLevel = 0;
        }
        interface_unlock_shared(this);
        result = SL_RESULT_SUCCESS;
    }

    SL_LEAVE_INTERFACE
}


static SLresult IEffectSend_SetSendLevel(SLEffectSendItf self, const void *pAuxEffect,
    SLmillibel sendLevel)
{
    SL_ENTER_INTERFACE

    if (!((SL_MILLIBEL_MIN <= sendLevel) && (sendLevel <= 0))) {
        result = SL_RESULT_PARAMETER_INVALID;
    } else {
        IEffectSend *this = (IEffectSend *) self;
        struct EnableLevel *enableLevel = getEnableLevel(this, pAuxEffect);
        if (NULL == enableLevel) {
            result = SL_RESULT_PARAMETER_INVALID;
        } else {
            result = SL_RESULT_SUCCESS;
            // EnableEffectSend is exclusive, so this has to be also
            interface_lock_exclusive(this);
            enableLevel->mSendLevel = sendLevel;
#if defined(ANDROID) && !defined(USE_BACKPORT)
            CAudioPlayer *ap = (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) ?
                    (CAudioPlayer *) this->mThis : NULL;
            if (NULL != ap) {
                // the send level set here is the total energy on the aux bus, so it must take
                // into account the player volume level
                result = android_fxSend_setSendLevel(ap, sendLevel + ap->mVolume.mLevel);
            }
#endif
            interface_unlock_exclusive(this);

        }
    }

    SL_LEAVE_INTERFACE
}


static SLresult IEffectSend_GetSendLevel(SLEffectSendItf self, const void *pAuxEffect,
    SLmillibel *pSendLevel)
{
    SL_ENTER_INTERFACE

    if (NULL == pSendLevel) {
        result = SL_RESULT_PARAMETER_INVALID;
    } else {
        IEffectSend *this = (IEffectSend *) self;
        struct EnableLevel *enableLevel = getEnableLevel(this, pAuxEffect);
        if (NULL == enableLevel) {
            result = SL_RESULT_PARAMETER_INVALID;
        } else {
            interface_lock_shared(this);
            SLmillibel sendLevel = enableLevel->mSendLevel;
            interface_unlock_shared(this);
            *pSendLevel = sendLevel;
            result = SL_RESULT_SUCCESS;
        }
    }

    SL_LEAVE_INTERFACE
}


static const struct SLEffectSendItf_ IEffectSend_Itf = {
    IEffectSend_EnableEffectSend,
    IEffectSend_IsEnabled,
    IEffectSend_SetDirectLevel,
    IEffectSend_GetDirectLevel,
    IEffectSend_SetSendLevel,
    IEffectSend_GetSendLevel
};

void IEffectSend_init(void *self)
{
    IEffectSend *this = (IEffectSend *) self;
    this->mItf = &IEffectSend_Itf;
    struct EnableLevel *enableLevel = this->mEnableLevels;
    unsigned aux;
    for (aux = 0; aux < AUX_MAX; ++aux, ++enableLevel) {
        enableLevel->mEnable = SL_BOOLEAN_FALSE;
        enableLevel->mSendLevel = SL_MILLIBEL_MIN;
    }
}
