/*
 * Copyright (C) 2017-2018 Intel Corporation
 *
 * SPDX-License-Identifier: MIT
 *
 */

#include "runtime/helpers/options.h"
#include "runtime/os_interface/os_time.h"
#include "runtime/os_interface/os_interface.h"
#include "unit_tests/fixtures/device_instrumentation_fixture.h"
#include "unit_tests/os_interface/mock_performance_counters.h"
#include "unit_tests/mocks/mock_device.h"
#include "gtest/gtest.h"

using namespace OCLRT;

struct PerformanceCountersDeviceTest : public PerformanceCountersDeviceFixture,
                                       public DeviceInstrumentationFixture,
                                       public ::testing::Test {
    void SetUp() override {
        PerformanceCountersDeviceFixture::SetUp();
        PerfCounterFlags::resetPerfCountersFlags();
    }
    void TearDown() override {
        PerformanceCountersDeviceFixture::TearDown();
    }
};

TEST_F(PerformanceCountersDeviceTest, createDeviceWithPerformanceCounters) {
    DeviceInstrumentationFixture::SetUp(true);
    EXPECT_NE(nullptr, device->getPerformanceCounters());
    EXPECT_EQ(1, PerfCounterFlags::initalizeCalled);
}

TEST_F(PerformanceCountersDeviceTest, createDeviceWithoutPerformanceCounters) {
    DeviceInstrumentationFixture::SetUp(false);
    EXPECT_EQ(nullptr, device->getPerformanceCounters());
    EXPECT_EQ(0, PerfCounterFlags::initalizeCalled);
}

struct PerformanceCountersTest : public PerformanceCountersFixture,
                                 public ::testing::Test {
  public:
    void SetUp() override {
        PerformanceCountersFixture::SetUp();
    }

    void TearDown() override {
        PerformanceCountersFixture::TearDown();
    }
};

TEST_F(PerformanceCountersTest, createPerformanceCounters) {
    auto performanceCounters = PerformanceCounters::create(osTimeBase.get());
    EXPECT_NE(nullptr, performanceCounters);
    EXPECT_NE(nullptr, performanceCounters.get());

    EXPECT_FALSE(performanceCounters->isAvailable());
}

TEST_F(PerformanceCountersTest, shutdownPerformanceCountersWithoutEnabling) {
    createPerfCounters();
    performanceCountersBase->shutdown();
    EXPECT_EQ(0, PerfCounterFlags::escHwMetricsCalled);
    EXPECT_EQ(0, PerfCounterFlags::autoSamplingFuncCalled);
    EXPECT_EQ(0, PerfCounterFlags::autoSamplingStopped);
}

TEST_F(PerformanceCountersTest, givenPerformanceCountersWhenCreatedThenAllValuesProperlyInitialized) {
    createPerfCounters();

    EXPECT_EQ(IGFX_UNKNOWN_CORE, performanceCountersBase->getGfxFamily());

    EXPECT_EQ(nullptr, performanceCountersBase->getCbData().hDevice);

    EXPECT_FALSE(performanceCountersBase->getHwMetricsEnabled());
    EXPECT_FALSE(performanceCountersBase->getUseMIRPC());
    EXPECT_EQ(nullptr, performanceCountersBase->getPAutoSamplingInterface());
    EXPECT_EQ(0u, performanceCountersBase->getCpuRawTimestamp());
    EXPECT_EQ(1u, performanceCountersBase->getReportId());
    EXPECT_FALSE(performanceCountersBase->getAvailable());
    EXPECT_EQ(0u, performanceCountersBase->getPerfCountersReferenceNumber());
}

TEST_F(PerformanceCountersTest, givenPerformanceCountersNotEnabledWhenGetPmRegsCfgIsCalledThenReturnsNullptr) {
    createPerfCounters();
    auto pConfig = performanceCountersBase->getPmRegsCfg(0);
    EXPECT_EQ(nullptr, pConfig);
}

