/* -------------------------------------------------------------------------
 *  This file is part of the oGRAC project.
 * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
 *
 * oGRAC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 *
 * call_cl.c
 *
 *
 * IDENTIFICATION
 * src/ogsql/pl/parser/call_cl.c
 *
 * -------------------------------------------------------------------------
 */

#include "call_cl.h"
#include "base_compiler.h"
#include "pl_dc.h"
#include "pl_common.h"
#include "func_mgr.h"
#include "ast_cl.h"
#include "ogsql_privilege.h"

static status_t plc_check_is_proc(expr_node_t *node)
{
    if (node->type == EXPR_NODE_V_METHOD) {
        uint16 option = g_coll_methods[0][node->value.v_method.id].option;
        if (option == AS_FUNC) {
            OG_SRC_THROW_ERROR_EX(node->loc, ERR_PL_SYNTAX_ERROR_FMT, "%s method is not allowed here.",
                GET_COLL_METHOD_DESC(node->value.v_method.id));
            return OG_ERROR;
        }
        return OG_SUCCESS;
    }

    if (node->type != EXPR_NODE_PROC && node->type != EXPR_NODE_USER_PROC) {
        OG_SRC_THROW_ERROR(node->loc, ERR_PL_SYNTAX_ERROR_FMT, "an undefined procedure was called");
        return OG_ERROR;
    }

    return OG_SUCCESS;
}

status_t plc_compile_call(pl_compiler_t *compiler, expr_node_t *expr, pl_line_normal_t *line)
{
    uint32 excl_flags;
    line->ctrl.type = LINE_PROC;
    line->proc = expr;

    excl_flags = SQL_EXCL_AGGR | SQL_EXCL_STAR | SQL_EXCL_JOIN | SQL_EXCL_ROWNUM | SQL_EXCL_ROWID | SQL_EXCL_DEFAULT |
        SQL_EXCL_SUBSELECT | SQL_EXCL_COLUMN | SQL_EXCL_ROWSCN | SQL_EXCL_ROWNODEID;
    OG_RETURN_IFERR(plc_verify_expr_node(compiler, line->proc, (void *)line, excl_flags));
    return plc_check_is_proc(line->proc);
}

static status_t plc_language_verify_args(galist_t *decls)
{
    plv_decl_t *decl = NULL;

    for (uint32 i = 0; i < decls->count; i++) {
        decl = (plv_decl_t *)cm_galist_get(decls, i);
        if (decl->type != PLV_VAR) {
            OG_THROW_ERROR(ERR_PL_SYNTAX_ERROR_FMT, "language c only support scalar datatype argument");
            return OG_ERROR;
        }
        typmode_t type = decl->variant.type;

        switch (type.datatype) {
            case OG_TYPE_BOOLEAN:
            case OG_TYPE_SMALLINT:
            case OG_TYPE_USMALLINT:
            case OG_TYPE_INTEGER:
            case OG_TYPE_UINT32:
            case OG_TYPE_UINT64:
            case OG_TYPE_BIGINT:
            case OG_TYPE_FLOAT:
            case OG_TYPE_REAL:
            case OG_TYPE_BINARY:
            case OG_TYPE_VARBINARY:
            case OG_TYPE_RAW:
            case OG_TYPE_VARCHAR:
            case OG_TYPE_CHAR:
                break;
            default:
                OG_SET_ERROR_MISMATCH_EX(type.datatype);
                return OG_ERROR;
        }
    }
    return OG_SUCCESS;
}

static status_t pl_convert_object_name(sql_stmt_t *stmt, pl_entity_t *entity, word_t *word, pl_line_begin_t *begin_line)
{
    if (word->ex_count == 1) {
        if (pl_copy_prefix_tenant(stmt, (text_t *)&word->text, &begin_line->lib_user, pl_copy_name) != OG_SUCCESS) {
            return OG_ERROR;
        }
        if (pl_copy_object_name(entity, word->ex_words[0].type, (text_t *)&word->ex_words[0].text,
            &begin_line->lib_name) != OG_SUCCESS) {
            return OG_ERROR;
        }
    } else if (word->ex_count == 0) {
        text_t user;

        cm_str2text(stmt->session->curr_schema, &user);
        pl_copy_text(entity, &user, &begin_line->lib_user);
        if (pl_copy_object_name(entity, word->type, (text_t *)&word->text, &begin_line->lib_name) != OG_SUCCESS) {
            return OG_ERROR;
        }
    } else {
        OG_SRC_THROW_ERROR_EX(word->text.loc, ERR_SQL_SYNTAX_ERROR, "invalid name");
        return OG_ERROR;
    }

    return OG_SUCCESS;
}

