#include <gmpxx.h>
#include <vector>

using namespace std;
//This is the implementation of the number class that manipulates the algebraic extension of Q by one root of an irreducible polynomial
class Number{
public: 
  // first we deal with the minimal polynomial this data is shared by all the instances of the class
  static vector<mpq_class> poly;
  static size_t deg;
  static vector<pair<mpq_class,mpq_class>> rApp;//An interval of rationals that contain the root
  static void improveAp0(){  //This function improves the rational approximation of the root by a factor 2
    mpq_class aver= (rApp[0].first + rApp[0].second)/2; 
    mpq_class tot = 0;
    mpq_class pow = 1;
    for(size_t i=0; i<poly.size(); i++){
      tot+= poly[i]*pow;
      pow*=aver;
    }
    if((tot*rApp[0].second )>0) rApp[0].second = aver;
    else rApp[0].first = aver;
  }
  static  void improveAp(int n=8){ //This function improves the rational approximation of the root (by a factor 256) and of the powers of the root
    for(int i=0; i<n; i++) improveAp0();
    for(size_t i=1; i<deg; i++){
      vector<mpq_class> t(4);
      t[0] = rApp[i-1].first*rApp[0].first;
      t[1] = rApp[i-1].second*rApp[0].second;
      t[2] = rApp[i-1].first*rApp[0].second;
      t[3] = rApp[i-1].second*rApp[0].first;
      rApp[i].first=rApp[i].second=t[0];
      for(int j=1;j<4;j++){
        if(t[j]<rApp[i].first) rApp[i].first = t[j];
        if(t[j]>rApp[i].second) rApp[i].second = t[j];
      }
    }
  }
  static void setPoly(vector<mpq_class>&p, pair<mpq_class,mpq_class> & a){//This function initializes the shared data of the class
    poly = p; 
    deg = poly.size()-1;
    rApp = vector<pair<mpq_class,mpq_class>>(deg);
    rApp[0]=a;
    while(a.first*a.second<0) improveAp0();
    improveAp();
    cout<<"Number class has been properly initialized"<<endl;
    cout<<" Poly is: ";
    for(size_t i=0; i<poly.size();i++) cout<<((i!=0)?'+':' ')<< poly[i]<<" * x^"<<i;
    cout<<endl;
    cout<< "Approx of the root is: "<< rApp[0].first<<"   "<<rApp[0].second<<endl;
  }
  
  //Now we have everything related to one of these numbers
  vector<mpq_class> val;//This is the polynomial of Q[X]/P
  pair<mpq_class,mpq_class> approx;//This is a rational interval that contains the number
  
  Number():val(deg, 0),approx(0,0){}
  Number(int n):val(deg,0), approx(n,n){val[0] = n;}
  Number(mpq_class n):val(deg,0), approx(n,n){val[0] = n;}
  
  void setApprox(){//computes an approximation of the number
    approx.first=approx.second=val[0];
    for(size_t i=0;i<deg-1;i++){//doesnt work if the root is <0
      if(val[i]<0){
        approx.first += val[i+1]*rApp[i].second;
        approx.second += val[i+1]*rApp[i].first;
      }else{
        approx.first += val[i+1]*rApp[i].first; 
        approx.second += val[i+1]*rApp[i].second;        
      }
    }
  }
  
  friend ostream& operator<<(ostream& out, const Number& x){
    out<<x.val[0];
    for(size_t i=1;i<deg;i++) if(x.val[i]) out << " + "<<x.val[i]<< ".x^"<<i;
		return out;
	}
	// some basic arithmetic operations
	Number& operator++(){          
		val[0]+=1;
    approx.first+=1;
    approx.second+=1;
		return *this; 
	}
	Number operator++(int){
		Number tmp(*this);
		operator++(); 
		return tmp; 
	}
	Number& operator--(){          
		val[0]-=1;
    approx.first-=1;
    approx.second-=1;
		return *this; 
	}
	Number operator--(int){
		Number tmp(*this);
		operator--(); 
		return tmp; 
	}
	Number& operator+=(const Number& rhs){   
    for(size_t i=0;i<deg;i++) val[i]+=rhs.val[i];
    approx.first+=rhs.approx.first;
    approx.second+=rhs.approx.second;
		return *this; 
	}	
	friend Number operator+(Number lhs, const Number& rhs){
		lhs += rhs; 
		return lhs; 
	}	
	Number& operator-=(const Number& rhs){    
    for(size_t i=0; i<deg; i++) val[i]-=rhs.val[i];
    approx.first-=rhs.approx.second;
    approx.second-=rhs.approx.first;
		return *this; 
	}	
	friend Number operator-(Number lhs, const Number& rhs){
		lhs -= rhs; 
		return lhs; 
	}			
	Number& operator*=(const Number& rhs){
		vector<mpq_class> temp(deg*2-1,0);
    for(size_t i=0; i<deg; i++)
      for(size_t j=0; j<deg; j++)
        temp[i+j]+=rhs.val[i]*val[j];
    for(size_t i=deg*2-2; i>=deg;i--){
      mpq_class t= temp[i]/poly[deg];
      for(size_t j=0;j<=deg;j++) temp[i-deg+j]-=t*poly[j];
    }
    for(size_t i=0; i<deg;i++) val[i]= temp[i];
    setApprox();
		return *this; 
	}	
	
