/*
 * 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.
 */

#define LOG_TAG "broadcastradio.vts"

#include <VtsHalHidlTargetTestBase.h>
#include <android-base/logging.h>
#include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h>
#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
#include <android/hardware/broadcastradio/1.1/ITuner.h>
#include <android/hardware/broadcastradio/1.1/ITunerCallback.h>
#include <android/hardware/broadcastradio/1.1/types.h>
#include <broadcastradio-utils-1x/Utils.h>
#include <broadcastradio-vts-utils/call-barrier.h>
#include <broadcastradio-vts-utils/environment-utils.h>
#include <broadcastradio-vts-utils/mock-timeout.h>
#include <broadcastradio-vts-utils/pointer-utils.h>
#include <cutils/native_handle.h>
#include <cutils/properties.h>
#include <gmock/gmock.h>
#include <hidl/HidlTransportSupport.h>
#include <utils/threads.h>

#include <chrono>

namespace android {
namespace hardware {
namespace broadcastradio {
namespace V1_1 {
namespace vts {

using namespace std::chrono_literals;

using testing::_;
using testing::AnyNumber;
using testing::ByMove;
using testing::DoAll;
using testing::Invoke;
using testing::SaveArg;

using broadcastradio::vts::CallBarrier;
using V1_0::BandConfig;
using V1_0::Class;
using V1_0::MetaData;
using V1_0::MetadataKey;
using V1_0::MetadataType;

using broadcastradio::vts::clearAndWait;
using broadcastradio::vts::BroadcastRadioHidlEnvironment;

static constexpr auto kConfigTimeout = 10s;
static constexpr auto kConnectModuleTimeout = 1s;
static constexpr auto kTuneTimeout = 30s;
static constexpr auto kEventPropagationTimeout = 1s;
static constexpr auto kFullScanTimeout = 1min;

static constexpr ProgramType kStandardProgramTypes[] = {
    ProgramType::AM,  ProgramType::FM,   ProgramType::AM_HD, ProgramType::FM_HD,
    ProgramType::DAB, ProgramType::DRMO, ProgramType::SXM};

static void printSkipped(std::string msg) {
    std::cout << "[  SKIPPED ] " << msg << std::endl;
}

struct TunerCallbackMock : public ITunerCallback {
    TunerCallbackMock() { EXPECT_CALL(*this, hardwareFailure()).Times(0); }

