/* pmpq -- PostgreSQL data type for GMP mpq
 *
 * 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/.
 */

#ifndef __PMPQ_H__
#define __PMPQ_H__

#include <gmp.h>
#include "postgres.h"
#if PG_VERSION_NUM >= 160000
#include "varatt.h"
#endif

typedef struct
{
    char        vl_len_[4];     /* varlena header */
    unsigned    mdata;          /* version, sign, limbs in numer */
    mp_limb_t   data[1];        /* limbs: numer, then denom */

} pmpq;

/* Postgres only allows 2^30 bytes in varlena. Because each limb is at least 4
 * bytes we need at most 28 bits to store the size. So we can use the
 * higher 4 bits for other stuff: we use 2 bits for the version, 1 for the
 * sign and 1 for the order in which denom and numer limbs are stored. */

#define PMPQ_VERSION_MASK           0x30000000U
#define PMPQ_DENOM_FIRST_MASK       0x40000000U
#define PMPQ_SIGN_MASK              0x80000000U
#define PMPQ_SIZE_FIRST_MASK        0x0FFFFFFFU

/* Must be not larger than PGMP_MAX_HDRSIZE */
#define PMPQ_HDRSIZE   MAXALIGN(offsetof(pmpq,data))

#define PMPQ_VERSION(mq) ((((mq)->mdata) & PMPQ_VERSION_MASK) >> 28)
#define PMPQ_SET_VERSION(mdata,v) \
    (((mdata) & ~PMPQ_VERSION_MASK) | (((v) << 28) & PMPQ_VERSION_MASK))

#define PMPQ_SET_NEGATIVE(mdata)    ((mdata) | PMPQ_SIGN_MASK)
#define PMPQ_SET_POSITIVE(mdata)    ((mdata) & ~PMPQ_SIGN_MASK)
#define PMPQ_NEGATIVE(mq)           (((mq)->mdata) & PMPQ_SIGN_MASK)

#define PMPQ_SET_NUMER_FIRST(mdata) ((mdata) & ~PMPQ_DENOM_FIRST_MASK)
#define PMPQ_SET_DENOM_FIRST(mdata) ((mdata) | PMPQ_DENOM_FIRST_MASK)
#define PMPQ_NUMER_FIRST(mq)        (!(((mq)->mdata) & PMPQ_DENOM_FIRST_MASK))
#define PMPQ_DENOM_FIRST(mq)        (((mq)->mdata) & PMPQ_DENOM_FIRST_MASK)

#define PMPQ_NLIMBS(mq) ((VARSIZE(mq) - PMPQ_HDRSIZE) / sizeof(mp_limb_t))
#define PMPQ_SIZE_FIRST(mq)     (((mq)->mdata) & PMPQ_SIZE_FIRST_MASK)
#define PMPQ_SIZE_SECOND(mq)    (PMPQ_NLIMBS(mq) - PMPQ_SIZE_FIRST(mq))
#define PMPQ_SET_SIZE_FIRST(mdata,s) \
    (((mdata) & ~PMPQ_SIZE_FIRST_MASK) | ((s) & PMPQ_SIZE_FIRST_MASK))


/* Macros to convert mpz arguments and return values */

#define PGMP_GETARG_PMPQ(x) \
    ((pmpq*)(PG_DETOAST_DATUM(PG_GETARG_DATUM(x))))

#define PGMP_GETARG_MPQ(q,n) \
    mpq_from_pmpq(q, PGMP_GETARG_PMPQ(n));

#define PGMP_RETURN_MPQ(q) \
    PG_RETURN_POINTER(pmpq_from_mpq(q))


pmpq * pmpq_from_mpq(mpq_ptr q);
void mpq_from_pmpq(mpq_srcptr q, const pmpq *pq);


/* Macros to be used in functions wrappers to limit the arguments domain */

/* TODO: this macro should be somewhere I can import from,
 * I'd prefer avoiding to import pmpz.h here */
#ifndef MPZ_IS_ZERO
#define MPZ_IS_ZERO(z) (SIZ(z) == 0)
#endif

#define PMPQ_NO_CHECK(arg)

#define PMPQ_CHECK_DIV0(arg) \
do { \
    if (UNLIKELY(MPZ_IS_ZERO(mpq_numref(arg)))) \
    { \
        ereport(ERROR, ( \
            errcode(ERRCODE_DIVISION_BY_ZERO), \
            errmsg("division by zero"))); \
    } \
} while (0)

/* used in mpq creation. Note that the argument is a z */
#define ERROR_IF_DENOM_ZERO(arg) \
do { \
    if (UNLIKELY(MPZ_IS_ZERO(arg))) \
    { \
        ereport(ERROR, ( \
            errcode(ERRCODE_DIVISION_BY_ZERO), \
            errmsg("denominator can't be zero"))); \
    } \
} while (0)


#endif  /* __PMPQ_H__ */

