#include "BasicTCPSocket.h"
#include "DebugService.h"
#include "ObjectRegistryDatabase.h"
#include "StandardParser.h"
#include "StreamString.h"
#include "GlobalObjectsDatabase.h"
#include "RealTimeApplication.h"
#include <assert.h>
#include <stdio.h>

using namespace MARTe;

const char8 * const config_command_text = 
"DebugService = {"
"    Class = DebugService "
"    ControlPort = 8100 "
"    UdpPort = 8101 "
"    StreamIP = \"127.0.0.1\" "
"    MyCustomField = \"HelloConfig\" "
"}"
"App = {"
"    Class = RealTimeApplication "
"    +Functions = {"
"        Class = ReferenceContainer "
"        +GAM1 = {"
"            Class = IOGAM "
"            CustomGAMField = \"GAMValue\" "
"            InputSignals = {"
"                Counter = { DataSource = Timer Type = uint32 Frequency = 1000 PVName = \"PROC:VAR:1\" }"
"                Time = { DataSource = Timer Type = uint32 }"
"            }"
"            OutputSignals = {"
"                Counter = { DataSource = DDB Type = uint32 }"
"                Time = { DataSource = DDB Type = uint32 }"
"            }"
"        }"
"    }"
"    +Data = {"
"        Class = ReferenceContainer "
"        +Timer = { Class = LinuxTimer SleepTime = 1000 Signals = { Counter = { Type = uint32 } Time = { Type = uint32 } } }"
"        +DDB = { Class = GAMDataSource Signals = { Counter = { Type = uint32 } Time = { Type = uint32 } } }"
"        +DAMS = { Class = TimingDataSource }"
"    }"
"    +States = {"
"        Class = ReferenceContainer "
"        +State1 = { Class = RealTimeState +Threads = { Class = ReferenceContainer +Thread1 = { Class = RealTimeThread Functions = {GAM1} } } }"
"    }"
"    +Scheduler = { Class = GAMScheduler TimingDataSource = DAMS }"
"}";

static bool SendCommandAndGetReply(uint16 port, const char8* cmd, StreamString &reply) {
    BasicTCPSocket client;
    if (!client.Open()) return false;
    if (!client.Connect("127.0.0.1", port)) return false;
    
    uint32 s = StringHelper::Length(cmd);
    if (!client.Write(cmd, s)) return false;
    
    char buffer[4096];
    uint32 size = 4096;
    TimeoutType timeout(2000000); // 2s
    if (client.Read(buffer, size, timeout)) {
        reply.Write(buffer, size);
        client.Close();
        return true;
    }
    client.Close();
    return false;
}

void TestConfigCommands() {
    printf("--- MARTe2 Config & Metadata Enrichment Test ---\n");

    ObjectRegistryDatabase::Instance()->Purge();

    ConfigurationDatabase cdb;
    StreamString ss = config_command_text;
    ss.Seek(0);
    StandardParser parser(ss, cdb);
    assert(parser.Parse());
    
    cdb.MoveToRoot();
    uint32 n = cdb.GetNumberOfChildren();
    for (uint32 i=0; i<n; i++) {
        const char8* name = cdb.GetChildName(i);
        ConfigurationDatabase child;
        cdb.MoveRelative(name);
        cdb.Copy(child);
        cdb.MoveToAncestor(1u);
        StreamString className;
        child.Read("Class", className);
        Reference ref(className.Buffer(), GlobalObjectsDatabase::Instance()->GetStandardHeap());
        ref->SetName(name);
        assert(ref->Initialise(child));
        ObjectRegistryDatabase::Instance()->Insert(ref);
    }

    printf("Application and DebugService (port 8100) initialised.\n");

    // Start the application to trigger broker execution and signal registration
    ReferenceT<RealTimeApplication> app = ObjectRegistryDatabase::Instance()->Find("App");
    assert(app.IsValid());
    assert(app->ConfigureApplication());
    assert(app->PrepareNextState("State1") == ErrorManagement::NoError);
    assert(app->StartNextStateExecution() == ErrorManagement::NoError);
    printf("Application started (for signal registration).\n");
    Sleep::MSec(500); // Wait for some cycles

    ReferenceT<DebugService> service = ObjectRegistryDatabase::Instance()->Find("DebugService");
    if (service.IsValid()) {
        service->SetFullConfig(cdb);
    }
    
    Sleep::MSec(1000);

    // 1. Test CONFIG command
    {
        printf("Testing CONFIG command...\n");
        StreamString reply;
        assert(SendCommandAndGetReply(8100, "CONFIG\n", reply));
        printf("\n%s\n", reply.Buffer());
        // Verify it contains some key parts of the config
        assert(StringHelper::SearchString(reply.Buffer(), "MyCustomField") != NULL_PTR(const char8*));
        assert(StringHelper::SearchString(reply.Buffer(), "HelloConfig") != NULL_PTR(const char8*));
        assert(StringHelper::SearchString(reply.Buffer(), "PROC:VAR:1") != NULL_PTR(const char8*));
        assert(StringHelper::SearchString(reply.Buffer(), "OK CONFIG") != NULL_PTR(const char8*));
        printf("SUCCESS: CONFIG command validated.\n");
    }

    // 2. Test INFO on object with enrichment
    {
        printf("Testing INFO on App.Functions.GAM1...\n");
        StreamString reply;
        assert(SendCommandAndGetReply(8100, "INFO App.Functions.GAM1\n", reply));
        // Check standard MARTe fields (Name, Class)
        assert(StringHelper::SearchString(reply.Buffer(), "\"Name\": \"GAM1\"") != NULL_PTR(const char8*));
        // Check enriched fields from fullConfig
        assert(StringHelper::SearchString(reply.Buffer(), "\"CustomGAMField\": \"GAMValue\"") != NULL_PTR(const char8*));
        assert(StringHelper::SearchString(reply.Buffer(), "OK INFO") != NULL_PTR(const char8*));
        printf("SUCCESS: Object metadata enrichment validated.\n");
    }

    // 3. Test INFO on signal with enrichment
    {
        printf("Testing INFO on App.Functions.GAM1.In.Counter...\n");
        StreamString reply;
        assert(SendCommandAndGetReply(8100, "INFO App.Functions.GAM1.In.Counter\n", reply));
        
        // Check enriched fields from signal configuration
        assert(StringHelper::SearchString(reply.Buffer(), "\"Frequency\": \"1000\"") != NULL_PTR(const char8*));
        assert(StringHelper::SearchString(reply.Buffer(), "\"PVName\": \"PROC:VAR:1\"") != NULL_PTR(const char8*));
        assert(StringHelper::SearchString(reply.Buffer(), "OK INFO") != NULL_PTR(const char8*));
        printf("SUCCESS: Signal metadata enrichment validated.\n");
    }

    if (app.IsValid()) {
        app->StopCurrentStateExecution();
    }

    ObjectRegistryDatabase::Instance()->Purge();
}
