#include "field_rationals.h"

#include <memory>
#include <assert.h>
#include <gmp.h>

#include "lp_cdd.h"
#include "printer.h"

#include "timer.h"

static Timer rationalTimer("Rational",1);


int FieldElementRationalsLiving;

class FieldElementRational : public FieldElementImplementation
{
  mpq_t value;
 public:
  FieldElementRational():FieldElementImplementation()
    {
      FieldElementRationalsLiving++;
      mpq_init(value);
    }
  FieldElementRational(int n_):FieldElementImplementation()
    {
      FieldElementRationalsLiving++;
      mpz_init_set_si(mpq_numref(value), n_);
      mpz_init_set_ui(mpq_denref(value), 1);
    }
  ~FieldElementRational()
    {
      FieldElementRationalsLiving--;
      mpq_clear(value);
    }
  FieldElementRational& operator=(const FieldElementRational& a)
    {
      assert(0);
      const FieldElementRational *A=(const FieldElementRational*)&a;
      if (this != A) {
        mpq_clear(value);
        mpz_init_set(mpq_numref(value), mpq_numref(a.value));
        mpz_init_set(mpq_denref(value), mpq_denref(a.value));
      }
      return *this;
    }
  
  mpq_t const *getGmpRationalTemporaryPointer()const
    {
      return &value;
    }
  void operator*=(const FieldElementImplementation &a)
    {
      const FieldElementRational *A=(const FieldElementRational*)&a;
      assert(A);

      TimerScope ts(&rationalTimer);
      mpq_mul(value,value,A->value);
    }

  FieldElementRational *one() const;
  bool isZero() const
    {
      return mpq_sgn(value)==0;
    }

  FieldElementRational *sum(const FieldElementImplementation &b)const
    {
      TimerScope ts(&rationalTimer);
      const FieldElementRational *B=(const FieldElementRational*)&b;
      FieldElementRational *r= new FieldElementRational;
      mpq_add(r->value,value,B->value);
      return r;
    }
  FieldElementRational *difference(const FieldElementImplementation &b)const
    {
      TimerScope ts(&rationalTimer);
      const FieldElementRational *B=(const FieldElementRational*)&b;
      FieldElementRational *r= new FieldElementRational;
      mpq_sub(r->value,value,B->value);
      return r;
    }
  FieldElementRational *negation()const
    {
      FieldElementRational *r= new FieldElementRational;
      mpq_neg(r->value,value);

      return r;
    }
  FieldElementImplementation *inverse()const
  {
    FieldElementRational *r= new FieldElementRational;

    if(isZero())
      {
	AsciiPrinter P(Stderr);
	P.printString("Error inverting FieldElement: ");
	//	P.printFieldElement(*this);
	P.printString("\n");
	assert(0);
      }
    
    mpq_inv(r->value,value);
    return r;
  }

  static string LaTeXTranslator(const string &s)
  {
    int startIndex=0;
    string sign;
    if(s[0]=='-')
      {
	sign=string("-");
	startIndex=1;
      }
    int slashIndex=-1;
    for(int i=startIndex;i<s.length();i++)if(s[i]=='/')slashIndex=i;
    if(slashIndex==-1)
      return string(s);

    return sign+string("{").append(s,startIndex,slashIndex-startIndex)+string("\\over ").append(s,slashIndex+1,s.length()-slashIndex-1)+string("}");
  }

  std::string toString(bool writeIfOne=true, bool alwaysWriteSign=false, bool latexMode=false) const
  {
    FieldElementRational *tempOne=one();
    FieldElementRational *temp=difference(*tempOne);
    bool isOne=temp->isZero();
    delete temp;
    temp=sum(*tempOne);
    bool isMinusOne=temp->isZero();
    delete temp;
    delete tempOne;

    if(!writeIfOne && isOne)
      {
	if(alwaysWriteSign)return std::string("+");
	return std::string("");
      }
    if(!writeIfOne && isMinusOne)
      return std::string("-");
    static char s[1290*1000];
    //    mpq_get_str(s,10,value); //// CHECK BUFFER SIZE!!!!
    // Changed to make code gmp 3.1.1 compatible
    mpz_get_str(s,10,mpq_numref(value)); //CHECK BUFFER SIZE!!!!
    string S(s);
    if(mpz_cmp_ui(mpq_denref(value),1)!=0)
      {
	mpz_get_str(s,10,mpq_denref(value)); //CHECK BUFFER SIZE!!!!
	S=S+string("/")+string(s);
      }

    if(latexMode)S=LaTeXTranslator(S);

    if(alwaysWriteSign && mpq_sgn(value)!=-1)
      return std::string("+")+S;
    return S;
  }
  
  FieldElementRational *copy()const
  {
    FieldElementRational *r= new FieldElementRational;

    mpq_clear(r->value);
    mpz_init_set(mpq_numref(r->value), mpq_numref(value));
    mpz_init_set(mpq_denref(r->value), mpq_denref(value));

    return r;
  }
};


class FieldRationals : public Field
{
  FieldElement zHomomorphism(int n)
  {
    return FieldElement(new FieldElementRational(n));
  }

  const char *name()
  {
    return "GmpRationals";
  }
public:
  FieldRationals()
  {
    next=list;
    list=this;
  }
};

static FieldRationals theField;


FieldElementRational *FieldElementRational::one() const
{
  return new FieldElementRational(1);
}


IntegerVector primitiveVector(vector<FieldElement> const &v)
{
  int n=v.size();
  
  mpq_t *point = new mpq_t [n];
  for(int i=0;i<n;i++)mpq_init(point[i]);

  for(int i=0;i<n;i++)
    {
      mpq_set(point[i],*(v[i].getGmpRationalTemporaryPointer()));
    }

  scaleToIntegerVector(point,n);

  IntegerVector ret=arrayToIntegerVector(point,n);

  for(int i=0;i<n;i++)mpq_clear(point[i]);
  
  delete [] point;
  
  return ret;
  //  return IntegerVector(n);
}