TEST_F(PerformanceCountersTest, givenPerformanceCountersEnabledWithPassingGetPmRegsCfgFuncWhenGetPmRegsCfgIsCalledThenReturnsPtr) {
    createPerfCounters();
    performanceCountersBase->setGetPmRegsCfgFunc(getPmRegsCfgPassing);
    performanceCountersBase->initialize(platformDevices[0]);
    performanceCountersBase->enable();
    auto pConfig = performanceCountersBase->getPmRegsCfg(0);
    EXPECT_EQ(1, PerfCounterFlags::getPmRegsCfgCalled);
    EXPECT_NE(nullptr, pConfig);
    delete pConfig;
    performanceCountersBase->shutdown();
}

TEST_F(PerformanceCountersTest, givenPerformanceCountersEnabledWithFailingGetPmRegsCfgFuncWhenGetPmRegsCfgIsCalledThenReturnsNullptr) {
    createPerfCounters();
    performanceCountersBase->setGetPmRegsCfgFunc(getPmRegsCfgFailing);
    performanceCountersBase->initialize(platformDevices[0]);
    performanceCountersBase->enable();
    auto pConfig = performanceCountersBase->getPmRegsCfg(0);
    EXPECT_EQ(1, PerfCounterFlags::getPmRegsCfgCalled);
    EXPECT_EQ(nullptr, pConfig);
    performanceCountersBase->shutdown();
}

TEST_F(PerformanceCountersTest, givenPerfCountersWhenGetReportIdsIsCalledManyTimesThenReturnNextNumbers) {
    int retVal;
    createPerfCounters();
    for (int i = 0; i < 4095; i++) {
        retVal = performanceCountersBase->getReportId();
        EXPECT_EQ((i + 1), retVal);
    }
    retVal = performanceCountersBase->getReportId();
    EXPECT_EQ(0, retVal);
}

TEST_F(PerformanceCountersTest, sendPerfConfigPositiveReadRegsTag) {
    cl_int ret;
    cl_uint count = 2;
    cl_uint offsets[2];
    cl_uint values[2];
    offsets[0] = INSTR_READ_REGS_CFG_TAG - 1;
    createPerfCounters();
    performanceCountersBase->initialize(platformDevices[0]);
    ret = performanceCountersBase->sendPerfConfiguration(count, offsets, values);
    EXPECT_EQ(CL_SUCCESS, ret);
    EXPECT_EQ(1, PerfCounterFlags::setPmRegsCfgCalled);
    EXPECT_EQ(0, PerfCounterFlags::sendReadRegsCfgCalled);
}

TEST_F(PerformanceCountersTest, sendPerfConfigPositiveReadRegs) {
    cl_int ret;
    cl_uint count = 2;
    cl_uint offsets[2];
    cl_uint values[2];
    offsets[0] = INSTR_READ_REGS_CFG_TAG;
    createPerfCounters();
    performanceCountersBase->initialize(platformDevices[0]);
    ret = performanceCountersBase->sendPerfConfiguration(count, offsets, values);
    EXPECT_EQ(CL_SUCCESS, ret);
    EXPECT_EQ(0, PerfCounterFlags::setPmRegsCfgCalled);
    EXPECT_EQ(1, PerfCounterFlags::sendReadRegsCfgCalled);
}

TEST_F(PerformanceCountersTest, negativeInvalidVal) {
    cl_int ret;
    cl_uint count = 2;
    cl_uint offsets[2];
    cl_uint values[2];
    createPerfCounters();
    performanceCountersBase->initialize(platformDevices[0]);
    ret = performanceCountersBase->sendPerfConfiguration(0, offsets, values);
    EXPECT_EQ(CL_INVALID_VALUE, ret);
    ret = performanceCountersBase->sendPerfConfiguration(count, nullptr, values);
    EXPECT_EQ(CL_INVALID_VALUE, ret);
    ret = performanceCountersBase->sendPerfConfiguration(count, offsets, nullptr);
    EXPECT_EQ(CL_INVALID_VALUE, ret);

    EXPECT_EQ(0, PerfCounterFlags::setPmRegsCfgCalled);
    EXPECT_EQ(0, PerfCounterFlags::sendReadRegsCfgCalled);
}

