/* -------------------------------------------------------------------------
 *
 * wparser.c
 *		Standard interface to word parser
 *
 * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/wparser.c
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"
#include "knl/knl_variable.h"

#include "funcapi.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "tsearch/ts_cache.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"

typedef struct {
    int cur;
    LexDescr* list;
} TSTokenTypeStorage;

static void tt_setup_firstcall(FuncCallContext* funcctx, Oid prsid)
{
    TupleDesc tupdesc;
    MemoryContext oldcontext;
    TSTokenTypeStorage* st = NULL;
    TSParserCacheEntry* prs = lookup_ts_parser_cache(prsid);

    if (!OidIsValid(prs->lextypeOid))
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NODE_STATE),
                errmsg("method lextype isn't defined for text search parser %u", prsid)));

    oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

    st = (TSTokenTypeStorage*)palloc(sizeof(TSTokenTypeStorage));
    st->cur = 0;
    /* lextype takes one dummy argument */
    st->list = (LexDescr*)DatumGetPointer(OidFunctionCall1(prs->lextypeOid, (Datum)0));
    funcctx->user_fctx = (void*)st;

    tupdesc = CreateTemplateTupleDesc(3, false);
    TupleDescInitEntry(tupdesc, (AttrNumber)1, "tokid", INT4OID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber)2, "alias", TEXTOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber)3, "description", TEXTOID, -1, 0);

    funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
    (void)MemoryContextSwitchTo(oldcontext);
}

static Datum tt_process_call(FuncCallContext* funcctx)
{
    TSTokenTypeStorage* st = NULL;

    st = (TSTokenTypeStorage*)funcctx->user_fctx;
    if (st->list && st->list[st->cur].lexid) {
        Datum result;
        char* values[3];
        char txtid[16];
        HeapTuple tuple;
        errno_t errorno = EOK;

        errorno = sprintf_s(txtid, sizeof(txtid), "%d", st->list[st->cur].lexid);
        securec_check_ss(errorno, "\0", "\0");
        values[0] = txtid;
        values[1] = st->list[st->cur].alias;
        values[2] = st->list[st->cur].descr;

        tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
        result = HeapTupleGetDatum(tuple);

        pfree_ext(values[1]);
        pfree_ext(values[2]);
        st->cur++;
        return result;
    }
    if (st->list != NULL)
        pfree_ext(st->list);
    pfree_ext(st);
    return (Datum)0;
}

Datum ts_token_type_byid(PG_FUNCTION_ARGS)
{
    ts_check_feature_disable();
    FuncCallContext* funcctx = NULL;
    Datum result;

    if (SRF_IS_FIRSTCALL()) {
        funcctx = SRF_FIRSTCALL_INIT();
        tt_setup_firstcall(funcctx, PG_GETARG_OID(0));
    }

    funcctx = SRF_PERCALL_SETUP();
    if ((result = tt_process_call(funcctx)) != (Datum)0)
        SRF_RETURN_NEXT(funcctx, result);
    SRF_RETURN_DONE(funcctx);
}

Datum ts_token_type_byname(PG_FUNCTION_ARGS)
{
    ts_check_feature_disable();
    FuncCallContext* funcctx = NULL;
    Datum result;

    if (SRF_IS_FIRSTCALL()) {
        text* prsname = PG_GETARG_TEXT_P(0);
        Oid prsId;

        funcctx = SRF_FIRSTCALL_INIT();
        prsId = get_ts_parser_oid(textToQualifiedNameList(prsname), false);
        tt_setup_firstcall(funcctx, prsId);
    }

    funcctx = SRF_PERCALL_SETUP();
    if ((result = tt_process_call(funcctx)) != (Datum)0)
        SRF_RETURN_NEXT(funcctx, result);
    SRF_RETURN_DONE(funcctx);
}

typedef struct {
    int type;
    char* lexeme;
} LexemeEntry;

