#include <DevCommand/WScriptCommand/WScriptCommand.hpp>
#include <Log/Log.hpp>
#include <ScriptEngine/ScriptEngine.hpp>
#include <World/LocalWorld/ScriptedWorld/ScriptedWorld.hpp>
#include <WorldIndex/WorldIndex.hpp>
#include <WorldType/WorldType.hpp>
#include <string>
#include <WorldTypeRegistry/WorldTypeRegistry.hpp>
#include <AtomicDataStore/AtomicDataStore.hpp>
#include <ChunkWindow/ChunkWindow.hpp>

int WScriptCommand::execute(const std::vector<std::string> &args)
{
    if (args.size() != 4)
    {
        Log::error("Syntax: mkworld <filename> <script-dir>");
        return 1;
    }

    const std::string &path = args[2];
    const std::string &scriptDir = args[3];

    std::string initScriptPath = scriptDir + "/init.lua";

    ScriptEngine engine;
    if (!engine.execFromFile(initScriptPath))
    {
        Log::error(formatString("Failed to execute init.lua: $+", engine.pop<std::string>()));
        return 1;
    }

    glm::ivec3 agentStartPos;
    agentStartPos.z = engine.pop<int>();
    agentStartPos.y = engine.pop<int>();
    agentStartPos.x = engine.pop<int>();

    std::string initialAgentType = engine.pop<std::string>();

    auto worldSettings = engine.pop<std::vector<std::string>>();
    std::string worldTypeID = engine.pop<std::string>();

    WorldType *worldType = worldTypeRegistry.getByID(worldTypeID);
    if (worldType == nullptr)
    {
        Log::error(formatString("Unknown world type '$+'.", worldTypeID));
        return 1;
    }

    WorldIndex worldIndex = WorldIndex::create(true, WorldAvatar::Voxelverse);
    std::shared_ptr<WorldTypeSettings> settings = worldType->parseSettingsFromCmdArgs(worldSettings.data(), worldSettings.size());
    if (settings == nullptr)
    {
        return 1;
    }

    worldIndex.setSettings(worldTypeID, *settings);

    {
        AtomicDataStore store;
        store.create(path);
        const std::string &storeError = store.getError();

        if (!storeError.empty())
        {
            Log::error(storeError);
            return 1;
        }

        AtomicDataStore::Plan plan;
        serializeInto(plan["windex.dat"], worldIndex);
        store.update(plan, 0);
    }

    Log::info("World created successfully, running agents...");

    ScriptedWorld world(path, scriptDir, initialAgentType, agentStartPos);
    const std::string &worldError = world.getFatalError();
    if (!worldError.empty())
    {
        Log::error(formatString("Failed to open world: $+", worldError));
        return 1;
    }

    while (!world.isDone())
    {
        world.update();
    }

    Log::info("All agents done, closing world.");
    world.close();

    while (world.getPurgeQueueSize() != 0 || !world.isShutdownReady())
    {
        world.housekeep();
    }

    bool tasksFinished = false;
    std::condition_variable cond;
    std::mutex mutex;

    afterAsyncBarrier(AsyncBarrier::CurrentlyDispatched, [&tasksFinished, &cond, &mutex]()
    {
        std::unique_lock<std::mutex> lock(mutex);

        tasksFinished = true;
        lock.unlock();
        cond.notify_one();
    });

    std::unique_lock<std::mutex> lock(mutex);
    while (!tasksFinished)
    {
        cond.wait(lock);
    }

    lock.unlock();
    world.doShutdownSanityChecks();

    if (!worldError.empty())
    {
        Log::error(formatString("World encountered an error: $+", worldError));
        return 1;
    }

    Log::info("Scripts done and world written.");

    return 0;
}