	friend Number operator*(Number lhs, const Number& rhs){
		lhs*= rhs; 
		return lhs; 
	}	
	//the only complicated arithmetic operation is division. We use the extended euclidean algorithm to get the Bezout coeficients
	Number& operator/=(const Number& rhs){
    vector<mpq_class> t(deg+1,0), newt(deg+1,0), r(poly), newr(rhs.val);
    newr.push_back(0);
    newt[0]=1;   
    while(true){ 
      int degr=-1, degnewr=-1;
      for(int i=0; i<=deg;i++){
        if(r[i]!=0) degr=i;
        if(newr[i]!=0) degnewr=i;
      }
      if(degnewr==-1) break; //<=> newr==0
      vector<mpq_class> newttemp(t), newrtemp(r); 
      for(int i = degr; i >= degnewr; i--){
        mpq_class coef = newrtemp[i]/newr[degnewr];
        for(int j= i-degnewr;j<=deg;j++){
          newrtemp[j] -= newr[j-i+degnewr]*coef;
          newttemp[j] -= newt[j-i+degnewr]*coef;
        }
      }
      r.swap(newr); t.swap(newt);
      newr.swap(newrtemp); newt.swap(newttemp);
    }
    for(int i=0;i<=deg;i++) t[i]/=r[0];// now t contains the inverse of rhs and we can multiply
    vector<mpq_class> temp(deg*2-1,0);
    for(size_t i=0; i<deg; i++)
      for(size_t j=0; j<deg; j++)
        temp[i+j]+=t[i]*val[j];
    for(size_t i=deg*2-2; i>=deg;i--){
      mpq_class coef= temp[i]/poly[deg];
      for(size_t j=0;j<=deg;j++) temp[i-deg+j]-=coef*poly[j];
    }
    for(size_t i=0; i<deg;i++) val[i]= temp[i];
    setApprox();
		return *this; 
  }
	
	friend Number operator/(Number lhs, const Number& rhs){
    lhs/=rhs;
		return lhs; 
	}	
  //computes the power of the number
	Number power(int n){
    if(n==1) return *this; 
    else if(n==0) return 1;
    Number temp(*this);
    temp*=(*this);
    if(n%2==1) return (*this)*(temp.power(n/2));
    else return temp.power(n/2);
  }
	
	//two numbers are equal if they have the same polynomial
	bool isNull(){
    for(size_t i=0;i<deg;i++) if(val[i]!=0) return false;
    return true;
  }
  
	friend inline bool operator==(const Number& lhs, const Number& rhs){
    for(size_t i=0;i<deg;i++) 
      if(lhs.val[i]!=rhs.val[i]) return false; 
    return true;
  }
  //We use the intervals to do comparisons
  //There are many slightly different versions of each comparison because each of them allow to avoid copy of different arguments and the compiler can produce more optimized binary code
	bool isnonneg(){
    if(isNull()) return true;
    setApprox();
    while(true){
      if(approx.first>0) return true;
      if(approx.second<0) return false;
      improveAp();
      cout<<"/!\\ Running with new prec. New prec is : "<< rApp[0].first<<"   "<<rApp[0].second<<endl;
      setApprox();
    }
	}
  
	friend inline bool operator< (Number& lhs, Number& rhs){
    if(lhs==rhs) return false;      
    lhs.setApprox();
    rhs.setApprox();
    while(true){
      if(lhs.approx.second<rhs.approx.first){ return true;}
      if(rhs.approx.second<lhs.approx.first){ return false;}
      improveAp();
      cout<<"/!\\ Running with new prec. New prec is : "<< rApp[0].first<<"   "<<rApp[0].second<<endl;
      lhs.setApprox();
      rhs.setApprox();
    }	
	}
	
