/*
 * Normaliz
 * Copyright (C) 2007-2014  Winfried Bruns, Bogdan Ichim, Christof Soeger
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * As an exception, when this program is distributed through (i) the App Store
 * by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or (iii) Google Play
 * by Google Inc., then that store may impose any digital rights management,
 * device limits and/or redistribution restrictions that are required by its
 * terms of service.
 */

#ifdef NMZ_MIC_OFFLOAD
#pragma offload_attribute (push, target(mic))
#endif

#include <cassert>
#include <iostream>
#include <sstream>
#include <map>
#include <algorithm>

#include "libnormaliz/HilbertSeries.h"
#include "libnormaliz/vector_operations.h"
#include "libnormaliz/map_operations.h"
#include "libnormaliz/integer.h"
#include "libnormaliz/convert.h"
#include "libnormaliz/my_omp.h"

#include "libnormaliz/matrix.h"

#ifdef NMZ_FLINT
#include "flint/flint.h"
#include "flint/fmpz_poly.h"
#endif

//---------------------------------------------------------------------------

namespace libnormaliz {
using std::cout; using std::endl; using std::flush;
using std::istringstream; using std::ostringstream;
using std::pair;

#ifdef NMZ_FLINT
void flint_poly(fmpz_poly_t flp, const vector<mpz_class>& nmzp){
    
    slong n= (slong) nmzp.size();
    fmpz_poly_fit_length(flp,n);
    for(size_t i=0;i<nmzp.size();++i){
        fmpz_poly_set_coeff_mpz(flp,(slong) i, nmzp[i].get_mpz_t());
    }    
}

void nmz_poly(vector<mpz_class>& nmzp, const fmpz_poly_t flp){
    
    size_t n=(size_t) fmpz_poly_length(flp);
    nmzp.resize(n);
    mpz_t c;
    mpz_init(c);
    for(size_t i=0;i<nmzp.size();++i){
        fmpz_poly_get_coeff_mpz(c,flp,i);
        nmzp[i]=mpz_class(c);
    }
    mpz_clear(c);
}
#endif


template<typename Integer>
vector<Integer> poly_mult(const vector<Integer>& a, const vector<Integer>& b) {
    size_t a_size = a.size();
    size_t b_size = b.size();
    
    if(a_size*b_size>1000 && a_size >10 && b_size>10){
        // omp_set_nested(1);
        return karatsubamult(a,b);
        // omp_set_nested(0);
    }
    
    vector<Integer> p( a_size + b_size - 1 );
    size_t i,j;
    for (i=0; i<a_size; ++i) {
        if (a[i] == 0) continue;
        for (j=0; j<b_size; ++j) {
            if (b[j] == 0) continue;
            p[i+j] += a[i]*b[j];
        }
    }
    return p;
}

#ifdef NMZ_FLINT
template<>
vector<mpz_class> poly_mult(const vector<mpz_class>& a, const vector<mpz_class>& b) {
    size_t a_size = a.size();
    size_t b_size = b.size();
    
    vector<mpz_class> p( a_size + b_size - 1 );
    fmpz_poly_t flp1,flp2;
    fmpz_poly_init(flp1);
    fmpz_poly_init(flp2);
    
    flint_poly(flp1,a);
    flint_poly(flp2,b);
    fmpz_poly_mul(flp1,flp1,flp2);
    nmz_poly(p,flp1);    
    
    fmpz_poly_clear(flp1);
    fmpz_poly_clear(flp2);

    return p;
}
#endif

// division with remainder, a = q * b + r, deg(r) < deg(b), needs |leadcoef(b)| = 1
template<typename Integer>
void poly_div(vector<Integer>& q, vector<Integer>& r, const vector<Integer>& a, const vector<Integer>&b) {
    assert(b.back()!=0); // no unneeded zeros
    assert(b.back()==1 || b.back()==-1); // then division is always possible
    r = a;
    remove_zeros(r);
    size_t b_size = b.size();
    int degdiff = r.size()-b_size; // degree differenz
    if (r.size() < b_size) {
        q = vector<Integer>();
    } else {
        q = vector<Integer>(degdiff+1);
    }
    Integer divisor;
    size_t i=0;

    while (r.size() >= b_size) {
        
        divisor = r.back()/b.back();
        q[degdiff] = divisor;
        // r -= divisor * t^degdiff * b
        for (i=0; i<b_size; ++i) {
            r[i+degdiff] -= divisor * b[i];
        }
        remove_zeros(r);
        degdiff = r.size()-b_size;
    }

    return;
}

#ifdef NMZ_FLINT
template<>
void poly_div(vector<mpz_class>& q, vector<mpz_class>& r, const vector<mpz_class>& a, const vector<mpz_class>&b) {
    assert(b.back()!=0); // no unneeded zeros
     assert(b.back()==1 || b.back()==-1); // then division is always possible 

    fmpz_poly_t flpa,flpb,flpq,flpr;
    fmpz_poly_init(flpa);
    fmpz_poly_init(flpb);
    fmpz_poly_init(flpq);
    fmpz_poly_init(flpr);
    
    flint_poly(flpa,a);
    flint_poly(flpb,b);
    
    fmpz_poly_divrem(flpq,flpr,flpa,flpb);
    nmz_poly(q,flpq);
    nmz_poly(r,flpr);    
    
    fmpz_poly_clear(flpa);
    fmpz_poly_clear(flpb);
    fmpz_poly_clear(flpq);
    fmpz_poly_clear(flpr);
    
    return;
}
#endif

template<typename Integer>
vector<Integer> cyclotomicPoly(long n) {
    
    // the static variable is initialized only once and then stored
    static map<long, vector<Integer> > CyclotomicPoly = map<long, vector<Integer> >();
    if (CyclotomicPoly.count(n) == 0) { //it was not computed so far
        vector<Integer> poly, q, r;
        for (long i = 1; i <= n; ++i) {
            // compute needed and uncomputed factors
            if( n % i == 0 && CyclotomicPoly.count(i) == 0) {
                // compute the i-th poly by dividing X^i-1 by the 
                // d-th cycl.poly. with d divides i
                poly = vector<Integer>(i+1);
                poly[0] = -1; poly[i] = 1;  // X^i - 1
                for (long d = 1; d < i; ++d) { // <= i/2 should be ok
                    if( i % d == 0) {
                        poly_div(q, r, poly, CyclotomicPoly[d]);
                        assert(r.empty());
                        poly = q;
                    }
                }
                CyclotomicPoly[i] = poly;
                //cout << i << "-th cycl. pol.: " << CyclotomicPoly[i];
            }
        }
    }
    assert(CyclotomicPoly.count(n)>0);
    return CyclotomicPoly[n];
}

#ifdef NMZ_FLINT
template<>
vector<mpz_class> cyclotomicPoly(long n) {
    
    // the static variable is initialized only once and then stored
    static map<long, vector<mpz_class> > CyclotomicPoly = map<long, vector<mpz_class> >();
    if (CyclotomicPoly.count(n) == 0) { //it was not computed so far
        vector<mpz_class> poly;
        fmpz_poly_t cyc;
        fmpz_poly_init(cyc);
        fmpz_poly_cyclotomic(cyc, (ulong) n);
        nmz_poly(poly,cyc);
        CyclotomicPoly[n] = poly;
        fmpz_poly_clear(cyc);
        //cout << i << "-th cycl. pol.: " << CyclotomicPoly[i];
    }
    assert(CyclotomicPoly.count(n)>0);
    return CyclotomicPoly[n];
}
#endif

long lcm_of_keys(const map<long, denom_t>& m){
    long l = 1;
    map<long, denom_t>::const_iterator it;
    for (it = m.begin(); it != m.end(); ++it) {
        if (it->second != 0)
            l = lcm(l,it->first);
    }
    return l;
}

// compute the hsop numerator by multiplying the HS with a denominator
// of the form product of (1-t^i)
void HilbertSeries::compute_hsop_num() const{
        // get the denominator as a polynomial by mutliplying the (1-t^i) terms
        vector<mpz_class> hsop_denom_poly=vector<mpz_class>(1,1);
        map<long,denom_t>::iterator it;
        long factor;
        for (it=hsop_denom.begin();it!=hsop_denom.end();++it){
            factor = it->first;
            denom_t& denom_i = it->second;
            poly_mult_to(hsop_denom_poly,factor,denom_i);
        }
        //cout << "new denominator as polynomial: " << hsop_denom_poly << endl;
        vector<mpz_class>  quot,remainder,cyclo_poly;
        //first divide the new denom by the cyclo polynomials
        for (auto it=cyclo_denom.begin();it!=cyclo_denom.end();++it){
            for(long i=0;i<it->second;i++){
                cyclo_poly = cyclotomicPoly<mpz_class>(it->first);
                //cout << "the cyclotomic polynomial is " << cyclo_poly << endl;
                // TODO: easier polynomial division possible?
                poly_div(quot,remainder,hsop_denom_poly,cyclo_poly);
                //cout << "the quotient is " << quot << endl;
                hsop_denom_poly=quot;
                assert(remainder.size()==0);
            }
        }
        // multiply with the old numerator
        hsop_num = poly_mult(hsop_denom_poly,cyclo_num);
}

//---------------------------------------------------------------------------

void HilbertSeries::initialize(){
    
    is_simplified = false;
    shift = 0;
    verbose = false;
    nr_coeff_quasipol=-1; // all coefficients
    expansion_degree=-1;
    period_bounded=true;
}

// Constructor, creates 0/1
HilbertSeries::HilbertSeries() {
    num = vector<mpz_class>(1,0);
    //denom just default constructed
    initialize();
}

// Constructor, creates num/denom, see class description for format
HilbertSeries::HilbertSeries(const vector<num_t>& numerator, const vector<denom_t>& gen_degrees) {
    num = vector<mpz_class>(1,0);
    add(numerator, gen_degrees);
    initialize();
}

// Constructor, creates num/denom, see class description for format
HilbertSeries::HilbertSeries(const vector<mpz_class>& numerator, const map<long, denom_t>& denominator) {
    num = numerator;
    denom = denominator;
    initialize();
}

// Constructor, string as created by to_string_rep
HilbertSeries::HilbertSeries(const string& str) {
    from_string_rep(str);
    initialize();
}


void HilbertSeries::reset() {
    num.clear();
    num.push_back(0);
    denom.clear();
    denom_classes.clear();
    shift = 0;
    is_simplified = false;
}

void HilbertSeries::set_nr_coeff_quasipol(long nr_coeff){
    nr_coeff_quasipol=nr_coeff;
}

long HilbertSeries::get_nr_coeff_quasipol() const{
    return nr_coeff_quasipol;
}

void HilbertSeries::set_period_bounded(bool on_off) const{ //period_bounded is mutable
    period_bounded=on_off;
}

bool HilbertSeries::get_period_bounded() const{
    return period_bounded;
}
// add another HilbertSeries to this
void HilbertSeries::add(const vector<num_t>& num, const vector<denom_t>& gen_degrees) {
    vector<denom_t> sorted_gd(gen_degrees);
    sort(sorted_gd.begin(), sorted_gd.end());
    if (gen_degrees.size() > 0) 
        assert(sorted_gd[0]>0); //TODO InputException?
    poly_add_to(denom_classes[sorted_gd], num);
    if (denom_classes.size() > DENOM_CLASSES_BOUND)
        collectData();
    is_simplified = false;
}


// add another HilbertSeries to this
HilbertSeries& HilbertSeries::operator+=(const HilbertSeries& other) {
    // add denom_classes
    map< vector<denom_t>, vector<num_t> >::const_iterator it;
    for (it = other.denom_classes.begin(); it != other.denom_classes.end(); ++it) {
        poly_add_to(denom_classes[it->first], it->second);
    }
    // add accumulated data
    vector<mpz_class> num_copy(other.num);
    performAdd(num_copy, other.denom);
    return (*this);
}

void HilbertSeries::performAdd(const vector<num_t>& numerator, const vector<denom_t>& gen_degrees) const {
    map<long, denom_t> other_denom;
    size_t i, s = gen_degrees.size();
    for (i=0; i<s; ++i) {
        assert(gen_degrees[i]>0);
        other_denom[gen_degrees[i]]++;
    }
    // convert numerator to mpz
    vector<mpz_class> other_num(numerator.size());
    convert(other_num, numerator);
    performAdd(other_num, other_denom);
}

//modifies other_num!!
void HilbertSeries::performAdd(vector<mpz_class>& other_num, const map<long, denom_t>& oth_denom) const {
    map<long, denom_t> other_denom(oth_denom);  //TODO redesign, dont change other_denom
    // adjust denominators
    denom_t diff;
    map<long, denom_t>::iterator it;
    for (it = denom.begin(); it != denom.end(); ++it) {  // augment other
        denom_t& ref = other_denom[it->first];
        diff = it->second - ref;
        if (diff > 0) {
            ref += diff;
            poly_mult_to(other_num, it->first, diff);
        }
    }
    for (it = other_denom.begin(); it != other_denom.end(); ++it) {  // augment this
        denom_t& ref = denom[it->first];
        diff = it->second - ref;
        if (diff > 0) {
            ref += diff;
            poly_mult_to(num, it->first, diff);
        }
    }
    assert (denom == other_denom);

    // now just add the numerators
    poly_add_to(num,other_num);
    remove_zeros(num);
    is_simplified = false;
}

void HilbertSeries::collectData() const {
    if (denom_classes.empty()) return;
	if (verbose) verboseOutput() << "Adding " << denom_classes.size() << " denominator classes..." << flush;
    map< vector<denom_t>, vector<num_t> >::iterator it;
    for (it = denom_classes.begin(); it != denom_classes.end(); ++it) {
        
        INTERRUPT_COMPUTATION_BY_EXCEPTION
        
        performAdd(it->second, it->first);
    }
    denom_classes.clear();
	if (verbose) verboseOutput() << " done." << endl;
}

// simplify, see class description
void HilbertSeries::simplify() const {
    if (is_simplified)
        return;
    collectData();
/*    if (verbose) {
        verboseOutput() << "Hilbert series before simplification: "<< endl << *this;
    }*/
    vector<mpz_class> q, r, poly; //polynomials
    // In denom_cyclo we collect cyclotomic polynomials in the denominator.
    // During this method the Hilbert series is given by num/(denom*cdenom)
    // where denom | cdenom are exponent vectors of (1-t^i) | i-th cyclotminc poly.
    map<long, denom_t> cdenom;

    map<long, denom_t> save_denom=denom;
    vector<mpz_class> save_num=num;
    map<long, denom_t>::reverse_iterator rit;
    long i;
    for (rit = denom.rbegin(); rit != denom.rend(); ++rit) {
        // check if we can divide the numerator by (1-t^i)
        i = rit->first;
        denom_t& denom_i = rit->second;
        poly = coeff_vector<mpz_class>(i);
        while (denom_i > 0) {
            poly_div(q, r, num, poly);
            if (r.size() == 0) { // numerator is divisable by poly
                num = q;
                denom_i--;
            }
            else {
                break;
            }
        }
        if (denom_i == 0)
            continue;

        // decompose (1-t^i) into cyclotomic polynomial
        for(long d=1; d<=i/2; ++d) {
            if (i % d == 0)
                cdenom[d] += denom_i;
        }
        cdenom[i] += denom_i;
        // the product of the cyclo. is t^i-1 = -(1-t^i)
        if (denom_i%2 == 1)
            v_scalar_multiplication(num,mpz_class(-1));
    } // end for
    denom.clear();
 
    map<long, denom_t>::iterator it = cdenom.begin(); 
    while (it != cdenom.end()) {
        // check if we can divide the numerator by i-th cyclotomic polynomial
        
        INTERRUPT_COMPUTATION_BY_EXCEPTION
            
        i = it->first;
        denom_t& cyclo_i = it->second;
        poly = cyclotomicPoly<mpz_class>(i);
        while (cyclo_i > 0) {
            poly_div(q, r, num, poly);
            if (r.empty()) { // numerator is divisable by poly
                num = q;
                cyclo_i--;
            }
            else {
               break;
            }
        }

        if (cyclo_i == 0) {
            cdenom.erase(it++);
        } else {
            ++it;
        }
    }
    // done with canceling
    // save this representation
    cyclo_num = num;
    cyclo_denom = cdenom;

    // now collect the cyclotomic polynomials in (1-t^i) factors
    it = cdenom.find(1);
    if (it != cdenom.end())
        dim = it->second;
    else
        dim = 0;
    period = lcm_of_keys(cdenom);
    if (period_bounded && period > 10*PERIOD_BOUND) {
        if (verbose) {
            errorOutput() << "WARNING: Period is too big, the representation of the Hilbert series may have more than dimension many factors in the denominator!" << endl;
        }
        denom=save_denom;
        num=save_num;
    }
    else{
        while(true){
            //create a (1-t^k) factor in the denominator out of all cyclotomic poly.
            
            INTERRUPT_COMPUTATION_BY_EXCEPTION
            
            long k=1;                
            bool empty=true;
            vector<mpz_class> existing_factor(1,1); //collects the existing cyclotomic gactors in the denom
            for(it=cdenom.begin();it!=cdenom.end();++it){          // with multiplicvity 1
                if(it-> second>0){
                    empty=false;
                    k=libnormaliz::lcm(k,it->first);
                    existing_factor=poly_mult(existing_factor,cyclotomicPoly<mpz_class>(it->first));
                    it->second--;
                }     
            }
            if(empty)
                break;
            denom[k]++;
            vector<mpz_class> new_factor=coeff_vector<mpz_class>(k);
            vector<mpz_class> quotient, dummy;
            poly_div(quotient,dummy,new_factor,existing_factor);
            assert(dummy.empty()); //assert remainder r is 0
            num=poly_mult(num,quotient);
        }
    }

/*    if (verbose) {
        verboseOutput() << "Simplified Hilbert series: " << endl << *this;
    }*/
    if (!hsop_denom.empty()){
        compute_hsop_num();
    }
    is_simplified = true;
    computeDegreeAsRationalFunction();
    quasi_poly.clear();
}

void HilbertSeries::computeDegreeAsRationalFunction() const {
    simplify();
    long num_deg = num.size() - 1 + shift;
    long denom_deg = 0;
    for (auto it = denom.begin(); it != denom.end(); ++it) {
            denom_deg += it->first * it->second;
    }
    degree = num_deg - denom_deg;
}

long HilbertSeries::getDegreeAsRationalFunction() const {
    simplify();
    return degree;
}

long HilbertSeries::getPeriod() const {
    simplify();
    return period;
}

bool HilbertSeries::isHilbertQuasiPolynomialComputed() const {
    return is_simplified && !quasi_poly.empty();
}

void HilbertSeries::resetHilbertQuasiPolynomial(){
    quasi_poly.clear();    
}

vector< vector<mpz_class> > HilbertSeries::getHilbertQuasiPolynomial() const {
    computeHilbertQuasiPolynomial();
    if (quasi_poly.empty()) throw NotComputableException("HilbertQuasiPolynomial");
    return quasi_poly;
}

mpz_class HilbertSeries::getHilbertQuasiPolynomialDenom() const {
    computeHilbertQuasiPolynomial();
    if (quasi_poly.empty()) throw NotComputableException("HilbertQuasiPolynomial");
    return quasi_denom;
}

void HilbertSeries::computeHilbertQuasiPolynomial() const {

    if (isHilbertQuasiPolynomialComputed() || nr_coeff_quasipol==0) return;
    simplify();
    
    omp_set_nested(0);
    
    vector<long> denom_vec=to_vector(denom);    
    if(nr_coeff_quasipol > (long) denom_vec.size()){
        if(verbose)
            verboseOutput() << "Number of coeff of quasipol too large. Reset to deault value." << endl;
        nr_coeff_quasipol=-1;
    }
        
    if (period_bounded &&  period > PERIOD_BOUND) {
        if (verbose) {
            errorOutput()<<"WARNING: We skip the computation of the Hilbert-quasi-polynomial because the period "<< period <<" is too big!" <<endl;
        }
        return;
    }
    if (verbose && period > 1) {
        verboseOutput() << "Computing Hilbert quasipolynomial of period "
                        << period <<" ..." << flush;
    }
    long i,j;
    //period und dim encode the denominator
    //now adjust the numerator
    long num_size = num.size();
    vector<mpz_class> norm_num(num_size);  //normalized numerator
    for (i = 0;  i < num_size;  ++i) {
        norm_num[i] = num[i];
    }
    map<long, denom_t>::reverse_iterator rit;
    long d;
    vector<mpz_class> r;
    for (rit = denom.rbegin(); rit != denom.rend(); ++rit) {
        
        INTERRUPT_COMPUTATION_BY_EXCEPTION
            
        d = rit->first;
        //nothing to do if it already has the correct t-power
        if (d != period) {
            //norm_num *= (1-t^p / 1-t^d)^denom[d]
            //first by multiply: norm_num *= (1-t^p)^denom[d]
            poly_mult_to(norm_num, period, rit->second);

            //then divide: norm_num /= (1-t^d)^denom[d]
            for (i=0; i < rit->second; ++i) {
                poly_div(norm_num, r, norm_num, coeff_vector<mpz_class>(d));
                assert(r.empty()); //assert remainder r is 0
            }
        }
    }
    
    // determine the common period of the coefficients that will be computed and printed
    long reduced_period;
    if(nr_coeff_quasipol>=0){
        reduced_period=1;
        for(long j=0;j< nr_coeff_quasipol;++j)
            reduced_period=lcm(reduced_period,denom_vec[j]);
    }
    else
        reduced_period=period;
    
    //cut numerator into period many pieces and apply standard method
    // we make only reduced_period many components
    quasi_poly = vector< vector<mpz_class> >(reduced_period);
    long nn_size = norm_num.size();
    for (j=0; j<reduced_period; ++j) {
        quasi_poly[j].reserve(dim);
    }
    for (i=0; i<nn_size; ++i) {
        if(i%period<reduced_period)
            quasi_poly[i%period].push_back(norm_num[i]);
    }

    #pragma omp parallel for
    for (j=0; j<reduced_period; ++j) {
        
        INTERRUPT_COMPUTATION_BY_EXCEPTION
        
        quasi_poly[j] = compute_polynomial(quasi_poly[j], dim);
    }
    
    //substitute t by t/period:
    //dividing by period^dim and multipling the coeff with powers of period
    mpz_class pp=1;
    for (i = dim-2; i >= 0; --i) {
        pp *= period; //p^i   ok, it is p^(dim-1-i)
        for (j=0; j<reduced_period; ++j) {
            quasi_poly[j][i] *= pp;
        }
    } //at the end pp=p^dim-1
    //the common denominator for all coefficients, dim! * pp
    quasi_denom = permutations<mpz_class>(1,dim) * pp;
    //substitute t by t-j
    for (j=0; j<reduced_period; ++j) {
        // X |--> X - (j + shift)
        linear_substitution<mpz_class>(quasi_poly[j], j + shift); // replaces quasi_poly[j]
    }
    //divide by gcd //TODO operate directly on vector
    Matrix<mpz_class> QP(quasi_poly);
    mpz_class g = QP.matrix_gcd();
    g = libnormaliz::gcd(g,quasi_denom);
    quasi_denom /= g;
    QP.scalar_division(g);
    //we use a normed shift, so that the cylcic shift % period always yields a non-negative integer
    long normed_shift = -shift;
    while (normed_shift < 0) normed_shift += reduced_period;
    for (j=0; j<reduced_period; ++j) {
        quasi_poly[j] = QP[(j+normed_shift)%reduced_period]; // QP[ (j - shift) % p ]
    }
    
    long delete_coeff=0;
    if(nr_coeff_quasipol>=0)
        delete_coeff=(long) quasi_poly[0].size()-nr_coeff_quasipol;
    
    for(size_t i=0;i<quasi_poly.size();++i) // delete coefficients that have not been computed completely
        for(long j=0;j <delete_coeff;++j)
            quasi_poly[i][j]=0;
        
    if (verbose && period > 1) {
        verboseOutput() << " done." << endl;
    }
}

// expands the series to degree to_degree
void HilbertSeries::compute_expansion() const{
    
    expansion.clear();
    vector<mpz_class> denom_expansion=expand_denom();
    expansion=poly_mult(num,denom_expansion);
    if((long) expansion.size()> expansion_degree+1)
        expansion.resize(expansion_degree+1);    
}

vector<mpz_class> HilbertSeries::getExpansion() const{

    compute_expansion();
    return expansion;    
}

long HilbertSeries::get_expansion_degree() const{
    return expansion_degree;    
}

void HilbertSeries::set_expansion_degree(long degree){
    expansion_degree=degree;
}

vector<mpz_class> HilbertSeries::expand_denom() const{
    
    vector<long> denom_vec=to_vector(denom);
    vector<mpz_class> result(1,1); // the constant 1
    for(size_t i=0;i<denom_vec.size();++i){
        vector<mpz_class> this_factor=expand_inverse(denom_vec[i], expansion_degree);
        result=poly_mult(result,this_factor);
        if((long) result.size()>expansion_degree+1)
            result.resize(expansion_degree+1);
    }
    return result;
}

// computes the series expansion of 1/(1-t^e)
vector<mpz_class> expand_inverse(size_t exponent, long to_degree){
    
    vector<mpz_class> expansion(to_degree+1,0);
    for(long i=0;i<= to_degree; i+=exponent)
        expansion[i]=1;
    return expansion;    
}

// returns the numerator, repr. as vector of coefficients, the h-vector
const vector<mpz_class>& HilbertSeries::getNum() const {
    simplify();
    return num;
}
// returns the denominator, repr. as a map of the exponents of (1-t^i)^e
const map<long, denom_t>& HilbertSeries::getDenom() const {
    simplify();
    return denom;
}

// returns the numerator, repr. as vector of coefficients
const vector<mpz_class>& HilbertSeries::getCyclotomicNum() const {
    simplify();
    return cyclo_num;
}
// returns the denominator, repr. as a map of the exponents of (1-t^i)^e
const map<long, denom_t>& HilbertSeries::getCyclotomicDenom() const {
    simplify();
    return cyclo_denom;
}

const map<long, denom_t>& HilbertSeries::getHSOPDenom() const {
    simplify();
    return hsop_denom;
}

const vector<mpz_class>& HilbertSeries::getHSOPNum() const {
    simplify();
    assert(v_non_negative(hsop_num));
    return hsop_num;
}

// shift
void HilbertSeries::setShift(long s) {
    if (shift != s) {
        is_simplified = false;
        // remove quasi-poly //TODO could also be adjusted
        quasi_poly.clear();
        quasi_denom = 1;
        shift = s;
    }
}

void HilbertSeries::setHSOPDenom(vector<denom_t> new_denom){
    hsop_denom=count_in_map<long,denom_t>(new_denom);
}

void HilbertSeries::setHSOPDenom(map<long,denom_t> new_denom){
    hsop_denom=new_denom;
}

long HilbertSeries::getShift() const {
    return shift;
}

void HilbertSeries::adjustShift() {
    collectData();
    size_t adj = 0; // adjust shift by
    while (adj < num.size() && num[adj] == 0) adj++;
    if (adj > 0) {
        shift += adj;
        num.erase(num.begin(),num.begin()+adj);
        if (cyclo_num.size() != 0) {
            assert (cyclo_num.size() >= adj);
            cyclo_num.erase(cyclo_num.begin(),cyclo_num.begin()+adj);
        }
    }
}

// methods for textual transfer of a Hilbert Series
string HilbertSeries::to_string_rep() const {

    collectData();
    ostringstream s;

    s << num.size() << " ";
    s << num;
    vector<denom_t> denom_vector(to_vector(denom));
    s << denom_vector.size() << " ";
    s << denom_vector;
    return s.str();
}

void HilbertSeries::from_string_rep(const string& input) {

    istringstream s(input);
    long i,size;

    s >> size;
    num.resize(size);
    for (i = 0; i < size; ++i) {
        s >> num[i];
    }

    vector<denom_t> denom_vector;
    s >> size;
    denom_vector.resize(size);
    for (i = 0; i < size; ++i) {
        s >> denom_vector[i];
    }

    denom = count_in_map<long,denom_t>(denom_vector);
    is_simplified = false;
}

// writes in a human readable format
ostream& operator<< (ostream& out, const HilbertSeries& HS) {
    HS.collectData();
    out << "(";
    // i == 0
    if (HS.num.size()>0) out << " " << HS.num[0];
    if (HS.shift != 0)   out << "*t^" << HS.shift;
    for (size_t i=1; i<HS.num.size(); ++i) {
             if ( HS.num[i]== 1 ) out << " +t^"<< i + HS.shift;
        else if ( HS.num[i]==-1 ) out << " -t^"<< i + HS.shift;
        else if ( HS.num[i] > 0 ) out << " +"  << HS.num[i] << "*t^" << i + HS.shift;
        else if ( HS.num[i] < 0 ) out << " -"  <<-HS.num[i] << "*t^" << i + HS.shift;
    }
    out << " ) / (";
    if (HS.denom.empty()) {
        out << " 1";
    }
    map<long, denom_t>::const_iterator it;
    for (it = HS.denom.begin(); it != HS.denom.end(); ++it) { 
        if ( it->second != 0 ) out << " (1-t^"<< it->first <<")^" << it->second;
    }
    out << " )" << std::endl;
    return out;
}



//---------------------------------------------------------------------------
// polynomial operations, for polynomials repr. as vector of coefficients
//---------------------------------------------------------------------------

// returns the coefficient vector of 1-t^i
template<typename Integer>
vector<Integer> coeff_vector(size_t i) {
    vector<Integer> p(i+1,0);
    p[0] =  1;
    p[i] = -1;
    return p;
}

template<typename Integer>
void remove_zeros(vector<Integer>& a) {
    size_t i=a.size();
    while ( i>0 && a[i-1]==0 ) --i;

    if (i < a.size()) {
        a.resize(i);
    }
}

// a += b  (also possible to define the += op for vector)
template<typename Integer>
void poly_add_to (vector<Integer>& a, const vector<Integer>& b) {
    size_t b_size = b.size();
    if (a.size() < b_size) {
        a.resize(b_size);
    }
    for (size_t i=0; i<b_size; ++i) {
        a[i]+=b[i];
    }
    remove_zeros(a);
}

// a += b*t^m
template<typename Integer>
void poly_add_to_tm (vector<Integer>& a, const vector<Integer>& b,long m) {
    size_t b_size=b.size();
    size_t b_m = b_size+m;
    if (a.size() < b_m) {
        a.resize(b_m);
    }
    for (size_t i=0; i<b_size; ++i) {
        a[i+m]+=b[i];
    }
    remove_zeros(a);
}

// a -= b  (also possible to define the -= op for vector)
template<typename Integer>
void poly_sub_to (vector<Integer>& a, const vector<Integer>& b) {
    size_t b_size = b.size();
    if (a.size() < b_size) {
        a.resize(b_size);
    }
    for (size_t i=0; i<b_size; ++i) {
        a[i]-=b[i];
    }
    remove_zeros(a);
}

// a *= t^m
template<typename Integer>
void poly_mult_by_tm(vector<Integer>& a, long m){
        long a_ori_size=a.size();
        a.resize(a_ori_size+m);
        for(long i=a_ori_size-1; i>=0;--i)
            a[i+m]=a[i];
        for(long i=0;i<m;++i)
            a[i]=0; 
}

// a * b

/* template<typename Integer>
vector<Integer> old_poly_mult(const vector<Integer>& a, const vector<Integer>& b) {
    size_t a_size = a.size();
    size_t b_size = b.size();

    
    vector<Integer> p( a_size + b_size - 1 );
    size_t i,j;
    for (i=0; i<a_size; ++i) {
        if (a[i] == 0) continue;
        for (j=0; j<b_size; ++j) {
            if (b[j] == 0) continue;
            p[i+j] += a[i]*b[j];
        }
    }
    return p;
}*/

template<typename Integer>
vector<Integer> karatsubamult(const vector<Integer>& a, const vector<Integer>& b) {

    size_t a_size = a.size();
    size_t b_size = b.size();
    if(a_size*b_size<=1000 || a_size <=10 || b_size<=10){
        return poly_mult(a,b);
    }

    size_t m=(a_size+1)/2;
    if(2*m<(b_size+1)){
        m=(b_size+1)/2;
    }
    
    vector<Integer> f0(m),f1(m),g0(m),g1(m);
    for(size_t i=0;i<m && i<a_size;++i)
        f0[i]=a[i];
    for(size_t i=m;i<a_size;++i)
        f1[i-m]=a[i];
    for(size_t i=0;i<m && i<b_size;++i)
        g0[i]=b[i];
    for(size_t i=m;i< b_size;++i)
        g1[i-m]=b[i];
    remove_zeros(f0);
    remove_zeros(f1);
    remove_zeros(g0);
    remove_zeros(g1);
    
    vector<Integer> sf=f0;
    vector<Integer> sg=g0;
    
    vector<Integer> mix;
    vector<Integer> h00;
    vector<Integer> h11;

    #pragma omp parallel // num_threads(3)
    {
    
    #pragma omp single nowait
    {
    h00=karatsubamult(f0,g0); // h00 = f0 * g0
    }
    
   #pragma omp single nowait
    {  
    h11=karatsubamult(f1,g1); // h11 = f1 * g1
    }

    #pragma omp single nowait
    {
    poly_add_to(sf,f1); // f0+f1
    poly_add_to(sg,g1); // g0 + g1
    mix=karatsubamult(sf,sg); // (f0 + f1)*(g0 + g1)
    }
    
    } // parallel
    
    f0.clear();
    g0.clear();
    f1.clear();
    g1.clear();
    
    poly_sub_to(mix,h00);  // mix = mix - f0*g0
    poly_sub_to(mix,h11);  // mix = mix - f1*g1
    
    poly_add_to_tm(h00,mix,m);
    poly_add_to_tm(h00,h11,2*m);

    return h00;
}




// a *= (1-t^d)^e
template<typename Integer>
void poly_mult_to(vector<Integer>& a, long d, long e) {
    assert(d>0);
    assert(e>=0);
    long i;
    a.reserve(a.size() + d*e);
    while (e>0) {
        a.resize(a.size() + d);
        for (i=a.size()-1; i>=d; --i) {
            a[i] -= a[i-d];
        }
        e--;
    }
}






//---------------------------------------------------------------------------
// computing the Hilbert polynomial from h-vector
//---------------------------------------------------------------------------

// The algorithm follows "Cohen-Macaulay rings", 4.1.5 and 4.1.9.
// The E_vector is the vector of higher multiplicities.
// It is assumed that (d-1)! is used as a common denominator in the calling routine.

template<typename Integer>
vector<Integer> compute_e_vector(vector<Integer> Q, int dim){
    size_t j;
    int i;
    vector <Integer> E_Vector(dim,0); 
    // cout << "QQQ " << Q;  
    // Q.resize(dim+1);
    int bound=Q.size();
    if(bound>dim)
        bound=dim;  
    for (i = 0; i <bound; i++) {
        for (j = 0; j < Q.size()-i; j++) {  
            E_Vector[i] += Q[j];
        }
        E_Vector[i]/=permutations<Integer>(1,i);
        for (j = 1; j <Q.size()-i; j++) {
            Q[j-1]=j*Q[j];
        }
    }
    return E_Vector;
}

//---------------------------------------------------------------------------

template<typename Integer>
vector<Integer> compute_polynomial(vector<Integer> h_vector, int dim) {
    // handle dimension 0
    if (dim == 0)
        return vector<Integer>(dim);

    vector<Integer> Hilbert_Polynomial = vector<Integer>(dim);
    int i,j;
    
    Integer mult_factor;
    vector <Integer> E_Vector=compute_e_vector(h_vector, dim);
    vector <Integer> C(dim,0);
    C[0]=1;
    for (i = 0; i <dim; i++) {
        mult_factor=permutations<Integer>(i,dim);
        if (((dim-1-i)%2)==0) {
            for (j = 0; j <dim; j++) {
                Hilbert_Polynomial[j]+=mult_factor*E_Vector[dim-1-i]*C[j];
            }
        }
        else {
            for (j = 0; j <dim; j++) {
                Hilbert_Polynomial[j]-=mult_factor*E_Vector[dim-1-i]*C[j];
            }
        }
        for (j = dim-1; 0 <j; j--) {
            C[j]=(unsigned long)(i+1)*C[j]+C[j-1];
        }
        C[0]=permutations<Integer>(1,i+1);
    }

    return Hilbert_Polynomial;
}

//---------------------------------------------------------------------------

// substitutes t by (t-a), overwrites the polynomial!
template<typename Integer>
void linear_substitution(vector<Integer>& poly, const Integer& a) {
    long deg = poly.size()-1;
    // Iterated division by (t+a)
    for (long step=0; step<deg; ++step) {
        for (long i = deg-1; i >= step; --i) {
            poly[i] -= a * poly[i+1];
        }
        //the remainders are the coefficients of the transformed polynomial
    }
}

//---------------------------------------------------------------------------
IntegrationData::IntegrationData(){
}

void IntegrationData::set_nr_coeff_quasipol(long nr_coeff){
    weighted_Ehrhart_series.first.set_nr_coeff_quasipol(nr_coeff);
}

void IntegrationData::set_expansion_degree(long degree){
    weighted_Ehrhart_series.first.set_expansion_degree(degree);
}

string IntegrationData::getPolynomial() const{
    return polynomial;
}

long IntegrationData::getDegreeOfPolynomial() const{
    return degree_of_polynomial; 
}

void IntegrationData::setDegreeOfPolynomial(const long d){
    degree_of_polynomial=d; 
}

IntegrationData::IntegrationData(const string& poly){
    polynomial=poly;
    polynomial_is_homogeneous=false; // to be on the safe side
}

bool IntegrationData::isWeightedEhrhartQuasiPolynomialComputed() const{
    return weighted_Ehrhart_series.first.isHilbertQuasiPolynomialComputed();
}

vector< vector<mpz_class> > IntegrationData::getWeightedEhrhartQuasiPolynomial() const{
    return weighted_Ehrhart_series.first.getHilbertQuasiPolynomial();
}

void IntegrationData::resetHilbertQuasiPolynomial(){
    weighted_Ehrhart_series.first.resetHilbertQuasiPolynomial();
}

vector<mpz_class> IntegrationData::getExpansion() const{
    return weighted_Ehrhart_series.first.getExpansion();
}

void IntegrationData::computeWeightedEhrhartQuasiPolynomial(){
    weighted_Ehrhart_series.first.computeHilbertQuasiPolynomial();
}


mpz_class IntegrationData::getWeightedEhrhartQuasiPolynomialDenom() const{
    return weighted_Ehrhart_series.first.getHilbertQuasiPolynomialDenom()*weighted_Ehrhart_series.second;         
}

const vector<mpz_class>& IntegrationData::getNum_ZZ() const{
    return weighted_Ehrhart_series.first.getNum();
}

const map<long, denom_t>& IntegrationData::getDenom() const{
    return weighted_Ehrhart_series.first.getDenom();
}

const vector<mpz_class>& IntegrationData::getCyclotomicNum_ZZ() const{
    return weighted_Ehrhart_series.first.getCyclotomicNum();   
}

const map<long, denom_t>& IntegrationData::getCyclotomicDenom() const{
    return weighted_Ehrhart_series.first.getCyclotomicDenom();  
}

const pair<HilbertSeries, mpz_class>& IntegrationData::getWeightedEhrhartSeries() const{
    return weighted_Ehrhart_series;
}

mpq_class IntegrationData::getIntegral() const{
    return integral;
}

mpz_class IntegrationData::getNumeratorCommonDenom() const{
    return weighted_Ehrhart_series.second;
}

mpq_class IntegrationData::getVirtualMultiplicity() const{
        return virtual_multiplicity;
}

void IntegrationData::setIntegral(const mpq_class I){
    integral=I;
}

void IntegrationData::setVirtualMultiplicity(const mpq_class I){
        virtual_multiplicity=I;
}

void IntegrationData::setWeightedEhrhartSeries(const pair<HilbertSeries, mpz_class>& E){
    weighted_Ehrhart_series=E;
    weighted_Ehrhart_series.first.adjustShift();
}

void IntegrationData::setHomogeneity(const bool hom){
    polynomial_is_homogeneous=hom;
}


bool IntegrationData::isPolynomialHomogeneous() const{
    return polynomial_is_homogeneous;
}

} //end namespace libnormaliz

#ifdef NMZ_MIC_OFFLOAD
#pragma offload_attribute (pop)
#endif
