/*
 * 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
 *    Eder F. Zulian
 *    Felipe S. Prado
 */

#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <stdexcept>

#include "DRAMSys.h"
#include "Setup.h"
#include "../common/TlmRecorder.h"
#include "../common/DebugManager.h"
#include "../common/xmlAddressdecoder.h"
#include "../common/congenAddressDecoder.h"
#include "../controller/core/ControllerCore.h"
#include "../controller/core/configuration/ConfigurationLoader.h"
#include "../common/Utils.h"
#include "../simulation/TemperatureController.h"
#include "../controller/Controller.h"
#include "../error/ecchamming.h"

using namespace std;

DRAMSys::DRAMSys(sc_module_name __attribute__((unused)) name,
                 string simulationToRun,
                 string pathToResources) : tSocket("DRAMSys_tSocket")
{
    // Initialize ecc pointer
    ecc = nullptr;

    logo();

    // Read Configuration Setup:
    string memspec;
    string mcconfig;
    string amconfig;
    string simconfig;
    string thermalconfig;

    Setup setup(simulationToRun,
                memspec,
                mcconfig,
                amconfig,
                simconfig,
                thermalconfig);

    Configuration::getInstance().setPathToResources(pathToResources);

    // The xmlAddressDecoder MUST be initialized before calling the
    // ConfigurationLoader because some information from the xmlAddressDecoder
    // is needed to assure the coherence of the configuration.

    if (xmlAddressDecoder::testConfigFile(pathToResources + "configs/amconfigs/" +
                                          amconfig)) {
        AddressDecoder::createInstance(AddressDecoder::Type::XML);
        AddressDecoder::getInstance().setConfiguration(pathToResources
                                                       + "configs/amconfigs/"
                                                       + amconfig);
    } else if (CongenAddressDecoder::testConfigFile(pathToResources +
                                                    "configs/amconfigs/" + amconfig)) {
        AddressDecoder::createInstance(AddressDecoder::Type::CONGEN);
        AddressDecoder::getInstance().setConfiguration(pathToResources
                                                       + "configs/amconfigs/"
                                                       + amconfig);
    } else {
        throw std::runtime_error("No address mapping loaded. Unsupported file format");
    }

    AddressDecoder::getInstance().print();

    ConfigurationLoader::loadMemSpec(Configuration::getInstance(),
                                     pathToResources
                                     + "configs/memspecs/"
                                     + memspec);

    // Setup the memory controller with the propriate xml file
    ConfigurationLoader::loadMCConfig(Configuration::getInstance(),
                                      pathToResources
                                      + "configs/mcconfigs/"
                                      + mcconfig);

    ConfigurationLoader::loadSimConfig(Configuration::getInstance(),
                                       pathToResources
                                       + "configs/simulator/"
                                       + simconfig);

    ConfigurationLoader::loadTemperatureSimConfig(Configuration::getInstance(),
                                                  pathToResources
                                                  + "configs/thermalsim/"
                                                  + thermalconfig);

    // If a simulation file is passed as argument to DRAMSys the simulation ID
    // is prepended to the simulation name if found.
    std::string simName;
    simName = Configuration::getInstance().SimulationName;
    tinyxml2::XMLDocument simulationdoc;
    loadXML(simulationToRun, simulationdoc);
    tinyxml2::XMLElement *simulation =
        simulationdoc.FirstChildElement("simulation");
    if (simulation != NULL) {
        tinyxml2::XMLElement *simid = simulation->FirstChildElement("simulationid");
        if (simid != NULL) {
            auto r = simid->Attribute("id");
            if (r != NULL) {
                std::string sid;
                sid = r;
                simName = sid + '_' + Configuration::getInstance().SimulationName;
            }
        }
    }
    // Instantiate all internal DRAMSys modules:
    instantiateModules(simName, pathToResources);
    // Connect all internal DRAMSys modules:
    bindSockets();

    // Setup the debug manager:
    setupDebugManager(Configuration::getInstance().SimulationName);

    report(headline);
}

void DRAMSys::logo()
{
#define REDTXT(s) string(("\033[0;31m"+string((s))+"\033[0m"))
#define BOLDBLUETXT(s) string(("\033[1;34m"+string((s))+"\033[0m"))
    cout << endl;
    cout << REDTXT("  |||") << endl;
    cout << REDTXT(" +---+   Microelectronic Systems") << endl;
    cout << REDTXT("=|   |=  Design Research Group") << endl;
    cout << REDTXT("=|   |=  ") << BOLDBLUETXT("University of Kaiserslautern")
         << endl;
    cout << REDTXT(" +---+   ") << endl;
    cout << REDTXT("  |||    ") << "DRAMSys v3.0" << endl;
    cout << endl;
#undef REDTXT
#undef BOLDBLUETXT
}

void DRAMSys::setupDebugManager(const string &traceName)
{
    auto &dbg = DebugManager::getInstance();
    dbg.writeToConsole = true;
    dbg.writeToFile = true;
    if (dbg.writeToFile)
        dbg.openDebugFile(traceName + ".txt");
}

