//
// Copyright (c) 2010-2025 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
#include "renode_bus.h"
#include "communication/socket_channel.h"
#include "src/renode.h"
static RenodeAgent* renodeAgent;

#define IO_THREADS 1

//=================================================
// RenodeAgent
//=================================================

RenodeAgent::RenodeAgent() { }

void RenodeAgent::addBus(BaseBus* bus)
{
    bus->setAgent(this);
    if(targetInterfaces.empty())
    {
        firstInterface = bus;
    }
}
void RenodeAgent::addBus(BaseTargetBus* bus)
{
    addBus((BaseBus*)bus);
    targetInterfaces.push_back(std::unique_ptr<BaseTargetBus>(bus));
}

void RenodeAgent::addBus(BaseInitiatorBus* bus)
{
    addBus((BaseBus*)bus);
    initatorInterfaces.push_back(std::unique_ptr<BaseInitiatorBus>(bus));
}

void RenodeAgent::writeToBus(int width, uint64_t addr, uint64_t value)
{
    try {
        targetInterfaces[0]->write(width, addr, value);
        communicationChannel->sendMain(Protocol(ok, 0, 0));
    }
    catch(const char* msg) {
        log(LOG_LEVEL_ERROR, msg);
        communicationChannel->sendMain(Protocol(error, 0, 0));
    }
}

void RenodeAgent::readFromBus(int width, uint64_t addr)
{
    try {
        uint64_t readValue = targetInterfaces[0]->read(width, addr);
        communicationChannel->sendMain(Protocol(readRequest, addr, readValue));
    }
    catch(const char* msg) {
        log(LOG_LEVEL_ERROR, msg);
        communicationChannel->sendMain(Protocol(error, 0, 0));
    }
}

void RenodeAgent::pushByteToAgent(uint64_t addr, uint8_t value)
{
    pushToAgent(pushByte, addr, value);
}

void RenodeAgent::pushWordToAgent(uint64_t addr, uint16_t value)
{
    pushToAgent(pushWord, addr, value);
}

void RenodeAgent::pushDoubleWordToAgent(uint64_t addr, uint32_t value)
{
    pushToAgent(pushDoubleWord, addr, value);
}

uint64_t RenodeAgent::requestDoubleWordFromAgent(uint64_t addr)
{
    return requestFromAgent(getDoubleWord, addr);
}

void RenodeAgent::pushToAgent(Action action, uint64_t addr, uint64_t value)
{
    communicationChannel->sendSender(Protocol(action, addr, value));
    Protocol* received = communicationChannel->receive();
    while (received->actionId != pushConfirmation)
    {
        handleRequest(received);
        delete received;
        received = communicationChannel->receive();
    }
    delete received;
}

uint64_t RenodeAgent::requestFromAgent(Action action, uint64_t addr)
{
    communicationChannel->sendSender(Protocol(action, addr, 0));
    Protocol* received = communicationChannel->receive();
    while (received->actionId != writeRequest)
    {
        handleRequest(received);
        delete received;
        received = communicationChannel->receive();
    }
    auto result = received->value;
    delete received;
    return result;
}

void RenodeAgent::tick(bool countEnable, uint64_t steps)
{
    for(auto& b : targetInterfaces)
        b->tick(countEnable, steps);
    for(auto& b : initatorInterfaces)
        b->tick(countEnable, steps);
}

void RenodeAgent::timeoutTick(uint8_t* signal, uint8_t expectedValue, int timeout)
{
    for(auto& b : targetInterfaces)
        b->timeoutTick(signal, expectedValue, timeout);
    for(auto& b : initatorInterfaces)
        b->timeoutTick(signal, expectedValue, timeout);
}

void RenodeAgent::reset()
{
    for(auto& b : targetInterfaces)
        b->reset();
    for(auto& b : initatorInterfaces)
        b->reset();
}

void RenodeAgent::fatalError()
{
    communicationChannel->sendSender(Protocol(error, 0, 0));
    handleDisconnect();
}

void RenodeAgent::handleCustomRequestType(Protocol* message)
{
    log(LOG_LEVEL_WARNING, "Unhandled request type: %d", message->actionId);
}

void RenodeAgent::log(int level, const char* fmt, ...)
{
    char s[1024];
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(s, 1024, fmt, ap);
    communicationChannel->log(level, s);
    va_end(ap);
}

Protocol* RenodeAgent::receive()
{
    return communicationChannel->receive();
}

