/*
 * Copyright 2018 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_NDEBUG 0
#define LOG_TAG "audio_utils_timestampverifier_tests"

#include <audio_utils/TimestampVerifier.h>

#include <stdio.h>
#include <gtest/gtest.h>

// Ensure that all TimestampVerifier mutators are really constexpr and free from
// nasty system calls (in case called from a SCHED_FIFO thread).
static constexpr auto makeVerifier(
        size_t N, uint32_t sampleRate, size_t errors, size_t discontinuities) {
    android::TimestampVerifier<int64_t, int64_t> tv;

    int64_t f = 0;
    int64_t t = 0;
    for (size_t i = 0; i < N; ++i) {
        tv.add(f, t, sampleRate);
        f += sampleRate;
        t += (int64_t)1e9;
    }
    for (size_t i = 0; i < discontinuities; ++i) {
        tv.discontinuity(tv.DISCONTINUITY_MODE_CONTINUOUS);
    }
    for (size_t i = 0; i < errors; ++i) {
        tv.error();
    }
    return tv;
}

TEST(TimestampVerifier, sanity)
{
    constexpr android::TimestampVerifier<int64_t, int64_t> tv;

    // The timestamp verifier must be embeddable in a memcpy structure just like pod.
    // We use is_trivially_copyable and is_trivially_destructible for this test.
    static_assert(std::is_trivially_copyable<decltype(tv)>::value,
        "TimestampVerifier must be trivially copyable");
    static_assert(std::is_trivially_destructible<decltype(tv)>::value,
        "TimestampVerifier must be trivially destructible");

    constexpr android::audio_utils::Statistics<double> s = tv.getJitterMs();

    EXPECT_EQ(std::numeric_limits<double>::infinity(), s.getMin());
    EXPECT_EQ(-std::numeric_limits<double>::infinity(), s.getMax());

    constexpr int64_t frames[] { 0, 48000 };
    constexpr int64_t timeNs[] { 0, 1000000000 };
    constexpr android::TimestampVerifier<int64_t, int64_t> tv2(frames, timeNs, 48000);
    EXPECT_EQ(0., tv2.getJitterMs().getMax());
    EXPECT_EQ(0., tv2.getJitterMs().getMin());
    EXPECT_EQ(0., tv2.getJitterMs().getMean());
    EXPECT_EQ(1, tv2.getJitterMs().getN());

    // We should get a perfect straight line estimate as there is no noise.
    double a, b, r2;
    tv2.estimateSampleRate(a, b, r2);
    EXPECT_EQ(0., a);
    EXPECT_EQ(48000., b);
    EXPECT_NEAR(1., r2, std::numeric_limits<double>::epsilon());

    constexpr android::TimestampVerifier<int64_t, int64_t> tv3 =
            makeVerifier(8 /* N */, 48000 /* sampleRate */, 10 /* errors */, 10 /* disc */);
    EXPECT_EQ(8, tv3.getN());
    EXPECT_EQ(10, tv3.getErrors());
    EXPECT_EQ(1, tv3.getDiscontinuities());  // consecutive discontinuities read as 1.
    EXPECT_EQ(0., tv3.getJitterMs().getMax());
    EXPECT_EQ(0., tv3.getJitterMs().getMin());
    EXPECT_EQ(0., tv3.getJitterMs().getMean());

    constexpr auto first = tv3.getFirstTimestamp();
    constexpr auto last = tv3.getLastTimestamp();

    EXPECT_EQ(0, first.mFrames);
    EXPECT_EQ(0, first.mTimeNs);
    EXPECT_EQ(48000 * (8 - 1), last.mFrames);
    EXPECT_EQ((int64_t)1e9 * (8 - 1), last.mTimeNs);
    EXPECT_EQ((uint32_t)48000, tv3.getSampleRate());
    EXPECT_EQ(0, tv3.getColds());

    tv3.estimateSampleRate(a, b, r2);
    EXPECT_EQ(0., a);
    EXPECT_EQ(48000., b);
    EXPECT_NEAR(1., r2, std::numeric_limits<double>::epsilon());
}

TEST(TimestampVerifier, discontinuity_zero)
{
    android::TimestampVerifier<int64_t, int64_t> tv;

    // Add timestamps advancing at normal rate over 2 seconds
    tv.add(0, 0, 48000);
    tv.add(48000, 1000000000, 48000);
    tv.add(96000, 2000000000, 48000);

    // Raise (mode zero) discontinuity at "3 seconds"
    tv.discontinuity(tv.DISCONTINUITY_MODE_ZERO);
    // Add timestamp where frame count has reset to zero (and not advancing)
    tv.add(0, 3000000000, 48000);

    // The last corrected timestamp after discontinuity (mode zero) should be zeroed
    EXPECT_EQ(0., tv.getLastCorrectedTimestamp().mFrames);
    EXPECT_EQ(3000000000., tv.getLastCorrectedTimestamp().mTimeNs);

    // Add timestamp where frame count has not advanced from zero, but time has advanced 100 ms more
    tv.add(0, 3100000000, 48000);

    // The last corrected frame should be the raw timestamp if not advancing at normal rate
    EXPECT_EQ(0., tv.getLastCorrectedTimestamp().mFrames);
    EXPECT_EQ(3100000000., tv.getLastCorrectedTimestamp().mTimeNs);

    // Add imperfect normal advancing timestamps
    tv.add(48000*0.9, 4100000000*1.1, 48000);
    tv.add(96000*1.1, 5100000000*0.9, 48000);

    // Last corrected timestamp frame count should not be raw (or zero) as timestamps are now
    // advancing at a (imperfect) normal rate (but the time should, as implementation uses frame
    // rather than time correction).
    EXPECT_NE(0, tv.getLastCorrectedTimestamp().mFrames);
    EXPECT_NE(96000*1.1, tv.getLastCorrectedTimestamp().mFrames);
    EXPECT_EQ(5100000000*0.9, tv.getLastCorrectedTimestamp().mTimeNs);
}