//
//  RuntimeLuaImpl.cpp
//  Simulator
//
//

#include "RuntimeLuaImpl.h"

#if (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)

#include <cstdio>
#include <string>

#include "lua_debugger.h"
#include "CCLuaEngine.h"
#include "LuaBasicConversions.h"
#include "lua_module_register.h"

#include "runtime/Runtime.h"
#include "runtime/ConfigParser.h"
#include "runtime/FileServer.h"

extern std::string g_projectPath; // Runtime.cpp

USING_NS_CC;
using namespace std;

static void resetLuaModule(const string& fileName)
{
    if (fileName.empty())
    {
        return;
    }
    auto engine = LuaEngine::getInstance();
    LuaStack* luaStack = engine->getLuaStack();
    lua_State* stack = luaStack->getLuaState();
    lua_getglobal(stack, "package");                         /* L: package */
    lua_getfield(stack, -1, "loaded");                       /* L: package loaded */
    lua_pushnil(stack);                                     /* L: lotable ?-.. nil */
    while (0 != lua_next(stack, -2))                     /* L: lotable ?-.. key value */
    {
        //CCLOG("%s - %s \n", tolua_tostring(stack, -2, ""), lua_typename(stack, lua_type(stack, -1)));
        std::string key = tolua_tostring(stack, -2, "");
        std::string tableKey = key;
        size_t found = tableKey.rfind(".lua");
        if (found != std::string::npos)
        tableKey = tableKey.substr(0, found);
        tableKey = replaceAll(tableKey, ".", "/");
        tableKey = replaceAll(tableKey, "\\", "/");
        tableKey.append(".lua");
        found = fileName.rfind(tableKey);
        if (0 == found || (found != std::string::npos && fileName.at(found - 1) == '/'))
        {
            lua_pushstring(stack, key.c_str());
            lua_pushnil(stack);
            if (lua_istable(stack, -5))
            {
                lua_settable(stack, -5);
            }
        }
        lua_pop(stack, 1);
    }
    lua_pop(stack, 2);
}

bool reloadScript(const string& file)
{
    auto director = Director::getInstance();
    FontFNT::purgeCachedData();
    if (director->getOpenGLView())
    {
        SpriteFrameCache::getInstance()->removeSpriteFrames();
        director->getTextureCache()->removeAllTextures();
    }
    FileUtils::getInstance()->purgeCachedEntries();
    string modulefile = file;
    
    if (! modulefile.empty())
    {
        resetLuaModule(modulefile);
    }
    else
    {
        modulefile = ConfigParser::getInstance()->getEntryFile().c_str();
    }
    
    auto engine = LuaEngine::getInstance();
    LuaStack* luaStack = engine->getLuaStack();
    std::string require = "require \'" + modulefile + "\'";
    return luaStack->executeString(require.c_str());
}

int lua_cocos2dx_runtime_addSearchPath(lua_State* tolua_S)
{
    int argc = 0;
    cocos2d::FileUtils* cobj = nullptr;
    bool ok  = true;
    
#if COCOS2D_DEBUG >= 1
    tolua_Error tolua_err;
#endif
    
    
#if COCOS2D_DEBUG >= 1
    if (!tolua_isusertype(tolua_S,1,"cc.FileUtils",0,&tolua_err)) goto tolua_lerror;
#endif
    
    cobj = (cocos2d::FileUtils*)tolua_tousertype(tolua_S,1,0);
    
#if COCOS2D_DEBUG >= 1
    if (!cobj)
    {
        tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_FileUtils_addSearchPath'", nullptr);
        return 0;
    }
#endif
    
    argc = lua_gettop(tolua_S)-1;
    if (argc == 1 || argc == 2)
    {
        std::string arg0;
        bool arg1 = false;
        
        ok &= luaval_to_std_string(tolua_S, 2,&arg0);
        
        if (argc == 2)
        {
            ok &= luaval_to_boolean(tolua_S, 3, &arg1);
        }
        
        if(!ok)
        return 0;
        
        if (! FileUtils::getInstance()->isAbsolutePath(arg0))
        {
            // add write path to search path
            if (FileServer::getShareInstance()->getIsUsingWritePath())
            {
                cobj->addSearchPath(FileServer::getShareInstance()->getWritePath() + arg0, arg1);
            } else
            {
                cobj->addSearchPath(arg0, arg1);
            }
            
#if(CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
            // add project path to search path
            cobj->addSearchPath(g_projectPath + arg0, arg1);
#endif
        }
        return 0;
    }
    CCLOG("%s has wrong number of arguments: %d, was expecting %d \n", "addSearchPath",argc, 1);
    return 0;
    
#if COCOS2D_DEBUG >= 1
tolua_lerror:
    tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_FileUtils_addSearchPath'.",&tolua_err);
#endif
    
    return 0;
}

