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

#include <math.h>
#include <vector>

#include <gtest/gtest.h>

#include <audio_utils/channels.h>

// TODO: Make a common include file for helper functions.

template<typename T>
void checkMonotone(const T *ary, size_t size)
{
    for (size_t i = 1; i < size; ++i) {
        EXPECT_LT(ary[i-1], ary[i]);
    }
}

template<typename T>
void checkUnsignedMonotoneOrZero(const T *ary, size_t size)
{
    if (size == 0) return;

    T least = ary[0];
    for (size_t i = 1; i < size; ++i) {
        if (ary[i]) {
            EXPECT_LT(least, ary[i]);
            least = ary[i];
        }
    }
}

template<typename T>
void expectEq(const T &c1, const T &c2) {
    EXPECT_EQ(c1.size(), c2.size());
    EXPECT_EQ(0, memcmp(c1.data(), c2.data(), sizeof(c1[0]) * std::min(c1.size(), c2.size())));
}

TEST(audio_utils_channels, geometry_constexpr) {
    using namespace android::audio_utils::channels;
    // fails to compile if not const.
    constexpr size_t RIGHT_IDX = 1;  // bit position of AUDIO_CHANNEL_OUT_FRONT_RIGHT;
    static constexpr AUDIO_GEOMETRY_SIDE checkConstexprSide = sideFromChannelIdx(RIGHT_IDX);
    static constexpr AUDIO_GEOMETRY_HEIGHT checkConstexprHeight = heightFromChannelIdx(RIGHT_IDX);
    static constexpr AUDIO_GEOMETRY_DEPTH checkConstexprDepth = depthFromChannelIdx(RIGHT_IDX);
    (void) checkConstexprSide;
    (void) checkConstexprHeight;
    (void) checkConstexprDepth;

    static constexpr ssize_t leftIdx = pairIdxFromChannelIdx(RIGHT_IDX);
    ASSERT_EQ(0, leftIdx);
}

TEST(audio_utils_channels, geometry_range) {
    using namespace android::audio_utils::channels;
    for (size_t i = 0; i < FCC_26 + 2 /* sic */; ++i) {
        const AUDIO_GEOMETRY_SIDE side = sideFromChannelIdx(i);
        const AUDIO_GEOMETRY_HEIGHT height = heightFromChannelIdx(i);
        const AUDIO_GEOMETRY_DEPTH depth = depthFromChannelIdx(i);
        ASSERT_TRUE(side == AUDIO_GEOMETRY_SIDE_LEFT
                || side == AUDIO_GEOMETRY_SIDE_RIGHT
                || side == AUDIO_GEOMETRY_SIDE_CENTER);
        ASSERT_TRUE(height == AUDIO_GEOMETRY_HEIGHT_BOTTOM
                || height == AUDIO_GEOMETRY_HEIGHT_MIDDLE
                || height == AUDIO_GEOMETRY_HEIGHT_TOP);
        ASSERT_TRUE(depth == AUDIO_GEOMETRY_DEPTH_FRONT
                || depth == AUDIO_GEOMETRY_DEPTH_MIDDLE
                || depth == AUDIO_GEOMETRY_DEPTH_BACK);
    }
}

TEST(audio_utils_channels, array_lr_pair_matching) {
    using namespace android::audio_utils::channels;
    for (size_t i = 0; i < FCC_26; ++i) {
        const AUDIO_GEOMETRY_SIDE side = sideFromChannelIdx(i);
        const ssize_t pairIdx = pairIdxFromChannelIdx(i);
        switch (side) {
        case AUDIO_GEOMETRY_SIDE_LEFT:
        case AUDIO_GEOMETRY_SIDE_RIGHT: {
            ASSERT_GE(pairIdx, 0);
            ASSERT_LT(pairIdx, FCC_26);
            const AUDIO_GEOMETRY_SIDE pairSide = side == AUDIO_GEOMETRY_SIDE_LEFT
                    ? AUDIO_GEOMETRY_SIDE_RIGHT : AUDIO_GEOMETRY_SIDE_LEFT;
            ASSERT_EQ(pairSide, sideFromChannelIdx(pairIdx));
        } break;
        case AUDIO_GEOMETRY_SIDE_CENTER:
            ASSERT_EQ(-1, pairIdx);
            break;
        }
    }
}

TEST(audio_utils_channels, adjust_channels) {
    constexpr size_t size = 65536;
    std::vector<uint16_t> u16ref(size);
    std::vector<uint16_t> u16expand(size * 2);
    std::vector<uint16_t> u16ary(size);

    // reference buffer is monotonic.
    for (size_t i = 0; i < u16ref.size(); ++i) {
        u16ref[i] = i;
    }

    // expand channels from stereo to quad.
    adjust_channels(
            u16ref.data() /*in_buff*/,
            2 /*in_channels*/,
            u16expand.data() /*out_buff*/,
            4 /*out_channels*/,
            sizeof(u16ref[0]) /*sample_size_in_bytes*/,
            sizeof(u16ref[0]) * u16ref.size() /*num_in_bytes*/);

    // expanded buffer must increase (or be zero).
    checkUnsignedMonotoneOrZero(u16expand.data(), u16expand.size());

    // contract channels back to stereo.
    adjust_channels(
            u16expand.data() /*in_buff*/,
            4 /*in_channels*/,
            u16ary.data() /*out_buff*/,
            2 /*out_channels*/,
            sizeof(u16expand[0]) /*sample_size_in_bytes*/,
            sizeof(u16expand[0]) * u16expand.size() /*num_in_bytes*/);

    // contracted array must be identical to original.
    expectEq(u16ary, u16ref);
}