    MOCK_METHOD0(hardwareFailure, Return<void>());
    MOCK_TIMEOUT_METHOD2(configChange, Return<void>(Result, const BandConfig&));
    MOCK_METHOD2(tuneComplete, Return<void>(Result, const V1_0::ProgramInfo&));
    MOCK_TIMEOUT_METHOD2(tuneComplete_1_1, Return<void>(Result, const ProgramSelector&));
    MOCK_METHOD1(afSwitch, Return<void>(const V1_0::ProgramInfo&));
    MOCK_METHOD1(antennaStateChange, Return<void>(bool connected));
    MOCK_METHOD1(trafficAnnouncement, Return<void>(bool active));
    MOCK_METHOD1(emergencyAnnouncement, Return<void>(bool active));
    MOCK_METHOD3(newMetadata, Return<void>(uint32_t ch, uint32_t subCh, const hidl_vec<MetaData>&));
    MOCK_METHOD1(backgroundScanAvailable, Return<void>(bool));
    MOCK_TIMEOUT_METHOD1(backgroundScanComplete, Return<void>(ProgramListResult));
    MOCK_METHOD0(programListChanged, Return<void>());
    MOCK_TIMEOUT_METHOD1(currentProgramInfoChanged, Return<void>(const ProgramInfo&));
};

static BroadcastRadioHidlEnvironment<IBroadcastRadioFactory>* gEnv = nullptr;

class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase,
                              public ::testing::WithParamInterface<Class> {
   protected:
    virtual void SetUp() override;
    virtual void TearDown() override;

    bool openTuner();
    bool nextBand();
    bool getProgramList(std::function<void(const hidl_vec<ProgramInfo>& list)> cb);

    Class radioClass;
    bool skipped = false;

    sp<IBroadcastRadio> mRadioModule;
    sp<ITuner> mTuner;
    sp<TunerCallbackMock> mCallback = new TunerCallbackMock();

   private:
    const BandConfig& getBand(unsigned idx);

    unsigned currentBandIndex = 0;
    hidl_vec<BandConfig> mBands;
};

void BroadcastRadioHalTest::SetUp() {
    radioClass = GetParam();

    // lookup HIDL service
    auto factory =
        getService<IBroadcastRadioFactory>(gEnv->getServiceName<IBroadcastRadioFactory>());
    ASSERT_NE(nullptr, factory.get());

    // connect radio module
    Result connectResult;
    CallBarrier onConnect;
    factory->connectModule(radioClass, [&](Result ret, const sp<V1_0::IBroadcastRadio>& radio) {
        connectResult = ret;
        if (ret == Result::OK) mRadioModule = IBroadcastRadio::castFrom(radio);
        onConnect.call();
    });
    ASSERT_TRUE(onConnect.waitForCall(kConnectModuleTimeout));

    if (connectResult == Result::INVALID_ARGUMENTS) {
        printSkipped("This device class is not supported.");
        skipped = true;
        return;
    }
    ASSERT_EQ(connectResult, Result::OK);
    ASSERT_NE(nullptr, mRadioModule.get());

    // get module properties
    Properties prop11;
    auto& prop10 = prop11.base;
    auto propResult =
        mRadioModule->getProperties_1_1([&](const Properties& properties) { prop11 = properties; });

    ASSERT_TRUE(propResult.isOk());
    EXPECT_EQ(radioClass, prop10.classId);
    EXPECT_GT(prop10.numTuners, 0u);
    EXPECT_GT(prop11.supportedProgramTypes.size(), 0u);
    EXPECT_GT(prop11.supportedIdentifierTypes.size(), 0u);
    if (radioClass == Class::AM_FM) {
        EXPECT_GT(prop10.bands.size(), 0u);
    }
    mBands = prop10.bands;
}

void BroadcastRadioHalTest::TearDown() {
    mTuner.clear();
    mRadioModule.clear();
    clearAndWait(mCallback, 1s);
}

bool BroadcastRadioHalTest::openTuner() {
    EXPECT_EQ(nullptr, mTuner.get());

    if (radioClass == Class::AM_FM) {
        EXPECT_TIMEOUT_CALL(*mCallback, configChange, Result::OK, _);
    }

    Result halResult = Result::NOT_INITIALIZED;
    auto openCb = [&](Result result, const sp<V1_0::ITuner>& tuner) {
        halResult = result;
        if (result != Result::OK) return;
        mTuner = ITuner::castFrom(tuner);
    };
    currentBandIndex = 0;
    auto hidlResult = mRadioModule->openTuner(getBand(0), true, mCallback, openCb);

    EXPECT_TRUE(hidlResult.isOk());
    EXPECT_EQ(Result::OK, halResult);
    EXPECT_NE(nullptr, mTuner.get());
    if (radioClass == Class::AM_FM && mTuner != nullptr) {
        EXPECT_TIMEOUT_CALL_WAIT(*mCallback, configChange, kConfigTimeout);

        BandConfig halConfig;
        Result halResult = Result::NOT_INITIALIZED;
        mTuner->getConfiguration([&](Result result, const BandConfig& config) {
            halResult = result;
            halConfig = config;
        });
        EXPECT_EQ(Result::OK, halResult);
        EXPECT_TRUE(halConfig.antennaConnected);
    }

    EXPECT_NE(nullptr, mTuner.get());
    return nullptr != mTuner.get();
}

const BandConfig& BroadcastRadioHalTest::getBand(unsigned idx) {
    static const BandConfig dummyBandConfig = {};

    if (radioClass != Class::AM_FM) {
        ALOGD("Not AM/FM radio, returning dummy band config");
        return dummyBandConfig;
    }

    EXPECT_GT(mBands.size(), idx);
    if (mBands.size() <= idx) {
        ALOGD("Band index out of bound, returning dummy band config");
        return dummyBandConfig;
    }

    auto& band = mBands[idx];
    ALOGD("Returning %s band", toString(band.type).c_str());
    return band;
}

bool BroadcastRadioHalTest::nextBand() {
    if (currentBandIndex + 1 >= mBands.size()) return false;
    currentBandIndex++;

    BandConfig bandCb;
    EXPECT_TIMEOUT_CALL(*mCallback, configChange, Result::OK, _)
        .WillOnce(DoAll(SaveArg<1>(&bandCb), testing::Return(ByMove(Void()))));
    auto hidlResult = mTuner->setConfiguration(getBand(currentBandIndex));
    EXPECT_EQ(Result::OK, hidlResult);
    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, configChange, kConfigTimeout);
    EXPECT_EQ(getBand(currentBandIndex), bandCb);

