/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "postgres.h"

#include "utils/builtins.h"
#include "utils/sortsupport.h"

#include "utils/graphid.h"

static int graphid_btree_fast_cmp(Datum x, Datum y, SortSupport ssup);

/* global storage of  OID for graphid and _graphid */
static Oid g_GRAPHIDOID = InvalidOid;
static Oid g_GRAPHIDARRAYOID = InvalidOid;

/* helper function to quickly set, if necessary, and retrieve GRAPHIDOID */
Oid get_GRAPHIDOID(void)
{
    if (g_GRAPHIDOID == InvalidOid)
    {
        g_GRAPHIDOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
                                       CStringGetDatum("graphid"),
                                       ObjectIdGetDatum(ag_catalog_namespace_id()));
    }

    return g_GRAPHIDOID;
}

/* helper function to quickly set, if necessary, and retrieve GRAPHIDARRAYOID */
Oid get_GRAPHIDARRAYOID(void)
{
    if (g_GRAPHIDARRAYOID == InvalidOid)
    {
        g_GRAPHIDARRAYOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
                                            CStringGetDatum("_graphid"),
                                            ObjectIdGetDatum(ag_catalog_namespace_id()));
    }

    return g_GRAPHIDARRAYOID;
}

/* helper function to clear the GRAPHOIDs after a drop extension */
void clear_global_Oids_GRAPHID(void)
{
    g_GRAPHIDOID = InvalidOid;
    g_GRAPHIDARRAYOID = InvalidOid;
}

PG_FUNCTION_INFO_V1(graphid_in);

// graphid type input function
Datum graphid_in(PG_FUNCTION_ARGS)
{
    char *str = PG_GETARG_CSTRING(0);
    char *endptr;
    int64 i;

    errno = 0;
    i = strtol(str, &endptr, 10);
    if (errno != 0 || endptr == str || *endptr != '\0')
    {
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid value for type graphid: \"%s\"", str)));
    }

    AG_RETURN_GRAPHID(i);
}

PG_FUNCTION_INFO_V1(graphid_out);

// graphid type output function
Datum graphid_out(PG_FUNCTION_ARGS)
{
    graphid gid = AG_GETARG_GRAPHID(0);
    char buf[32]; // greater than MAXINT8LEN+1
    char *out;

    pg_lltoa(gid, buf);
    out = pstrdup(buf);

    PG_RETURN_CSTRING(out);
}

PG_FUNCTION_INFO_V1(graphid_eq);

Datum graphid_eq(PG_FUNCTION_ARGS)
{
    graphid lgid = AG_GETARG_GRAPHID(0);
    graphid rgid = AG_GETARG_GRAPHID(1);

    PG_RETURN_BOOL(lgid == rgid);
}

PG_FUNCTION_INFO_V1(graphid_ne);

Datum graphid_ne(PG_FUNCTION_ARGS)
{
    graphid lgid = AG_GETARG_GRAPHID(0);
    graphid rgid = AG_GETARG_GRAPHID(1);

    PG_RETURN_BOOL(lgid != rgid);
}

PG_FUNCTION_INFO_V1(graphid_lt);

Datum graphid_lt(PG_FUNCTION_ARGS)
{
    graphid lgid = AG_GETARG_GRAPHID(0);
    graphid rgid = AG_GETARG_GRAPHID(1);

    PG_RETURN_BOOL(lgid < rgid);
}

PG_FUNCTION_INFO_V1(graphid_gt);

Datum graphid_gt(PG_FUNCTION_ARGS)
{
    graphid lgid = AG_GETARG_GRAPHID(0);
    graphid rgid = AG_GETARG_GRAPHID(1);

    PG_RETURN_BOOL(lgid > rgid);
}

PG_FUNCTION_INFO_V1(graphid_le);

Datum graphid_le(PG_FUNCTION_ARGS)
{
    graphid lgid = AG_GETARG_GRAPHID(0);
    graphid rgid = AG_GETARG_GRAPHID(1);

    PG_RETURN_BOOL(lgid <= rgid);
}

PG_FUNCTION_INFO_V1(graphid_ge);

Datum graphid_ge(PG_FUNCTION_ARGS)
{
    graphid lgid = AG_GETARG_GRAPHID(0);
    graphid rgid = AG_GETARG_GRAPHID(1);

    PG_RETURN_BOOL(lgid >= rgid);
}

PG_FUNCTION_INFO_V1(graphid_btree_cmp);

Datum graphid_btree_cmp(PG_FUNCTION_ARGS)
{
    graphid lgid = AG_GETARG_GRAPHID(0);
    graphid rgid = AG_GETARG_GRAPHID(1);

    if (lgid > rgid)
        PG_RETURN_INT32(1);
    else if (lgid == rgid)
        PG_RETURN_INT32(0);
    else
        PG_RETURN_INT32(-1);
}

PG_FUNCTION_INFO_V1(graphid_btree_sort);

Datum graphid_btree_sort(PG_FUNCTION_ARGS)
{
    SortSupport ssup = (SortSupport)PG_GETARG_POINTER(0);

    ssup->comparator = graphid_btree_fast_cmp;
    PG_RETURN_VOID();
}

static int graphid_btree_fast_cmp(Datum x, Datum y, SortSupport ssup)
{
    graphid lgid = DATUM_GET_GRAPHID(x);
    graphid rgid = DATUM_GET_GRAPHID(y);

    if (lgid > rgid)
        return 1;
    else if (lgid == rgid)
        return 0;
    else
        return -1;
}

graphid make_graphid(const int32 label_id, const int64 entry_id)
{
    uint64 tmp;

    if (!label_id_is_valid(label_id))
    {
        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                        errmsg("label_id must be %d .. %d",
                               LABEL_ID_MIN, LABEL_ID_MAX)));
    }
    if (!entry_id_is_valid(entry_id))
    {
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("entry_id must be " INT64_FORMAT " .. " INT64_FORMAT,
                        ENTRY_ID_MIN, ENTRY_ID_MAX)));
    }

    tmp = (((uint64)label_id) << ENTRY_ID_BITS) |
          (((uint64)entry_id) & ENTRY_ID_MASK);

    return (graphid)tmp;
}

int32 get_graphid_label_id(const graphid gid)
{
    return (int32)(((uint64)gid) >> ENTRY_ID_BITS);
}

int64 get_graphid_entry_id(const graphid gid)
{
    return (int64)(((uint64)gid) & ENTRY_ID_MASK);
}

PG_FUNCTION_INFO_V1(_graphid);

Datum _graphid(PG_FUNCTION_ARGS)
{
    int32 label_id;
    int64 entry_id;
    graphid gid;

    if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
    {
        ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                        errmsg("label_id and entry_id must not be null")));
    }
    label_id = PG_GETARG_INT32(0);
    entry_id = PG_GETARG_INT64(1);

    gid = make_graphid(label_id, entry_id);

    AG_RETURN_GRAPHID(gid);
}

//Hashing Function for Hash Indexes
PG_FUNCTION_INFO_V1(graphid_hash_cmp);

Datum graphid_hash_cmp(PG_FUNCTION_ARGS)
{
    graphid l = AG_GETARG_GRAPHID(0);
    int hash = (int) ((l >> 32) ^ l);// ^ seed;

    PG_RETURN_INT32(hash);
}