TEST(audio_utils_channels, adjust_selected_channels) {
    constexpr size_t size = 65536;
    std::vector<uint16_t> u16ref(size);
    std::vector<uint16_t> u16contract(size / 2);
    std::vector<uint16_t> u16ary(size);

    // reference buffer is monotonic.
    for (size_t i = 0; i < u16ref.size(); ++i) {
        u16ref[i] = i;
    }

    // contract from quad to stereo.
    adjust_selected_channels(
            u16ref.data() /*in_buff*/,
            4 /*in_channels*/,
            u16contract.data() /*out_buff*/,
            2 /*out_channels*/,
            sizeof(u16ref[0]) /*sample_size_in_bytes*/,
            sizeof(u16ref[0]) * u16ref.size() /*num_in_bytes*/);

    // contracted buffer must increase.
    checkMonotone(u16contract.data(), u16contract.size());

    // initialize channels 3 and 4 of final comparison array.
    for (size_t i = 0; i < u16ary.size() / 4; ++i) {
        u16ary[i * 4 + 2] = u16ref[i * 4 + 2];
        u16ary[i * 4 + 3] = u16ref[i * 4 + 3];
    }

    // expand stereo into channels 1 and 2 of quad comparison array.
    adjust_selected_channels(
            u16contract.data() /*in_buff*/,
            2 /*in_channels*/,
            u16ary.data() /*out_buff*/,
            4 /*out_channels*/,
            sizeof(u16contract[0]) /*sample_size_in_bytes*/,
            sizeof(u16contract[0]) * u16contract.size() /*num_in_bytes*/);

    // comparison array must be identical to original.
    expectEq(u16ary, u16ref);
}

TEST(audio_utils_channels, adjust_channels_non_destructive) {
    constexpr size_t size = 65536; /* arbitrary large multiple of 8 */
    std::vector<uint16_t> u16ref(size);
    std::vector<uint16_t> u16contracted(size);
    std::vector<uint16_t> u16expanded(size);
    std::vector<uint16_t> u16inout(size);

    // Reference buffer increases monotonically.
    // For second test, in/out buffer begins identical to ref.
    for (size_t i = 0; i < u16ref.size(); ++i) {
        u16ref[i] = i;
        u16inout[i] = i;
    }

    // *** First test: different in/out buffers ***

    // Contract from quad to stereo.
    adjust_channels_non_destructive(
            u16ref.data() /*in_buff*/,
            4 /*in_channels*/,
            u16contracted.data() /*out_buff*/,
            2 /*out_channels*/,
            sizeof(u16ref[0]) /*sample_size_in_bytes*/,
            sizeof(u16ref[0]) * u16ref.size() /*num_in_bytes*/);

    // Each half of contracted buffer should increase monotonically.
    checkMonotone(u16contracted.data(), u16contracted.size() / 2);
    checkMonotone(&u16contracted[u16contracted.size() / 2], u16contracted.size() / 2);

    // Expand stereo to quad
    adjust_channels_non_destructive(
            u16contracted.data() /*in_buff*/,
            2 /*in_channels*/,
            u16expanded.data() /*out_buff*/,
            4 /*out_channels*/,
            sizeof(u16contracted[0]) /*sample_size_in_bytes*/,
            sizeof(u16contracted[0]) * (u16contracted.size() / 2) /*num_in_bytes*/);

    // Comparison array must be identical to reference.
    expectEq(u16expanded, u16ref);

    // *** Second test: in_buff == out_buff ***

    // Contract from eight channels to stereo.
    adjust_channels_non_destructive(
            u16inout.data() /*in_buff*/,
            8 /*in_channels*/,
            u16inout.data() /*out_buff*/,
            2 /*out_channels*/,
            sizeof(u16inout[0]) /*sample_size_in_bytes*/,
            sizeof(u16inout[0]) * u16inout.size() /*num_in_bytes*/);

    // Each section [1/4][3/4] of contracted buffer should increase monotonically.
    checkMonotone(u16inout.data(), u16inout.size() / 4);
    checkMonotone(&u16inout[u16inout.size() / 4], (u16inout.size() * 3) / 4);

    // Expand stereo to eight channels.
    adjust_channels_non_destructive(
            u16inout.data() /*in_buff*/,
            2 /*in_channels*/,
            u16inout.data() /*out_buff*/,
            8 /*out_channels*/,
            sizeof(u16inout[0]) /*sample_size_in_bytes*/,
            sizeof(u16inout[0]) * (u16inout.size() / 4) /*num_in_bytes*/);

    // Comparison array must be identical to reference.
    expectEq(u16inout, u16ref);
}