    return true;
}

bool BroadcastRadioHalTest::getProgramList(
    std::function<void(const hidl_vec<ProgramInfo>& list)> cb) {
    ProgramListResult getListResult = ProgramListResult::NOT_INITIALIZED;
    bool isListEmpty = true;
    auto getListCb = [&](ProgramListResult result, const hidl_vec<ProgramInfo>& list) {
        ALOGD("getListCb(%s, ProgramInfo[%zu])", toString(result).c_str(), list.size());
        getListResult = result;
        if (result != ProgramListResult::OK) return;
        isListEmpty = (list.size() == 0);
        if (!isListEmpty) cb(list);
    };

    // first try...
    EXPECT_TIMEOUT_CALL(*mCallback, backgroundScanComplete, ProgramListResult::OK)
        .Times(AnyNumber());
    auto hidlResult = mTuner->getProgramList({}, getListCb);
    EXPECT_TRUE(hidlResult.isOk());
    if (!hidlResult.isOk()) return false;

    if (getListResult == ProgramListResult::NOT_STARTED) {
        auto result = mTuner->startBackgroundScan();
        EXPECT_EQ(ProgramListResult::OK, result);
        getListResult = ProgramListResult::NOT_READY;  // continue as in NOT_READY case
    }
    if (getListResult == ProgramListResult::NOT_READY) {
        EXPECT_TIMEOUT_CALL_WAIT(*mCallback, backgroundScanComplete, kFullScanTimeout);

        // second (last) try...
        hidlResult = mTuner->getProgramList({}, getListCb);
        EXPECT_TRUE(hidlResult.isOk());
        if (!hidlResult.isOk()) return false;
        EXPECT_EQ(ProgramListResult::OK, getListResult);
    }

    return !isListEmpty;
}

/**
 * Test IBroadcastRadio::openTuner() method called twice.
 *
 * Verifies that:
 *  - the openTuner method succeeds when called for the second time without
 *    deleting previous ITuner instance.
 *
 * This is a more strict requirement than in 1.0, where a second openTuner
 * might fail.
 */
TEST_P(BroadcastRadioHalTest, OpenTunerTwice) {
    if (skipped) return;

    ASSERT_TRUE(openTuner());

    auto secondTuner = mTuner;
    mTuner.clear();

    ASSERT_TRUE(openTuner());
}

/**
 * Test tuning to program list entry.
 *
 * Verifies that:
 *  - getProgramList either succeeds or returns NOT_STARTED/NOT_READY status;
 *  - if the program list is NOT_STARTED, startBackgroundScan makes it completed
 *    within a full scan timeout and the next getProgramList call succeeds;
 *  - if the program list is not empty, tuneByProgramSelector call succeeds;
 *  - getProgramInformation_1_1 returns the same selector as returned in tuneComplete_1_1 call.
 */
TEST_P(BroadcastRadioHalTest, TuneFromProgramList) {
    if (skipped) return;
    ASSERT_TRUE(openTuner());

    ProgramInfo firstProgram;
    bool foundAny = false;
    do {
        auto getCb = [&](const hidl_vec<ProgramInfo>& list) {
            // don't copy the whole list out, it might be heavy
            firstProgram = list[0];
        };
        if (getProgramList(getCb)) foundAny = true;
    } while (nextBand());
    if (HasFailure()) return;
    if (!foundAny) {
        printSkipped("Program list is empty.");
        return;
    }

    ProgramInfo infoCb;
    ProgramSelector selCb;
    EXPECT_CALL(*mCallback, tuneComplete(_, _)).Times(0);
    EXPECT_TIMEOUT_CALL(*mCallback, tuneComplete_1_1, Result::OK, _)
        .WillOnce(DoAll(SaveArg<1>(&selCb), testing::Return(ByMove(Void()))));
    EXPECT_TIMEOUT_CALL(*mCallback, currentProgramInfoChanged, _)
        .WillOnce(DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(Void()))));
    auto tuneResult = mTuner->tuneByProgramSelector(firstProgram.selector);
    ASSERT_EQ(Result::OK, tuneResult);
    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, tuneComplete_1_1, kTuneTimeout);
    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, currentProgramInfoChanged, kEventPropagationTimeout);
    EXPECT_EQ(firstProgram.selector.primaryId, selCb.primaryId);
    EXPECT_EQ(infoCb.selector, selCb);

