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

#ifndef SIMPLEARBITER_H
#define SIMPLEARBITER_H

#include "IArbiter.h"
#include "../common/xmlAddressdecoder.h"
#include "../common/dramExtension.h"
#include "../controller/core/TimingCalculation.h"

using namespace std;
using namespace tlm;

// Annotated References [X,Y] --> Please refer to TLM AT Cheat Sheet on README

struct SimpleArbiter: public IArbiter {
public:
    SimpleArbiter(sc_module_name name) : IArbiter(name)
    {
    }

    void setTlmRecorder(TlmRecorder *recorder)
    {
        tlmRecorder = recorder;
    }

    virtual bool isOutputBufferFull(unsigned int /*initiatorSocket*/)
    {
        return false;
    }

    virtual void incrementNumberOfOutputBufferTransactions(unsigned
                                                           int /*initiatorSocket*/)
    {
    }

protected:
    TlmRecorder *tlmRecorder;
    // Initiated by initiator side
    // This function is called when an arbiter's target socket receives a transaction from a device
    virtual tlm_sync_enum nb_transport_fw(int id, tlm_generic_payload &payload,
                                          tlm_phase &phase, sc_time &fwDelay)
    {
        if (phase == BEGIN_REQ) {
            payload.acquire();
            // adjust address offset, e.g. for gem5 simulation
            payload.set_address(payload.get_address() -
                                Configuration::getInstance().AddressOffset);

            // In the begin request phase the socket ID is appended to the payload.
            // It will extracted from the payload and used later.
            appendDramExtension(id, payload);

            tlmRecorder->recordArbiterPhase(payload, phase, sc_time_stamp() + fwDelay);
            tlmRecorder->recordArbiterPhase(payload, END_REQ, sc_time_stamp() + fwDelay);
            tlmRecorder->recordPhase(payload, phase, sc_time_stamp() + fwDelay);

            // Forward Path [1.0]
            iSocket[getISocketIndex(payload)]->nb_transport_fw(payload, phase, fwDelay);
        } else if (phase == END_RESP) {
            payload.release();
            tlmRecorder->recordArbiterPhase(payload, phase, sc_time_stamp() + fwDelay);
        } else {
            SC_REPORT_FATAL("Arbiter", "Illegal phase received by initiator");
        }

        // 4-Phase Handshake [1.1]
        // 4-Phase Handshake [1.7]
        return TLM_ACCEPTED;
    }

    // Initiated by dram side
    // This function is called when an arbiter's initiator socket receives a transaction from a memory controller
    virtual tlm_sync_enum nb_transport_bw(int /*channelId*/,
                                          tlm_generic_payload &payload, tlm_phase &phase, sc_time &bwDelay)
    {
        tlmRecorder->recordPhase(payload, phase, bwDelay + sc_time_stamp());

        tSocket[DramExtension::getThread(payload).ID()]->nb_transport_bw(payload,
                                                                         TPhase, bwDelay);

        if (phase == BEGIN_RESP) {
            // Early Completion [3.1]
            tlmRecorder->recordPhase(payload, END_RESP, bwDelay + sc_time_stamp());
            tlmRecorder->recordArbiterPhase(payload, BEGIN_RESP, sc_time_stamp() + bwDelay);
            return TLM_COMPLETED;
        }
        // 4-Phase Handshake [1.3]
        return TLM_ACCEPTED;
    }

    virtual unsigned int transport_dbg(int /*id*/, tlm::tlm_generic_payload &trans)
    {
        // adjust address offset:
        trans.set_address(trans.get_address() -
                          Configuration::getInstance().AddressOffset);

        return iSocket[getISocketIndex(trans)]->transport_dbg(trans);
    }

    virtual unsigned int getISocketIndex(tlm_generic_payload &payload)
    {
        return DramExtension::getBank(payload).ID();
    }
};

#endif // SIMPLEARBITER_H