void DRAMSys::setupTlmRecorders(const string &traceName,
                                const string &pathToResources)
{
    // Create TLM Recorders, one per channel.
    for (size_t i = 0;
            i < Configuration::getInstance().NumberOfMemChannels;
            i++) {
        std::string sqlScriptURI = pathToResources
                                   + string("scripts/createTraceDB.sql");

        std::string dbName = traceName
                             + string("_ch")
                             + std::to_string(i)
                             + ".tdb";

        std::string recorderName = "tlmRecorder" + std::to_string(i);

        TlmRecorder *tlmRecorder =
            new TlmRecorder(recorderName.c_str(),
                            sqlScriptURI.c_str(),
                            dbName.c_str());
        tlmRecorder->recordMCconfig(Configuration::getInstance().mcconfigUri);
        tlmRecorder->recordMemspec(Configuration::getInstance().memspecUri);

        std::string traceNames = Configuration::getInstance().SimulationName;
        tlmRecorder->recordTracenames(traceNames);

        tlmRecorders.push_back(tlmRecorder);
    }
}

void DRAMSys::instantiateModules(const string &traceName,
                                 const string &pathToResources)
{
    // The first call to getInstance() creates the Temperature Controller.
    // The same instance will be accessed by all other modules.
    TemperatureController::getInstance();

    // Create and properly initialize TLM recorders.
    // They need to be ready before creating some modules.
    bool recordingEnabled = Configuration::getInstance().DatabaseRecording;
    if (recordingEnabled)
        setupTlmRecorders(traceName, pathToResources);

    // Create new ECC Controller
    switch (Configuration::getInstance().ECCMode) {
    case ECCControllerMode::Hamming:
        ecc = new ECCHamming("ECCHamming");
        break;
    default:
        ecc = nullptr;
        break;
    }

    // Save ECC Controller into the configuration struct to adjust it dynamically
    Configuration::getInstance().pECC = ecc;

    // Create arbiter
    arbiter = new Arbiter("arbiter");

    // Create DRAM
    for (size_t i = 0;
            i < Configuration::getInstance().NumberOfMemChannels;
            i++) {
        std::string str = "controller" + std::to_string(i);

        Controller *controller;
        if (recordingEnabled)
            controller = new RecordableController(str.c_str(), tlmRecorders[i]);
        else
            controller = new Controller(str.c_str());
        controllers.push_back(controller);

        str = "dram" + std::to_string(i);
        Dram *dram;
        if (recordingEnabled)
            dram = new RecordableDram(str.c_str(), tlmRecorders[i]);
        else
            dram = new Dram(str.c_str());
        dram->setDramController(controllers[i]);
        drams.push_back(dram);

        if (Configuration::getInstance().CheckTLM2Protocol) {
            str = "TLMCheckerController" + std::to_string(i);
            tlm_utils::tlm2_base_protocol_checker<> *controllerTlmChecker =
                new tlm_utils::tlm2_base_protocol_checker<>(str.c_str());
            controllersTlmCheckers.push_back(controllerTlmChecker);
        }
    }
}

void DRAMSys::bindSockets()
{
    // If ECC Controller enabled, put it between Trace and arbiter
    if (Configuration::getInstance().ECCMode != ECCControllerMode::Disabled) {
        assert(ecc != nullptr);
        tSocket.bind(ecc->t_socket);
        ecc->i_socket.bind(arbiter->tSocket);
    } else {
        tSocket.bind(arbiter->tSocket);
    }

    if (Configuration::getInstance().CheckTLM2Protocol) {
        for (size_t i = 0;
                i < Configuration::getInstance().NumberOfMemChannels;
                i++) {
            arbiter->iSocket.bind(controllersTlmCheckers[i]->target_socket);
            controllersTlmCheckers[i]->initiator_socket.bind(
                controllers[i]->tSocket);
            controllers[i]->iSocket.bind(drams[i]->tSocket);
        }
    } else {
        for (size_t i = 0;
                i < Configuration::getInstance().NumberOfMemChannels;
                i++) {
            arbiter->iSocket.bind(controllers[i]->tSocket);
            controllers[i]->iSocket.bind(drams[i]->tSocket);
        }
    }
}

DRAMSys::~DRAMSys()
{
    if (ecc)
        delete ecc;

    delete arbiter;

    for (auto dram : drams) {
        delete dram;
    }

    for (auto rec : tlmRecorders) {
        delete rec;
    }

    for (auto tlmChecker : playersTlmCheckers) {
        delete tlmChecker;
    }

    for (auto tlmChecker : controllersTlmCheckers) {
        delete tlmChecker;
    }

    for (auto controller : controllers) {
        delete controller;
    }
}

void DRAMSys::report(string message)
{
    DebugManager::getInstance().printDebugMessage(this->name(), message);
    cout << message << endl;
}
