#include "DemoAgent.h"

#include "communication/SocketException.h"

DemoAgent::DemoAgent()
{
    eventName = "DemoEvent";

    eventPeriod = 5000;

    try
    {
        rls = RemoteServerPtr(new RemoteServer("127.0.0.1", Agent::DEFAULT_PORT, "admin", "admin", false)); // Agent support non-SSL only

        agentContext = new AgentContext(rls, "cpp", true);

        agent = AgentPtr(new Agent(agentContext));
        agent->connect();

        InitializeAgentContext(agent->getContext());

        boost::thread ath(&DemoAgent::AgentThread, this);
        ath.detach();
    }
    catch(SocketException)
    {
        throw SocketException("Can not connect to host!");
    }
}

void DemoAgent::setPeriod(long period)
{
    eventPeriod = period;
}

long DemoAgent::getPeriod()
{
    return eventPeriod;
}

// DataTablePtr DemoAgent::getAsset()
// {
//     return asset;
// }

void DemoAgent::InitializeAgentContext(Context *context)
{
    // Add Demo Variable (Sync Update)

    FieldFormat *ffVar = FieldFormat::create("period", AGG_GLOBAL.LONG_FIELD);
    ffVar->setEditor(AGG_GLOBAL.EDITOR_PERIOD);

    TableFormatPtr tfVar = TableFormatPtr(new TableFormat(1, 1));

    tfVar->addField(ffVar);

    VariableDefinitionPtr vd = VariableDefinitionPtr(new VariableDefinition("period", tfVar, true, true, "Event Generation Period", ContextUtils::GROUP_REMOTE()));

    typedef boost::shared_ptr<VariableGetterTest> VariableGetterTestPtr;
    VariableGetterTestPtr getterVar = VariableGetterTestPtr(new VariableGetterTest(this, tfVar));

    typedef boost::shared_ptr<VariableSetterTest> VariableSetterTestPtr;
    VariableSetterTestPtr setterVar = VariableSetterTestPtr(new VariableSetterTest(this));

    vd->setGetter(getterVar);
    vd->setSetter(setterVar);

    context->addVariableDefinition(vd);

    // Add Demo Variable (Async Update)

    FieldFormat *ffVarAsync = FieldFormat::create("periodAsync", AGG_GLOBAL.INTEGER_FIELD);

    TableFormatPtr tfVarAsync = TableFormatPtr(new TableFormat(1, 1));

    tfVarAsync->addField(ffVarAsync);

    VariableDefinitionPtr vdAsync = VariableDefinitionPtr(new VariableDefinition("async", tfVarAsync, true, false, "Random Asynchronously Updated Variable", ContextUtils::GROUP_REMOTE()));

    // You can add a getter also for the synchronous update (will called at the variable synchronization)
    // Setter is not needed for read-only variable

    context->addVariableDefinition(vdAsync);

    boost::thread asth(&DemoAgent::AsyncUpdateThread, this, agent);
    asth.detach();

    // Add demo function

    FieldFormat *ffFunc = FieldFormat::create("sqrt", AGG_GLOBAL.DOUBLE_FIELD);

    TableFormatPtr tfFunc = TableFormatPtr(new TableFormat(1, 1));

    tfFunc->addField(ffFunc);

    FunctionDefinitionPtr fd = FunctionDefinitionPtr(new FunctionDefinition("Sqrt", tfFunc, tfFunc, "Calculate Sqrt", ContextUtils::GROUP_REMOTE()));

    typedef boost::shared_ptr<FunctionImplementationTest> FunctionImplementationTestPtr;
    FunctionImplementationTestPtr funcImpl = FunctionImplementationTestPtr(new FunctionImplementationTest(tfFunc));

    fd->setImplementation(funcImpl);

    context->addFunctionDefinition(fd);

    // Add demo event    

    FieldFormat *ffEv = FieldFormat::create("Random", AGG_GLOBAL.INTEGER_FIELD);

    TableFormatPtr tfEv = TableFormatPtr(new TableFormat(1, 1));

    tfEv->addField(ffEv);

    EventDefinitionPtr ed = EventDefinitionPtr(new EventDefinition(eventName, tfEv, "Agent Event", ContextUtils::GROUP_REMOTE()));
    context->addEventDefinition(ed);

    typedef boost::shared_ptr<DefaultContextEventListenerConf> DefaultContextEventListenerConfPtr;
    DefaultContextEventListenerConfPtr evConf = DefaultContextEventListenerConfPtr(new DefaultContextEventListenerConf());

    context->addEventListener(AgentContext::E_EVENT_CONFIRMED(), evConf);

    typedef boost::shared_ptr<DefaultContextEventListenerAck> DefaultContextEventListenerAckPtr;
    DefaultContextEventListenerAckPtr evAck = DefaultContextEventListenerAckPtr(new DefaultContextEventListenerAck());

    context->addEventListener(AgentContext::E_EVENT_ACKNOWLEDGED(), evAck);

    boost::thread eth(&DemoAgent::EventThread, this, agent, tfEv);
    eth.detach();

    // Add asset demo

    // For assets adding you should create DataTable with AgentContext::FOFT_ASSET format, add addVariableDefinition with AgentContext::V_ASSETS name and getter for it with created DataTable.
    // You can add assets by adding records to created DataTable for previous step. For example:

    // asset = DataTablePtr(new DataTable(AgentContext::FOFT_ASSET, false));
    // VariableDefinitionPtr avd = VariableDefinitionPtr(new VariableDefinition(AgentContext::V_ASSETS(), AgentContext::FOFT_ASSET, true, false, ""));

    // typedef boost::shared_ptr<VariableGetterAsset> VariableGetterAssetPtr;
    // VariableGetterAssetPtr getterAsset = VariableGetterAssetPtr(new VariableGetterAsset(this, AgentContext::FOFT_ASSET));

    // avd->setGetter(getterAsset);

    // DataRecordPtr adrSub = DataRecordPtr(new DataRecord(AgentContext::FOFT_ASSET));
    // adrSub->addString("Child Asset");
    // adrSub->addString("Child Asset");
    // adrSub->addBoolean(true);
    // adrSub->addDataTable(DataTablePtr());

    // DataRecordPtr adrRt = DataRecordPtr(new DataRecord(AgentContext::FOFT_ASSET));
    // adrRt->addString("Root Asset");
    // adrRt->addString("Root Asset");
    // adrRt->addBoolean(true);
    // adrRt->addDataTable(DataTablePtr(new DataTable(adrSub)));

    // asset->addRecord(adrRt);

    // context->addVariableDefinition(avd);

    // If we add some assets here, we'll need to make sure group of all entities is remote|assetId|subassetID[|...].
    // For example:

    // VariableDefinitionPtr vdAsset = VariableDefinitionPtr(new VariableDefinition("period", tfVar, true, true, "Event Generation Period",
    //                                                                         ContextUtils::GROUP_REMOTE() + ContextUtils::ENTITY_GROUP_SEPARATOR() + "Root Asset" +
    //                                                                         ContextUtils::ENTITY_GROUP_SEPARATOR() + "Child Asset"));
}

