#include "fips202.h"
#include "ntt.h"
#include "poly.h"
#include "reduce.h"

/*************************************************
* Name:        coeff_freeze
*
* Description: Fully reduces an integer modulo q in constant time
*
* Arguments:   uint16_t x: input integer to be reduced
*
* Returns integer in {0,...,q-1} congruent to x modulo q
**************************************************/
static uint16_t coeff_freeze(uint16_t x) {
    uint16_t m, r;
    int16_t c;
    r = x % NEWHOPE_Q;

    m = r - NEWHOPE_Q;
    c = m;
    c >>= 15;
    r = m ^ ((r ^ m)&c);

    return r;
}

/*************************************************
* Name:        flipabs
*
* Description: Computes |(x mod q) - Q/2|
*
* Arguments:   uint16_t x: input coefficient
*
* Returns |(x mod q) - Q/2|
**************************************************/
static uint16_t flipabs(uint16_t x) {
    int16_t r, m;
    r = coeff_freeze(x);

    r = r - NEWHOPE_Q / 2;
    m = r >> 15;
    return (r + m) ^ m;
}

/*************************************************
* Name:        poly_frombytes
*
* Description: De-serialization of a polynomial
*
* Arguments:   - poly *r:                pointer to output polynomial
*              - const unsigned char *a: pointer to input byte array
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_frombytes(poly *r, const unsigned char *a) {
    int i;
    for (i = 0; i < NEWHOPE_N / 4; i++) {
        r->coeffs[4 * i + 0] =                               a[7 * i + 0]        | (((uint16_t)a[7 * i + 1] & 0x3f) << 8);
        r->coeffs[4 * i + 1] = (a[7 * i + 1] >> 6) | (((uint16_t)a[7 * i + 2]) << 2) | (((uint16_t)a[7 * i + 3] & 0x0f) << 10);
        r->coeffs[4 * i + 2] = (a[7 * i + 3] >> 4) | (((uint16_t)a[7 * i + 4]) << 4) | (((uint16_t)a[7 * i + 5] & 0x03) << 12);
        r->coeffs[4 * i + 3] = (a[7 * i + 5] >> 2) | (((uint16_t)a[7 * i + 6]) << 6);
    }
}

/*************************************************
* Name:        poly_tobytes
*
* Description: Serialization of a polynomial
*
* Arguments:   - unsigned char *r: pointer to output byte array
*              - const poly *p:    pointer to input polynomial
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_tobytes(unsigned char *r, const poly *p) {
    int i;
    uint16_t t0, t1, t2, t3;
    for (i = 0; i < NEWHOPE_N / 4; i++) {
        t0 = coeff_freeze(p->coeffs[4 * i + 0]);
        t1 = coeff_freeze(p->coeffs[4 * i + 1]);
        t2 = coeff_freeze(p->coeffs[4 * i + 2]);
        t3 = coeff_freeze(p->coeffs[4 * i + 3]);

        r[7 * i + 0] =  t0 & 0xff;
        r[7 * i + 1] = (unsigned char) ((t0 >> 8) | (t1 << 6));
        r[7 * i + 2] = (unsigned char) ((t1 >> 2));
        r[7 * i + 3] = (unsigned char) ((t1 >> 10) | (t2 << 4));
        r[7 * i + 4] = (unsigned char) ((t2 >> 4));
        r[7 * i + 5] = (unsigned char) ((t2 >> 12) | (t3 << 2));
        r[7 * i + 6] = (unsigned char) ((t3 >> 6));
    }
}

/*************************************************
* Name:        poly_compress
*
* Description: Compression and subsequent serialization of a polynomial
*
* Arguments:   - unsigned char *r: pointer to output byte array
*              - const poly *p:    pointer to input polynomial
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_compress(unsigned char *r, const poly *p) {
    unsigned int i, j, k = 0;

    uint32_t t[8];

    for (i = 0; i < NEWHOPE_N; i += 8) {
        for (j = 0; j < 8; j++) {
            t[j] = coeff_freeze(p->coeffs[i + j]);
            t[j] = (((t[j] << 3) + NEWHOPE_Q / 2) / NEWHOPE_Q) & 0x7;
        }

        r[k]   =  (unsigned char) (t[0]       | (t[1] << 3) | (t[2] << 6));
        r[k + 1] = (unsigned char) ((t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7));
        r[k + 2] = (unsigned char) ((t[5] >> 1) | (t[6] << 2) | (t[7] << 5));
        k += 3;
    }
}

/*************************************************
* Name:        poly_decompress
*
* Description: De-serialization and subsequent decompression of a polynomial;
*              approximate inverse of poly_compress
*
* Arguments:   - poly *r:                pointer to output polynomial
*              - const unsigned char *a: pointer to input byte array
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_decompress(poly *r, const unsigned char *a) {
    unsigned int i, j;
    for (i = 0; i < NEWHOPE_N; i += 8) {
        r->coeffs[i + 0] =  a[0] & 7;
        r->coeffs[i + 1] = (a[0] >> 3) & 7;
        r->coeffs[i + 2] = (a[0] >> 6) | ((a[1] << 2) & 4);
        r->coeffs[i + 3] = (a[1] >> 1) & 7;
        r->coeffs[i + 4] = (a[1] >> 4) & 7;
        r->coeffs[i + 5] = (a[1] >> 7) | ((a[2] << 1) & 6);
        r->coeffs[i + 6] = (a[2] >> 2) & 7;
        r->coeffs[i + 7] = (a[2] >> 5);
        a += 3;
        for (j = 0; j < 8; j++) {
            r->coeffs[i + j] = ((uint32_t)r->coeffs[i + j] * NEWHOPE_Q + 4) >> 3;
        }
    }
}

/*************************************************
* Name:        poly_frommsg
*
* Description: Convert 32-byte message to polynomial
*
* Arguments:   - poly *r:                  pointer to output polynomial
*              - const unsigned char *msg: pointer to input message
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_frommsg(poly *r, const unsigned char *msg) {
    unsigned int i, j, mask;
    for (i = 0; i < NEWHOPE_SYMBYTES; i++) {
        for (j = 0; j < 8; j++) {
            mask = -((msg[i] >> j) & 1);
            r->coeffs[8 * i + j +  0] = mask & (NEWHOPE_Q / 2);
            r->coeffs[8 * i + j + 256] = mask & (NEWHOPE_Q / 2);
        }
    }
}

/*************************************************
* Name:        poly_tomsg
*
* Description: Convert polynomial to 32-byte message
*
* Arguments:   - unsigned char *msg: pointer to output message
*              - const poly *x:      pointer to input polynomial
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_tomsg(unsigned char *msg, const poly *x) {
    unsigned int i;
    uint16_t t;

    for (i = 0; i < 32; i++) {
        msg[i] = 0;
    }

    for (i = 0; i < 256; i++) {
        t  = flipabs(x->coeffs[i +  0]);
        t += flipabs(x->coeffs[i + 256]);
        t = ((t - NEWHOPE_Q / 2));

        t >>= 15;
        msg[i >> 3] |= t << (i & 7);
    }
}

/*************************************************
* Name:        poly_uniform
*
* Description: Sample a polynomial deterministically from a seed,
*              with output polynomial looking uniformly random
*
* Arguments:   - poly *a:                   pointer to output polynomial
*              - const unsigned char *seed: pointer to input seed
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_uniform(poly *a, const unsigned char *seed) {
    unsigned int ctr = 0;
    uint16_t val;
    shake128ctx state;
    uint8_t buf[SHAKE128_RATE];
    uint8_t extseed[NEWHOPE_SYMBYTES + 1];
    int i, j;

    for (i = 0; i < NEWHOPE_SYMBYTES; i++) {
        extseed[i] = seed[i];
    }

    for (i = 0; i < NEWHOPE_N / 64; i++) { /* generate a in blocks of 64 coefficients */
        ctr = 0;
        extseed[NEWHOPE_SYMBYTES] = (unsigned char) i; /* domain-separate the 16 independent calls */
        shake128_absorb(&state, extseed, NEWHOPE_SYMBYTES + 1);
        while (ctr < 64) { /* Very unlikely to run more than once */
            shake128_squeezeblocks(buf, 1, &state);
            for (j = 0; j < SHAKE128_RATE && ctr < 64; j += 2) {
                val = (buf[j] | ((uint16_t) buf[j + 1] << 8));
                if (val < 5 * NEWHOPE_Q) {
                    a->coeffs[i * 64 + ctr] = val;
                    ctr++;
                }
            }
        }
    }
}

