/*
 * Copyright (c) 2015, University of Kaiserslautern
 * 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:
 *    Janik Schlemminger
 *    Matthias Jung
 *    Éder F. Zulian
 *    Felipe S. Prado
 */

#include <cassert>

#include "Configuration.h"
#include "ConfigurationLoader.h"
#include "../../../common/xmlAddressdecoder.h"

using namespace std;

string Configuration::memspecUri = "";
string Configuration::mcconfigUri = "";

Configuration::Configuration()
{
}

bool string2bool(string s)
{
    if (s.compare("0") == 0) {
        return false;
    } else if (s.compare("1") == 0) {
        return true;
    } else {
        SC_REPORT_FATAL("Configuration", ("Could not convert to bool: " + s).c_str());
        return false;
    }
}

int string2int(string s)
{
    return std::stoi(s);
}

unsigned long long string2ull(string s)
{
    return std::stoull(s);
}

StorageMode string2StoreMode(string s)
{
    if (s == "NoStorage")
        return StorageMode::NoStorage;
    else if (s == "Store")
        return StorageMode::Store;
    else if (s == "ErrorModel")
        return StorageMode::ErrorModel;
    else {
        SC_REPORT_FATAL("Configuration", ("Unknown StorageMode: " + s).c_str());
        throw;
    }
}

EPowerDownMode string2PDNMode(string s)
{
    if (s == "NoPowerDown")
        return EPowerDownMode::NoPowerDown;
    else if (s == "Staggered")
        return EPowerDownMode::Staggered;
    else if (s == "TimeoutPDN")
        return EPowerDownMode::TimeoutPDN;
    else if (s == "TimeoutSREF")
        return EPowerDownMode::TimeoutSREF;
    else {
        SC_REPORT_FATAL("Configuration", ("Unknown PowerDownMode: " + s).c_str());
        throw;
    }
}

ECCControllerMode string2ECCControllerMode(string s)
{
    if (s == "Disabled")
        return ECCControllerMode::Disabled;
    else if (s == "Hamming")
        return ECCControllerMode::Hamming;
    else {
        SC_REPORT_FATAL("Configuration", ("Unknown ECCControllerMode: " + s).c_str());
        throw;
    }
}

enum sc_time_unit string2TimeUnit(string s)
{
    if (s == "s")
        return SC_SEC;
    else if (s == "ms")
        return SC_MS;
    else if (s == "us")
        return SC_US;
    else if (s == "ns")
        return SC_NS;
    else if (s == "ps")
        return SC_PS;
    else if (s == "fs")
        return SC_FS;
    else {
        SC_REPORT_FATAL("Configuration",
                        ("Could not convert to enum sc_time_unit: " + s).c_str());
        throw;
    }
}

