/* 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/.
 */

#include "pmpq.h"
#include "pgmp-impl.h"

#include "fmgr.h"


/*
 * Create a pmpq structure from the content of a mpq
 *
 * The function is not const as the numerator will be realloc'd to make room
 * to the denom limbs after it. For this reason this function must never
 * receive directly data read from the database.
 */
pmpq *
pmpq_from_mpq(mpq_ptr q)
{
    pmpq        *res;
    mpz_ptr     num     = mpq_numref(q);

    if (LIKELY(ALLOC(num)))
    {
        /* Make enough room after the numer to store the denom limbs */
        int     nsize       = SIZ(num);
        int     nalloc      = ABS(nsize);
        int     dsize       = SIZ(mpq_denref(q));
        mpz_ptr den         = mpq_denref(q);

        if (nalloc >= dsize)
        {
            LIMBS(num) = _mpz_realloc(num, nalloc + dsize);
            res = (pmpq *)((char *)LIMBS(num) - PMPQ_HDRSIZE);

            /* copy the denom after the numer */
            memcpy(res->data + nalloc, LIMBS(den), dsize * sizeof(mp_limb_t));

            /* Set the number of limbs and order and implicitly version 0 */
            res->mdata = PMPQ_SET_SIZE_FIRST(PMPQ_SET_NUMER_FIRST(0), nalloc);
        }
        else {
            LIMBS(den) = _mpz_realloc(den, nalloc + dsize);
            res = (pmpq *)((char *)LIMBS(den) - PMPQ_HDRSIZE);

            /* copy the numer after the denom */
            memcpy(res->data + dsize, LIMBS(num), nalloc * sizeof(mp_limb_t));

            /* Set the number of limbs and order and implicitly version 0 */
            res->mdata = PMPQ_SET_SIZE_FIRST(PMPQ_SET_DENOM_FIRST(0), dsize);
        }

        SET_VARSIZE(res, PMPQ_HDRSIZE + (nalloc + dsize) * sizeof(mp_limb_t));

        /* Set the sign */
        if (nsize < 0) { res->mdata = PMPQ_SET_NEGATIVE(res->mdata); }
    }
    else
    {
        /* No allocation for the limbs: allocate something of ours */
        res = palloc(PMPQ_HDRSIZE);
        SET_VARSIZE(res, PMPQ_HDRSIZE);
        res->mdata = 0;
    }

    return res;
}


/*
 * Initialize a mpq from the content of a datum
 *
 * NOTE: the function takes a pointer to a const and changes the structure.
 * This allows to define the structure as const in the calling function and
 * avoid the risk to change it inplace, which may corrupt the database data.
 *
 * The structure populated doesn't own the pointed data, so it must not be
 * changed in any way and must not be cleared.
 */
void
mpq_from_pmpq(mpq_srcptr q, const pmpq *pq)
{
    /* discard the const qualifier */
    mpq_ptr     wq      = (mpq_ptr)q;
    mpz_ptr     num     = mpq_numref(wq);
    mpz_ptr     den     = mpq_denref(wq);

    if (UNLIKELY(0 != (PMPQ_VERSION(pq)))) {
        ereport(ERROR, (
            errcode(ERRCODE_DATA_EXCEPTION),
            errmsg("unsupported mpq version: %d", PMPQ_VERSION(pq))));
    }

    if (0 != PMPQ_NLIMBS(pq))
    {
        mpz_ptr fst, snd;

        if (PMPQ_NUMER_FIRST(pq)) {
            fst = num; snd = den;
        }
        else {
            fst = den; snd = num;
        }

        /* We have data from numer and denom into the datum */
        ALLOC(fst) = SIZ(fst) = PMPQ_SIZE_FIRST(pq);
        LIMBS(fst) = (mp_limb_t *)pq->data;

        ALLOC(snd) = SIZ(snd) = PMPQ_SIZE_SECOND(pq);
        LIMBS(snd) = (mp_limb_t *)pq->data + ALLOC(fst);

        if (PMPQ_NEGATIVE(pq)) { SIZ(num) = -SIZ(num); }
    }
    else {
        /* in the datum there is not 1/0,
         * so let's just refer to some static const */
        ALLOC(num) = 1;
        SIZ(num) = 0;
        LIMBS(num) = (mp_limb_t *)(&_pgmp_limb_0);

        ALLOC(den) = 1;
        SIZ(den) = 1;
        LIMBS(den) = (mp_limb_t *)(&_pgmp_limb_1);
    }
}

