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

#ifndef CONTROLLERWRAPPER_H_
#define CONTROLLERWRAPPER_H_

#include <map>
#include <string>
#include <vector>
#include <iostream>

#include <systemc>
#include <tlm>
#include <tlm_utils/peq_with_cb_and_phase.h>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>

#include "../common/dramExtension.h"
#include "../common/DebugManager.h"
#include "../common/protocol.h"
#include "../common/TlmRecorder.h"
#include "../common/Utils.h"
#include "core/configuration/Configuration.h"
#include "core/configuration/MemSpec.h"
#include "Command.h"
#include "core/ControllerCore.h"
#include "ControllerState.h"
#include "IController.h"
#include "core/powerdown/IPowerDownManager.h"
#include "core/scheduling/ScheduledCommand.h"
#include "core/scheduling/Trigger.h"
#include "core/TimingCalculation.h"
#include "scheduler/Fifo.h"
#include "scheduler/grp.h"
#include "scheduler/FifoStrict.h"
#include "scheduler/Fr_Fcfs.h"
#include "scheduler/Fr_Fcfs_read_priority.h"
#include "scheduler/Fr_Fcfs_grouper.h"
#include "scheduler/SMS.h"
#include "scheduler/IScheduler.h"

using namespace std;
using namespace tlm;

DECLARE_EXTENDED_PHASE(PendingRequest);

class Controller : public sc_module, public IController
{
public:
    Controller(sc_module_name /*name*/) :
        frontendPEQ(this, &Controller::frontendPEQCallback), dramPEQ(this,
                                                                     &Controller::dramPEQCallback), controllerCorePEQ(this,
                                                                             &Controller::controllerCorePEQCallback),
        debugManager(DebugManager::getInstance())
    {
        controllerCore = new ControllerCore("core", *this, numberOfPayloadsInSystem);
        buildScheduler();
        iSocket.register_nb_transport_bw(this, &Controller::nb_transport_bw);
        tSocket.register_nb_transport_fw(this, &Controller::nb_transport_fw);
        tSocket.register_transport_dbg(this, &Controller::transport_dbg);
    }

    virtual ~Controller()
    {
        delete controllerCore;
        delete scheduler;
    }

    void terminateSimulation();

    sc_time getIdleTime();
    sc_time getEndTime();
    sc_time getStartTime();

    // ------- CONTROLLER CORE ---------
    virtual void send(const ScheduledCommand &command,
                      tlm_generic_payload &payload) override;
    virtual void send(Trigger trigger, sc_time time,
                      tlm_generic_payload &payload) override;

    tlm_utils::simple_initiator_socket<Controller> iSocket;
    tlm_utils::simple_target_socket<Controller> tSocket;
    unsigned int getTotalNumberOfPayloadsInSystem();
    void scheduleNextFromScheduler(Bank bank) override;

    static unsigned int ControllerThreadId()
    {
        return controllerThreadId;
    }

protected:
    void buildScheduler();
    void payloadEntersSystem(tlm_generic_payload &payload);
    void payloadLeavesSystem(tlm_generic_payload &payload);

    // --- FRONTEND ------
    virtual tlm_sync_enum nb_transport_fw(tlm_generic_payload &payload,
                                          tlm_phase &phase, sc_time &fwDelay);
    virtual unsigned int transport_dbg(tlm::tlm_generic_payload &trans);
    void frontendPEQCallback(tlm_generic_payload &payload, const tlm_phase &phase);
    virtual void sendToFrontend(tlm_generic_payload &payload,
                                const tlm_phase &phase, const sc_time &delay);

    // --- DRAM ------
    virtual tlm_sync_enum nb_transport_bw(tlm_generic_payload &payload,
                                          tlm_phase &phase, sc_time &bwDelay);
    void dramPEQCallback(tlm_generic_payload &payload, const tlm_phase &phase);
    void sendToDram(tlm_generic_payload &payload, const tlm_phase &phase,
                    const sc_time &delay);

    // ------- CONTROLLER CORE ---------
    virtual void schedule(Command command, gp &payload);
    void controllerCorePEQCallback(tlm_generic_payload &payload,
                                   const tlm_phase &phase);

    // Helpers TODO move them
    void printDebugMessage(string message);
    bool containsPhase(tlm_phase phase, std::vector<tlm_phase> phases);

    ControllerCore *controllerCore;
    //Scheduler* scheduler;
    IScheduler *scheduler;
    std::map<Bank, int> numberOfPayloadsInSystem;
    std::vector<gp * > refreshCollisionRequets;
    tlm::tlm_generic_payload *backpressure = NULL;
    std::queue<gp * > responseQueue;

    tlm_utils::peq_with_cb_and_phase<Controller> frontendPEQ;
    tlm_utils::peq_with_cb_and_phase<Controller> dramPEQ;
    tlm_utils::peq_with_cb_and_phase<Controller> controllerCorePEQ;

    DebugManager &debugManager;

    // Bandwidth realted:
    sc_time idleStart;
    bool idleState = false;
    sc_time idleTime;
    sc_time endTime;
    sc_time startTime;
    int startTimeSet = false;
    void startBandwidthIdleCollector();
    void endBandwidthIdleCollector();

    // SystemC related:
    virtual void end_of_simulation() override;

    static const unsigned int controllerThreadId = INT_MAX;
};

#endif /* CONTROLLERWRAPPER_H_ */

