/*
 * Copyright (c) 2018, 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:
 *    Matthias Jung
 */

#include "grp.h"

// GRP just reorders w.r.t. read write grouping, however is not aware of the
// row buffer. For a row buffer aware grouper refer to FR_FCFS_GRP.
// TODO: what is missed is a check if the buffers are full. This will only work
// if we have buffers with a fixed size (Prado's future patch).

std::pair<Command, gp *> GRP::getNextRequest(Bank bank)
{
    // If the bank is empty we do nothing:
    if (buffer[bank].empty()) {
        return pair<Command, tlm::tlm_generic_payload *>(Command::NOP, NULL);
    }

    // If we are in write mode we should check if we should switch to read mode
    // because there are no writes anymore in the buffer.
    if (readMode == false) {
        if (getNumberOfRequest(tlm::TLM_WRITE_COMMAND) == 0) {
            readMode = true;
        }
    } else { // If we are in read mode but all reads are served we switch to write
        if (getNumberOfRequest(tlm::TLM_READ_COMMAND) == 0) {
            readMode = false;
        }
    }

    // Now lets search for read and write commands. However keep in mind that
    // readMode is a shared variable for all the banks!
    if (readMode == true) {
        // 1. Seach for read hit:
        for (auto it = buffer[bank].begin(); it != buffer[bank].end(); it++) {
            gp *read = *it;

            if (read->get_command() == tlm::TLM_READ_COMMAND) {
                // If there is a row hit:
                if (DramExtension::getRow(read)
                        == controllerCore.getRowBufferStates()
                        .getRowInRowBuffer(bank)) {
                    if (hazardDetection(bank, it) == false) {
                        buffer[bank].erase(it);
                        printDebugMessage("Read Hit found");
                        return pair<Command, gp *>(getReadWriteCommand(*read),
                                                   read);
                    } else {
                        // If there was a hazard, switch the mode and try again:
                        readMode = false;
                        return getNextRequest(bank);
                    }
                } else { // if there is a row miss:
                    if (hazardDetection(bank, it) == false) {
                        printDebugMessage("Read miss found");
                        return pair<Command, gp *>(getNextCommand(read), read);
                    } else {
                        // If there was a hazard, switch the mode and try again:
                        readMode = false;
                        return getNextRequest(bank);
                    }
                }
            }
        }

    } else { // write mode:
        // 3. Search for write hit:
        for (auto it = buffer[bank].begin(); it != buffer[bank].end(); it++) {
            gp *write = *it;

            if (write->get_command() == tlm::TLM_WRITE_COMMAND) {
                // If there is a row hit:
                if (DramExtension::getRow(write)
                        == controllerCore.getRowBufferStates()
                        .getRowInRowBuffer(bank)) {
                    buffer[bank].erase(it);
                    printDebugMessage("Write Hit found");
                    return pair<Command, gp *>(getReadWriteCommand(*write),
                                               write);
                } else {
                    printDebugMessage("Write miss found");
                    return pair<Command, gp *>(getNextCommand(write), write);
                }
            }
        }
    }

    // If nothing was found we check the other banks before we switch the mode:
    pair<Command, gp *> other(Command::NOP, NULL);
    unsigned int B = Configuration::getInstance().memSpec.NumberOfBanks;

    for (unsigned int i = 1; i < B; i++) {
        Bank nextBank((bank.ID() + i) % B);
        ctrl->scheduleNextFromScheduler(nextBank);
    }

    // If nothing was found in the current mode, switch the mode and try again:
    // FIXME: this is in my opinion not so clever yet, because we switch maybe
    // even though there are still reads/writes request on other banks ...
    readMode = !readMode;
    return getNextRequest(bank);

    reportFatal("GRP", "Never should go here ...");
}

// There is a hazard if a read is found which will be scheduled before a write
// to the same column and the same row of the same bank:
bool GRP::hazardDetection(Bank bank, std::deque<gp *>::iterator ext)
{
    gp *read = *ext;

    //for(unsigned long i=0; i < id; i++)
    for (auto it = buffer[bank].begin(); it != ext; it++) {
        gp *write = *it;
        if (write->get_command() == tlm::TLM_WRITE_COMMAND) {
            if ((DramExtension::getExtension(read).getColumn()
                    == DramExtension::getExtension(write).getColumn())
                    && (DramExtension::getExtension(read).getRow()
                        == DramExtension::getExtension(write).getRow())) {
                printDebugMessage("Hazard Detected");
                return true;
            }
        }
    }
    return false;
}

// Estimate the number of writes/reads in all bank buffers:
unsigned int GRP::getNumberOfRequest(tlm::tlm_command cmd)
{
    unsigned int numberOfRequests = 0;
    for (unsigned int i = 0;
            i < Configuration::getInstance().memSpec.NumberOfBanks;
            i++) {
        for (auto it = buffer[i].begin(); it != buffer[i].end(); it++) {
            gp *trans = *it;
            if (trans->get_command() == cmd) {
                numberOfRequests++;
            }
        }
    }

    return numberOfRequests;
}

void GRP::printDebugMessage(std::string message)
{
    DebugManager::getInstance().printDebugMessage("FR_FCFS_GRP", message);
}