TEST_F(PerformanceCountersTest, negativeNoMatchingData) {
    cl_int ret;
    cl_uint count = 1;
    cl_uint offsets[2];
    cl_uint values[2];
    offsets[0] = INSTR_READ_REGS_CFG_TAG;
    createPerfCounters();
    performanceCountersBase->initialize(platformDevices[0]);
    ret = performanceCountersBase->sendPerfConfiguration(count, offsets, values);
    EXPECT_EQ(CL_PROFILING_INFO_NOT_AVAILABLE, ret);

    EXPECT_EQ(0, PerfCounterFlags::setPmRegsCfgCalled);
    EXPECT_EQ(0, PerfCounterFlags::sendReadRegsCfgCalled);
}

TEST_F(PerformanceCountersTest, sendPerfConfigNegativeFailingReadRegsTag) {
    cl_int ret;
    cl_uint count = 1;
    cl_uint offsets[1];
    cl_uint values[1];
    offsets[0] = INSTR_READ_REGS_CFG_TAG - 1;
    createPerfCounters();
    performanceCountersBase->setSetPmRegsCfgFunc(setPmRegsCfgFuncFailing);
    performanceCountersBase->initialize(platformDevices[0]);
    ret = performanceCountersBase->sendPerfConfiguration(count, offsets, values);
    EXPECT_EQ(CL_PROFILING_INFO_NOT_AVAILABLE, ret);

    EXPECT_EQ(1, PerfCounterFlags::setPmRegsCfgCalled);
    EXPECT_EQ(0, PerfCounterFlags::sendReadRegsCfgCalled);
}

TEST_F(PerformanceCountersTest, sendPerfConfigNegativeFailingReadRegs) {
    cl_int ret;
    cl_uint count = 2;
    cl_uint offsets[2];
    cl_uint values[2];
    offsets[0] = INSTR_READ_REGS_CFG_TAG;
    createPerfCounters();
    performanceCountersBase->setSendReadRegsCfgFunc(sendReadRegsCfgFuncFailing);
    performanceCountersBase->initialize(platformDevices[0]);
    ret = performanceCountersBase->sendPerfConfiguration(count, offsets, values);
    EXPECT_EQ(CL_PROFILING_INFO_NOT_AVAILABLE, ret);

    EXPECT_EQ(0, PerfCounterFlags::setPmRegsCfgCalled);
    EXPECT_EQ(1, PerfCounterFlags::sendReadRegsCfgCalled);
}

struct PerformanceCountersGetConfigTest : public PerformanceCountersTest,
                                          public ::testing::WithParamInterface<std::tuple<
                                              unsigned int /*given configuration type*/,
                                              bool /*expected value*/>> {
    void SetUp() override {
        PerformanceCountersTest::SetUp();
    }

    void TearDown() override {
        PerformanceCountersTest::TearDown();
    }

    unsigned int givenConfigType;
    bool expectedValue;
};

TEST_P(PerformanceCountersGetConfigTest, givenPerformanceCountersEnabledWithPassingGetPmRegsCfgFuncWhenGetPmRegsCfgIsCalledWithProperConfigurationTypeThenReturnsPtrOtherwiseNullptr) {
    std::tie(givenConfigType, expectedValue) = GetParam();
    createPerfCounters();
    performanceCountersBase->setGetPmRegsCfgFunc(getPmRegsCfgPassing);
    performanceCountersBase->initialize(platformDevices[0]);
    performanceCountersBase->enable();
    auto pConfig = performanceCountersBase->getPmRegsCfg(givenConfigType);
    if (expectedValue) {
        EXPECT_EQ(1, PerfCounterFlags::getPmRegsCfgCalled);
        EXPECT_NE(nullptr, pConfig);
        delete pConfig;
    } else {
        EXPECT_EQ(0, PerfCounterFlags::getPmRegsCfgCalled);
        EXPECT_EQ(nullptr, pConfig);
    }
    performanceCountersBase->shutdown();
}