    bool called = false;
    auto getResult = mTuner->getProgramInformation_1_1([&](Result result, ProgramInfo info) {
        called = true;
        EXPECT_EQ(Result::OK, result);
        EXPECT_EQ(selCb, info.selector);
    });
    ASSERT_TRUE(getResult.isOk());
    ASSERT_TRUE(called);
}

/**
 * Test that primary vendor identifier isn't used for standard program types.
 *
 * Verifies that:
 *  - tuneByProgramSelector fails when VENDORn_PRIMARY is set as a primary
 *    identifier for program types other than VENDORn.
 */
TEST_P(BroadcastRadioHalTest, TuneFailsForPrimaryVendor) {
    if (skipped) return;
    ASSERT_TRUE(openTuner());

    for (auto ptype : kStandardProgramTypes) {
        ALOGD("Checking %s...", toString(ptype).c_str());
        ProgramSelector sel = {};
        sel.programType = static_cast<uint32_t>(ptype);
        sel.primaryId.type = static_cast<uint32_t>(IdentifierType::VENDOR_PRIMARY_START);

        auto tuneResult = mTuner->tuneByProgramSelector(sel);
        ASSERT_NE(Result::OK, tuneResult);
    }
}

/**
 * Test that tune with unknown program type fails.
 *
 * Verifies that:
 *  - tuneByProgramSelector fails with INVALID_ARGUMENT when unknown program type is passed.
 */
TEST_P(BroadcastRadioHalTest, TuneFailsForUnknownProgram) {
    if (skipped) return;
    ASSERT_TRUE(openTuner());

    // Program type is 1-based, so 0 will be always invalid.
    ProgramSelector sel = {};
    auto tuneResult = mTuner->tuneByProgramSelector(sel);
    ASSERT_EQ(Result::INVALID_ARGUMENTS, tuneResult);
}

/**
 * Test cancelling announcement.
 *
 * Verifies that:
 *  - cancelAnnouncement succeeds either when there is an announcement or there is none.
 */
TEST_P(BroadcastRadioHalTest, CancelAnnouncement) {
    if (skipped) return;
    ASSERT_TRUE(openTuner());

    auto hidlResult = mTuner->cancelAnnouncement();
    EXPECT_EQ(Result::OK, hidlResult);
}

/**
 * Test getImage call with invalid image ID.
 *
 * Verifies that:
 * - getImage call handles argument 0 gracefully.
 */
TEST_P(BroadcastRadioHalTest, GetNoImage) {
    if (skipped) return;

    size_t len = 0;
    auto hidlResult =
        mRadioModule->getImage(0, [&](hidl_vec<uint8_t> rawImage) { len = rawImage.size(); });

    ASSERT_TRUE(hidlResult.isOk());
    ASSERT_EQ(0u, len);
}

