#include "FlashErase.hpp"

#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp"

#include "src/Services/StringService.hpp"
#include "src/Logger/Logger.hpp"
#include "src/Exceptions/Exception.hpp"

namespace DebugServer::Gdb::RiscVGdb::CommandPackets
{
    using Services::TargetControllerService;

    using ResponsePackets::ErrorResponsePacket;
    using ResponsePackets::OkResponsePacket;

    using namespace Exceptions;

    FlashErase::FlashErase(const RawPacket& rawPacket)
        : CommandPacket(rawPacket)
    {
        using Services::StringService;

        if (rawPacket.size() < 8) {
            throw Exception{"Invalid packet length"};
        }

        const auto command = std::string{this->data.begin() + 12, this->data.end()};

        const auto delimiterPos = command.find_first_of(',');
        if (delimiterPos == std::string::npos) {
            throw Exception{"Invalid packet"};
        }

        this->startAddress = StringService::toUint32(command.substr(0, delimiterPos), 16);
        this->bytes = StringService::toUint32(command.substr(delimiterPos + 1), 16);
    }

    void FlashErase::handle(
        Gdb::DebugSession& debugSession,
        const RiscVGdbTargetDescriptor& gdbTargetDescriptor,
        const Targets::TargetDescriptor& targetDescriptor,
        const Targets::TargetState& targetState,
        TargetControllerService& targetControllerService
    ) {
        Logger::info("Handling FlashErase packet");

        try {
            const auto segmentDescriptorOpt = gdbTargetDescriptor.systemAddressSpaceDescriptor.getContainingMemorySegmentDescriptor(
                this->startAddress
            );

            if (!segmentDescriptorOpt.has_value()) {
                throw Exception{"Invalid command - no containing memory segment found for the given start address"};
            }

            const auto& segmentDescriptor = segmentDescriptorOpt->get();
            if (!segmentDescriptor.programmingModeAccess.writeable) {
                throw Exception{"Memory segment (\"" + segmentDescriptor.name + "\") not writable in programming mode"};
            }

            Logger::debug("Erase segment key: " + Services::StringService::formatKey(segmentDescriptor.key));

            targetControllerService.enableProgrammingMode();

            // We don't erase a specific address range - we just erase the entire segment
            targetControllerService.eraseMemory(
                gdbTargetDescriptor.systemAddressSpaceDescriptor,
                segmentDescriptor
            );

            debugSession.connection.writePacket(OkResponsePacket{});

        } catch (const Exception& exception) {
            Logger::error("Failed to erase flash memory - " + exception.getMessage());
            debugSession.programmingSession.reset();

            try {
                targetControllerService.disableProgrammingMode();

            } catch (const Exception& exception) {
                Logger::error("Failed to disable programming mode - " + exception.getMessage());
            }

            debugSession.connection.writePacket(ErrorResponsePacket{});
        }
    }
}