void DemoAgent::AgentThread()
{
    while(true)
    {
        if (!agent->isConnected())
        {
            boost::this_thread::sleep(boost::posix_time::seconds(1));

            try
            {
                agent->reconnect();
            }
            catch (SocketException /*ex*/) {}

            continue;
        }

        try
        {
            agent->run();
        }
        catch (DisconnectionException /*ex*/)
        {
            // You can stop all threads, delete agent instance and create new. Or call reconnect() function as above.
        }
    };
}

void DemoAgent::EventThread(AgentPtr context, TableFormatPtr tf)
{
    while (true)
    {
        if (context->getContext()->isSynchronized())
        {
            boost::random::mt19937 rng((uint32_t)std::time(0));
            boost::random::uniform_int_distribution<> gen(0, 100000);

            DataRecordPtr drEv = DataRecordPtr(new DataRecord(tf));
            drEv->addInt(gen(rng));

            DataTablePtr dtEv = DataTablePtr(new DataTable(tf));
            dtEv->addRecord(drEv);

            context->getContext()->fireEvent(eventName, EventLevel::INFO(), dtEv);

            boost::this_thread::sleep(boost::posix_time::milliseconds(eventPeriod));
        }
    }
}

void DemoAgent::AsyncUpdateThread(AgentPtr context)
{
    while (true)
    {
        if (context->getContext()->isSynchronized())
        {
            boost::random::mt19937 rng((uint32_t)std::time(0));
            boost::random::uniform_int_distribution<> gen(0, INT_MAX);

            std::list<AgObjectPtr> list;
            list.push_back(AgObjectPtr(new AgInteger(gen(rng))));
            context->getContext()->setVariable("async", list);

            boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
        }
    }
}