void Configuration::setParameter(std::string name, std::string value)
{
    if (name == "BankwiseLogic")
        BankwiseLogic = string2bool(value);
    else if (name == "OpenPagePolicy")
        OpenPagePolicy = string2bool(value);
    else if (name == "MaxNrOfTransactions")
        MaxNrOfTransactions = string2int(value);
    else if (name == "Scheduler")
        Scheduler = value;
    else if (name == "SJFProbability")
        if (string2int(value) > 100 || string2int(value) < 0) {
            SC_REPORT_FATAL("Configuration",
                            ("Invalid value for parameter " + name +
                             ". This parameter must be between 0 and 100.").c_str());
        } else {
            SJFProbability = string2int(value);
        }
    else if (name == "RequestBufferSize")
        RequestBufferSize = string2int(value);
    else if (name == "Capsize")
        Capsize = string2int(value);
    else if (name == "PowerDownTimeout")
        powerDownTimeoutInClk = string2int(value);
    else if (name == "PowerDownMode")
        PowerDownMode = string2PDNMode(value);
    else if (name == "ReadWriteGrouping")
        ReadWriteGrouping = string2bool(value);
    else if (name == "ReorderBuffer")
        ReorderBuffer = string2bool(value);

    //SimConfig------------------------------------------------
    else if (name == "SimulationName")
        SimulationName = value;
    else if (name == "DatabaseRecording")
        DatabaseRecording = string2bool(value);
    else if (name == "PowerAnalysis")
        PowerAnalysis = string2bool(value);
    else if (name == "EnableWindowing")
        EnableWindowing = string2bool(value);
    else if (name == "WindowSize")
        if (string2int(value) < 1) {
            SC_REPORT_FATAL("Configuration",
                            ("Invalid value for parameter " + name +
                             ". This parameter must be at least one.").c_str());
        } else
            WindowSize = string2int(value);
    else if (name == "Debug")
        Debug = string2bool(value);
    else if (name == "NumberOfMemChannels") {
        NumberOfMemChannels = string2int(value);
        unsigned int maxNumberofMemChannels =
            AddressDecoder::getInstance().amount["channel"];
        if (NumberOfMemChannels > maxNumberofMemChannels) {
            SC_REPORT_FATAL("Configuration", ("Invalid value for parameter "
                                              + name
                                              + ". Value is out of range. The maximum value according to "
                                              + "the address mapping configuration file is "
                                              + std::to_string(maxNumberofMemChannels) + ".").c_str());
        }
    } else if (name == "ControllerCoreRefDisable")
        ControllerCoreRefDisable = string2bool(value);
    else if (name == "ControllerCoreRGR")
        RowGranularRef = string2bool(value);
    else if (name == "ControllerCoreRGRRowInc")
        RowInc = string2int(value);
    else if (name == "ControllerCoreRefMode") {
        RefMode = string2int(value);
        if (RefMode != 1 && RefMode != 2 && RefMode != 4)
            SC_REPORT_FATAL("Configuration", (name + " invalid value.").c_str());
    } else if (name == "ControllerCoreRefNumARCmdsIntREFI")
        NumAR = string2int(value);
    else if (name == "ControllerCoreRGRB0")
        RGRB0 = string2bool(value);
    else if (name == "ControllerCoreRGRB1")
        RGRB1 = string2bool(value);
    else if (name == "ControllerCoreRGRB2")
        RGRB2 = string2bool(value);
    else if (name == "ControllerCoreRGRB3")
        RGRB3 = string2bool(value);
    else if (name == "ControllerCoreRGRB4")
        RGRB4 = string2bool(value);
    else if (name == "ControllerCoreRGRB5")
        RGRB5 = string2bool(value);
    else if (name == "ControllerCoreRGRB6")
        RGRB6 = string2bool(value);
    else if (name == "ControllerCoreRGRB7")
        RGRB7 = string2bool(value);
    else if (name == "ControllerCoreRGRB8")
        RGRB8 = string2bool(value);
    else if (name == "ControllerCoreRGRB9")
        RGRB9 = string2bool(value);
    else if (name == "ControllerCoreRGRB10")
        RGRB10 = string2bool(value);
    else if (name == "ControllerCoreRGRB11")
        RGRB11 = string2bool(value);
    else if (name == "ControllerCoreRGRB12")
        RGRB12 = string2bool(value);
    else if (name == "ControllerCoreRGRB13")
        RGRB13 = string2bool(value);
    else if (name == "ControllerCoreRGRB14")
        RGRB14 = string2bool(value);
    else if (name == "ControllerCoreRGRB15")
        RGRB15 = string2bool(value);
    else if (name == "ControllerCoreRGRtRASBInClkCycles")
        trasbclk = string2int(value);
    else if (name == "ControllerCoreRGRtRRDB_LInClkCycles")
        trrdblclk = string2int(value);
    else if (name == "ControllerCoreRGRtRRDB_SInClkCycles")
        trrdbsclk = string2int(value);
    else if (name == "ControllerCoreRGRtRPBInClkCycles")
        trpbclk = string2int(value);
    else if (name == "ControllerCoreRGRtRCBInClkCycles")
        trcbclk = string2int(value);
    else if (name == "ControllerCoreRGRtFAWBInClkCycles")
        tfawbclk = string2int(value);
    else if (name == "ControllerCoreRefForceMaxPostponeBurst")
        ControllerCoreRefForceMaxPostponeBurst = string2bool(value);
    else if (name == "ControllerCoreRefEnablePostpone") {
        ControllerCoreRefEnablePostpone = string2bool(value);
    } else if (name == "ControllerCoreRefEnablePullIn") {
        ControllerCoreRefEnablePullIn = string2bool(value);
    } else if (name == "ControllerCoreRefMaxPostponed")
        ControllerCoreRefMaxPostponed = string2int(value);
    else if (name == "ControllerCoreRefMaxPulledIn")
        ControllerCoreRefMaxPulledIn = string2int(value);
    else if (name == "ThermalSimulation")
        ThermalSimulation = string2bool(value);
    else if (name == "SimulationProgressBar")
        SimulationProgressBar = string2bool(value);
    else if (name == "NumberOfDevicesOnDIMM")
        if (string2int(value) < 1) {
            SC_REPORT_FATAL("Configuration",
                            ("Invalid value for parameter " + name +
                             ". This parameter must be at least one.").c_str());
        } else
            NumberOfDevicesOnDIMM = string2int(value);
    else if (name == "AddressOffset") {
#ifdef DRAMSYS_GEM5
        AddressOffset = string2ull(value);
#else
        AddressOffset = 0;
#endif
    } else if (name == "UseMalloc") {
        UseMalloc = string2bool(value);
    } else if (name == "CheckTLM2Protocol")
        CheckTLM2Protocol = string2bool(value);
    else if (name == "ECCControllerMode")
        ECCMode = string2ECCControllerMode(value);
    // Specification for ErrorChipSeed, ErrorCSVFile path and StoreMode
    else if (name == "ErrorChipSeed")
        ErrorChipSeed = string2int(value);
    else if (name == "ErrorCSVFile")
        ErrorCSVFile = value;
    else if (name == "StoreMode")
        StoreMode = string2StoreMode(value);
    // Temperature Simulation related
    else if (name == "TemperatureScale") {
        if (value != "Celsius" && value != "Fahrenheit" && value != "Kelvin") {
            SC_REPORT_FATAL("Configuration",
                            ("Invalid value for parameter " + name + ".").c_str());
        }
        temperatureSim.TemperatureScale = value;
    } else if (name == "StaticTemperatureDefaultValue")
        temperatureSim.StaticTemperatureDefaultValue = string2int(value);
    else if (name == "ThermalSimPeriod")
        temperatureSim.ThermalSimPeriod = std::stod(value.c_str());
    else if (name == "ThermalSimUnit")
        temperatureSim.ThermalSimUnit = string2TimeUnit(value);
    else if (name == "PowerInfoFile") {
        temperatureSim.powerInfoFile = value;
        temperatureSim.parsePowerInfoFile();
    } else if (name == "IceServerIp")
        temperatureSim.IceServerIp = value;
    else if (name == "IceServerPort")
        temperatureSim.IceServerPort = string2int(value);
    else if (name == "SimPeriodAdjustFactor")
        temperatureSim.SimPeriodAdjustFactor = std::stoi(value.c_str());
    else if (name == "NPowStableCyclesToIncreasePeriod")
        temperatureSim.NPowStableCyclesToIncreasePeriod = std::stoi(value.c_str());
    else if (name == "GenerateTemperatureMap")
        temperatureSim.GenerateTemperatureMap = string2bool(value);
    else if (name == "GeneratePowerMap")
        temperatureSim.GeneratePowerMap = string2bool(value);
    else {
        SC_REPORT_FATAL("Configuration",
                        ("Parameter " + name + " not defined in Configuration").c_str());
    }
}