	friend inline bool operator> (Number& lhs, Number& rhs){return rhs < lhs;}
	friend inline bool operator<=(Number& lhs, Number& rhs){return !(lhs > rhs);}
	friend inline bool operator>=(Number& lhs, Number& rhs){return !(lhs < rhs);}
	friend inline bool operator!=(Number& lhs, Number& rhs){return !(rhs==lhs);}	
	
	// version with rvalues reference for the case where something is wrong
	friend inline bool operator< (Number&& lhs, Number&& rhs){
    if(lhs==rhs) return false;   
    lhs.setApprox();
    rhs.setApprox();
    while(true){
      if(lhs.approx.second<rhs.approx.first) return true;
      if(rhs.approx.second<lhs.approx.first) return false;
      improveAp();
      cout<<"/!\\ Running with new prec. New prec is : "<< rApp[0].first<<"   "<<rApp[0].second<<endl;
      lhs.setApprox();
      rhs.setApprox();
    }	
	}
	friend inline bool operator< (Number& lhs, Number&& rhs){
    if(lhs == rhs) return false;   
    lhs.setApprox();
    rhs.setApprox();
    while(true){
      if(lhs.approx.second<rhs.approx.first) return true;
      if(rhs.approx.second<lhs.approx.first) return false;
      improveAp();
      cout<<"/!\\ Running with new prec. New prec is : "<< rApp[0].first<<"   "<<rApp[0].second<<endl;
      lhs.setApprox();
      rhs.setApprox();
    }	
	}
	friend inline bool operator< (Number&& lhs, Number& rhs){
    if(lhs==rhs) return false;   
    lhs.setApprox();
    rhs.setApprox();
    while(true){
      if(lhs.approx.second<rhs.approx.first) return true;
      if(rhs.approx.second<lhs.approx.first) return false;
      improveAp();
      cout<<"/!\\ Running with new prec. New prec is : "<< rApp[0].first<<"   "<<rApp[0].second<<endl;
      lhs.setApprox();
      rhs.setApprox();
    }	
	}
	friend inline bool operator> (Number& lhs, Number&& rhs){return rhs < lhs;}
	friend inline bool operator<=(Number& lhs, Number&& rhs){return !(lhs > rhs);}
	friend inline bool operator>=(Number& lhs, Number&& rhs){return !(lhs < rhs);}
	friend inline bool operator!=(Number& lhs, Number&& rhs){return !(rhs==lhs);}
	
	friend inline bool operator> (Number&& lhs, Number& rhs){return rhs < lhs;}
	friend inline bool operator<=(Number&& lhs, Number& rhs){return !(lhs > rhs);}
	friend inline bool operator>=(Number&& lhs, Number& rhs){return !(lhs < rhs);}
	friend inline bool operator!=(Number&& lhs, Number& rhs){return !(rhs==lhs);}
	
	friend inline bool operator> (Number&& lhs, Number&& rhs){return rhs < lhs;}
	friend inline bool operator<=(Number&& lhs, Number&& rhs){return !(lhs > rhs);}
	friend inline bool operator>=(Number&& lhs, Number&& rhs){return !(lhs < rhs);}
	friend inline bool operator!=(Number&& lhs, Number&& rhs){return !(rhs==lhs);}		
	
	friend inline bool operator< (const Number& lh, const Number& rh){Number rhs(rh); Number lhs(lh); return lhs < rhs;}
	friend inline bool operator> (const Number& lh, const Number& rh){Number rhs(rh); Number lhs(lh); return rhs < lhs;}
	friend inline bool operator<=(const Number& lh, const Number& rh){Number rhs(rh); Number lhs(lh); return !(lhs > rhs);}
	friend inline bool operator>=(const Number& lh, const Number& rh){Number rhs(rh); Number lhs(lh); return !(lhs < rhs);}
	friend inline bool operator!=(const Number& lh, const Number& rh){Number rhs(rh); Number lhs(lh); return !(rhs==lhs);}
	
	Number abs(){
		Number ret=0;
		if (!isnonneg()) ret-= *this;
		else ret = *this;
		return ret;
	}	
// 	//return a decent approximation of the number
// 	//this is only usefull to get an approximation of the root
	double get_d(){
    setApprox();
		return (double)(approx.first.get_num().get_si())/(double)(approx.first.get_den().get_si());		
	}
};

Number abs(Number v){
 return v.abs(); 
}

vector<mpq_class> Number::poly;
size_t Number::deg;
vector<pair<mpq_class,mpq_class>> Number::rApp;

