/*
 * Copyright (C) 2015 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.
 */

#define LOG_TAG "APM::AudioPort"
//#define LOG_NDEBUG 0
#include "TypeConverter.h"
#include "AudioPort.h"
#include "HwModule.h"
#include "AudioGain.h"
#include <policy.h>
#include <cutils/atomic.h>

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif

namespace android {

int32_t volatile AudioPort::mNextUniqueId = 1;

// --- AudioPort class implementation
void AudioPort::attach(const sp<HwModule>& module)
{
    mModule = module;
}

// Note that is a different namespace than AudioFlinger unique IDs
audio_port_handle_t AudioPort::getNextUniqueId()
{
    return static_cast<audio_port_handle_t>(android_atomic_inc(&mNextUniqueId));
}

audio_module_handle_t AudioPort::getModuleHandle() const
{
    if (mModule == 0) {
        return AUDIO_MODULE_HANDLE_NONE;
    }
    return mModule->mHandle;
}

uint32_t AudioPort::getModuleVersionMajor() const
{
    if (mModule == 0) {
        return 0;
    }
    return mModule->getHalVersionMajor();
}

const char *AudioPort::getModuleName() const
{
    if (mModule == 0) {
        return "invalid module";
    }
    return mModule->getName();
}

void AudioPort::toAudioPort(struct audio_port *port) const
{
    // TODO: update this function once audio_port structure reflects the new profile definition.
    // For compatibility reason: flatening the AudioProfile into audio_port structure.
    SortedVector<audio_format_t> flatenedFormats;
    SampleRateVector flatenedRates;
    ChannelsVector flatenedChannels;
    for (size_t profileIndex = 0; profileIndex < mProfiles.size(); profileIndex++) {
        if (mProfiles[profileIndex]->isValid()) {
            audio_format_t formatToExport = mProfiles[profileIndex]->getFormat();
            const SampleRateVector &ratesToExport = mProfiles[profileIndex]->getSampleRates();
            const ChannelsVector &channelsToExport = mProfiles[profileIndex]->getChannels();

            if (flatenedFormats.indexOf(formatToExport) < 0) {
                flatenedFormats.add(formatToExport);
            }
            for (size_t rateIndex = 0; rateIndex < ratesToExport.size(); rateIndex++) {
                uint32_t rate = ratesToExport[rateIndex];
                if (flatenedRates.indexOf(rate) < 0) {
                    flatenedRates.add(rate);
                }
            }
            for (size_t chanIndex = 0; chanIndex < channelsToExport.size(); chanIndex++) {
                audio_channel_mask_t channels = channelsToExport[chanIndex];
                if (flatenedChannels.indexOf(channels) < 0) {
                    flatenedChannels.add(channels);
                }
            }
            if (flatenedRates.size() > AUDIO_PORT_MAX_SAMPLING_RATES ||
                    flatenedChannels.size() > AUDIO_PORT_MAX_CHANNEL_MASKS ||
                    flatenedFormats.size() > AUDIO_PORT_MAX_FORMATS) {
                ALOGE("%s: bailing out: cannot export profiles to port config", __FUNCTION__);
                return;
            }
        }
    }
    port->role = mRole;
    port->type = mType;
    strlcpy(port->name, mName, AUDIO_PORT_MAX_NAME_LEN);
    port->num_sample_rates = flatenedRates.size();
    port->num_channel_masks = flatenedChannels.size();
    port->num_formats = flatenedFormats.size();
    for (size_t i = 0; i < flatenedRates.size(); i++) {
        port->sample_rates[i] = flatenedRates[i];
    }
    for (size_t i = 0; i < flatenedChannels.size(); i++) {
        port->channel_masks[i] = flatenedChannels[i];
    }
    for (size_t i = 0; i < flatenedFormats.size(); i++) {
        port->formats[i] = flatenedFormats[i];
    }

    ALOGV("AudioPort::toAudioPort() num gains %zu", mGains.size());

    uint32_t i;
    for (i = 0; i < mGains.size() && i < AUDIO_PORT_MAX_GAINS; i++) {
        port->gains[i] = mGains[i]->getGain();
    }
    port->num_gains = i;
}

void AudioPort::importAudioPort(const sp<AudioPort>& port)
{
    size_t indexToImport;
    for (indexToImport = 0; indexToImport < port->mProfiles.size(); indexToImport++) {
        const sp<AudioProfile> &profileToImport = port->mProfiles[indexToImport];
        if (profileToImport->isValid()) {
            // Import only valid port, i.e. valid format, non empty rates and channels masks
            bool hasSameProfile = false;
            for (size_t profileIndex = 0; profileIndex < mProfiles.size(); profileIndex++) {
                if (*mProfiles[profileIndex] == *profileToImport) {
                    // never import a profile twice
                    hasSameProfile = true;
                    break;
                }
            }
            if (hasSameProfile) { // never import a same profile twice
                continue;
            }
            addAudioProfile(profileToImport);
        }
    }
}

void AudioPort::pickSamplingRate(uint32_t &pickedRate,const SampleRateVector &samplingRates) const
{
    pickedRate = 0;
    // For direct outputs, pick minimum sampling rate: this helps ensuring that the
    // channel count / sampling rate combination chosen will be supported by the connected
    // sink
    if (isDirectOutput()) {
        uint32_t samplingRate = UINT_MAX;
        for (size_t i = 0; i < samplingRates.size(); i ++) {
            if ((samplingRates[i] < samplingRate) && (samplingRates[i] > 0)) {
                samplingRate = samplingRates[i];
            }
        }
        pickedRate = (samplingRate == UINT_MAX) ? 0 : samplingRate;
    } else {
        uint32_t maxRate = SAMPLE_RATE_HZ_MAX;

        // For mixed output and inputs, use max mixer sampling rates. Do not
        // limit sampling rate otherwise
        // For inputs, also see checkCompatibleSamplingRate().
        if (mType != AUDIO_PORT_TYPE_MIX) {
            maxRate = UINT_MAX;
        }
        // TODO: should mSamplingRates[] be ordered in terms of our preference
        // and we return the first (and hence most preferred) match?  This is of concern if
        // we want to choose 96kHz over 192kHz for USB driver stability or resource constraints.
        for (size_t i = 0; i < samplingRates.size(); i ++) {
            if ((samplingRates[i] > pickedRate) && (samplingRates[i] <= maxRate)) {
                pickedRate = samplingRates[i];
            }
        }
    }
}

void AudioPort::pickChannelMask(audio_channel_mask_t &pickedChannelMask,
                                const ChannelsVector &channelMasks) const
{
    pickedChannelMask = AUDIO_CHANNEL_NONE;
    // For direct outputs, pick minimum channel count: this helps ensuring that the
    // channel count / sampling rate combination chosen will be supported by the connected
    // sink
    if (isDirectOutput()) {
        uint32_t channelCount = UINT_MAX;
        for (size_t i = 0; i < channelMasks.size(); i ++) {
            uint32_t cnlCount;
            if (useInputChannelMask()) {
                cnlCount = audio_channel_count_from_in_mask(channelMasks[i]);
            } else {
                cnlCount = audio_channel_count_from_out_mask(channelMasks[i]);
            }
            if ((cnlCount < channelCount) && (cnlCount > 0)) {
                pickedChannelMask = channelMasks[i];
                channelCount = cnlCount;
            }
        }
    } else {
        uint32_t channelCount = 0;
        uint32_t maxCount = MAX_MIXER_CHANNEL_COUNT;

        // For mixed output and inputs, use max mixer channel count. Do not
        // limit channel count otherwise
        if (mType != AUDIO_PORT_TYPE_MIX) {
            maxCount = UINT_MAX;
        }
        for (size_t i = 0; i < channelMasks.size(); i ++) {
            uint32_t cnlCount;
            if (useInputChannelMask()) {
                cnlCount = audio_channel_count_from_in_mask(channelMasks[i]);
            } else {
                cnlCount = audio_channel_count_from_out_mask(channelMasks[i]);
            }
            if ((cnlCount > channelCount) && (cnlCount <= maxCount)) {
                pickedChannelMask = channelMasks[i];
                channelCount = cnlCount;
            }
        }
    }
}

/* format in order of increasing preference */
const audio_format_t AudioPort::sPcmFormatCompareTable[] = {
        AUDIO_FORMAT_DEFAULT,
        AUDIO_FORMAT_PCM_16_BIT,
        AUDIO_FORMAT_PCM_8_24_BIT,
        AUDIO_FORMAT_PCM_24_BIT_PACKED,
        AUDIO_FORMAT_PCM_32_BIT,
        AUDIO_FORMAT_PCM_FLOAT,
};

int AudioPort::compareFormats(audio_format_t format1, audio_format_t format2)
{
    // NOTE: AUDIO_FORMAT_INVALID is also considered not PCM and will be compared equal to any
    // compressed format and better than any PCM format. This is by design of pickFormat()
    if (!audio_is_linear_pcm(format1)) {
        if (!audio_is_linear_pcm(format2)) {
            return 0;
        }
        return 1;
    }
    if (!audio_is_linear_pcm(format2)) {
        return -1;
    }

    int index1 = -1, index2 = -1;
    for (size_t i = 0;
            (i < ARRAY_SIZE(sPcmFormatCompareTable)) && ((index1 == -1) || (index2 == -1));
            i ++) {
        if (sPcmFormatCompareTable[i] == format1) {
            index1 = i;
        }
        if (sPcmFormatCompareTable[i] == format2) {
            index2 = i;
        }
    }
    // format1 not found => index1 < 0 => format2 > format1
    // format2 not found => index2 < 0 => format2 < format1
    return index1 - index2;
}

bool AudioPort::isBetterFormatMatch(audio_format_t newFormat,
                                    audio_format_t currentFormat,
                                    audio_format_t targetFormat)
{
    if (newFormat == currentFormat) {
        return false;
    }
    if (currentFormat == AUDIO_FORMAT_INVALID) {
        return true;
    }
    if (newFormat == targetFormat) {
        return true;
    }
    int currentDiffBytes = (int)audio_bytes_per_sample(targetFormat) -
            audio_bytes_per_sample(currentFormat);
    int newDiffBytes = (int)audio_bytes_per_sample(targetFormat) -
            audio_bytes_per_sample(newFormat);

    if (abs(newDiffBytes) < abs(currentDiffBytes)) {
        return true;
    } else if (abs(newDiffBytes) == abs(currentDiffBytes)) {
        return (newDiffBytes >= 0);
    }
    return false;
}

void AudioPort::pickAudioProfile(uint32_t &samplingRate,
                                 audio_channel_mask_t &channelMask,
                                 audio_format_t &format) const
{
    format = AUDIO_FORMAT_DEFAULT;
    samplingRate = 0;
    channelMask = AUDIO_CHANNEL_NONE;

    // special case for uninitialized dynamic profile
    if (!mProfiles.hasValidProfile()) {
        return;
    }
    audio_format_t bestFormat = sPcmFormatCompareTable[ARRAY_SIZE(sPcmFormatCompareTable) - 1];
    // For mixed output and inputs, use best mixer output format.
    // Do not limit format otherwise
    if ((mType != AUDIO_PORT_TYPE_MIX) || isDirectOutput()) {
        bestFormat = AUDIO_FORMAT_INVALID;
    }

    for (size_t i = 0; i < mProfiles.size(); i ++) {
        if (!mProfiles[i]->isValid()) {
            continue;
        }
        audio_format_t formatToCompare = mProfiles[i]->getFormat();
        if ((compareFormats(formatToCompare, format) > 0) &&
                (compareFormats(formatToCompare, bestFormat) <= 0)) {
            uint32_t pickedSamplingRate = 0;
            audio_channel_mask_t pickedChannelMask = AUDIO_CHANNEL_NONE;
            pickChannelMask(pickedChannelMask, mProfiles[i]->getChannels());
            pickSamplingRate(pickedSamplingRate, mProfiles[i]->getSampleRates());

            if (formatToCompare != AUDIO_FORMAT_DEFAULT && pickedChannelMask != AUDIO_CHANNEL_NONE
                    && pickedSamplingRate != 0) {
                format = formatToCompare;
                channelMask = pickedChannelMask;
                samplingRate = pickedSamplingRate;
                // TODO: shall we return on the first one or still trying to pick a better Profile?
            }
        }
    }
    ALOGV("%s Port[nm:%s] profile rate=%d, format=%d, channels=%d", __FUNCTION__, mName.string(),
          samplingRate, channelMask, format);
}

status_t AudioPort::checkGain(const struct audio_gain_config *gainConfig, int index) const
{
    if (index < 0 || (size_t)index >= mGains.size()) {
        return BAD_VALUE;
    }
    return mGains[index]->checkConfig(gainConfig);
}

void AudioPort::dump(int fd, int spaces, bool verbose) const
{
    const size_t SIZE = 256;
    char buffer[SIZE];
    String8 result;

    if (!mName.isEmpty()) {
        snprintf(buffer, SIZE, "%*s- name: %s\n", spaces, "", mName.string());
        result.append(buffer);
        write(fd, result.string(), result.size());
    }
    if (verbose) {
        mProfiles.dump(fd, spaces);

        if (mGains.size() != 0) {
            snprintf(buffer, SIZE, "%*s- gains:\n", spaces, "");
            result = buffer;
            write(fd, result.string(), result.size());
            for (size_t i = 0; i < mGains.size(); i++) {
                mGains[i]->dump(fd, spaces + 2, i);
            }
        }
    }
}

void AudioPort::log(const char* indent) const
{
    ALOGI("%s Port[nm:%s, type:%d, role:%d]", indent, mName.string(), mType, mRole);
}

// --- AudioPortConfig class implementation

AudioPortConfig::AudioPortConfig()
{
    mSamplingRate = 0;
    mChannelMask = AUDIO_CHANNEL_NONE;
    mFormat = AUDIO_FORMAT_INVALID;
    mGain.index = -1;
}

status_t AudioPortConfig::applyAudioPortConfig(const struct audio_port_config *config,
                                               struct audio_port_config *backupConfig)
{
    struct audio_port_config localBackupConfig;
    status_t status = NO_ERROR;

