/* pmpz_agg -- mpz aggregation functions
 *
 * Copyright (C) 2011-2020 Daniele Varrazzo
 *
 * This file is part of the PostgreSQL GMP Module
 *
 * The PostgreSQL GMP Module is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * The PostgreSQL GMP Module is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the PostgreSQL GMP Module.  If not, see
 * https://www.gnu.org/licenses/.
 */

#include "pmpz.h"
#include "pgmp_utils.h"             /* for AggCheckCallContext on PG < 9.0 */
#include "pgmp-impl.h"

#include "fmgr.h"


/* Convert an inplace accumulator into a pmpz structure.
 *
 * This function is strict, so don't care about NULLs
 */
PGMP_PG_FUNCTION(_pmpz_from_agg)
{
    mpz_t       *a;

    a = (mpz_t *)PG_GETARG_POINTER(0);
    PGMP_RETURN_MPZ(*a);
}


/* Macro to create an accumulation function from a gmp operator.
 *
 * This function can't be strict because the internal state is not compatible
 * with the base type.
 */
#define PMPZ_AGG(op, BLOCK, rel) \
 \
PGMP_PG_FUNCTION(_pmpz_agg_ ## op) \
{ \
    mpz_t           *a; \
    const mpz_t     z = {0}; \
    MemoryContext   oldctx; \
    MemoryContext   aggctx; \
 \
    if (UNLIKELY(!AggCheckCallContext(fcinfo, &aggctx))) \
    { \
        ereport(ERROR, \
            (errcode(ERRCODE_DATA_EXCEPTION), \
            errmsg("_mpz_agg_" #op " can only be called in accumulation"))); \
    } \
 \
    if (PG_ARGISNULL(1)) { \
        if (PG_ARGISNULL(0)) { \
            PG_RETURN_NULL(); \
        } \
        else { \
            PG_RETURN_POINTER(PG_GETARG_POINTER(0)); \
        } \
    } \
 \
    PGMP_GETARG_MPZ(z, 1); \
 \
    oldctx = MemoryContextSwitchTo(aggctx); \
 \
    if (LIKELY(!PG_ARGISNULL(0))) { \
        a = (mpz_t *)PG_GETARG_POINTER(0); \
        BLOCK(op, rel); \
    } \
    else {                      /* uninitialized */ \
        a = (mpz_t *)palloc(sizeof(mpz_t)); \
        mpz_init_set(*a, z); \
    } \
 \
    MemoryContextSwitchTo(oldctx); \
 \
    PG_RETURN_POINTER(a); \
}


#define PMPZ_AGG_OP(op, rel) \
    mpz_ ## op (*a, *a, z)

PMPZ_AGG(add, PMPZ_AGG_OP, 0)
PMPZ_AGG(mul, PMPZ_AGG_OP, 0)
PMPZ_AGG(and, PMPZ_AGG_OP, 0)
PMPZ_AGG(ior, PMPZ_AGG_OP, 0)
PMPZ_AGG(xor, PMPZ_AGG_OP, 0)


#define PMPZ_AGG_REL(op, rel) \
do { \
    if (mpz_cmp(*a, z) rel 0) { \
        mpz_set(*a, z); \
    } \
} while (0)

PMPZ_AGG(min, PMPZ_AGG_REL, >)
PMPZ_AGG(max, PMPZ_AGG_REL, <)

