/*++
Copyright (c) 2011 Microsoft Corporation

Module Name:

    mpn.cpp

Abstract:

    Multi Precision Natural Numbers

Author:

    Christoph Wintersteiger (cwinter) 2011-11-16.

Revision History:

--*/
#include "util/debug.h"
#include "util/trace.h"
#include "util/buffer.h"
#include "util/mpn.h"

typedef uint64_t mpn_double_digit;
static_assert(sizeof(mpn_double_digit) == 2 * sizeof(mpn_digit), "size alignment");

int mpn_manager::compare(mpn_digit const * a, unsigned lnga, 
                         mpn_digit const * b, unsigned lngb) const {
    int res = 0;

    trace(a, lnga);

    unsigned j = std::max(lnga, lngb);
    for (; j-- > 0 && res == 0;) {
        mpn_digit u_j = (j < lnga) ? a[j] : 0;
        mpn_digit v_j = (j < lngb) ? b[j] : 0;
        if (u_j > v_j)
            res = 1;
        else if (u_j < v_j)
            res = -1;
    }

    TRACE("mpn", tout << ((res == 1) ? " > " : (res == -1) ? " < " : " == "); );
    
    trace_nl(b, lngb);
    return res;
}

bool mpn_manager::add(mpn_digit const * a, unsigned lnga,
                      mpn_digit const * b, unsigned lngb,
                      mpn_digit * c, unsigned lngc_alloc,
                      unsigned * plngc) const {
    trace(a, lnga, b, lngb, "+");
    // Essentially Knuth's Algorithm A
    unsigned len = std::max(lnga, lngb);
    SASSERT(lngc_alloc == len+1 && len > 0);    
    mpn_digit k = 0;
    mpn_digit r;
    bool c1, c2;
    for (unsigned j = 0; j < len; j++) {
        mpn_digit u_j = (j < lnga) ? a[j] : 0;
        mpn_digit v_j = (j < lngb) ? b[j] : 0;
        r = u_j + v_j; c1 = r < u_j;
        c[j] = r + k;  c2 = c[j] < r;
        k = c1 | c2;
    }
    c[len] = k;
    unsigned &os = *plngc;
    for (os = len+1; os > 1 && c[os-1] == 0; ) os--;
    SASSERT(os > 0 && os <= len+1);
    trace_nl(c, os);
    return true; // return k != 0?
}

bool mpn_manager::sub(mpn_digit const * a, unsigned lnga,
                      mpn_digit const * b, unsigned lngb,
                      mpn_digit * c, mpn_digit * pborrow) const {
    trace(a, lnga, b, lngb, "-");
    // Essentially Knuth's Algorithm S
    unsigned len = std::max(lnga, lngb);
    mpn_digit & k = *pborrow; k = 0;
    mpn_digit r;
    bool c1, c2;
    for (unsigned j = 0; j < len; j++) {
        mpn_digit u_j = (j < lnga) ? a[j] : 0;
        mpn_digit v_j = (j < lngb) ? b[j] : 0;
        r = u_j - v_j; c1 = r > u_j;
        c[j] = r - k;  c2 = c[j] > r;
        k = c1 | c2;
    }
    trace_nl(c, lnga);
    return true; // return k != 0?
}

bool mpn_manager::mul(mpn_digit const * a, unsigned lnga,
                      mpn_digit const * b, unsigned lngb,
                      mpn_digit * c) const {
    trace(a, lnga, b, lngb, "*");
    // Essentially Knuth's Algorithm M. 
    // Perhaps implement a more efficient version, see e.g., Knuth, Section 4.3.3.    
    unsigned i;
    mpn_digit k;

#define DIGIT_BITS (sizeof(mpn_digit)*8)
#define HALF_BITS (sizeof(mpn_digit)*4)

    for (unsigned i = 0; i < lnga; i++)
        c[i] = 0;

    for (unsigned j = 0; j < lngb; j++) {        
        mpn_digit v_j = b[j];
        if (v_j == 0) { // This branch may be omitted according to Knuth.
            c[j+lnga] = 0;
        }
        else {
            k = 0;
            for (i = 0; i < lnga; i++) {
                mpn_digit u_i = a[i];
                mpn_double_digit t;
                t = ((mpn_double_digit)u_i * (mpn_double_digit)v_j) + 
                    (mpn_double_digit) c[i+j] + 
                    (mpn_double_digit) k;
                
                c[i+j] = (t << DIGIT_BITS) >> DIGIT_BITS;
                k = t >> DIGIT_BITS;
            }
            c[j+lnga] = k;
        }        
    }
    
    trace_nl(c, lnga+lngb);
    return true;
}

