/* -------------------------------------------------------------------------
 *  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.
 * -------------------------------------------------------------------------
 *
 * var_typmode.c
 *
 *
 * IDENTIFICATION
 * src/common/variant/var_typmode.c
 *
 * -------------------------------------------------------------------------
 */
#include "var_typmode.h"
#include "cm_interval.h"
#include "cm_decimal.h"
#include "var_inc.h"

inline bool cm_is_null_typmode(const typmode_t typmode)
{
    return typmode.datatype == OG_TYPE_VARCHAR && typmode.size == 0;
}

status_t cm_typmode2text(const typmode_t *typmod, text_t *txt, uint32 max_len)
{
    switch (typmod->datatype) {
        case OG_TYPE_CHAR:
        case OG_TYPE_VARCHAR:
        case OG_TYPE_STRING:
            if (typmod->is_rowid_type) {
                OG_RETURN_IFERR(cm_concat_string(txt, max_len, "ROWID"));
                return OG_SUCCESS;
            }
            cm_concat_text(txt, max_len, get_datatype_name(typmod->datatype));
            if ((uint32)typmod->size > 0) {
                cm_concat_fmt(txt, OG_MAX_DATATYPE_STRLEN, "(%u %s)",
                    (uint32)typmod->size,
                    typmod->is_char ? "CHAR" : "BYTE");
            }
            return OG_SUCCESS;

        case OG_TYPE_BINARY:
        case OG_TYPE_VARBINARY:
        case OG_TYPE_RAW:
            cm_concat_fmt(txt, OG_MAX_DATATYPE_STRLEN, "%s(%u)",
                get_datatype_name_str(typmod->datatype),
                (uint32)typmod->size);
            return OG_SUCCESS;

        case OG_TYPE_UINT32:
        case OG_TYPE_INTEGER:
        case OG_TYPE_BOOLEAN:
        case OG_TYPE_BIGINT:
        case OG_TYPE_REAL:
        case OG_TYPE_DATE:
        case OG_TYPE_BLOB:
        case OG_TYPE_CLOB:
        case OG_TYPE_IMAGE:
            cm_concat_text(txt, max_len, get_datatype_name(typmod->datatype));
            return OG_SUCCESS;

        case OG_TYPE_TIMESTAMP:
        case OG_TYPE_TIMESTAMP_TZ_FAKE:
            cm_concat_fmt(txt, OG_MAX_DATATYPE_STRLEN, "%s(%u)",
                get_datatype_name_str(typmod->datatype),
                (uint32)typmod->precision);
            return OG_SUCCESS;

        case OG_TYPE_TIMESTAMP_TZ:
            cm_concat_fmt(txt, OG_MAX_DATATYPE_STRLEN, "%s(%u) WITH TIME ZONE",
                get_datatype_name_str(OG_TYPE_TIMESTAMP),
                (uint32)typmod->precision);
            return OG_SUCCESS;

        case OG_TYPE_TIMESTAMP_LTZ:
            cm_concat_fmt(txt, OG_MAX_DATATYPE_STRLEN, "%s(%u) WITH LOCAL TIME ZONE",
                get_datatype_name_str(OG_TYPE_TIMESTAMP),
                (uint32)typmod->precision);
            return OG_SUCCESS;

        case OG_TYPE_NUMBER:
        case OG_TYPE_DECIMAL:
        case OG_TYPE_NUMBER2:
            cm_concat_text(txt, max_len, get_datatype_name(typmod->datatype));
            OG_RETSUC_IFTRUE((typmod->precision == OG_UNSPECIFIED_NUM_PREC));
            if (typmod->scale == 0) {
                cm_concat_fmt(txt, OG_MAX_DATATYPE_STRLEN, "(%u)", (uint32)typmod->precision);
            } else {
                cm_concat_fmt(txt, OG_MAX_DATATYPE_STRLEN, "(%u, %d)", (uint32)typmod->precision, (int32)typmod->scale);
            }
            return OG_SUCCESS;

        case OG_TYPE_INTERVAL_DS:
            cm_concat_fmt(txt, OG_MAX_DATATYPE_STRLEN, "INTERVAL DAY(%u) TO SECOND(%u)",
                (uint32)typmod->day_prec, (uint32)typmod->frac_prec);
            return OG_SUCCESS;

        case OG_TYPE_INTERVAL_YM:
            cm_concat_fmt(txt, OG_MAX_DATATYPE_STRLEN, "INTERVAL YEAR(%u) TO MONTH", (uint32)typmod->year_prec);
            return OG_SUCCESS;

        case OG_TYPE_BASE:
            cm_concat_text(txt, max_len, get_datatype_name(typmod->datatype));
            return OG_SUCCESS;

        default:
            OG_THROW_ERROR(ERR_UNSUPPORT_DATATYPE,
                get_datatype_name_str(typmod->datatype));
            return OG_ERROR;
    }
}

