#ifndef EXAMPLEINITIATOR
#define EXAMPLEINITIATOR

#include <stdlib.h>
#include <iostream>

#include "MemoryManager.h"
#include "../common/dramExtension.h"
#include "TracePlayer.h"

using namespace std;

struct ExampleInitiator: sc_module {
    // TLM-2 socket, defaults to 32-bits wide, base protocol
    tlm_utils::simple_initiator_socket<ExampleInitiator> socket;

    SC_CTOR(ExampleInitiator)
        : socket("socket")  // Construct and name socket
        , request_in_progress(0)
        , m_peq(this, &ExampleInitiator::peq_cb)
    {
        socket.register_nb_transport_bw(this, &ExampleInitiator::nb_transport_bw);

        SC_THREAD(thread_process);
    }

    void thread_process()
    {
        tlm::tlm_generic_payload *trans;
        tlm::tlm_phase phase;
        sc_time delay;

        dump_mem();
        init_mem();
        dump_mem();

        for (int i = 0; i < 64; i++)
            data[i] = 0x55;

        // Generate 2 write transactions
        for (int i = 0; i < 2; i++) {
            int adr = i * 64;

            tlm::tlm_command cmd = tlm::TLM_WRITE_COMMAND;

            // Grab a new transaction from the memory manager
            trans = m_mm.allocate();
            trans->acquire();

            trans->set_command( cmd );
            trans->set_address( adr );
            trans->set_data_ptr( reinterpret_cast<unsigned char *>(&data[0]) );
            trans->set_data_length( 64 );
            trans->set_streaming_width( 4 );
            trans->set_byte_enable_ptr( 0 );
            trans->set_dmi_allowed( false );
            trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE );

            // ExampleInitiator must honor BEGIN_REQ/END_REQ exclusion rule
            if (request_in_progress)
                wait(end_request_event);
            request_in_progress = trans;
            phase = tlm::BEGIN_REQ;

            // Timing annotation models processing time of initiator prior to call
            delay = sc_time(100000, SC_PS);

            cout << "Address " << hex << adr << " new, cmd=" << (cmd ? "write" : "read")
                 << ", data=" << hex << data[0] << " at time " << sc_time_stamp()
                 << " in " << name() << endl;

            GenerationExtension *genExtension = new GenerationExtension(sc_time_stamp());
            trans->set_auto_extension(genExtension);


            // Non-blocking transport call on the forward path
            tlm::tlm_sync_enum status;
            status = socket->nb_transport_fw( *trans, phase, delay );

            // Check value returned from nb_transport_fw
            if (status == tlm::TLM_UPDATED) {
                // The timing annotation must be honored
                m_peq.notify( *trans, phase, delay );
            } else if (status == tlm::TLM_COMPLETED) {
                // The completion of the transaction necessarily ends the BEGIN_REQ phase
                request_in_progress = 0;

                // The target has terminated the transaction
                check_transaction( *trans );

                // Allow the memory manager to free the transaction object
                trans->release();
            }

            wait( sc_time(500, SC_NS) );

            dump_mem();
        }

        wait( sc_time(500, SC_NS) );
        sc_stop();
    }

    void init_mem()
    {
        unsigned char buffer[64];
        for (int i = 0; i < 64; i++) {
            buffer[i] = 0xff;
        }

        for (int addr = 0; addr < 128; addr += 64) {
            tlm::tlm_generic_payload trans;
            trans.set_command( tlm::TLM_WRITE_COMMAND );
            trans.set_address( addr );
            trans.set_data_ptr( buffer );
            trans.set_data_length( 64 );

            socket->transport_dbg( trans );
        }
    }

    void dump_mem()
    {
        for (int addr = 0; addr < 128; addr += 64) {
            unsigned char buffer[64];
            tlm::tlm_generic_payload trans;
            trans.set_command( tlm::TLM_READ_COMMAND );
            trans.set_address( addr );
            trans.set_data_ptr( buffer );
            trans.set_data_length( 64 );

            socket->transport_dbg( trans );

            cout << "\nMemory dump\n";
            for (int i = 0; i < 64; i++)
                cout << "mem[" << addr + i << "] = " << hex << (int)buffer[i] << endl;
        }
    }

    // TLM-2 backward non-blocking transport method

    virtual tlm::tlm_sync_enum nb_transport_bw( tlm::tlm_generic_payload &trans,
                                                tlm::tlm_phase &phase, sc_time &delay )
    {
        m_peq.notify( trans, phase, delay );
        return tlm::TLM_ACCEPTED;
    }

    // Payload event queue callback to handle transactions from target
    // Transaction could have arrived through return path or backward path

    void peq_cb(tlm::tlm_generic_payload &trans, const tlm::tlm_phase &phase)
    {
        if (phase == tlm::END_REQ || (&trans == request_in_progress
                                      && phase == tlm::BEGIN_RESP)) {
            // The end of the BEGIN_REQ phase
            request_in_progress = 0;
            end_request_event.notify();
        } else if (phase == tlm::BEGIN_REQ || phase == tlm::END_RESP)
            SC_REPORT_FATAL("TLM-2", "Illegal transaction phase received by initiator");

        if (phase == tlm::BEGIN_RESP) {
            check_transaction( trans );

            // Send final phase transition to target
            tlm::tlm_phase fw_phase = tlm::END_RESP;
            sc_time delay = sc_time(60000, SC_PS);
            socket->nb_transport_fw( trans, fw_phase, delay );
            // Ignore return value

            // Allow the memory manager to free the transaction object
            trans.release();
        }
    }

    // Called on receiving BEGIN_RESP or TLM_COMPLETED
    void check_transaction(tlm::tlm_generic_payload &trans)
    {
        if ( trans.is_response_error() ) {
            char txt[100];
            sprintf(txt, "Transaction returned with error, response status = %s",
                    trans.get_response_string().c_str());
            SC_REPORT_ERROR("TLM-2", txt);
        }

        tlm::tlm_command cmd = trans.get_command();
        sc_dt::uint64    adr = trans.get_address();
        int             *ptr = reinterpret_cast<int *>( trans.get_data_ptr() );

        cout << hex << adr << " check, cmd=" << (cmd ? "write" : "read")
             << ", data=" << hex << *ptr << " at time " << sc_time_stamp()
             << " in " << name() << endl;

        if (cmd == tlm::TLM_READ_COMMAND)
            assert( *ptr == -int(adr) );
    }

    MemoryManager m_mm;
    unsigned char data[64];
    tlm::tlm_generic_payload *request_in_progress;
    sc_event end_request_event;
    tlm_utils::peq_with_cb_and_phase<ExampleInitiator> m_peq;
};

#endif // EXAMPLEINITIATOR