void Configuration::setPathToResources(std::string path)
{
    pathToResources = path;
    temperatureSim.setPathToResources(path);
}

std::string Configuration::getPathToResources()
{
    return pathToResources;
}

void Configuration::setParameters(std::map<std::string, std::string>
                                  parameterMap)
{
    for (auto item : parameterMap) {
        setParameter(item.first, item.second);
    }
}

// Returns the total memory size in bytes
std::uint64_t Configuration::getSimMemSizeInBytes()
{
    // 1. Get number of banks, rows, columns and data width in bits for one die (or chip)
    std::uint64_t banks = memSpec.NumberOfBanks;
    std::uint64_t rows = memSpec.NumberOfRows;
    std::uint64_t columns = memSpec.NumberOfColumns;
    std::uint64_t bitWidth = memSpec.bitWidth;
    // 2. Calculate size of one DRAM chip in bits
    std::uint64_t chipBitSize = banks * rows * columns * bitWidth;
    // 3. Calculate size of one DRAM chip in bytes
    std::uint64_t chipSize = chipBitSize / 8;
    // 4. Total memory size in Bytes of one DIMM (with only support of 1 rank on a DIMM)
    std::uint64_t memorySize = chipSize * NumberOfDevicesOnDIMM;

    std::cout << headline << std::endl << std::endl;
    std::cout << std::setw(24) << "Memory size in bytes : " << memorySize            << std::endl;
    std::cout << std::setw(24) << "Number of banks      : " << banks                 << std::endl;
    std::cout << std::setw(24) << "Number of rows       : " << rows                  << std::endl;
    std::cout << std::setw(24) << "Number of columns    : " << columns               << std::endl;
    std::cout << std::setw(24) << "Chip data bus width  : " << bitWidth              << std::endl;
    std::cout << std::setw(24) << "Chip size in bits    : " << chipBitSize           << std::endl;
    std::cout << std::setw(24) << "Chip Size in bytes   : " << chipSize              << std::endl;
    std::cout << std::setw(24) << "Devices/Chips on DIMM: " << NumberOfDevicesOnDIMM << std::endl;
    std::cout << std::endl;

    assert(memorySize > 0);
    return memorySize;
}

