/*
 * Copyright (c) 2021, RPTU Kaiserslautern-Landau
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors:
 *    Derek Christ
 */

#include <DRAMSys/config/DRAMSysConfiguration.h>
#include <DRAMSys/util/json.h>

#include <fstream>
#include <gtest/gtest.h>
#include <iostream>
#include <unordered_map>

using namespace DRAMSys::Config;

class ConfigurationTest : public ::testing::Test
{
protected:
    ConfigurationTest() :
        memSpec(createMemSpec()),
        tracePlayer(createTracePlayer()),
        traceGeneratorOneState(createTraceGeneratorOneState()),
        traceGeneratorMultipleStates(createTraceGeneratorMultipleStates()),
        traceHammer(createTraceHammer())
    {
    }

    static DRAMSys::Config::MemSpec createMemSpec();

    static DRAMSys::Config::TracePlayer createTracePlayer();
    static DRAMSys::Config::TrafficGenerator createTraceGeneratorOneState();
    static DRAMSys::Config::TrafficGeneratorStateMachine createTraceGeneratorMultipleStates();
    static DRAMSys::Config::RowHammer createTraceHammer();

    DRAMSys::Config::AddressMapping addressMapping{
        {{0, 1}},
        {{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}},
        {{16}},
        {{13, 14, 15}},
        {{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}},
        {{33}},
        std::nullopt,
        std::nullopt,
        std::nullopt};

    DRAMSys::Config::McConfig mcConfig{PagePolicyType::Open,
                                       SchedulerType::FrFcfs,
                                       0,
                                       0,
                                       SchedulerBufferType::Bankwise,
                                       8,
                                       CmdMuxType::Oldest,
                                       RespQueueType::Fifo,
                                       RefreshPolicyType::AllBank,
                                       0,
                                       0,
                                       PowerDownPolicyType::NoPowerDown,
                                       ArbiterType::Simple,
                                       128,
                                       std::nullopt,
                                       std::nullopt,
                                       std::nullopt,
                                       std::nullopt,
                                       std::nullopt,
                                       std::nullopt,
                                       std::nullopt,
                                       std::nullopt,
                                       std::nullopt};

    DRAMSys::Config::SimConfig simConfig{0,
                                         false,
                                         true,
                                         false,
                                         false,
                                         {"error.csv"},
                                         42,
                                         false,
                                         {"ddr5"},
                                         true,
                                         DRAMSys::Config::StoreModeType::NoStorage,
                                         false,
                                         false,
                                         1000};

    DRAMSys::Config::MemSpec memSpec;

    DRAMSys::Config::TracePlayer tracePlayer;
    DRAMSys::Config::TrafficGenerator traceGeneratorOneState;
    DRAMSys::Config::TrafficGeneratorStateMachine traceGeneratorMultipleStates;
    DRAMSys::Config::RowHammer traceHammer;
    std::vector<DRAMSys::Config::Initiator> traceSetup{
        {tracePlayer, traceGeneratorOneState, traceGeneratorMultipleStates, traceHammer}};

    DRAMSys::Config::Configuration configuration{
        addressMapping, mcConfig, memSpec, simConfig, "std::string_simulationId", traceSetup};
};

