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

#include <systemc.h>
#include "ControllerCore.h"
#include "scheduling/checker/ActBChecker.h"
#include "scheduling/checker/ActivateChecker.h"
#include "scheduling/checker/PreBChecker.h"
#include "scheduling/checker/PrechargeChecker.h"
#include "scheduling/checker/PrechargeAllChecker.h"
#include "scheduling/checker/ReadChecker.h"
#include "scheduling/checker/WriteChecker.h"
#include "scheduling/checker/RefreshChecker.h"
#include "scheduling/checker/PowerDownChecker.h"
#include "refresh/RefreshManagerBankwise.h"
#include "refresh/RefreshManager.h"
#include "refresh/RGR.h"
#include "../../common/dramExtension.h"
#include "../../common/Utils.h"
#include "TimingCalculation.h"

#include "powerdown/PowerDownManager.h"
#include "powerdown/PowerDownManagerTimeout.h"
#include "powerdown/PowerDownManagerBankwise.h"
#include "powerdown/PowerDownManagerTimeoutBankwise.h"
#include "powerdown/NoPowerDown.h"
#include "../../common/DebugManager.h"

ControllerCore::ControllerCore(sc_module_name /*name*/,
                               IController &wrapperConnector, std::map<Bank, int> &numberOfPayloads) :
    config(Configuration::getInstance()), controller(wrapperConnector),
    numberOfPayloads(numberOfPayloads), commandChecker()
{
    state = new ControllerState(name(), &config);

    commandChecker[Command::ActB] = new ActBChecker(config, *state);
    commandChecker[Command::Activate] = new ActivateChecker(config, *state);
    commandChecker[Command::PreB] = new PreBChecker(config, *state);
    commandChecker[Command::Precharge] = new PrechargeChecker(config, *state);
    commandChecker[Command::PrechargeAll] = new PrechargeAllChecker(config, *state);
    commandChecker[Command::Read] = new ReadChecker(config, *state);
    commandChecker[Command::ReadA] = commandChecker[Command::Read];
    commandChecker[Command::Write] = new WriteChecker(config, *state);
    commandChecker[Command::WriteA] = commandChecker[Command::Write];

    commandChecker[Command::AutoRefresh] = new RefreshChecker(config, *state);
    commandChecker[Command::PDNA] = new PowerDownChecker(config, *state);
    commandChecker[Command::PDNP] = commandChecker[Command::PDNA];
    commandChecker[Command::SREF] = commandChecker[Command::PDNA];
    commandChecker[Command::PDNAX] = commandChecker[Command::PDNA];
    commandChecker[Command::PDNPX] = commandChecker[Command::PDNA];
    commandChecker[Command::SREFX] = commandChecker[Command::PDNA];

    if (config.RowGranularRef) {
        refreshManager = new RGR("RGR", *this);
        assert(config.getTrasb() <= config.memSpec.tRAS);
        assert(config.getTrasb() >= config.memSpec.tRCD);
        assert(config.getTrrdb_L() <= config.memSpec.tRRD_L);
        assert(config.getTrrdb_S() <= config.memSpec.tRRD_S);
        assert(config.getTrpb() <= config.memSpec.tRP);
        assert(config.getTrcb() <= config.memSpec.tRC);
        assert(config.getTfawb() <= config.memSpec.tNAW);
    } else {
        if (config.BankwiseLogic) {
            refreshManager = new RefreshManagerBankwise("refManagerBw", *this);
        } else {
            refreshManager = new RefreshManager("refManager", *this);
        }
    }

    if (config.PowerDownMode == EPowerDownMode::Staggered) {
        if (config.BankwiseLogic)
            powerDownManager = new PowerDownManagerBankwise("pdnManagerBw", *this);
        else
            powerDownManager = new PowerDownManager("pdnManager", *this);
    } else if (config.PowerDownMode == EPowerDownMode::TimeoutPDN
               || config.PowerDownMode == EPowerDownMode::TimeoutSREF) {
        if (config.BankwiseLogic)
            powerDownManager = new PowerDownManagerTimeoutBankwise("pdnManagerBw", *this);
        else
            powerDownManager = new PowerDownManagerTimeout("pdnManager", *this);
    } else if (config.PowerDownMode == EPowerDownMode::NoPowerDown) {
        powerDownManager = new NoPowerDown();
    } else {
        SC_REPORT_FATAL(0,
                        "Unsupported powerdown mode in constructor of controller core");
    }
}

ControllerCore::~ControllerCore()
{
    delete commandChecker[Command::Activate];
    delete commandChecker[Command::Precharge];
    delete commandChecker[Command::PrechargeAll];
    delete commandChecker[Command::Read];
    delete commandChecker[Command::Write];
    delete commandChecker[Command::AutoRefresh];
    delete commandChecker[Command::PDNA];
    delete refreshManager;
    delete powerDownManager;
    delete state;
}