typedef struct {
    int cur;
    int len;
    LexemeEntry* list;
} PrsStorage;

static void prs_setup_firstcall(FuncCallContext* funcctx, Oid prsid, text* txt)
{
    TupleDesc tupdesc;
    MemoryContext oldcontext;
    PrsStorage* st = NULL;
    TSParserCacheEntry* prs = lookup_ts_parser_cache(prsid);
    char* lex = NULL;
    int llen = 0;
    int type = 0;
    void* prsdata = NULL;

    oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
    errno_t errorno = EOK;

    st = (PrsStorage*)palloc(sizeof(PrsStorage));
    st->cur = 0;
    st->len = 16;
    st->list = (LexemeEntry*)palloc(sizeof(LexemeEntry) * st->len);

    /*
     * Add paramater configuration oid, so the parser can fetch parser options, this
     * function is directly called by ts_parse(innner function ists_parse_byid/ts_parse_byname),
     * that means there is no given configuration for this parsing, in fact we will use
     * guc para to dominate the parsing
     */
    prsdata = (void*)DatumGetPointer(FunctionCall3(
        &prs->prsstart, PointerGetDatum(VARDATA(txt)), Int32GetDatum(VARSIZE(txt) - VARHDRSZ), ObjectIdGetDatum(0)));

    while ((type = DatumGetInt32(FunctionCall3(
                &prs->prstoken, PointerGetDatum(prsdata), PointerGetDatum(&lex), PointerGetDatum(&llen)))) != 0) {
        if (st->cur >= st->len) {
            st->len = 2 * st->len;
            st->list = (LexemeEntry*)repalloc(st->list, sizeof(LexemeEntry) * st->len);
        }
        st->list[st->cur].lexeme = (char*)palloc(llen + 1);
        errorno = memcpy_s(st->list[st->cur].lexeme, llen + 1, lex, llen);
        securec_check(errorno, "\0", "\0");
        st->list[st->cur].lexeme[llen] = '\0';
        st->list[st->cur].type = type;
        st->cur++;
    }

    FunctionCall1(&prs->prsend, PointerGetDatum(prsdata));

    st->len = st->cur;
    st->cur = 0;

    funcctx->user_fctx = (void*)st;
    tupdesc = CreateTemplateTupleDesc(2, false);
    TupleDescInitEntry(tupdesc, (AttrNumber)1, "tokid", INT4OID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber)2, "token", TEXTOID, -1, 0);

    funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
    (void)MemoryContextSwitchTo(oldcontext);
}

static Datum prs_process_call(FuncCallContext* funcctx)
{
    PrsStorage* st = NULL;

    st = (PrsStorage*)funcctx->user_fctx;
    if (st->cur < st->len) {
        Datum result;
        char* values[2];
        char tid[16];
        HeapTuple tuple;
        errno_t errorno = EOK;

        values[0] = tid;
        errorno = sprintf_s(tid, sizeof(tid), "%d", st->list[st->cur].type);
        securec_check_ss(errorno, "\0", "\0");
        values[1] = st->list[st->cur].lexeme;
        tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
        result = HeapTupleGetDatum(tuple);

        pfree_ext(values[1]);
        st->cur++;
        return result;
    } else {
        pfree_ext(st->list);
        pfree_ext(st);
    }
    return (Datum)0;
}

Datum ts_parse_byid(PG_FUNCTION_ARGS)
{
    ts_check_feature_disable();
    FuncCallContext* funcctx = NULL;
    Datum result;

    if (SRF_IS_FIRSTCALL()) {
        text* txt = PG_GETARG_TEXT_P(1);

        funcctx = SRF_FIRSTCALL_INIT();
        prs_setup_firstcall(funcctx, PG_GETARG_OID(0), txt);
        PG_FREE_IF_COPY(txt, 1);
    }

    funcctx = SRF_PERCALL_SETUP();
    if ((result = prs_process_call(funcctx)) != (Datum)0)
        SRF_RETURN_NEXT(funcctx, result);
    SRF_RETURN_DONE(funcctx);
}