DRAMSys::Config::MemSpec ConfigurationTest::createMemSpec()
{
    MemArchitectureSpecType memArchitectureSpec{{{"burstLength", 16},
                                                 {"dataRate", 2},
                                                 {"nbrOfBankGroups", 8},
                                                 {"nbrOfBanks", 16},
                                                 {"nbrOfColumns", 2048},
                                                 {"nbrOfRanks", 1},
                                                 {"nbrOfDIMMRanks", 1},
                                                 {"nbrOfPhysicalRanks", 1},
                                                 {"nbrOfLogicalRanks", 1},
                                                 {"nbrOfRows", 65536},
                                                 {"width", 4},
                                                 {"nbrOfDevices", 8},
                                                 {"nbrOfChannels", 2},
                                                 {"cmdMode", 1},
                                                 {"refMode", 1},
                                                 {"RAAIMT", 32},
                                                 {"RAAMMT", 96},
                                                 {"RAADEC", 1}}};

    MemTimingSpecType memTimingSpec{{{
        {"RCD", 22},        {"PPD", 2},          {"RP", 22},           {"RAS", 52},
        {"RL", 22},         {"RTP", 12},         {"RPRE", 1},          {"RPST", 0},
        {"RDDQS", 0},       {"WL", 20},          {"WPRE", 2},          {"WPST", 0},
        {"WR", 48},         {"CCD_L_slr", 8},    {"CCD_L_WR_slr", 32}, {"CCD_L_WR2_slr", 16},
        {"CCD_S_slr", 8},   {"CCD_S_WR_slr", 8}, {"CCD_dlr", 0},       {"CCD_WR_dlr", 0},
        {"CCD_WR_dpr", 0},  {"RRD_L_slr", 8},    {"RRD_S_slr", 8},     {"RRD_dlr", 0},
        {"FAW_slr", 32},    {"FAW_dlr", 0},      {"WTR_L", 16},        {"WTR_S", 4},
        {"RFC1_slr", 312},  {"RFC2_slr", 208},   {"RFC1_dlr", 0},      {"RFC2_dlr", 0},
        {"RFC1_dpr", 0},    {"RFC2_dpr", 0},     {"RFCsb_slr", 184},   {"RFCsb_dlr", 0},
        {"REFI1", 6240},    {"REFI2", 3120},     {"REFISB", 1560},     {"REFSBRD_slr", 48},
        {"REFSBRD_dlr", 0}, {"RTRS", 2},         {"CPDED", 8},         {"PD", 12},
        {"XP", 12},         {"ACTPDEN", 2},      {"PRPDEN", 2},        {"REFPDEN", 2},
        {"clkMhz", 1600},
    }}};

    return {memArchitectureSpec, "JEDEC_2x8x2Gbx4_DDR5-3200A", "DDR5", memTimingSpec, {}};
}

DRAMSys::Config::TracePlayer ConfigurationTest::createTracePlayer()
{
    DRAMSys::Config::TracePlayer player;
    player.clkMhz = 100;
    player.name = "mytrace.stl";

    return player;
}

DRAMSys::Config::TrafficGenerator ConfigurationTest::createTraceGeneratorOneState()
{
    DRAMSys::Config::TrafficGenerator gen;
    gen.clkMhz = 100;
    gen.name = "MyTestGen";

    gen.numRequests = 1000;
    gen.rwRatio = 0.5;
    gen.addressDistribution = DRAMSys::Config::AddressDistribution::Random;
    gen.addressIncrement = {};
    gen.minAddress = {};
    gen.maxAddress = {};

    return gen;
}

DRAMSys::Config::TrafficGeneratorStateMachine
ConfigurationTest::createTraceGeneratorMultipleStates()
{
    DRAMSys::Config::TrafficGeneratorStateMachine gen;

    gen.clkMhz = 100;
    gen.name = "MyTestGen";
    gen.maxPendingReadRequests = 8;

    DRAMSys::Config::TrafficGeneratorActiveState state0;
    state0.numRequests = 1000;
    state0.rwRatio = 0.5;
    state0.addressDistribution = DRAMSys::Config::AddressDistribution::Sequential;
    state0.addressIncrement = 256;
    state0.minAddress = {};
    state0.maxAddress = 1024;
    state0.id = 0;

    DRAMSys::Config::TrafficGeneratorActiveState state1;
    state1.numRequests = 100;
    state1.rwRatio = 0.75;
    state1.addressDistribution = DRAMSys::Config::AddressDistribution::Sequential;
    state1.addressIncrement = 512;
    state1.minAddress = 1024;
    state1.maxAddress = 2048;
    state1.id = 1;

    gen.states.push_back(state0);
    gen.states.push_back(state1);

    DRAMSys::Config::TrafficGeneratorStateTransition transistion0{0, 1, 1.0};

    gen.transitions.push_back(transistion0);

    return gen;
}

DRAMSys::Config::RowHammer ConfigurationTest::createTraceHammer()
{
    DRAMSys::Config::RowHammer hammer;

    hammer.clkMhz = 100;
    hammer.name = "MyTestHammer";
    hammer.numRequests = 4000;
    hammer.rowIncrement = 2097152;

    return hammer;
}

TEST_F(ConfigurationTest, ToJson)
{
    json_t config_json;
    config_json["simulation"] = configuration;

    std::ifstream file("reference.json");
    ASSERT_TRUE(file.is_open());

    json_t reference_json = json_t::parse(file);
    std::cout << reference_json.dump(4) << std::endl;
    EXPECT_EQ(config_json, reference_json);
}

TEST(Configuration, FromToJson)
{
    std::ifstream file("reference.json");
    ASSERT_TRUE(file.is_open());

    json_t reference_json = json_t::parse(file);
    DRAMSys::Config::Configuration reference_configuration =
        reference_json["simulation"].get<DRAMSys::Config::Configuration>();

    json_t new_json;
    new_json["simulation"] = reference_configuration;

    EXPECT_EQ(new_json, reference_json);
}