void ControllerCore::triggerRefresh(tlm::tlm_generic_payload &payload)
{
    /* Refresh can be disabled for tests purpose */
    if (config.ControllerCoreRefDisable == false) {
        sc_time time = sc_time_stamp();
        Bank bank = DramExtension::getExtension(payload).getBank();

        state->cleanUp(time);

        if (!refreshManager->isInvalidated(payload, time)
                && !powerDownManager->isInSelfRefresh(bank)) {
            printDebugMessage("Triggering refresh on bank " + to_string(bank.ID()));
            powerDownManager->wakeUpForRefresh(bank,
                                               time); //expects PDNA and PDNP to exit without delay
            bool pdnpToSrefTransition = false;
            if (config.PowerDownMode == EPowerDownMode::Staggered) {
                pdnpToSrefTransition = state->getLastCommand(Command::PDNPX,
                                                             bank).getStart() >= time;
            }
            if (pdnpToSrefTransition) {
                powerDownManager->sleep(bank, time);
            } else {
                refreshManager->scheduleRefresh(payload, time);
            }
        }
    }
}

bool ControllerCore::scheduleRequest(Command command,
                                     tlm::tlm_generic_payload &payload)
{
    sc_time start = clkAlign(sc_time_stamp());
    state->cleanUp(start);
    ScheduledCommand scheduledCommand = schedule(command, start, payload);
    if (config.ControllerCoreRefDisable == true) {
        state->change(scheduledCommand);
        controller.send(scheduledCommand, payload);
        return true;
    } else {
        if (!((command == Command::Precharge || command == Command::Activate)
                && refreshManager->hasCollision(scheduledCommand))) {
            state->change(scheduledCommand);
            controller.send(scheduledCommand, payload);
            return true;
        }
    }
    return false;
}

ScheduledCommand ControllerCore::schedule(Command command, sc_time start,
                                          tlm::tlm_generic_payload &payload)
{
    ControllerCore::printDebugMessage("Scheduling command " + commandToString(
                                          command) + " on " + DramExtension::getBank(payload).toString());
    ICommandChecker &checker = getCommandChecker(command);
    sc_time executionTime = getExecutionTime(command, payload);
    ScheduledCommand scheduledCommand(command, start, executionTime,
                                      DramExtension::getExtension(payload));
    checker.delayToSatisfyConstraints(scheduledCommand);
    return scheduledCommand;
}

bool ControllerCore::hasPendingRequests()
{
    for (Bank bank : getBanks()) {
        if (numberOfPayloads[bank] != 0)
            return true;
    }
    return false;
}

bool ControllerCore::hasPendingRequests(Bank bank)
{
    return (numberOfPayloads[bank] != 0) ? true : false;
}

bool ControllerCore::bankIsBusy(Bank bank)
{
    sc_time time = sc_time_stamp();
    ScheduledCommand lastScheduledCommand = state->getLastScheduledCommand(bank);

    if (lastScheduledCommand.isNoCommand())
        return false;
    else if (lastScheduledCommand.commandIsIn( { Command::Write, Command::Read })) {
        // Read and writes can overlap, so the bank should not be busy during a rd/wr
        return (time < lastScheduledCommand.getStart());
    }
    else if (lastScheduledCommand.commandIsIn( { Command::WriteA, Command::ReadA, Command::PreB, Command::Precharge, Command::PrechargeAll, Command::ActB, Command::Activate })) {
        return (time < lastScheduledCommand.getEnd());
    }
    else if (lastScheduledCommand.getCommand() == Command::AutoRefresh) {
        return (time < lastScheduledCommand.getEnd());
    } else if (lastScheduledCommand.commandIsIn( { Command::SREFX, Command::PDNPX, Command::PDNAX, Command::SREF, Command::PDNP,
                                                   Command::PDNA
                                                 })) {
        return false;
    }
    else {
        SC_REPORT_FATAL("Core", "Last command unkown");
        return false;
    }
}

const std::vector<Bank> &ControllerCore::getBanks()
{
    static std::vector<Bank> banks;

    if (banks.size() == 0) {
        for (unsigned int i = 0; i < config.memSpec.NumberOfBanks; i++) {
            banks.push_back(Bank(i));
        }
    }

    return banks;
}

std::vector<Bank> ControllerCore::getFreeBanks()
{
    std::vector<Bank> freeBanks;
    for (Bank bank : getBanks()) {
        if (!bankIsBusy(bank))
            freeBanks.push_back(bank);
    }
    return freeBanks;
}

ICommandChecker &ControllerCore::getCommandChecker(Command command)
{
    return *getElementFromMap(commandChecker, command);
}

void ControllerCore::printDebugMessage(string message)
{
    DebugManager::getInstance().printDebugMessage(this->name(), message);
}