Datum ts_parse_byname(PG_FUNCTION_ARGS)
{
    ts_check_feature_disable();
    FuncCallContext* funcctx = NULL;
    Datum result;

    if (SRF_IS_FIRSTCALL()) {
        text* prsname = PG_GETARG_TEXT_P(0);
        text* txt = PG_GETARG_TEXT_P(1);
        Oid prsId;

        funcctx = SRF_FIRSTCALL_INIT();
        prsId = get_ts_parser_oid(textToQualifiedNameList(prsname), false);
        prs_setup_firstcall(funcctx, prsId, txt);
    }

    funcctx = SRF_PERCALL_SETUP();
    if ((result = prs_process_call(funcctx)) != (Datum)0)
        SRF_RETURN_NEXT(funcctx, result);
    SRF_RETURN_DONE(funcctx);
}

Datum ts_headline_byid_opt(PG_FUNCTION_ARGS)
{
    ts_check_feature_disable();
    text* in = PG_GETARG_TEXT_P(1);
    TSQuery query = PG_GETARG_TSQUERY(2);
    text* opt = (PG_NARGS() > 3 && PG_GETARG_POINTER(3)) ? PG_GETARG_TEXT_P(3) : NULL;
    HeadlineParsedText prs;
    List* prsoptions = NIL;
    text* out = NULL;
    TSConfigCacheEntry* cfg = NULL;
    TSParserCacheEntry* prsobj = NULL;

    cfg = lookup_ts_config_cache(PG_GETARG_OID(0));
    prsobj = lookup_ts_parser_cache(cfg->prsId);
    errno_t errorno = EOK;

    if (!OidIsValid(prsobj->headlineOid))
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("text search parser does not support headline creation")));

    errorno = memset_s(&prs, sizeof(HeadlineParsedText), 0, sizeof(HeadlineParsedText));
    securec_check(errorno, "\0", "\0");
    prs.lenwords = 32;
    prs.words = (HeadlineWordEntry*)palloc(sizeof(HeadlineWordEntry) * prs.lenwords);

    hlparsetext(cfg->cfgId, &prs, query, VARDATA(in), VARSIZE(in) - VARHDRSZ);

    if (opt != NULL)
        prsoptions = deserialize_deflist(PointerGetDatum(opt));
    else
        prsoptions = NIL;

    FunctionCall3(&(prsobj->prsheadline), PointerGetDatum(&prs), PointerGetDatum(prsoptions), PointerGetDatum(query));

    out = generateHeadline(&prs);

    PG_FREE_IF_COPY(in, 1);
    PG_FREE_IF_COPY(query, 2);
    if (opt != NULL)
        PG_FREE_IF_COPY(opt, 3);
    pfree_ext(prs.words);
    pfree_ext(prs.startsel);
    pfree_ext(prs.stopsel);

    PG_RETURN_POINTER(out);
}

Datum ts_headline_byid(PG_FUNCTION_ARGS)
{
    ts_check_feature_disable();
    PG_RETURN_DATUM(
        DirectFunctionCall3(ts_headline_byid_opt, PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2)));
}

Datum ts_headline(PG_FUNCTION_ARGS)
{
    ts_check_feature_disable();
    PG_RETURN_DATUM(DirectFunctionCall3(
        ts_headline_byid_opt, ObjectIdGetDatum(getTSCurrentConfig(true)), PG_GETARG_DATUM(0), PG_GETARG_DATUM(1)));
}

Datum ts_headline_opt(PG_FUNCTION_ARGS)
{
    ts_check_feature_disable();
    PG_RETURN_DATUM(DirectFunctionCall4(ts_headline_byid_opt,
        ObjectIdGetDatum(getTSCurrentConfig(true)),
        PG_GETARG_DATUM(0),
        PG_GETARG_DATUM(1),
        PG_GETARG_DATUM(2)));
}