std::tuple<unsigned int, bool> configsForGetPmRegsCfgTests[] = {
    std::make_tuple(GTDI_CONFIGURATION_SET_DYNAMIC, true),
    std::make_tuple(GTDI_CONFIGURATION_SET_1, true),
    std::make_tuple(GTDI_CONFIGURATION_SET_2, true),
    std::make_tuple(GTDI_CONFIGURATION_SET_3, true),
    std::make_tuple(GTDI_CONFIGURATION_SET_4, false),
    std::make_tuple(GTDI_CONFIGURATION_SET_COUNT, false),
    std::make_tuple(GTDI_CONFIGURATION_SET_MAX, false)};

INSTANTIATE_TEST_CASE_P(
    PerfCountersTests,
    PerformanceCountersGetConfigTest,
    testing::ValuesIn(configsForGetPmRegsCfgTests));

struct PerformanceCountersProcessEventTest : public PerformanceCountersTest,
                                             public ::testing::WithParamInterface<bool> {

    void SetUp() override {
        PerformanceCountersTest::SetUp();
        createPerfCounters();
        performanceCountersBase->initialize(platformDevices[0]);
        privateData.reset(new HwPerfCounter());
        eventComplete = true;
        outputParamSize = 0;
        inputParam.reset(new GTDI_QUERY());
        inputParamSize = sizeof(GTDI_QUERY);
    }
    void TearDown() override {
        performanceCountersBase->shutdown();
        PerformanceCountersTest::TearDown();
    }
    std::unique_ptr<GTDI_QUERY> inputParam;
    size_t inputParamSize;
    size_t outputParamSize;
    std::unique_ptr<HwPerfCounter> privateData;
    bool eventComplete;
};

TEST_P(PerformanceCountersProcessEventTest, givenNullptrInputParamWhenProcessEventPerfCountersIsCalledThenReturnsFalse) {
    eventComplete = GetParam();
    auto retVal = performanceCountersBase->processEventReport(inputParamSize, nullptr, &outputParamSize, privateData.get(),
                                                              nullptr, eventComplete);

    EXPECT_FALSE(retVal);
    EXPECT_EQ(0, PerfCounterFlags::getPerfCountersQueryDataCalled);
}

TEST_P(PerformanceCountersProcessEventTest, givenCorrectInputParamWhenProcessEventPerfCountersIsCalledAndEventIsCompletedThenReturnsTrue) {
    eventComplete = GetParam();
    EXPECT_EQ(0ull, outputParamSize);
    auto retVal = performanceCountersBase->processEventReport(inputParamSize, inputParam.get(), &outputParamSize, privateData.get(),
                                                              nullptr, eventComplete);

    if (eventComplete) {
        EXPECT_TRUE(retVal);
        EXPECT_EQ(1, PerfCounterFlags::getPerfCountersQueryDataCalled);
        EXPECT_EQ(outputParamSize, inputParamSize);
    } else {
        EXPECT_FALSE(retVal);
        EXPECT_EQ(0, PerfCounterFlags::getPerfCountersQueryDataCalled);
        EXPECT_EQ(inputParamSize, outputParamSize);
    }
}

TEST_F(PerformanceCountersProcessEventTest, givenInvalidInputParamSizeWhenProcessEventPerfCountersIsCalledThenReturnsFalse) {
    EXPECT_EQ(0ull, outputParamSize);
    auto retVal = performanceCountersBase->processEventReport(inputParamSize - 1, inputParam.get(), &outputParamSize, privateData.get(),
                                                              nullptr, eventComplete);

    EXPECT_FALSE(retVal);
    EXPECT_EQ(0, PerfCounterFlags::getPerfCountersQueryDataCalled);
    EXPECT_EQ(outputParamSize, inputParamSize);
}

