/*

 * 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_interpreter.h"

#include <algorithm>

#include <fstream>

#include "dump.h"

#include "scope_guard.h"

#include "script_context.h"

#include "script_manager_impl.h"

#include "scanner.h"

#include "script_utils.h"



using namespace std;

using namespace Updater;



namespace Uscript {

static int32_t g_instanceId = 0;



int32_t ScriptInterpreter::ExecuteScript(ScriptManagerImpl *manager, Hpackage::PkgManager::StreamPtr pkgStream)

{

    Updater::UPDATER_INIT_RECORD;

    if (pkgStream == nullptr) {

        USCRIPT_LOGE("Param error");

        UPDATER_LAST_WORD(USCRIPT_INVALID_PARAM);

        return USCRIPT_INVALID_PARAM;

    }

    USCRIPT_LOGI("ExecuteScript %s", pkgStream->GetFileName().c_str());

    auto inter = new (std::nothrow) ScriptInterpreter(manager);

    if (inter == nullptr) {

        USCRIPT_LOGE("Fail to create ScriptInterpreter for script %s", pkgStream->GetFileName().c_str());

        UPDATER_LAST_WORD(USCRIPT_ERROR_CREATE_OBJ, pkgStream->GetFileName());

        return USCRIPT_ERROR_CREATE_OBJ;

    }

    ON_SCOPE_EXIT(ReleaseScriptInterpreter) {

        if (inter) {

            delete inter;

            inter = nullptr;

        }

    };

    int32_t ret = inter->LoadScript(pkgStream);

    if (ret != USCRIPT_SUCCESS) {

        USCRIPT_LOGE("Fail to loadScript script %s", pkgStream->GetFileName().c_str());

        UPDATER_LAST_WORD(ret, "Fail to loadScript script" + pkgStream->GetFileName());

        return ret;

    }

    ret = inter->Execute();

    USCRIPT_LOGI("ExecuteScript finish ret: %d  script: %s ",

        ret, pkgStream->GetFileName().c_str());

    return ret;

}



ScriptInterpreter::ScriptInterpreter(ScriptManagerImpl *manager) : scriptManager_(manager)

{

    instanceId_ = g_instanceId++;

}



ScriptInterpreter::~ScriptInterpreter()

{

    delete statements_;

    auto iter = functions_.begin();

    while (iter != functions_.end()) {

        auto entry = iter->second;

        if (entry) {

            delete entry;

        }

        iter = functions_.erase(iter);

    }

    functions_.clear();

    contextStack_.clear();

}



int32_t ScriptInterpreter::LoadScript(Hpackage::PkgManager::StreamPtr pkgStream)

{

    scanner_ = std::make_unique<Scanner>(this);

    parser_ = std::make_unique<Parser>(scanner_.get(), this);

    scanner_->set_debug(0);

    scanner_->SetPkgStream(pkgStream);

    int32_t ret = parser_->parse();

    return ret;

}



int32_t ScriptInterpreter::Execute()

{

    UScriptContextPtr context = std::make_shared<UScriptInterpretContext>(true);

    if (statements_ == nullptr) {

        return USCRIPT_INVALID_PARAM;

    }

    UScriptStatementResult result = statements_->Execute(*this, context);

    INTERPRETER_LOGD(*this, context, "statements_ execute result %s ",

        UScriptStatementResult::ScriptToString(&result).c_str());

    if (result.GetResultType() == UScriptStatementResult::STATEMENT_RESULT_TYPE_ERROR) {

        return result.GetError();

    }

    return USCRIPT_SUCCESS;

}



int32_t ScriptInterpreter::AddFunction(ScriptFunction *function)

{

    if (functions_.find(function->GetFunctionName()) != functions_.end()) {

        USCRIPT_LOGI("Fail to add function %s, function exist", function->GetFunctionName().c_str());

        return USCRIPT_SUCCESS;

    }

    functions_[function->GetFunctionName()] = function;

    return USCRIPT_SUCCESS;

}



ScriptFunction* ScriptInterpreter::FindFunction(const std::string &name)

{

    if (functions_.find(name) != functions_.end()) {

        return functions_[name];

    }

    return nullptr;

}



UScriptValuePtr ScriptInterpreter::ExecuteFunction(UScriptContextPtr context, const std::string &name,

    ScriptParams *params)

{

    ScriptFunction *function = FindFunction(name);

    if (function == nullptr) {

        USCRIPT_LOGI("Fail to find function %s", name.c_str());

        return std::make_shared<ErrorValue>(USCRIPT_NOTEXIST_INSTRUCTION);

    } else {

        return function->Execute(*this, context, params);

    }

}



UScriptValuePtr ScriptInterpreter::FindVariable(UScriptContextPtr local, std::string id)

{

    for (auto context = contextStack_.rbegin(); context != contextStack_.rend(); context++) {

        UScriptValuePtr variable = (*context)->FindVariable(*this, id);

        if (variable != nullptr) {

            return variable;

        }

        if ((*context)->IsTop()) {

            break;

        }

    }

    return nullptr;

}



UScriptValuePtr ScriptInterpreter::UpdateVariable(UScriptContextPtr local, std::string id, UScriptValuePtr var)

{

    for (auto context = contextStack_.rbegin(); context != contextStack_.rend(); context++) {

        UScriptValuePtr variable = (*context)->FindVariable(*this, id);

        if (variable != nullptr) {

            (*context)->UpdateVariable(*this, id, var);

        }

        if ((*context)->IsTop()) {

            break;

        }

    }

    return nullptr;

}



void ScriptInterpreter::AddStatement(UScriptStatement *statement)

{

    if (statements_ == nullptr) {

        statements_ = UScriptStatementList::CreateInstance(statement);

    } else {

        statements_->AddScriptStatement(statement);

    }

}



bool ScriptInterpreter::IsNativeFunction(std::string name)

{

    return scriptManager_->FindInstruction(name) != nullptr;

}



UScriptValuePtr ScriptInterpreter::ExecuteNativeFunc(UScriptContextPtr context,

    const std::string &name, ScriptParams *params)

{

    std::shared_ptr<ErrorValue> error = std::make_shared<ErrorValue>(USCRIPT_ERROR_INTERPRET);

    std::shared_ptr<ReturnValue> retValue = std::make_shared<ReturnValue>();

    INTERPRETER_LOGI(*this, context, "ExecuteNativeFunc::Execute %s ", name.c_str());

    UScriptInstruction* instruction = scriptManager_->FindInstruction(name);

    if (instruction == nullptr) {

        USCRIPT_LOGE("Fail to find instruction %s", name.c_str());

        return error;

    }



    std::shared_ptr<UScriptInstructionContext> funcContext = std::make_shared<UScriptInstructionContext>();

    if (funcContext == nullptr) {

        USCRIPT_LOGE("Fail to create context %s", name.c_str());

        return error;

    }

    if (params == nullptr) {

        int32_t ret = instruction->Execute(*scriptManager_->GetScriptEnv(name), *funcContext.get());

        if (ret != USCRIPT_SUCCESS && ret != USCRIPT_UPDATER_MODE_VALUE) {

            error->SetValue(ret);

            return error;

        }

        retValue->AddValues(funcContext->GetOutVar());

        INTERPRETER_LOGI(*this, context, "ExecuteNativeFunc::Execute %s result: %d", name.c_str(), ret);

        return retValue;

    }



    for (auto id : params->GetParams()) {

        UScriptValuePtr result = id->Execute(*this, context);

        if (result == nullptr || result->GetValueType() == UScriptValue::VALUE_TYPE_ERROR) {

            INTERPRETER_LOGI(*this, context, "ExecuteNativeFunc::Execute %s ", name.c_str());

            return error;

        }



        if (result->GetValueType() != UScriptValue::VALUE_TYPE_LIST) {

            funcContext->AddInputParam(result);

        } else {

            ReturnValue* values = (ReturnValue*)(result.get());

            for (auto out : values->GetValues()) {

                funcContext->AddInputParam(out);

            }

        }

    }



    int32_t ret = instruction->Execute(*scriptManager_->GetScriptEnv(name), *funcContext.get());

    INTERPRETER_LOGI(*this, context, "ExecuteNativeFunc::Execute %s result: %d", name.c_str(), ret);

    if (ret != USCRIPT_SUCCESS && ret != USCRIPT_UPDATER_MODE_VALUE) {

        error->SetValue(ret);

        return error;

    }

    retValue->AddValues(funcContext->GetOutVar());

    return retValue;

}

} // namespace Uscript