#define MASK_FIRST (~((mpn_digit)(-1) >> 1))
#define FIRST_BITS(N, X) ((X) >> (DIGIT_BITS-(N)))
#define LAST_BITS(N, X) (((X) << (DIGIT_BITS-(N))) >> (DIGIT_BITS-(N)))
#define BASE ((mpn_double_digit)0x01 << DIGIT_BITS)

bool mpn_manager::div(mpn_digit const * numer, unsigned lnum,
                      mpn_digit const * denom, unsigned lden,
                      mpn_digit * quot,
                      mpn_digit * rem) {
    trace(numer, lnum, denom, lden, "/");
    bool res = false;    

    if (lnum < lden) {
        for (unsigned i = 0; i < (lnum-lden+1); i++)
            quot[i] = 0;
        for (unsigned i = 0; i < lden; i++)
            rem[i] = (i < lnum) ? numer[i] : 0;
        return false;
    }

    SASSERT(denom[lden-1] != 0);

    if (lnum == 1 && lden == 1) {
        *quot = numer[0] / denom[0];
        *rem  = numer[0] % denom[0];
    }
    else if (lnum < lden || (lnum == lden && numer[lnum-1] < denom[lden-1])) {
        *quot = 0;        
        for (unsigned i = 0; i < lden; i++)
            rem[i] = (i < lnum) ? numer[i] : 0;       
    }        
    else  {
        mpn_sbuffer u, v, t_ms, t_ab;
        unsigned d = div_normalize(numer, lnum, denom, lden, u, v);
        if (lden == 1)
            res = div_1(u, v[0], quot);
        else
            res = div_n(u, v, quot, rem, t_ms, t_ab);
        div_unnormalize(u, v, d, rem);    
    }

    // TRACE("mpn_dbg", display_raw(tout, quot, lnum - lden + 1); tout << ", ";
    //                  display_raw(tout, rem, lden); tout << std::endl; );
    trace_nl(quot, lnum-lden+1);

    trace(numer, lnum, denom, lden, "%");
    trace_nl(rem, lden);

#ifdef Z3DEBUG
    mpn_sbuffer temp(lnum+1, 0);
    mul(quot, lnum-lden+1, denom, lden, temp.data());
    unsigned real_size;
    add(temp.data(), lnum, rem, lden, temp.data(), lnum+1, &real_size);
    bool ok = true;
    for (unsigned i = 0; i < lnum && ok; i++)
        if (temp[i] != numer[i]) ok = false;
    if (temp[lnum] != 0) ok = false;
    CTRACE("mpn_dbg", !ok, tout << "DIV BUG: quot * denom + rem = "; display_raw(tout, temp.data(), lnum+1); tout << std::endl; );
    SASSERT(ok);
#endif

    return res;
}