TEST_F(ConfigurationTest, DumpConfiguration)
{
    // This test does not test anything. It just dumps the configuration to stdout for manual
    // inspection.

    json_t json;
    json["simulation"] = configuration;

    std::cout << json.dump(4) << std::endl;
}

TEST(Configuration, ResourceDirectory)
{
    // Test should not throw exceptions
    Configuration config = from_path("resources/ddr5-example.json", "resources");
}

TEST(Configuration, FromPath)
{
    // Test should not throw exceptions
    Configuration config = from_path("reference.json");
}

TEST(RefreshPolicyType, BackwardsCompatibility)
{
    // Deserializing
    EXPECT_EQ(json_t("Rankwise").get<RefreshPolicyType>(), RefreshPolicyType::AllBank);
    EXPECT_EQ(json_t("Bankwise").get<RefreshPolicyType>(), RefreshPolicyType::PerBank);
    EXPECT_EQ(json_t("Groupwise").get<RefreshPolicyType>(), RefreshPolicyType::SameBank);

    // Serializing
    EXPECT_EQ(json_t(RefreshPolicyType::AllBank).get<std::string>(), "AllBank");
    EXPECT_EQ(json_t(RefreshPolicyType::PerBank).get<std::string>(), "PerBank");
    EXPECT_EQ(json_t(RefreshPolicyType::SameBank).get<std::string>(), "SameBank");
}

TEST_F(ConfigurationTest, SimConfig)
{
    std::string_view simconfig_string = R"(
        {
            "simconfig": {
                    "AddressOffset": 0,
                    "CheckTLM2Protocol": false,
                    "DatabaseRecording": true,
                    "Debug": false,
                    "EnableWindowing": false,
                    "ErrorCSVFile": "error.csv",
                    "ErrorChipSeed": 42,
                    "PowerAnalysis": false,
                    "SimulationName": "ddr5",
                    "SimulationProgressBar": true,
                    "StoreMode": "NoStorage",
                    "ThermalSimulation": false,
                    "UseMalloc": false,
                    "WindowSize": 1000
            }
        }
    )";

    json_t simconfig_reference = json_t::parse(simconfig_string);
    json_t simconfig_test;
    simconfig_test[SimConfig::KEY] = simConfig;

    EXPECT_EQ(simconfig_test, simconfig_reference);
}

TEST_F(ConfigurationTest, McConfig)
{
    std::string_view mcconfig_string = R"(
        {
            "mcconfig": {
                        "Arbiter": "Simple",
                        "CmdMux": "Oldest",
                        "HighWatermark": 0,
                        "LowWatermark": 0,
                        "MaxActiveTransactions": 128,
                        "PagePolicy": "Open",
                        "PowerDownPolicy": "NoPowerDown",
                        "RefreshMaxPostponed": 0,
                        "RefreshMaxPulledin": 0,
                        "RefreshPolicy": "AllBank",
                        "RequestBufferSize": 8,
                        "RespQueue": "Fifo",
                        "Scheduler": "FrFcfs",
                        "SchedulerBuffer": "Bankwise"
            }
        }
    )";

    json_t mcconfig_reference = json_t::parse(mcconfig_string);
    json_t mcconfig_test;
    mcconfig_test[McConfig::KEY] = mcConfig;

    EXPECT_EQ(mcconfig_test, mcconfig_reference);
}