int lua_cocos2dx_runtime_setSearchPaths(lua_State* tolua_S)
{
    int argc = 0;
    cocos2d::FileUtils* cobj = nullptr;
    bool ok  = true;
    
#if COCOS2D_DEBUG >= 1
    tolua_Error tolua_err;
#endif
    
    
#if COCOS2D_DEBUG >= 1
    if (!tolua_isusertype(tolua_S,1,"cc.FileUtils",0,&tolua_err)) goto tolua_lerror;
#endif
    
    cobj = (cocos2d::FileUtils*)tolua_tousertype(tolua_S,1,0);
    
#if COCOS2D_DEBUG >= 1
    if (!cobj)
    {
        tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_runtime_setSearchPaths'", nullptr);
        return 0;
    }
#endif
    
    argc = lua_gettop(tolua_S)-1;
    if (argc == 1)
    {
        std::vector<std::string> vecPaths, writePaths;
        
        ok &= luaval_to_std_vector_string(tolua_S, 2, &vecPaths);
        if(!ok)
        return 0;
        std::vector<std::string> originPath; // for IOS platform.
        std::vector<std::string> projPath; // for Desktop platform.
        for (size_t i = 0; i < vecPaths.size(); i++)
        {
            if (!FileUtils::getInstance()->isAbsolutePath(vecPaths[i]))
            {
                originPath.push_back(vecPaths[i]); // for IOS platform.
                projPath.push_back(g_projectPath + vecPaths[i]); //for Desktop platform.
                writePaths.push_back(FileServer::getShareInstance()->getWritePath() + vecPaths[i]);
            }
        }
        
#if(CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
        vecPaths.insert(vecPaths.end(), projPath.begin(), projPath.end());
#endif
        if (FileServer::getShareInstance()->getIsUsingWritePath())
        {
            vecPaths.insert(vecPaths.end(), writePaths.begin(), writePaths.end());
        } else
        {
            vecPaths.insert(vecPaths.end(), originPath.begin(), originPath.end());
        }
        
        cobj->setSearchPaths(vecPaths);
        return 0;
    }
    CCLOG("%s has wrong number of arguments: %d, was expecting %d \n", "setSearchPaths",argc, 1);
    return 0;
    
#if COCOS2D_DEBUG >= 1
tolua_lerror:
    tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_runtime_setSearchPaths'.",&tolua_err);
#endif
    
    return 0;
}

static void register_runtime_override_function(lua_State* tolua_S)
{
    lua_pushstring(tolua_S, "cc.FileUtils");
    lua_rawget(tolua_S, LUA_REGISTRYINDEX);
    if (lua_istable(tolua_S,-1)){
        tolua_function(tolua_S,"addSearchPath",lua_cocos2dx_runtime_addSearchPath);
        tolua_function(tolua_S,"setSearchPaths",lua_cocos2dx_runtime_setSearchPaths);
    }
    lua_pop(tolua_S, 1);
}

static void luaScriptLoader(std::string strDebugArg)
{

}

////////////////////////////////////////

RuntimeLuaImpl *RuntimeLuaImpl::create()
{
    auto instance = new RuntimeLuaImpl();
    return instance;
}

void RuntimeLuaImpl::onStartDebuger(const rapidjson::Document& dArgParse, rapidjson::Document& dReplyParse)
{
    // Lua
    char szDebugArg[1024] = {0};
    sprintf(szDebugArg, "require('debugger')(%s,'%s')",dArgParse["debugcfg"].GetString(), "");
    startScript(szDebugArg);
    dReplyParse.AddMember("code", 0, dReplyParse.GetAllocator());
}

void RuntimeLuaImpl::onReload(const rapidjson::Document &dArgParse, rapidjson::Document &dReplyParse)
{
    // lua
    if (dArgParse.HasMember("modulefiles"))
    {
        auto& allocator = dReplyParse.GetAllocator();
        rapidjson::Value bodyvalue(rapidjson::kObjectType);
        const rapidjson::Value& objectfiles = dArgParse["modulefiles"];
        for (rapidjson::SizeType i = 0; i < objectfiles.Size(); i++)
        {
            if (!reloadScript(objectfiles[i].GetString()))
            {
                bodyvalue.AddMember(rapidjson::Value(objectfiles[i].GetString(), allocator)
                                    , rapidjson::Value(1)
                                    , allocator);
            }
        }
        if (0 == objectfiles.Size())
        {
            reloadScript("");
        }
        dReplyParse.AddMember("body", bodyvalue, dReplyParse.GetAllocator());
    }
    
    dReplyParse.AddMember("code", 0, dReplyParse.GetAllocator());
}

void RuntimeLuaImpl::startScript(const std::string& strDebugArg)
{
    init();
    auto engine = LuaEngine::getInstance();
    auto stack = engine->getLuaStack();
    
    const ProjectConfig &project = RuntimeEngine::getInstance()->getProjectConfig();
    
    // set search path
    string path = FileUtils::getInstance()->fullPathForFilename(project.getScriptFileRealPath().c_str());
    size_t pos;
    while ((pos = path.find_first_of("\\")) != std::string::npos)
    {
        path.replace(pos, 1, "/");
    }
    size_t p = path.find_last_of("/");
    string workdir;
    if (p != path.npos)
    {
        workdir = path.substr(0, p);
        stack->addSearchPath(workdir.c_str());
        FileUtils::getInstance()->addSearchPath(workdir);
    }
    
    // register lua engine
    if (!strDebugArg.empty())
    {
        // open debugger.lua module
        cocos2d::log("debug args = %s", strDebugArg.c_str());
        luaopen_lua_debugger(engine->getLuaStack()->getLuaState());
        engine->executeString(strDebugArg.c_str());
    }
    std::string code("require \"");
    code.append(ConfigParser::getInstance()->getEntryFile().c_str());
    code.append("\"");
    engine->executeString(code.c_str());
}

//
// private
//

void RuntimeLuaImpl::init()
{
    auto engine = LuaEngine::getInstance();
    ScriptEngineManager::getInstance()->setScriptEngine(engine);
    lua_module_register(engine->getLuaStack()->getLuaState());
    register_runtime_override_function(engine->getLuaStack()->getLuaState());
    engine->getLuaStack()->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));
}

#endif // (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)