static status_t plc_compile_language_prepare(pl_compiler_t *compiler, function_t *func, pl_line_begin_t *begin_line)
{
    sql_stmt_t *stmt = compiler->stmt;
    pl_entity_t *entity = compiler->entity;
    lex_t *lex = compiler->stmt->session->lex;
    uint32 save_flags = lex->flags;
    uint32 matched_id;
    word_t word;

    OG_RETURN_IFERR(lex_expected_fetch_word(lex, "c"));
    OG_RETURN_IFERR(lex_expected_fetch_1of2(lex, "name", "library", &matched_id));

    lex->flags = LEX_WITH_OWNER;
    if (matched_id == 0) {
        OG_RETURN_IFERR(lex_expected_fetch_variant(lex, &word));
        OG_RETURN_IFERR(pl_copy_object_name_ci(entity, word.type, (text_t *)&word.text, &begin_line->func));
        OG_RETURN_IFERR(lex_expected_fetch_word(lex, "library"));
        OG_RETURN_IFERR(lex_expected_fetch_variant(lex, &word));
        OG_RETURN_IFERR(pl_convert_object_name(stmt, entity, &word, begin_line));
    } else {
        OG_RETURN_IFERR(lex_expected_fetch_variant(lex, &word));
        OG_RETURN_IFERR(pl_convert_object_name(stmt, entity, &word, begin_line));
        OG_RETURN_IFERR(lex_expected_fetch_word(lex, "name"));
        OG_RETURN_IFERR(lex_expected_fetch_variant(lex, &word));
        OG_RETURN_IFERR(pl_copy_object_name_ci(entity, word.type, (text_t *)&word.text, &begin_line->func));
    }
    lex->flags = save_flags;
    func->desc.lang_type = LANG_C;

    return OG_SUCCESS;
}

status_t plc_compile_language(pl_compiler_t *compiler, function_t *func)
{
    galist_t *decls = func->desc.params;
    pl_entity_t *entity = compiler->entity;
    sql_stmt_t *stmt = compiler->stmt;
    pl_line_begin_t *begin_line = NULL;
    uint32 param_count = (entity->pl_type == PL_PROCEDURE) ? decls->count : (decls->count - 1);
    if (param_count > FUNC_MAX_ARGS) {
        OG_THROW_ERROR(ERR_TOO_MANY_OBJECTS, param_count, "C-LANG function params count(100)");
        return OG_ERROR;
    }
    OG_RETURN_IFERR(plc_alloc_line(compiler, sizeof(pl_line_begin_t), LINE_BEGIN, (pl_line_ctrl_t **)&begin_line));
    func->body = (void *)begin_line;
    OG_RETURN_IFERR(plc_compile_language_prepare(compiler, func, begin_line));

    bool32 exists = OG_FALSE;
    uint32 uid = 0;
    text_t curr_user;

    if (!knl_get_user_id(KNL_SESSION(stmt), &begin_line->lib_user, &uid)) {
        OG_THROW_ERROR(ERR_USER_NOT_EXIST, T2S(&begin_line->lib_user));
        return OG_ERROR;
    }

    OG_RETURN_IFERR(pl_find_library(KNL_SESSION(stmt), uid, &begin_line->lib_name, NULL, &exists));
    if (!exists) {
        OG_SRC_THROW_ERROR(compiler->line_loc, ERR_LIBRARY_NOT_EXIST, T2S(&begin_line->lib_user),
            T2S_EX(&begin_line->lib_name));
        return OG_ERROR;
    }

    if (stmt->session->switched_schema) {
        cm_str2text(stmt->session->curr_schema, &curr_user);
    } else {
        curr_user = stmt->session->curr_user;
    }

    OG_RETURN_IFERR(sql_check_library_priv_core(stmt, &begin_line->lib_user, &begin_line->lib_name, &curr_user));
    OG_RETURN_IFERR(plc_language_verify_args(decls));

    return OG_SUCCESS;
}