/*************************************************
* Name:        hw
*
* Description: Compute the Hamming weight of a byte
*
* Arguments:   - unsigned char a: input byte
**************************************************/
static unsigned char hw(unsigned char a) {
    unsigned char i, r = 0;
    for (i = 0; i < 8; i++) {
        r += (a >> i) & 1;
    }
    return r;
}

/*************************************************
* Name:        poly_sample
*
* Description: Sample a polynomial deterministically from a seed and a nonce,
*              with output polynomial close to centered binomial distribution
*              with parameter k=8
*
* Arguments:   - poly *r:                   pointer to output polynomial
*              - const unsigned char *seed: pointer to input seed
*              - unsigned char nonce:       one-byte input nonce
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_sample(poly *r, const unsigned char *seed, unsigned char nonce) {
    unsigned char buf[128], a, b;
    int i, j;

    unsigned char extseed[NEWHOPE_SYMBYTES + 2];

    for (i = 0; i < NEWHOPE_SYMBYTES; i++) {
        extseed[i] = seed[i];
    }
    extseed[NEWHOPE_SYMBYTES] = nonce;

    for (i = 0; i < NEWHOPE_N / 64; i++) { /* Generate noise in blocks of 64 coefficients */
        extseed[NEWHOPE_SYMBYTES + 1] = (unsigned char) i;
        shake256(buf, 128, extseed, NEWHOPE_SYMBYTES + 2);
        for (j = 0; j < 64; j++) {
            a = buf[2 * j];
            b = buf[2 * j + 1];
            r->coeffs[64 * i + j] = hw(a) + NEWHOPE_Q - hw(b);
        }
    }
}