// Returns the width of the data bus.
// All DRAM chips on a DIMM operate in lockstep,
// which constituing aggregate data bus width = chip's bus width * # locksteep-operated chips
// The bus width is given in bits, e.g., 64-bit data bus, 128-bit data bus, etc.
unsigned int Configuration::getDataBusWidth()
{
    unsigned int dataBusWidth = memSpec.bitWidth * NumberOfDevicesOnDIMM;
    assert(dataBusWidth > 0);
    return dataBusWidth;
}

// Returns the number of bytes transfered in a burst
unsigned int Configuration::getBytesPerBurst()
{
    // First multiply to get the number of bits in a burst, then divide by 8 to get the value in bytes. The order is important. Think on a single x4 device.
    unsigned int bytesPerBurst = (memSpec.BurstLength * getDataBusWidth()) / 8;
    assert(bytesPerBurst > 0);

    if (NumberOfDevicesOnDIMM > 1) {
        // The least significant bits of the physical address are the byte
        // offset of the N-byte-wide memory module (DIMM) (a single data word
        // or burst element has N bytes. N = 2^(# bits for byte offset)).
        unsigned int burstElementSizeInBytes =
            AddressDecoder::getInstance().amount["bytes"];
        assert(bytesPerBurst == (burstElementSizeInBytes * memSpec.BurstLength));
    }

    return bytesPerBurst;
}

sc_time Configuration::getTrasb()
{
    return trasbclk * memSpec.clk;
}
sc_time Configuration::getTrrdb_L()
{
    return trrdblclk * memSpec.clk;
}
sc_time Configuration::getTrrdb_S()
{
    return trrdbsclk * memSpec.clk;
}
sc_time Configuration::getTrpb()
{
    return trpbclk * memSpec.clk;
}
sc_time Configuration::getTrcb()
{
    return trcbclk * memSpec.clk;
}
sc_time Configuration::getTfawb()
{
    return tfawbclk * memSpec.clk;
}
bool Configuration::getRGRBank(unsigned int w)
{
    bool flg = w == 0 ? RGRB0 : w == 1 ? RGRB1 : w == 2 ? RGRB2 : w == 3 ? RGRB3 : w
               == 4 ? RGRB4 : w == 5 ? RGRB5 : w == 6 ? RGRB6 : w == 7 ? RGRB7 : w == 8 ? RGRB8
               : w == 9 ? RGRB9 : w == 10 ? RGRB10 : w == 11 ? RGRB11 : w == 12 ? RGRB12 : w ==
               13 ? RGRB13 : w == 14 ? RGRB14 : w == 15 ? RGRB15 : true;
    return flg;
}
unsigned int Configuration::getNumAR(void)
{
    return NumAR;
}
unsigned int Configuration::getRowInc(void)
{
    return RowInc;
}

unsigned int Configuration::getRefMode(void)
{
    return RefMode;
}

// Changes the number of bytes depeding on the ECC Controller. This function is needed for modules which get data directly or indirectly from the ECC Controller
unsigned int Configuration::adjustNumBytesAfterECC(unsigned nBytes)
{
    // Manipulate the number of bytes only if there is an ECC Controller selected
    if (ECCMode == ECCControllerMode::Disabled)
        return nBytes;
    else {
        assert(pECC != nullptr);
        return pECC->AllocationSize(nBytes);
    }
}