TEST_F(PerformanceCountersProcessEventTest, givenNullptrOutputParamSizeWhenProcessEventPerfCountersIsCalledThenDoesNotReturnsOutputSize) {
    EXPECT_EQ(0ull, outputParamSize);
    auto retVal = performanceCountersBase->processEventReport(inputParamSize, inputParam.get(), nullptr, privateData.get(),
                                                              nullptr, eventComplete);

    EXPECT_TRUE(retVal);
    EXPECT_EQ(1, PerfCounterFlags::getPerfCountersQueryDataCalled);
    EXPECT_EQ(0ull, outputParamSize);
}

TEST_F(PerformanceCountersProcessEventTest, givenNullptrInputZeroSizeWhenProcessEventPerfCountersIsCalledThenQueryProperSize) {
    EXPECT_EQ(0ull, outputParamSize);
    auto retVal = performanceCountersBase->processEventReport(0, nullptr, &outputParamSize, privateData.get(),
                                                              nullptr, eventComplete);

    EXPECT_TRUE(retVal);
    EXPECT_EQ(0, PerfCounterFlags::getPerfCountersQueryDataCalled);
    EXPECT_EQ(inputParamSize, outputParamSize);
}

TEST_F(PerformanceCountersProcessEventTest, givenNullptrInputZeroSizeAndNullptrOutputSizeWhenProcessEventPerfCountersIsCalledThenReturnFalse) {
    EXPECT_EQ(0ull, outputParamSize);
    auto retVal = performanceCountersBase->processEventReport(0, nullptr, nullptr, privateData.get(),
                                                              nullptr, eventComplete);

    EXPECT_FALSE(retVal);
    EXPECT_EQ(0, PerfCounterFlags::getPerfCountersQueryDataCalled);
    EXPECT_EQ(0ull, outputParamSize);
}

INSTANTIATE_TEST_CASE_P(
    PerfCountersTests,
    PerformanceCountersProcessEventTest,
    testing::Bool());

struct PerformanceCountersSendConfigCommandPointersTest : public PerformanceCountersTest,
                                                          public ::testing::WithParamInterface<std::tuple<bool /*pConfig is nullptr*/,
                                                                                                          bool /*pLastConfigHandle is nullptr */,
                                                                                                          bool /*pLastConfigPending is nullptr*/>> {
    void SetUp() override {
        PerformanceCountersTest::SetUp();
        createPerfCounters();
        performanceCountersBase->initialize(platformDevices[0]);
        std::tie(pConfigIsNullptr, pLastConfigHandleIsNullptr, pLastConfigPendingIsNullptr) = GetParam();
    }

    void TearDown() override {
        performanceCountersBase->shutdown();
        PerformanceCountersTest::TearDown();
    }
    bool pConfigIsNullptr;
    bool pLastConfigHandleIsNullptr;
    bool pLastConfigPendingIsNullptr;
    InstrPmRegsCfg config;
    uint32_t lastConfigHandle;
    bool lastConfigPending;
};

