/*******************************************************************************
 * Copyright (C) 2024 Juan Neyra
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 ******************************************************************************/
#include "router_cs.h"
#include "ratatoskrUtils/utils/Structures.h"

TlmRouterCS::TlmRouterCS(sc_module_name name, uint8_t rout_pos[3], 
                                        uint8_t max_pos[3]):
                TlmRouter(name, rout_pos, max_pos) {
}

TlmRouterCS::~TlmRouterCS(){
}

void TlmRouterCS::initialize(){
    TlmRouter::initialize();
    cout<<"Inherited function called";
    for(int link=0; link<NUM_LINKS; link++){
        credit_counter[link] = NUM_CREDITS_CS;
        auto_rout_map[link] = Direction::invalid;
    }
}

Dir TlmRouterCS::routing(int link, tlm_gp& trans){
    return auto_rout_map[link];
}

void TlmRouterCS::set_auto_router_map(int link, Dir dir){
    auto_rout_map[link] = dir;
}

Dir TlmRouterCS::get_auto_router_map(int link){
    return auto_rout_map[link];
}

void TlmRouterCS::send_begin_req(int link, tlm_gp& trans, int dest_link){
    tlm_gp* new_trans = build_transaction(trans, dest_link);

    // send transaction in socket
    tlm_phase phase = BEGIN_REQ;
    sc_time delay = sc_time(REQ_INIT_DELAY, UNITS_DELAY);

    tlm_sync_enum status;
    status = (*init_socket[dest_link])->nb_transport_fw(
                                *new_trans, phase, delay);
    curr_req[dest_link] = new_trans;
    credit_counter[dest_link]--;
    // react to result
    if(status == TLM_COMPLETED) {
        log_error(link, "Request completed prematurely");
        curr_req[dest_link] = 0;
        check_transaction(link, *new_trans);
        credit_counter[dest_link]++;
        new_trans->release();
    }
}


void TlmRouterCS::configure_router(tlm_gp& trans){
    // get link and destination
    unsigned char* data = trans.get_data_ptr();
    int link = *data && 3;
    int destination = *data >> 2;
    // set auto router map
    set_auto_router_map(link, Dir(destination));
}

/******************* INIT SOCKET FUNCTIONS ********************/
tlm_sync_enum TlmRouterCS::nb_transport_bw_cb(int id, tlm_gp& trans, 
                                tlm_phase& phase, sc_time& delay) {
    log_info(id, "Backward transport callback start");
    return TLM_ACCEPTED;
}

/******************* TARGET SOCKET FUNCTIONS ********************/
void TlmRouterCS::target_peq_cb(tlm_gp& trans, const tlm_phase& phase){
    int link = DIR::getOppositeDir(get_link_from_extension(trans));
    log_info(link, "Target PEQ callback start");
    log_info(link, "Phase "+string(phase.get_name())+" received");
    switch (phase) {
        case BEGIN_REQ:
            trans.acquire();
            send_end_req(link, trans);
            break;
        default:
            if(phase == INTERNAL_PROC_PHASE){
                switching(link, trans);
            }
            else if(phase == CONF_ROUT_PHASE){
                configure_router(trans);
            }
            else{
                log_error(link,
                    "Illegal transaction phase received by target");
            }
            break;
    }
}

tlm_sync_enum TlmRouterCS::send_end_req(int link, tlm_gp& trans){
    log_info(link, "Send end request start");
    // Queue the acceptance and the response with the appropriate latency
    sc_time delay = sc_time(REQ_END_DELAY, UNITS_DELAY);
    tlm_phase phase = END_REQ;
    tlm_sync_enum status = (*target_socket[link])->nb_transport_bw(
                                        trans, phase, delay);
    if (status == TLM_COMPLETED) {
        log_warn(link,"Request completed, no response to be send");
        trans.release();
        return status;
    }
    delay = SC_ZERO_TIME; // no processing in circuit switching routers
    // Queue internal event to mark beginning of response
    int type = get_type_from_extension(trans);
    phase = (type == TYPE_STREAM) ? INTERNAL_PROC_PHASE : 
                                             CONF_ROUT_PHASE;
    target_peq.notify(trans, phase, delay);

    return status;
}

void TlmRouterCS::switching(int link, tlm_gp& trans){
    log_info(link, "Switching start");
    trans.set_response_status(TLM_OK_RESPONSE);
    // arbitration and send message to next router
    Dir destination = routing(link, trans);
    bool data_sent;
    if (destination == Dir::num_dirs) {
        data_sent = true;
        check_transaction(link, trans);
        // change to fatal?
        log_warn(link, "Routing failed. Message couldn't be delivered");
    }
    else{
        data_sent = send_data(link, destination, trans);
    }
    // validate pending data sent
    if (!data_sent) {
        log_error(link,"Attempt to have pending data in same destination");
    }
}