/**
 * Test proper image format in metadata.
 *
 * Verifies that:
 * - all images in metadata are provided out-of-band (by id, not as a binary blob);
 * - images are available for getImage call.
 */
TEST_P(BroadcastRadioHalTest, OobImagesOnly) {
    if (skipped) return;
    ASSERT_TRUE(openTuner());

    std::vector<int> imageIds;

    do {
        auto getCb = [&](const hidl_vec<ProgramInfo>& list) {
            for (auto&& program : list) {
                for (auto&& entry : program.base.metadata) {
                    EXPECT_NE(MetadataType::RAW, entry.type);
                    if (entry.key != MetadataKey::ICON && entry.key != MetadataKey::ART) continue;
                    EXPECT_NE(0, entry.intValue);
                    EXPECT_EQ(0u, entry.rawValue.size());
                    if (entry.intValue != 0) imageIds.push_back(entry.intValue);
                }
            }
        };
        getProgramList(getCb);
    } while (nextBand());

    if (imageIds.size() == 0) {
        printSkipped("No images found");
        return;
    }

    for (auto id : imageIds) {
        ALOGD("Checking image %d", id);

        size_t len = 0;
        auto hidlResult =
            mRadioModule->getImage(id, [&](hidl_vec<uint8_t> rawImage) { len = rawImage.size(); });

        ASSERT_TRUE(hidlResult.isOk());
        ASSERT_GT(len, 0u);
    }
}

/**
 * Test AnalogForced switch.
 *
 * Verifies that:
 * - setAnalogForced results either with INVALID_STATE, or isAnalogForced replying the same.
 */
TEST_P(BroadcastRadioHalTest, AnalogForcedSwitch) {
    if (skipped) return;
    ASSERT_TRUE(openTuner());

    bool forced;
    Result halIsResult;
    auto isCb = [&](Result result, bool isForced) {
        halIsResult = result;
        forced = isForced;
    };

    // set analog mode
    auto setResult = mTuner->setAnalogForced(true);
    ASSERT_TRUE(setResult.isOk());
    if (Result::INVALID_STATE == setResult) {
        // if setter fails, getter should fail too - it means the switch is not supported at all
        auto isResult = mTuner->isAnalogForced(isCb);
        ASSERT_TRUE(isResult.isOk());
        EXPECT_EQ(Result::INVALID_STATE, halIsResult);
        return;
    }
    ASSERT_EQ(Result::OK, setResult);

    // check, if it's analog
    auto isResult = mTuner->isAnalogForced(isCb);
    ASSERT_TRUE(isResult.isOk());
    EXPECT_EQ(Result::OK, halIsResult);
    ASSERT_TRUE(forced);

    // set digital mode
    setResult = mTuner->setAnalogForced(false);
    ASSERT_EQ(Result::OK, setResult);

    // check, if it's digital
    isResult = mTuner->isAnalogForced(isCb);
    ASSERT_TRUE(isResult.isOk());
    EXPECT_EQ(Result::OK, halIsResult);
    ASSERT_FALSE(forced);
}