    localBackupConfig.config_mask = config->config_mask;
    toAudioPortConfig(&localBackupConfig);

    sp<AudioPort> audioport = getAudioPort();
    if (audioport == 0) {
        status = NO_INIT;
        goto exit;
    }
    status = audioport->checkExactAudioProfile(config->sample_rate,
                                               config->channel_mask,
                                               config->format);
    if (status != NO_ERROR) {
        goto exit;
    }
    if (config->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE) {
        mSamplingRate = config->sample_rate;
    }
    if (config->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK) {
        mChannelMask = config->channel_mask;
    }
    if (config->config_mask & AUDIO_PORT_CONFIG_FORMAT) {
        mFormat = config->format;
    }
    if (config->config_mask & AUDIO_PORT_CONFIG_GAIN) {
        status = audioport->checkGain(&config->gain, config->gain.index);
        if (status != NO_ERROR) {
            goto exit;
        }
        mGain = config->gain;
    }

exit:
    if (status != NO_ERROR) {
        applyAudioPortConfig(&localBackupConfig);
    }
    if (backupConfig != NULL) {
        *backupConfig = localBackupConfig;
    }
    return status;
}

void AudioPortConfig::toAudioPortConfig(struct audio_port_config *dstConfig,
                                        const struct audio_port_config *srcConfig) const
{
    if (dstConfig->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE) {
        dstConfig->sample_rate = mSamplingRate;
        if ((srcConfig != NULL) && (srcConfig->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE)) {
            dstConfig->sample_rate = srcConfig->sample_rate;
        }
    } else {
        dstConfig->sample_rate = 0;
    }
    if (dstConfig->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK) {
        dstConfig->channel_mask = mChannelMask;
        if ((srcConfig != NULL) && (srcConfig->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK)) {
            dstConfig->channel_mask = srcConfig->channel_mask;
        }
    } else {
        dstConfig->channel_mask = AUDIO_CHANNEL_NONE;
    }
    if (dstConfig->config_mask & AUDIO_PORT_CONFIG_FORMAT) {
        dstConfig->format = mFormat;
        if ((srcConfig != NULL) && (srcConfig->config_mask & AUDIO_PORT_CONFIG_FORMAT)) {
            dstConfig->format = srcConfig->format;
        }
    } else {
        dstConfig->format = AUDIO_FORMAT_INVALID;
    }
    sp<AudioPort> audioport = getAudioPort();
    if ((dstConfig->config_mask & AUDIO_PORT_CONFIG_GAIN) && audioport != NULL) {
        dstConfig->gain = mGain;
        if ((srcConfig != NULL) && (srcConfig->config_mask & AUDIO_PORT_CONFIG_GAIN)
                && audioport->checkGain(&srcConfig->gain, srcConfig->gain.index) == OK) {
            dstConfig->gain = srcConfig->gain;
        }
    } else {
        dstConfig->gain.index = -1;
    }
    if (dstConfig->gain.index != -1) {
        dstConfig->config_mask |= AUDIO_PORT_CONFIG_GAIN;
    } else {
        dstConfig->config_mask &= ~AUDIO_PORT_CONFIG_GAIN;
    }
}

}; // namespace android