unsigned mpn_manager::div_normalize(mpn_digit const * numer, unsigned lnum,
                                  mpn_digit const * denom, unsigned lden,
                                  mpn_sbuffer & n_numer,
                                  mpn_sbuffer & n_denom) const
{    
    unsigned d = 0;
    while (lden > 0 && ((denom[lden-1] << d) & MASK_FIRST) == 0) d++;
    SASSERT(d < DIGIT_BITS);
    
    n_numer.resize(lnum+1);
    n_denom.resize(lden);
    
    if (d == 0) {
        n_numer[lnum] = 0;
        for (unsigned i = 0; i < lnum; i++)
            n_numer[i] = numer[i];
        for (unsigned i = 0; i < lden; i++)
            n_denom[i] = denom[i];
    }
    else if (lnum != 0) {
        SASSERT(lden > 0);
        mpn_digit q = FIRST_BITS(d, numer[lnum-1]);
        n_numer[lnum] = q;
        for (unsigned i = lnum-1; i > 0; i--)
            n_numer[i] = (numer[i] << d) | FIRST_BITS(d, numer[i-1]);
        n_numer[0] = numer[0] << d; 
        for (unsigned i = lden-1; i > 0; i--)
            n_denom[i] = denom[i] << d | FIRST_BITS(d, denom[i-1]);
        n_denom[0] = denom[0] << d;   
    }
    else {
        d = 0;
    }

    TRACE("mpn_norm", tout << "Normalized: n_numer="; display_raw(tout, n_numer.data(), n_numer.size()); 
                      tout << " n_denom="; display_raw(tout, n_denom.data(), n_denom.size()); tout << std::endl; );
    return d;
}

void mpn_manager::div_unnormalize(mpn_sbuffer & numer, mpn_sbuffer & denom,
                                  unsigned d, mpn_digit * rem) const {
    if (d == 0) {
        for (unsigned i = 0; i < denom.size(); i++)
            rem[i] = numer[i];
    }
    else {
        for (unsigned i = 0; i < denom.size()-1; i++)
            rem[i] = numer[i] >> d | (LAST_BITS(d, numer[i+1]) << (DIGIT_BITS-d));
        rem[denom.size()-1] = numer[denom.size()-1] >> d;
    }
}

bool mpn_manager::div_1(mpn_sbuffer & numer, mpn_digit const denom,
                        mpn_digit * quot) const {
    mpn_double_digit q_hat, temp, ms;
    mpn_digit borrow;

    for (unsigned j = numer.size()-1; j > 0; j--) {
        temp = (((mpn_double_digit)numer[j]) << DIGIT_BITS) | ((mpn_double_digit)numer[j-1]);
        q_hat = temp / (mpn_double_digit) denom;
        if (q_hat >= BASE) {
            UNREACHABLE(); // is this reachable with normalized v?
        }
        SASSERT(q_hat < BASE);
        ms = temp - (q_hat * (mpn_double_digit) denom);
        borrow = ms > temp;
        numer[j-1] = (mpn_digit) ms;
        numer[j] = ms >> DIGIT_BITS;
        quot[j-1] = (mpn_digit) q_hat;
        if (borrow) {
            quot[j-1]--;
            numer[j] = numer[j-1] + denom;
        }
        TRACE("mpn_div1", 
              mpn_double_digit r_hat = temp % (mpn_double_digit) denom;
              tout << "j=" << j << " q_hat=" << q_hat << " r_hat=" << r_hat;
              tout << " ms=" << ms;
              tout << " new numer="; display_raw(tout, numer.data(), numer.size());
              tout << " borrow=" << borrow;
              tout << std::endl; );
    }

    return true; // return rem != 0?
}