void RenodeAgent::registerInterrupt(uint8_t *irq, uint8_t irq_addr)
{
    if (irq == nullptr) {
        log(LOG_LEVEL_ERROR, "The irq address cannot be null");
        communicationChannel->sendMain(Protocol(error, 0, 0));
        return;
    }

    interrupts.push_back({irq, 0, irq_addr});
}

void RenodeAgent::handleInterrupts(void)
{
    for (unsigned long i = 0; i < interrupts.size(); i++) {
        if (*interrupts[i].irq != interrupts[i].prev_irq) {
            communicationChannel->sendSender(Protocol(interrupt, interrupts[i].irq_addr, *interrupts[i].irq));
            interrupts[i].prev_irq = *interrupts[i].irq;
        }
    }
}

void RenodeAgent::connect(int receiverPort, int senderPort, const char* address)
{
    renodeAgent = this;
    SocketCommunicationChannel* channel = new SocketCommunicationChannel();
    communicationChannel = channel;
    channel->connect(receiverPort, senderPort, address);
}

void RenodeAgent::connectNative()
{
    renodeAgent = this;
    communicationChannel = new NativeCommunicationChannel;
}

void RenodeAgent::simulate()
{
    Protocol* result;
    reset();

    while(communicationChannel->isConnected()) {
        result = receive();
        handleRequest(result);
        delete result;
    }
}

void RenodeAgent::handleRequest(Protocol* request)
{
    switch(request->actionId) {
        case invalidAction:
            break;
        case tickClock:
        {
            long ticks = request->value - firstInterface->tickCounter;
            if(ticks < 0) {
                firstInterface->tickCounter -= request->value;
            }
            else {
                tick(false, ticks);
            }
            firstInterface->tickCounter = 0;
            communicationChannel->sendSender(Protocol(tickClock, 0, 0));
        }
            break;
        case writeRequestByte:
            writeToBus(1, request->addr, request->value);
            break;
        case writeRequestWord:
            writeToBus(2, request->addr, request->value);
            break;
        case writeRequest: // due to historical reasons, writeRequest defaults to 32bits
        case writeRequestDoubleWord:
            writeToBus(4, request->addr, request->value);
            break;
        case writeRequestQuadWord:
            writeToBus(8, request->addr, request->value);
            break;
        case readRequestByte:
            readFromBus(1, request->addr);
            break;
        case readRequestWord:
            readFromBus(2, request->addr);
            break;
        case readRequest: // due to historical reasons, writeRequest defaults to 32bits
        case readRequestDoubleWord:
            readFromBus(4, request->addr);
            break;
        case readRequestQuadWord:
            readFromBus(8, request->addr);
            break;
        case resetPeripheral:
            reset();
            break;
        case disconnect:
            handleDisconnect();
            break;
        default:
            handleCustomRequestType(request);
            break;
    }
}

void RenodeAgent::handleDisconnect()
{
    SocketCommunicationChannel* channel;
    if((channel = dynamic_cast<SocketCommunicationChannel*>(communicationChannel)) != nullptr) {
        communicationChannel->sendSender(Protocol(ok, 0, 0));
        channel->disconnect();
    }
}

//=================================================
// NativeCommunicationChannel
//=================================================

extern void handleMainMessage(void* ptr);
extern void handleSenderMessage(void* ptr);
extern void receive(void* ptr);

EXTERNAL_AS(void, HandleMainMessage, handleMainMessage, voidptr);
EXTERNAL_AS(void, HandleSenderMessage, handleSenderMessage, voidptr);
EXTERNAL_AS(void, Receive, receive, voidptr);

void NativeCommunicationChannel::sendMain(const Protocol message)
{
    handleMainMessage(new Protocol(message));
}

void NativeCommunicationChannel::sendSender(const Protocol message)
{
    handleSenderMessage(new Protocol(message));
}

void NativeCommunicationChannel::log(int logLevel, const char* data)
{
    handleSenderMessage(new Protocol(logMessage, strlen(data) + 1, (uint64_t)data));
    handleSenderMessage(new Protocol(logMessage, 0, logLevel));
}

bool NativeCommunicationChannel::isConnected()
{
    return true;
}

Protocol* NativeCommunicationChannel::receive()
{
    Protocol* message = new Protocol;
    ::receive(message);
    return message;
}

//=================================================
// Functions exported to Renode
//=================================================

void initialize_native()
{
    renodeAgent = Init();
}

void handle_request(Protocol* request)
{
    renodeAgent->handleRequest(request);
}

void reset_peripheral()
{
    renodeAgent->reset();
}