/*************************************************
* Name:        poly_pointwise
*
* Description: Multiply two polynomials pointwise (i.e., coefficient-wise).
*
* Arguments:   - poly *r:       pointer to output polynomial
*              - const poly *a: pointer to first input polynomial
*              - const poly *b: pointer to second input polynomial
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_mul_pointwise(poly *r, const poly *a, const poly *b) {
    int i;
    uint16_t t;
    for (i = 0; i < NEWHOPE_N; i++) {
        t            = PQCLEAN_NEWHOPE512CPA_CLEAN_montgomery_reduce(3186 * b->coeffs[i]); /* t is now in Montgomery domain */
        r->coeffs[i] = PQCLEAN_NEWHOPE512CPA_CLEAN_montgomery_reduce(a->coeffs[i] * t);  /* r->coeffs[i] is back in normal domain */
    }
}

/*************************************************
* Name:        poly_add
*
* Description: Add two polynomials
*
* Arguments:   - poly *r:       pointer to output polynomial
*              - const poly *a: pointer to first input polynomial
*              - const poly *b: pointer to second input polynomial
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_add(poly *r, const poly *a, const poly *b) {
    int i;
    for (i = 0; i < NEWHOPE_N; i++) {
        r->coeffs[i] = (a->coeffs[i] + b->coeffs[i]) % NEWHOPE_Q;
    }
}

/*************************************************
* Name:        poly_sub
*
* Description: Subtract two polynomials
*
* Arguments:   - poly *r:       pointer to output polynomial
*              - const poly *a: pointer to first input polynomial
*              - const poly *b: pointer to second input polynomial
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_sub(poly *r, const poly *a, const poly *b) {
    int i;
    for (i = 0; i < NEWHOPE_N; i++) {
        r->coeffs[i] = (a->coeffs[i] + 3 * NEWHOPE_Q - b->coeffs[i]) % NEWHOPE_Q;
    }
}

/*************************************************
* Name:        poly_ntt
*
* Description: Forward NTT transform of a polynomial in place
*              Input is assumed to have coefficients in bitreversed order
*              Output has coefficients in normal order
*
* Arguments:   - poly *r: pointer to in/output polynomial
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_ntt(poly *r) {
    PQCLEAN_NEWHOPE512CPA_CLEAN_mul_coefficients(r->coeffs, PQCLEAN_NEWHOPE512CPA_CLEAN_gammas_bitrev_montgomery);
    PQCLEAN_NEWHOPE512CPA_CLEAN_ntt((uint16_t *)r->coeffs, PQCLEAN_NEWHOPE512CPA_CLEAN_gammas_bitrev_montgomery);
}

/*************************************************
* Name:        poly_invntt
*
* Description: Inverse NTT transform of a polynomial in place
*              Input is assumed to have coefficients in normal order
*              Output has coefficients in normal order
*
* Arguments:   - poly *r: pointer to in/output polynomial
**************************************************/
void PQCLEAN_NEWHOPE512CPA_CLEAN_poly_invntt(poly *r) {
    PQCLEAN_NEWHOPE512CPA_CLEAN_bitrev_vector(r->coeffs);
    PQCLEAN_NEWHOPE512CPA_CLEAN_ntt((uint16_t *)r->coeffs, PQCLEAN_NEWHOPE512CPA_CLEAN_omegas_inv_bitrev_montgomery);
    PQCLEAN_NEWHOPE512CPA_CLEAN_mul_coefficients(r->coeffs, PQCLEAN_NEWHOPE512CPA_CLEAN_gammas_inv_montgomery);
}