static void verifyIdentifier(const ProgramIdentifier& id) {
    EXPECT_NE(id.type, 0u);
    auto val = id.value;

    switch (static_cast<IdentifierType>(id.type)) {
        case IdentifierType::AMFM_FREQUENCY:
        case IdentifierType::DAB_FREQUENCY:
        case IdentifierType::DRMO_FREQUENCY:
            EXPECT_GT(val, 100u) << "Expected f > 100kHz";
            EXPECT_LT(val, 10000000u) << "Expected f < 10GHz";
            break;
        case IdentifierType::RDS_PI:
            EXPECT_GT(val, 0u);
            EXPECT_LE(val, 0xFFFFu) << "Expected 16bit id";
            break;
        case IdentifierType::HD_STATION_ID_EXT: {
            auto stationId = val & 0xFFFFFFFF;  // 32bit
            val >>= 32;
            auto subchannel = val & 0xF;  // 4bit
            val >>= 4;
            auto freq = val & 0x3FFFF;  // 18bit
            EXPECT_GT(stationId, 0u);
            EXPECT_LT(subchannel, 8u) << "Expected ch < 8";
            EXPECT_GT(freq, 100u) << "Expected f > 100kHz";
            EXPECT_LT(freq, 10000000u) << "Expected f < 10GHz";
            break;
        }
        case IdentifierType::HD_SUBCHANNEL:
            EXPECT_LT(val, 8u) << "Expected ch < 8";
            break;
        case IdentifierType::DAB_SIDECC: {
            auto sid = val & 0xFFFF;  // 16bit
            val >>= 16;
            auto ecc = val & 0xFF;  // 8bit
            EXPECT_NE(sid, 0u);
            EXPECT_GE(ecc, 0xA0u) << "Invalid ECC, see ETSI TS 101 756 V2.1.1";
            EXPECT_LE(ecc, 0xF6u) << "Invalid ECC, see ETSI TS 101 756 V2.1.1";
            break;
        }
        case IdentifierType::DAB_ENSEMBLE:
            EXPECT_GT(val, 0u);
            EXPECT_LE(val, 0xFFFFu) << "Expected 16bit id";
            break;
        case IdentifierType::DAB_SCID:
            EXPECT_GT(val, 0xFu) << "Expected 12bit SCId (not 4bit SCIdS)";
            EXPECT_LE(val, 0xFFFu) << "Expected 12bit id";
            break;
        case IdentifierType::DRMO_SERVICE_ID:
            EXPECT_GT(val, 0u);
            EXPECT_LE(val, 0xFFFFFFu) << "Expected 24bit id";
            break;
        case IdentifierType::DRMO_MODULATION:
            EXPECT_GE(val, static_cast<uint32_t>(Modulation::AM));
            EXPECT_LE(val, static_cast<uint32_t>(Modulation::FM));
            break;
        case IdentifierType::SXM_SERVICE_ID:
            EXPECT_GT(val, 0u);
            EXPECT_LE(val, 0xFFFFFFFFu) << "Expected 32bit id";
            break;
        case IdentifierType::SXM_CHANNEL:
            EXPECT_LT(val, 1000u);
            break;
        case IdentifierType::VENDOR_PRIMARY_START:
        case IdentifierType::VENDOR_PRIMARY_END:
            // skip
            break;
    }
}

/**
 * Test ProgramIdentifier format.
 *
 * Verifies that:
 * - values of ProgramIdentifier match their definitions at IdentifierType.
 */
TEST_P(BroadcastRadioHalTest, VerifyIdentifiersFormat) {
    if (skipped) return;
    ASSERT_TRUE(openTuner());

    do {
        auto getCb = [&](const hidl_vec<ProgramInfo>& list) {
            for (auto&& program : list) {
                verifyIdentifier(program.selector.primaryId);
                for (auto&& id : program.selector.secondaryIds) {
                    verifyIdentifier(id);
                }
            }
        };
        getProgramList(getCb);
    } while (nextBand());
}

INSTANTIATE_TEST_CASE_P(BroadcastRadioHalTestCases, BroadcastRadioHalTest,
                        ::testing::Values(Class::AM_FM, Class::SAT, Class::DT));

}  // namespace vts
}  // namespace V1_1
}  // namespace broadcastradio
}  // namespace hardware
}  // namespace android

int main(int argc, char** argv) {
    using android::hardware::broadcastradio::V1_1::vts::gEnv;
    using android::hardware::broadcastradio::V1_1::IBroadcastRadioFactory;
    using android::hardware::broadcastradio::vts::BroadcastRadioHidlEnvironment;
    gEnv = new BroadcastRadioHidlEnvironment<IBroadcastRadioFactory>;
    ::testing::AddGlobalTestEnvironment(gEnv);
    ::testing::InitGoogleTest(&argc, argv);
    gEnv->init(&argc, argv);
    int status = RUN_ALL_TESTS();
    ALOGI("Test result = %d", status);
    return status;
}