TEST_P(PerformanceCountersSendConfigCommandPointersTest, givenPerformanceCountersWithPassingFuncsWhenSendPmRegsCfgCommandsIsCalledWithAnyNullptrThenReturnsFalseOtherwiseTrue) {
    InstrPmRegsCfg *pConfig = nullptr;
    uint32_t *pLastConfigHandle = nullptr;
    bool *pLastConfigPending = nullptr;

    if (!pConfigIsNullptr) {
        pConfig = &config;
    }
    if (!pLastConfigHandleIsNullptr) {
        pLastConfigHandle = &lastConfigHandle;
    }
    if (!pLastConfigPendingIsNullptr) {
        lastConfigPending = false;
        pLastConfigPending = &lastConfigPending;
    }

    auto retVal = performanceCountersBase->sendPmRegsCfgCommands(pConfig, pLastConfigHandle, pLastConfigPending);
    if (pConfigIsNullptr || pLastConfigHandleIsNullptr || pLastConfigPendingIsNullptr) {
        EXPECT_FALSE(retVal);
        EXPECT_EQ(0, PerfCounterFlags::checkPmRegsCfgCalled);
        EXPECT_EQ(0, PerfCounterFlags::loadPmRegsCfgCalled);
    } else {
        EXPECT_TRUE(retVal);
        EXPECT_TRUE(lastConfigPending);
        EXPECT_EQ(1, PerfCounterFlags::checkPmRegsCfgCalled);
        EXPECT_EQ(1, PerfCounterFlags::loadPmRegsCfgCalled);
    }
}

INSTANTIATE_TEST_CASE_P(
    PerfCountersTests,
    PerformanceCountersSendConfigCommandPointersTest,
    testing::Combine(
        testing::Bool(),
        testing::Bool(),
        testing::Bool()));

struct PerformanceCountersSendConfigCommandFuncPointersTest : public PerformanceCountersTest,
                                                              public ::testing::WithParamInterface<std::tuple<bool /*check config func returns true*/,
                                                                                                              bool /*load config func returns true*/>> {
    void SetUp() override {

        PerformanceCountersTest::SetUp();
        createPerfCounters();
        std::tie(checkConfigFuncPassing, loadConfigFuncPassing) = GetParam();
        if (checkConfigFuncPassing) {
            performanceCountersBase->setCheckPmRegsCfgFunc(checkPmRegsCfgPassing);
        } else {
            performanceCountersBase->setCheckPmRegsCfgFunc(checkPmRegsCfgFailing);
        }
        if (loadConfigFuncPassing) {
            performanceCountersBase->setLoadPmRegsCfgFunc(loadPmRegsCfgPassing);
        } else {
            performanceCountersBase->setLoadPmRegsCfgFunc(loadPmRegsCfgFailing);
        }
        lastConfigPending = false;
        performanceCountersBase->initialize(platformDevices[0]);
    }

    void TearDown() override {
        performanceCountersBase->shutdown();
        PerformanceCountersTest::TearDown();
    }
    bool checkConfigFuncPassing;
    bool loadConfigFuncPassing;
    InstrPmRegsCfg config;
    uint32_t lastConfigHandle;
    bool lastConfigPending;
};

TEST_P(PerformanceCountersSendConfigCommandFuncPointersTest, givenPerformanceCountersSendPmRegsCfgCommandsFuncWhenAllLoadConfigFuncAndCheckConfigFuncAndGetPerfmonConfigReturnTrueThenReturnsTrueOtherwiseFalse) {
    auto retVal = performanceCountersBase->sendPmRegsCfgCommands(&config, &lastConfigHandle, &lastConfigPending);
    EXPECT_EQ(1, PerfCounterFlags::checkPmRegsCfgCalled);
    if (checkConfigFuncPassing) {
        EXPECT_EQ(1, PerfCounterFlags::loadPmRegsCfgCalled);
        if (loadConfigFuncPassing) {
            EXPECT_TRUE(retVal);
            EXPECT_TRUE(lastConfigPending);
        } else {
            EXPECT_FALSE(retVal);
            EXPECT_EQ(1, PerfCounterFlags::loadPmRegsCfgCalled);
        }
    } else {
        EXPECT_FALSE(retVal);
        EXPECT_EQ(0, PerfCounterFlags::loadPmRegsCfgCalled);
    }
}

INSTANTIATE_TEST_CASE_P(
    PerfCountersTests,
    PerformanceCountersSendConfigCommandFuncPointersTest,
    testing::Combine(
        testing::Bool(),
        testing::Bool()));
