/*
 * 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
 *    Robert Gernhardt
 *    Matthias Jung
 *    Éder F. Zulian
 */

#include "FifoStrict.h"

void FifoStrict::storeRequest(tlm::tlm_generic_payload *payload)
{
    Bank bank = DramExtension::getExtension(payload).getBank();
    buffer.push_back(std::pair<Bank, tlm::tlm_generic_payload *>(bank, payload));
}

std::pair<Command, tlm::tlm_generic_payload *> FifoStrict::getNextRequest(
    Bank bank)
{
    if (!buffer.empty()) {

        if (buffer.front().first == bank) {
            // The next request in the FIFO is for the bank passed as parameter

            tlm::tlm_generic_payload *payload = buffer.front().second;

            // For a given request (a given payload) one or more commands will
            // be sent to the DRAM in order to process it.
            //
            // getNextRequest() may be called more than once for the same
            // enqueued request until the appropriate sequence of commands is
            // sent to the DRAM.
            //
            // Every time getNextRequest() it is called it calls
            // getNextCommand() that returns the suitable command to be sent
            // to the DRAM.
            //
            // getNextCommand() returns the proper command based on the
            // internal status of the DRAM.
            //
            // In the worst case getNextCommand() need to be called three
            // times for a given element in the requests queue: first for
            // precharge, second for activate and finally for read or write
            // (accordingly the nature of the request). In contrast, for the
            // case of an already open row (due to a previous request) the
            // command itself could be directly issued.
            //
            Command command = IScheduler::getNextCommand(*payload);

            if (commandIsIn(command, {Command::Read, Command::Write, Command::ReadA, Command::WriteA})) {
                buffer.pop_front();

                // Check if the next transaction is a blocked read or write

                if (!buffer.empty()) {
                    tlm::tlm_generic_payload *p = buffer.front().second;
                    Command cmd = IScheduler::getNextCommand(*p);
                    if (commandIsIn(cmd, {Command::Read, Command::Write, Command::ReadA, Command::WriteA})) {
                        Bank b = DramExtension::getBank(p);
                        controller.blockedRequests.push(b);
                    }
                }
            }

            return pair<Command, tlm::tlm_generic_payload *>(command, payload);

        } else {
            // The next request in the FIFO is NOT for the bank passed as parameter.

            // Search for the next request related to the bank passed as parameter.
            for (auto req = buffer.begin(); req != buffer.end(); req++) {
                if (req->first == bank) {
                    // Found a request to this bank in the queue
                    tlm::tlm_generic_payload *payload = req->second;

                    // Get the appropriate command to be sent to the DRAM
                    // regarding this request.
                    //
                    // Commands other than read and write will be issued.
                    // Reads and writes will not be issued since this
                    // scheduler executes all read and writes in a strict
                    // order.
                    Command command = getNextCommand(*payload);
                    if (commandIsIn(command, {Command::Read, Command::Write, Command::ReadA, Command::WriteA})) {
                        // Reads and writes must be executed in order. Then if
                        // the next command for this request is read or write
                        // NOP will be returned and no operation will be
                        // performed.
                        return pair<Command, tlm::tlm_generic_payload *>(Command::NOP, NULL);
                    }
                    else {
                        // Commands other than read and write are issued normally.
                        return pair<Command, tlm::tlm_generic_payload *>(command, payload);
                    }
                }
            }
        }
    }

    return pair<Command, tlm::tlm_generic_payload *>(Command::NOP, NULL);
}

gp *FifoStrict::getPendingRequest(Bank /*bank*/)
{
    return NULL;
}