void cm_adjust_typmode(typmode_t *typmod)
{
    if (OG_IS_VARLEN_TYPE(typmod->datatype) && typmod->size > OG_MAX_COLUMN_SIZE) {
        typmod->size = OG_MAX_COLUMN_SIZE;
    }
}

static inline void cm_combine_charset_typmode(const typmode_t *tm_char, const typmode_t *tmx, typmode_t *tmr)
{
    if (tm_char->size * OG_CHAR_TO_BYTES_RATIO <= tmx->size) {
        tmr->is_char = OG_FALSE;
        tmr->size = tmx->size;
        return;
    }

    tmr->size = MAX(tm_char->size, tmx->size);
    tmr->is_char = OG_TRUE;
}

static inline void cm_combine_string_typmode(const typmode_t *tm1, const typmode_t *tm2, typmode_t *tmr)
{
    if (tm1->datatype == tm2->datatype) {
        tmr->datatype = tm1->datatype;
    } else {
        tmr->datatype = OG_TYPE_VARCHAR;
    }
    if (tm1->is_char == tm2->is_char) {
        tmr->size = MAX(tm1->size, tm2->size);
        tmr->is_char = tm1->is_char;
        return;
    }

    if (tm1->is_char) {
        cm_combine_charset_typmode(tm1, tm2, tmr);
    } else {
        cm_combine_charset_typmode(tm2, tm1, tmr);
    }
}

static inline void cm_combine_binary_typmode(const typmode_t *tm1, const typmode_t *tm2, typmode_t *tmr)
{
    tmr->datatype = (get_datatype_weight(tm1->datatype) > get_datatype_weight(tm2->datatype)) ? tm1->datatype :
        tm2->datatype;
    tmr->size = MAX(tm1->size, tm2->size);
}

static inline void cm_combine_numeric_typmode(const typmode_t *tm1, const typmode_t *tm2, typmode_t *tmr)
{
    *tmr = (get_datatype_weight(tm1->datatype) > get_datatype_weight(tm2->datatype)) ? *tm1 : *tm2;

    if (OG_IS_NUMBER_TYPE(tmr->datatype)) {
        if (tm1->size != tm2->size || tm1->precision != tm2->precision || tm1->scale != tm2->scale) {
            if (OG_IS_NUMBER2_TYPE(tmr->datatype)) {
                tmr->size = MAX_DEC2_BYTE_SZ;
            } else {
                tmr->size = MAX_DEC_BYTE_SZ;
            }
            tmr->precision = OG_UNSPECIFIED_NUM_PREC;
            tmr->scale = OG_UNSPECIFIED_NUM_SCALE;
        }
    } else if (OG_IS_DOUBLE_TYPE(tmr->datatype)) {
        tmr->size = sizeof(double);
        tmr->precision = OG_UNSPECIFIED_NUM_PREC;
        tmr->scale = OG_UNSPECIFIED_NUM_SCALE;
    }

    if ((tm1->datatype == OG_TYPE_UINT32 && tm2->datatype == OG_TYPE_INTEGER) ||
        (tm1->datatype == OG_TYPE_INTEGER && tm2->datatype == OG_TYPE_UINT32)) {
        tmr->datatype = OG_TYPE_BIGINT;
        tmr->size = OG_BIGINT_SIZE;
    }
}