TEST_F(ConfigurationTest, MemSpec)
{
    std::string_view memspec_string = R"(
        {
            "memspec": {
                "memarchitecturespec": {
                    "RAADEC": 1,
                    "RAAIMT": 32,
                    "RAAMMT": 96,
                    "burstLength": 16,
                    "cmdMode": 1,
                    "dataRate": 2,
                    "nbrOfBankGroups": 8,
                    "nbrOfBanks": 16,
                    "nbrOfChannels": 2,
                    "nbrOfColumns": 2048,
                    "nbrOfDIMMRanks": 1,
                    "nbrOfDevices": 8,
                    "nbrOfLogicalRanks": 1,
                    "nbrOfPhysicalRanks": 1,
                    "nbrOfRanks": 1,
                    "nbrOfRows": 65536,
                    "refMode": 1,
                    "width": 4
                },
                "memoryId": "JEDEC_2x8x2Gbx4_DDR5-3200A",
                "memoryType": "DDR5",
                "memtimingspec": {
                    "ACTPDEN": 2,
                    "CCD_L_WR2_slr": 16,
                    "CCD_L_WR_slr": 32,
                    "CCD_L_slr": 8,
                    "CCD_S_WR_slr": 8,
                    "CCD_S_slr": 8,
                    "CCD_WR_dlr": 0,
                    "CCD_WR_dpr": 0,
                    "CCD_dlr": 0,
                    "CPDED": 8,
                    "FAW_dlr": 0,
                    "FAW_slr": 32,
                    "PD": 12,
                    "PPD": 2,
                    "PRPDEN": 2,
                    "RAS": 52,
                    "RCD": 22,
                    "RDDQS": 0,
                    "REFI1": 6240,
                    "REFI2": 3120,
                    "REFISB": 1560,
                    "REFPDEN": 2,
                    "REFSBRD_dlr": 0,
                    "REFSBRD_slr": 48,
                    "RFC1_dlr": 0,
                    "RFC1_dpr": 0,
                    "RFC1_slr": 312,
                    "RFC2_dlr": 0,
                    "RFC2_dpr": 0,
                    "RFC2_slr": 208,
                    "RFCsb_dlr": 0,
                    "RFCsb_slr": 184,
                    "RL": 22,
                    "RP": 22,
                    "RPRE": 1,
                    "RPST": 0,
                    "RRD_L_slr": 8,
                    "RRD_S_slr": 8,
                    "RRD_dlr": 0,
                    "RTP": 12,
                    "RTRS": 2,
                    "WL": 20,
                    "WPRE": 2,
                    "WPST": 0,
                    "WR": 48,
                    "WTR_L": 16,
                    "WTR_S": 4,
                    "XP": 12,
                    "clkMhz": 1600
                }
            }
        }
    )";

    json_t memspec_reference = json_t::parse(memspec_string);
    json_t memspec_test;
    memspec_test[MemSpec::KEY] = memSpec;

    EXPECT_EQ(memspec_test, memspec_reference);
}

TEST_F(ConfigurationTest, AddressMapping)
{
    std::string_view addressmapping_string = R"(
        {
            "addressmapping": {
                "BANKGROUP_BIT": [
                    17,
                    18,
                    19,
                    20,
                    21,
                    22,
                    23,
                    24,
                    25,
                    26,
                    27,
                    28,
                    29,
                    30,
                    31,
                    32
                ],
                "BANK_BIT": [
                    13,
                    14,
                    15
                ],
                "BYTE_BIT": [
                    0,
                    1
                ],
                "COLUMN_BIT": [
                    2,
                    3,
                    4,
                    5,
                    6,
                    7,
                    8,
                    9,
                    10,
                    11,
                    12
                ],
                "RANK_BIT": [
                    33
                ],
                "ROW_BIT": [
                    16
                ]
            }
        }
    )";

    json_t addressmapping_reference = json_t::parse(addressmapping_string);
    json_t addressmapping_test;
    addressmapping_test[AddressMapping::KEY] = addressMapping;

    EXPECT_EQ(addressmapping_test, addressmapping_reference);
}

TEST_F(ConfigurationTest, TraceSetup)
{
    std::string_view tracesetup_string = R"(
        {
            "tracesetup": [
                {
                    "clkMhz": 100,
                    "name": "mytrace.stl"
                },
                {
                    "addressDistribution": "random",
                    "clkMhz": 100,
                    "name": "MyTestGen",
                    "numRequests": 1000,
                    "rwRatio": 0.5
                },
                {
                    "clkMhz": 100,
                    "maxPendingReadRequests": 8,
                    "name": "MyTestGen",
                    "states": [
                        {
                            "addressDistribution": "sequential",
                            "addressIncrement": 256,
                            "id": 0,
                            "maxAddress": 1024,
                            "numRequests": 1000,
                            "rwRatio": 0.5
                        },
                        {
                            "addressDistribution": "sequential",
                            "addressIncrement": 512,
                            "id": 1,
                            "maxAddress": 2048,
                            "minAddress": 1024,
                            "numRequests": 100,
                            "rwRatio": 0.75
                        }
                    ],
                    "transitions": [
                        {
                            "from": 0,
                            "probability": 1.0,
                            "to": 1
                        }
                    ]
                },
                {
                    "clkMhz": 100,
                    "name": "MyTestHammer",
                    "numRequests": 4000,
                    "rowIncrement": 2097152
                }
            ]
        }
    )";

    json_t tracesetup_reference = json_t::parse(tracesetup_string);
    json_t tracesetup_test;
    tracesetup_test[TraceSetupConstants::KEY] = traceSetup;

    EXPECT_EQ(tracesetup_test, tracesetup_reference);
}
