/*

 * Copyright (c) 2021 Huawei Device Co., Ltd.

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 * http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

#include "script_instructionhelper.h"

#include <dlfcn.h>

#include <set>

#include "dump.h"

#include "scope_guard.h"

#include "script_basicinstruction.h"

#include "script_loadscript.h"

#include "script_manager_impl.h"

#include "script_registercmd.h"

#include "script_updateprocesser.h"

#include "script_utils.h"



using namespace BasicInstruction;

using namespace Updater;



namespace Uscript {

static std::set<std::string> g_reservedInstructions = {

    "LoadScript", "RegisterCmd", "abort", "assert", "concat",

    "is_substring", "stdout", "sleep", "set_progress", "ui_print",

    "show_progress", "set_proportion"

    };



static ScriptInstructionHelper* g_instructionHelper = nullptr;



ScriptInstructionHelper* ScriptInstructionHelper::GetBasicInstructionHelper(ScriptManagerImpl *impl)

{

    if (g_instructionHelper == nullptr) {

        if (impl == nullptr) {

            return nullptr;

        }

        g_instructionHelper = new ScriptInstructionHelper(impl);

    }

    return g_instructionHelper;

}



void ScriptInstructionHelper::ReleaseBasicInstructionHelper()

{

    if (g_instructionHelper != nullptr) {

        delete g_instructionHelper;

    }

    g_instructionHelper = nullptr;

}



ScriptInstructionHelper::~ScriptInstructionHelper()

{

    if (instrLib_ != nullptr) {

        dlclose(instrLib_);

    }

    instrLib_ = nullptr;

}



int32_t ScriptInstructionHelper::RegisterInstructions() const

{

    scriptManager_->AddInstruction("RegisterCmder", new ScriptRegisterCmd());

    scriptManager_->AddInstruction("LoadScript", new ScriptLoadScript());

    scriptManager_->AddInstruction("Stdout", new UScriptInstructionStdout());

    scriptManager_->AddInstruction("Abort", new UScriptInstructionAbort());

    scriptManager_->AddInstruction("Assert", new UScriptInstructionAssert());

    scriptManager_->AddInstruction("Sleep", new UScriptInstructionSleep());

    scriptManager_->AddInstruction("Concat", new UScriptInstructionConcat());

    scriptManager_->AddInstruction("IsSubString", new UScriptInstructionIsSubString());

    scriptManager_->AddInstruction("set_progress", new UScriptInstructionSetProcess());

    scriptManager_->AddInstruction("show_progress", new UScriptInstructionShowProcess());

    scriptManager_->AddInstruction("ui_print", new UScriptInstructionUiPrint());

    scriptManager_->AddInstruction("DeleteFile", new UScriptInstructionDeleteFile());

    scriptManager_->AddInstruction("DeleteDir", new UScriptInstructionDeleteDir());

    scriptManager_->AddInstruction("set_proportion", new UScriptInstructionSetProportion());

    return USCRIPT_SUCCESS;

}



bool ScriptInstructionHelper::IsReservedInstruction(const std::string &scriptName) const

{

    if (g_reservedInstructions.find(scriptName) != g_reservedInstructions.end()) {

        return true;

    }

    return false;

}



int32_t ScriptInstructionHelper::AddScript(const std::string &scriptName, int32_t priority) const

{

    return scriptManager_->AddScript(scriptName, priority);

}



int32_t ScriptInstructionHelper::AddInstruction(const std::string &instrName, const UScriptInstructionPtr instr)

{

    if (IsReservedInstruction(instrName)) {

        USCRIPT_LOGE(" %s reserved", instrName.c_str());

        return USCRIPT_ERROR_REVERED;

    }

    return scriptManager_->AddInstruction(instrName, instr);

}



int32_t ScriptInstructionHelper::RegisterAddInstruction(const Uscript::UScriptInstructionFactoryPtr factory,

    const std::string &instrName)

{

    UPDATER_INIT_RECORD;

    // Create instruction and register it

    UScriptInstructionPtr instr = nullptr;

    int32_t ret = factory->CreateInstructionInstance(instr, instrName);

    if (ret != USCRIPT_SUCCESS || instr == nullptr) {

        USCRIPT_LOGE("Fail to create instruction for %s", instrName.c_str());

        UPDATER_LAST_WORD(ret, "Fail to create instruction", instrName);

        return ret == USCRIPT_SUCCESS ? USCRIPT_ERROR_CREATE_OBJ : USCRIPT_NOTEXIST_INSTRUCTION;

    }



    ret = AddInstruction(instrName, instr);

    if (ret != USCRIPT_SUCCESS) {

        USCRIPT_LOGE("Fail to add instruction for %s", instrName.c_str());

        UPDATER_LAST_WORD(ret, "Fail to add instruction for ", instrName);

        // ret is USCRIPT_ERROR_REVERED, instr register failed, can be deleted

        delete instr;

        instr = nullptr;

    }

    // ScriptManagerImpl::AddInstruction has saved instr, don't delete it here!!!

    return ret;

}



int32_t ScriptInstructionHelper::RegisterUserInstruction(const std::string& libName,

    const std::string &instrName)

{

    // first get realpath of libName, then compare with realLibName

    UPDATER_INIT_RECORD;

    char *realPath = realpath(libName.c_str(), nullptr);

    if (realPath == nullptr) {

        USCRIPT_LOGE("realPath is NULL %s", libName.c_str());

        UPDATER_LAST_WORD("realPath is NUL", libName);

        return USCRIPT_INVALID_PARAM;

    }

    std::string realLibName = realPath;

    free(realPath);

    if (!userInstrLibName_.empty() && userInstrLibName_.compare(realLibName) != 0) {

        USCRIPT_LOGE("Lib name must be equal %s ", realLibName.c_str());

        UPDATER_LAST_WORD("Lib name must be equal", realLibName);

        return USCRIPT_INVALID_PARAM;

    }



    userInstrLibName_.assign(realLibName);

    Uscript::UScriptInstructionFactoryPtr factory = nullptr;

    if (instrLib_ == nullptr) {

        instrLib_ = dlopen(realLibName.c_str(), RTLD_LAZY | RTLD_LOCAL);

    }

    if (instrLib_ == nullptr) {

        USCRIPT_LOGE("Fail to dlopen %s , dlerror: %s", libName.c_str(), dlerror());

        UPDATER_LAST_WORD(USCRIPT_INVALID_PARAM, "Fail to dlopen " + libName, dlerror());

        return USCRIPT_INVALID_PARAM;

    }

    auto pGetInstructionFactory =

        (Uscript::UScriptInstructionFactoryPtr(*)())dlsym(instrLib_, "GetInstructionFactory");

    auto pReleaseInstructionFactory =

        (void(*)(Uscript::UScriptInstructionFactoryPtr))dlsym(instrLib_, "ReleaseInstructionFactory");

    if (pReleaseInstructionFactory == nullptr || pGetInstructionFactory == nullptr) {

        USCRIPT_LOGE("Fail to get sym %s", libName.c_str());

        UPDATER_LAST_WORD("Fail to get sym", libName);

        return USCRIPT_INVALID_PARAM;

    }

    factory = pGetInstructionFactory();

    if (factory == nullptr) {

        USCRIPT_LOGE("Fail to create instruction factory for %s", instrName.c_str());

        UPDATER_LAST_WORD("Fail to create instruction factory for", instrName);

        return USCRIPT_INVALID_PARAM;

    }

    ON_SCOPE_EXIT(freeFactory) {

        pReleaseInstructionFactory(factory);

    };



    return RegisterAddInstruction(factory, instrName);

}



int32_t ScriptInstructionHelper::RegisterUserInstruction(const std::string &instrName,

    Uscript::UScriptInstructionFactory *factory)

{

    if (factory == nullptr) {

        USCRIPT_LOGE("%s factory is null", instrName.c_str());

        return USCRIPT_INVALID_PARAM;

    }



    // Create instruction and register it

    UScriptInstructionPtr instr = nullptr;

    int32_t ret = factory->CreateInstructionInstance(instr, instrName);

    if (ret != USCRIPT_SUCCESS || instr == nullptr) {

        USCRIPT_LOGE("Fail to create instruction for %s", instrName.c_str());

        // when instr == nullptr && ret == USCRIPT_SUCCESS, shouldn't return USCRIPT_SUCCESS

        return ret == USCRIPT_SUCCESS ? USCRIPT_ERROR_CREATE_OBJ : USCRIPT_NOTEXIST_INSTRUCTION;

    }



    ret = AddInstruction(instrName, instr);

    if (ret != USCRIPT_SUCCESS) {

        USCRIPT_LOGE("Fail to add instruction for %s", instrName.c_str());

        delete instr;

        instr = nullptr;

        return ret;

    }



    USCRIPT_LOGD("RegisterUserInstruction %s successfull", instrName.c_str());

    return ret;

}

} // namespace Uscript