static inline void cm_combine_datetime_typmode(const typmode_t *tm1, const typmode_t *tm2, typmode_t *tmr)
{
    tmr->datatype = (get_datatype_weight(tm1->datatype) > get_datatype_weight(tm2->datatype)) ? tm1->datatype :
        tm2->datatype;
    tmr->size = MAX(tm1->size, tm2->size);
    tmr->precision = MAX(tm1->precision, tm2->precision);
}

/**
* This function can combine two typemodes, when performing UNION [ALL], INTERSECT
* and MINUS operators, and inferring the datatype of CASE..WHEN, NVL and DECODE
* SQL function and expression.

*/
status_t cm_combine_typmode(typmode_t tm1, bool32 is_null1, typmode_t tm2, bool32 is_null2, typmode_t *tmr)
{
    if (is_null1 || cm_is_null_typmode(tm1) || OG_IS_UNKNOWN_TYPE(tm1.datatype)) {
        *tmr = tm2;
        return OG_SUCCESS;
    }

    if (is_null2 || cm_is_null_typmode(tm2) || OG_IS_UNKNOWN_TYPE(tm2.datatype)) {
        *tmr = tm1;
        return OG_SUCCESS;
    }

    if (CM_TYPMODE_IS_EQUAL(&tm1, &tm2)) {
        *tmr = tm1;
        return OG_SUCCESS;
    }

    if (tm2.is_array == OG_TRUE) {
        tmr->is_array = OG_TRUE;
    }

    if (OG_IS_STRING_TYPE2(tm1.datatype, tm2.datatype)) {
        cm_combine_string_typmode(&tm1, &tm2, tmr);
        return OG_SUCCESS;
    }

    if (OG_IS_NUMERIC_TYPE2(tm1.datatype, tm2.datatype)) {
        cm_combine_numeric_typmode(&tm1, &tm2, tmr);
        return OG_SUCCESS;
    }

    if (OG_IS_BINARY_TYPE2(tm1.datatype, tm2.datatype)) {
        cm_combine_binary_typmode(&tm1, &tm2, tmr);
        return OG_SUCCESS;
    }

    if (OG_IS_RAW_TYPE2(tm1.datatype, tm2.datatype)) {
        tmr->datatype = OG_TYPE_RAW;
        tmr->size = MAX(tm1.size, tm2.size);
        return OG_SUCCESS;
    }

    if (OG_IS_DATETIME_TYPE2(tm1.datatype, tm2.datatype)) {
        cm_combine_datetime_typmode(&tm1, &tm2, tmr);
        return OG_SUCCESS;
    }

    if (OG_IS_YMITVL_TYPE2(tm1.datatype, tm2.datatype)) {
        tmr->datatype = OG_TYPE_INTERVAL_YM;
        tmr->size = sizeof(interval_ym_t);
        tmr->year_prec = MAX(tm1.year_prec, tm2.year_prec);
        return OG_SUCCESS;
    }

    if (OG_IS_DSITVL_TYPE2(tm1.datatype, tm2.datatype)) {
        tmr->datatype = OG_TYPE_INTERVAL_DS;
        tmr->size = sizeof(interval_ds_t);
        tmr->day_prec = MAX(tm1.day_prec, tm2.day_prec);
        tmr->frac_prec = MAX(tm1.frac_prec, tm2.frac_prec);
        return OG_SUCCESS;
    }

    OG_THROW_ERROR(ERR_SQL_SYNTAX_ERROR, "expression must have same datatype as corresponding expression");
    return OG_ERROR;
}

status_t cm_typmode2str(const typmode_t *typmod, unsigned char is_array, char *buf, uint32 max_len)
{
    const text_t part = { "[]", 2 };
    text_t text;
    text.len = 0;
    text.str = buf;

    OG_RETURN_IFERR(cm_typmode2text(typmod, &text, max_len));

    if (is_array) {
        cm_concat_text(&text, max_len, &part);
    }

    CM_NULL_TERM(&text);

    return OG_SUCCESS;
}