bool mpn_manager::div_n(mpn_sbuffer & numer, mpn_sbuffer const & denom,
                        mpn_digit * quot, mpn_digit * rem,
                        mpn_sbuffer & ms, mpn_sbuffer & ab) const {
    SASSERT(denom.size() > 1);

    // This is essentially Knuth's Algorithm D.
    unsigned m = numer.size() - denom.size();
    unsigned n = denom.size();

    SASSERT(numer.size() == m+n);

    ms.resize(n+1);
    
    mpn_double_digit q_hat, temp, r_hat;
    mpn_digit borrow;

    for (unsigned j = m; j-- > 0; ) {
        temp = (((mpn_double_digit)numer[j+n]) << DIGIT_BITS) | ((mpn_double_digit)numer[j+n-1]);
        q_hat = temp / (mpn_double_digit) denom[n-1];
        r_hat = temp % (mpn_double_digit) denom[n-1];
        recheck:
        if (q_hat >= BASE || 
            ((q_hat * denom[n-2]) > ((r_hat << DIGIT_BITS) + numer[j+n-2]))) {
                q_hat--;
                r_hat += denom[n-1];
                if (r_hat < BASE) goto recheck;
        }
        SASSERT(q_hat < BASE);        
        // Replace numer[j+n]...numer[j] with 
        // numer[j+n]...numer[j] - q * (denom[n-1]...denom[0])
        mpn_digit q_hat_small = (mpn_digit)q_hat;
        mul(&q_hat_small, 1, denom.data(), n, ms.data());
        sub(&numer[j], n+1, ms.data(), n+1, &numer[j], &borrow);
        quot[j] = q_hat_small;
        if (borrow) {
            quot[j]--;
            ab.resize(n+2);
            unsigned real_size;
            add(denom.data(), n, &numer[j], n+1, ab.data(), n+2, &real_size);
            for (unsigned i = 0; i < n+1; i++)
                numer[j+i] = ab[i];
        }
        TRACE("mpn_div", tout << "q_hat=" << q_hat << " r_hat=" << r_hat;
                         tout << " ms="; display_raw(tout, ms.data(), n);
                         tout << " new numer="; display_raw(tout, numer.data(), m+n+1);
                         tout << " borrow=" << borrow;
                         tout << std::endl; );
    }
    
    return true; // return rem != 0?
}

char * mpn_manager::to_string(mpn_digit const * a, unsigned lng, char * buf, unsigned lbuf) const {
    SASSERT(buf && lbuf > 0);    
    TRACE("mpn_to_string", tout << "[mpn] to_string "; display_raw(tout, a, lng); tout << " == "; );

    if (lng == 1) {
#ifdef _WINDOWS
        sprintf_s(buf, lbuf, "%u", *a);
#else
        snprintf(buf, lbuf, "%u", *a);
#endif
    }
    else {
        mpn_sbuffer temp(lng, 0), t_numer(lng+1, 0), t_denom(1, 0);
        for (unsigned i = 0; i < lng; i++)
            temp[i] = a[i];
    
        unsigned j = 0;
        mpn_digit rem;
        mpn_digit ten = 10;        
        while (!temp.empty() && (temp.size() > 1 || temp[0] != 0)) {
            unsigned d = div_normalize(&temp[0], temp.size(), &ten, 1, t_numer, t_denom);
            div_1(t_numer, t_denom[0], &temp[0]);
            div_unnormalize(t_numer, t_denom, d, &rem);
            buf[j++] = '0' + rem;
            while (!temp.empty() && temp.back() == 0)
                temp.pop_back();
        }
        buf[j] = 0;

        j--;
        unsigned mid = (j/2) + ((j % 2) ? 1 : 0);        
        for (unsigned i = 0; i < mid; i++)
            std::swap(buf[i], buf[j-i]);
    }

    TRACE("mpn_to_string", tout << buf << std::endl; );

    return buf;
}

void mpn_manager::display_raw(std::ostream & out, mpn_digit const * a, unsigned lng) const {
    out << "[";
    for (unsigned i = lng; i-- > 0; ) { out << a[i]; if (i != 0) out << "|"; }
    out << "]";
}

void mpn_manager::trace(mpn_digit const * a, unsigned lnga, 
                        mpn_digit const * b, unsigned lngb, 
                        const char * op) const {
#ifdef Z3DEBUG
    char char_buf[4096];
    TRACE("mpn", tout << "[mpn] " << to_string(a, lnga, char_buf, sizeof(char_buf));
                 tout << " " << op << " " << to_string(b, lngb, char_buf, sizeof(char_buf));
                 tout << " == "; );
#endif
}

void mpn_manager::trace(mpn_digit const * a, unsigned lnga) const {
#ifdef Z3DEBUG
    char char_buf[4096];
    TRACE("mpn", tout << to_string(a, lnga, char_buf, sizeof(char_buf)); );
#endif
}

void mpn_manager::trace_nl(mpn_digit const * a, unsigned lnga) const {
#ifdef Z3DEBUG
    char char_buf[4096];
    TRACE("mpn", tout << to_string(a, lnga, char_buf, sizeof(char_buf)) << std::endl; );
#endif
}
