//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : quadratic_form.c 
// Author      : Michael Jacobson, Jr. (MJ)
// Last change : MJ, September 24, 1996
//

#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:quadratic_order.h>
#else
#include <LiDIA/quadratic_order.h>
#endif


inline bigint
qf_floor(bigint x, bigint y)
{ // private floor function
  bigint d,r;
  div_rem(d,r,x,y);
  if(x.sign()*y.sign()==-1 && !r.is_zero())
   dec(d);
  return d;
}

inline bigint
qf_round(bigint rn, bigint rd)
{
  rn.multiply_by_2();
  add(rn, rn, rd);
  rd.multiply_by_2();
  return qf_floor(rn,rd);
}



bigint
quadratic_form::norm_number()
{
  quadratic_form f;

  if (is_pos_definite())
    return norm_number_pos_def();
  else if (is_indefinite())
    return norm_number_indef();
  else if (is_neg_definite()) {
    f.assign(-a,b,-c);
    return f.norm_number_pos_def();
  }
  else
    return bigint(0);
}



bigint
quadratic_form::norm_number_pos_def()
{
  bigint _b,a_2(a);
  
  a_2.multiply_by_2();
  negate(_b,b);
  bigint s(qf_round(_b,a_2));

  return s;
}



bigint
quadratic_form::norm_number_indef()
{
  bigint sqrt_delta,abs_a,bn,abs_a_mult_2,tmp,s,_b(b);
  int sign_a;
  
  _b.negate();
  
  sqrt(sqrt_delta, discriminant());
  
  sign_a = a.sign();
  abs_a.assign(abs(a));
  
  shift_left(abs_a_mult_2,abs_a,1);
  if (abs_a.compare(sqrt_delta) > 0) {
    s=qf_round(_b, abs_a_mult_2);
    multiply(s,s,sign_a);
  }
  else {
    s=qf_floor(sqrt_delta - b , abs_a_mult_2);
    multiply(s,s,sign_a);
  }

  return s;
}



bool
quadratic_form::is_reduced_pos_def() const
{
  bool tmp;

  if( (c.compare(a)>0) || ((c==a)&& (b.is_ge_zero())))
    tmp = true;
  else
    tmp = false;
  
  tmp = (tmp && is_normal());

  return tmp;
}



bool
quadratic_form:: is_reduced_indef() const
{
  bigint sqrt_delta,abs_a,_abs_a,abs_a_2,bn;
  bool tmp;
  
  sqrt(sqrt_delta, discriminant());
  abs_a.assign(abs(a));
  
  negate(_abs_a,abs_a);
  shift_left(abs_a_2,abs_a,1);
  subtract(bn,sqrt_delta,abs_a_2);
  if (bn.is_negative())
    inc(bn);
  bn.absolute_value();
  tmp = ((bn<b) && (b <= sqrt_delta));
  
  return (tmp && is_normal());
}



bool
quadratic_form::is_reduced_irregular() const
{
  bool x;

  if ((a.is_zero())&&(b.is_zero()))
    x = true;
  else if ((a.is_zero())&&(b.is_positive())&&(c.is_ge_zero())&&(c < b))
    x = true;
  else
    x = false;

  return x;
}



void
quadratic_form::almost_reduce_irregular(matrix_GL2Z & U)
{
  matrix_GL2Z V((1),(0),(0),(1));
  bigint d;

  if(!a.is_zero()) {
    if(discriminant().is_zero())
      d.assign_zero();
    else
      sqrt(d,discriminant());
    bigint t(a);
    t.multiply_by_2();
    bigint s(b+d);
    s.negate();
    bigint u,v,g;
    g=xgcd(v,u,s,t);
    s=s/g;
    t=t/g;
      
    u.negate();
    V=matrix_GL2Z (s,u,t,v);
      
    transform(V);
    multiply(U,U,V);
  }
}



void
quadratic_form::reduce_irregular(matrix_GL2Z & U)
{
  matrix_GL2Z W;
  matrix_GL2Z D((0),(-1),(1),(0));

  almost_reduce_irregular(U);
 
  if(b.is_zero())
    return;

  if (b.is_negative()&&c.is_zero()) {
    b.negate();
    W=matrix_GL2Z((0),(-1),(1),(0));
    multiply(U,U,W);
    return;
  }
  
  if (b.is_negative()) {
    swap(a, c);
    b.negate();
    multiply(U,U,D);
    almost_reduce_irregular(U);
  }
  bigint s(qf_floor(c,b));
  s.negate();
  W=matrix_GL2Z ((1),s,(0),(1));
  transform(W);  
  multiply(U,U,W);

  return;
}



void
quadratic_form::reduce_irregular()
{
  matrix_GL2Z U((1),(0),(0),(1));

  reduce_irregular(U);
}



bool
quadratic_form::prop_equivalent_pos_def(const quadratic_form & g) const
{
  quadratic_form rf(*this), rg(g);
  
  rg.reduce();
  rf.reduce();

  return (!rf.compare(rg));
}



bool
quadratic_form::prop_equivalent_neg_def(const quadratic_form & g) const
{
  quadratic_form rf,rg;

  rf.assign(-a,b,-c);
  rg.assign(-g.a,g.b,-g.c);
  
  rg.reduce();
  rf.reduce();

  return (!rf.compare(rg));
}



bool
quadratic_form::prop_equivalent_indef(const quadratic_form & g) const
{
  quadratic_form rf(*this), rg(g),root;
  
  rf.reduce();
  rg.reduce();
  
  if (!rf.compare(rg))
    return true;
  
  root.assign(rg);
  do {
    rg.rho();
    if (rf.is_equal(rg))
      return true;
  } while (root.compare(rg));

  return false;
}



bool
quadratic_form::prop_equivalent_irregular(const quadratic_form & g) const
{
  quadratic_form rf(*this),rg(g);

  rf.reduce();
  rg.reduce();

  return (!rf.compare(rg));
}



bool
quadratic_form::prop_equivalent_pos_def(const quadratic_form & g, matrix_GL2Z & U) const
{
  quadratic_form rf(*this), rg(g);
  matrix_GL2Z V((1), (0), (0),(1));
  matrix_GL2Z S(V);
  bool equiv;

  rg.reduce(S);
  rf.reduce(V);
  if (rf == rg) {
    V.invert();
    multiply(S,S,V);
    multiply(U,U,S);
    equiv = true;
  }
  else
    equiv = false;

  return equiv;
}



bool
quadratic_form::prop_equivalent_neg_def(const quadratic_form & g, matrix_GL2Z & U) const
{
  quadratic_form rf(*this),rg(g);
  matrix_GL2Z V((1), (0), (0),(1));
  matrix_GL2Z S(V);
  bool equiv;

  rg.reduce(S);
  rf.reduce(V);
  if (rf == rg) {
    V.invert();
    multiply (S,S,V);
    multiply(U,U,S);
    equiv = true;
  }
  else
    equiv = false;

  return equiv;
}



bool
quadratic_form::prop_equivalent_indef(const quadratic_form & g, matrix_GL2Z & U) const
{
  quadratic_form rf(*this), rg(g),root;
  matrix_GL2Z V((1), (0), (0),(1));
  matrix_GL2Z S(V);
  
  rf.reduce(V);
  rg.reduce(S);
  
  if (rf == rg) {
    V.invert();
    multiply(S,S,V);
    multiply(U,U,S);
    return true;
  }
  
  root.assign(rg);
  do {
    rg.rho(S);
    if (!rf.compare(rg)) {
      V.invert();
      multiply(S,S,V);
      multiply(U,U,S);
      return true;
    }
  } while (root.compare(rg));

  return false;
}



bool
quadratic_form::prop_equivalent_irregular(const quadratic_form & g,matrix_GL2Z & U) const
{
  quadratic_form rf(*this),rg(g);
  matrix_GL2Z V ((1),(0), (0),(1));
  matrix_GL2Z W(V);
  bool equiv;

  rf.reduce(V);
  rg.reduce(W);
  if (rf.compare(rg))
    equiv = false;
  else {
    V.invert();
    multiply(W,W,V);
    multiply(U,U,W);
    equiv = true;
  }

  return equiv;
}



bool
quadratic_form::comp_reps_irregular(bigint_matrix & Reps, const bigint & N)
{
  quadratic_form g;
  rational_factorization Nfact;
  bigint Delta,temp,temp2,x,y,nx,ny;
  lidia_size_t num,i,j,k,cands,idx,nfacts,val,v;
  matrix_GL2Z U(1,0,0,1);
  base_vector<bigint> plist;

  Delta.assign(discriminant());

  plist.set_mode(EXPAND);
  plist.set_size(0);
  g.assign(*this);
  g.reduce(U);

  cout << "\nIN REPS IRR:  *this = " << *this << ", N = " << N << "\n" << flush;
  cout << "g = " << g << "\n" << flush;
  cout << "U =\n" << flush;
  cout << U << "\n" << flush;

  num = 0;

  if (N.is_zero()) {
    Reps.resize(num+1,2);
    Reps.sto(num,0,bigint(0));
    Reps.sto(num,1,bigint(0));
    ++num;

    y.assign(-1);
    multiply(temp,g.c,y);
    remainder(temp2,temp,g.b);
    while (!temp.is_zero()) {
      dec(y);
      multiply(temp,g.c,y);
      remainder(temp2,temp,g.b);
    }
    divide(x,y,g.b);
    Reps.resize(num+1,2);
    Reps.sto(num,0,x);
    Reps.sto(num,1,y);
    ++num;
  }
  else {
    if (Delta.is_zero()) {
      if (!(N % g.c)) {
        divide(y,N,g.c);
        sqrt(temp,y);
        if ((temp*temp) == y) {
          x.assign_zero();
          Reps.resize(num+1,2);
          Reps.sto(num,0,x);
          Reps.sto(num,1,temp);
          ++num;

          temp.negate();
          Reps.resize(num+1,2);
          Reps.sto(num,0,x);
          Reps.sto(num,1,temp);
          ++num;
        }
      }
    }
    else {
      Nfact.assign(abs(N));
      Nfact.factor();

      idx = 1;
      cands = 0;
      nfacts = Nfact.no_of_comp();
      for (i=0; i<nfacts; ++i) {
        k = Nfact.exponent(i);
        cands += k;
        for (j=0; j<k; ++j) {
          plist[idx] = Nfact.base(i);
          ++idx;
        }
      }

      idx = (1 << cands);
      y.assign(N);

      for (j=1; j<=idx; ++j) {
        cout << "trying y = " << y << "\n" << flush;
        /* x = N - Cy^2 */
        square(temp,y);
        multiply(x,g.c,temp);
        subtract(x,N,x);

        multiply(temp,g.b,y);
        remainder(temp2,x,temp);
        if (temp2.is_zero()) {
          divide(x,x,temp);
          cout << "FOUND:  x = " << x << ", y = " << y << "\n" << flush;
          multiply(temp,U.get_s(),x);
          multiply(temp2,U.get_u(),y);
          add(nx,temp,temp2);
          multiply(temp,U.get_t(),x);
          multiply(temp2,U.get_v(),y);
          add(ny,temp,temp2);
          cout << "nx = " << nx << ", ny = " << ny << "\n" << flush;
          Reps.resize(num+1,2);
          Reps.sto(num,0,nx);
          Reps.sto(num,1,ny);
          ++num;
        }

        if (j < idx) {
          val = (j << 1);
          v = 0;
          while (!(val & 1)) {
            val >>= 1;
            ++v;
          }
          ++val;
          val >>= 1;
          --v;
          if (val & 1)
            divide(y,y,plist.member(v));
          else
            multiply(y,y,plist.member(v));
        }
      }
    }
  }
  
  return (num > 0);
}



quadratic_form::quadratic_form()
{
  QO = NULL;
}



quadratic_form::quadratic_form(const qi_class & A)
{
  QO = NULL;
  a.assign(A.get_a());
  b.assign(A.get_b());
  c.assign(A.get_c());
  QO = quadratic_order::add_to_list(QO,*qi_class::get_current_order());
}



quadratic_form::quadratic_form(const qi_class_real & A)
{
  QO = NULL;
  a.assign(A.get_a());
  b.assign(A.get_b());
  c.assign(A.get_c());
  QO = quadratic_order::add_to_list(QO,*qi_class::get_current_order());
}



quadratic_form::quadratic_form(const quadratic_ideal & A)
{
  QO = NULL;
  a.assign(A.get_a());
  b.assign(A.get_b());
  c.assign(A.get_c());
  QO = quadratic_order::add_to_list(QO,*A.which_order());
}



quadratic_form::quadratic_form(const quadratic_form & f)
{
  QO = NULL;
  a.assign(f.a);
  b.assign(f.b);
  c.assign(f.c);
  QO = quadratic_order::add_to_list(QO,*f.which_order());
}



quadratic_form::~quadratic_form()
{
  quadratic_order::clear(QO);
}



void
quadratic_form::assign_zero()
{
  a.assign_zero();
  b.assign_zero();
  c.assign_zero();
}



void
quadratic_form::assign_one(const bigint & newDelta)
{
  bigint tmp(3);
  quadratic_order *newQO;
  int temp;

  temp = (int) remainder(newDelta,4);
  if (temp < 0)
    temp += 4;
  if (temp > 1)
    lidia_error_handler("quadratic_form","assign_one() - the parameter must \
be congruent to 0 or 1 modulo 4");

  a.assign_one();
  and(b, newDelta, tmp);
  square(tmp, b);
  subtract(c, tmp, newDelta);
  shift_right(c, c, 2);
 
  normalize();

  if (is_regular()) {
    if (quadratic_order::last_order()) {
      if (quadratic_order::last_order()->discriminant() == newDelta) {
        QO = quadratic_order::add_last(QO);
        return;
      }
    }
    newQO = new quadratic_order;
    newQO->assign(newDelta);
    QO = quadratic_order::add_dynamic_to_list(QO,*newQO);
  }
}



void
quadratic_form::assign(const bigint & a2, const bigint & b2, const bigint & c2)
{
  bigint Delta,temp;
  quadratic_order *newQO;

  a.assign(a2);
  b.assign(b2);
  c.assign(c2);
  
  square(Delta,b);
  multiply(temp,a,c);
  shift_left(temp,temp,2);
  subtract(Delta,Delta,temp);

  if (is_regular()) {
    if (quadratic_order::last_order())
      if (quadratic_order::last_order()->discriminant() == Delta) {
        QO = quadratic_order::add_last(QO);
        return;
      }
    newQO = new quadratic_order;
    newQO->assign(Delta);
    QO = quadratic_order::add_dynamic_to_list(QO,*newQO);
  }
}



void
quadratic_form::assign(const long a2, const long b2, const long c2)
{
  bigint Delta,temp;
  quadratic_order *newQO;

  a.assign(a2);
  b.assign(b2);
  c.assign(c2);
  
  square(Delta,b);
  multiply(temp,a,c);
  shift_left(temp,temp,2);
  subtract(Delta,Delta,temp);

  if (is_regular()) {
    if (quadratic_order::last_order())
      if (quadratic_order::last_order()->discriminant() == Delta) {
        QO = quadratic_order::add_last(QO);
        return;
      }
    newQO = new quadratic_order;
    newQO->assign(Delta);
    QO = quadratic_order::add_dynamic_to_list(QO,*newQO);
  }
}



void
quadratic_form::assign(const qi_class & A)
{
  a.assign(A.get_a());
  b.assign(A.get_b());
  c.assign(A.get_c());
  QO = quadratic_order::add_to_list(QO,*qi_class::get_current_order());
}



void
quadratic_form::assign(const qi_class_real & A)
{
  a.assign(A.get_a());
  b.assign(A.get_b());
  c.assign(A.get_c());
  QO = quadratic_order::add_to_list(QO,*qi_class::get_current_order());
}



void
quadratic_form::assign(const quadratic_ideal & A)
{
  a.assign(A.get_a());
  b.assign(A.get_b());
  c.assign(A.get_c());
  QO = quadratic_order::add_to_list(QO,*A.which_order());
}



void
quadratic_form::assign(const quadratic_form & g)
{
  a.assign(g.a);
  b.assign(g.b);
  c.assign(g.c);
  QO = quadratic_order::add_to_list(QO,*g.which_order());
}



quadratic_form & quadratic_form::operator = (const quadratic_form & g)
{
  assign(g);
  return *this;
}



bigint
quadratic_form::discriminant() const
{
  bigint Delta,temp;

  if (QO)
    if (QO->get_qo())
      return QO->get_qo()->discriminant();

  square(Delta,b);
  multiply(temp,a,c);
  shift_left(temp,temp,2);
  subtract(Delta,Delta,temp);
  return Delta;
}



quadratic_order *
quadratic_form::which_order() const
{
  if (QO)
    return QO->get_qo();
  else
    return (quadratic_order *) NULL;
}



quadratic_order *
which_order(const quadratic_form & f)
{
  if (f.QO)
    return f.QO->get_qo();
  else
    return (quadratic_order *) NULL;
}




/* FIX - Check irregular */
void
compose(quadratic_form & f, const quadratic_form & g1, const quadratic_form & g2)
{
  bigint Delta,newa,newb,newc,dpr,v,d,w,ab2,temp;

  if (g1.discriminant() != g2.discriminant())
    lidia_error_handler("quadratic_form","compose() - forms have different \
discriminants");

  if (!g1.is_regular() && ((g1.get_a() == 0) || (g2.get_a() == 0))) {
    quadratic_form h1,h2;
    
    h1.assign(g1);
    h2.assign(g2);
    h1.reduce();
    h2.reduce();
    multiply(newc,h1.c,h2.c);
    remainder(newc,newc,h1.b);
    newa.assign_zero();
    newb.assign(h1.b);
  }
  else {
    Delta.assign(g1.discriminant());
    dpr.assign(xgcd_left(v,g1.a,g2.a));

    multiply(newb,v,g1.a);
    subtract(temp,g2.b,g1.b);
    multiply(newb,newb,temp);

    multiply(newa,g1.a,g2.a);

    if (!dpr.is_one()) {
      add(ab2,g1.b,g2.b);
      ab2.divide_by_2();
      d.assign(xgcd(v,w,dpr,ab2));

      multiply(newb,newb,v);

      square(temp,g1.b);
      subtract(temp,Delta,temp);
      temp.divide_by_2();
      multiply(temp,temp,w);

      add(newb,newb,temp);
      divide(newb,newb,d);

      square(temp,d);
      divide(newa,newa,temp);

      dpr.assign(d);
    }

    add(newb,newb,g1.b);
    shift_left(ab2,newa,1);
    remainder(newb,newb,ab2);

    square(newc,newb);
    subtract(newc,newc,Delta);
    shift_left(temp,newa,2);
    divide(newc,newc,temp);
  }

  f.a.assign(newa);
  f.b.assign(newb);
  f.c.assign(newc);
  f.QO = quadratic_order::add_to_list(f.QO,*g1.which_order());
}



void
compose_reduce(quadratic_form & f, const quadratic_form & g1, const quadratic_form & g2)
{
  compose(f,g1,g2);
  f.reduce();
}



void
nucomp(quadratic_form & f, const quadratic_form & g1, const quadratic_form & g2)
{
  bigint a1,b1,c1,a2,b2,c2,s,n,d1,d;
  bigint u,v,u1,l,aa;
  bigint v1,v3;
  bigint b,e,ff,g,q;
  bigint temp,PEA_L;
  bool flag;

  if (g1.discriminant() != g2.discriminant())
    lidia_error_handler("quadratic_form","nucomp() - forms have different \
discriminants");

  if (!g1.is_pos_definite() && !g1.is_neg_definite())
    lidia_error_handler("quadratic_form","nucomp() - forms must be positive \
or negative definite");

  PEA_L.assign(g1.which_order()->nu_bound());

  if (abs(g1.a) < abs(g2.a)) {
    a1.assign(abs(g2.a));
    b1.assign(g2.b);
    c1.assign(abs(g2.c));

    a2.assign(abs(g1.a));
    b2.assign(g1.b);
    c2.assign(abs(g1.c));
  }
  else {
    a1.assign(abs(g1.a));
    b1.assign(g1.b);
    c1.assign(abs(g1.c));

    a2.assign(abs(g2.a));
    b2.assign(g2.b);
    c2.assign(abs(g2.c));
  }

  // initialize
  add(s,b1,b2);
  s.divide_by_2();
  subtract(n,b2,s);

  // first Euclidean pass
  /* solve d = u a1 + v a2 */
  d.assign(xgcd(u,v,a2,a1));
  if (d.is_one()) {
    multiply(aa,u,n);
    aa.negate();
    flag = false;
  }
  else {
    // second Euclidean pass
    d1.assign(xgcd_left(u1,s,d));

    if (!d1.is_one()) {
      divide(a1,a1,d1);
      divide(a2,a2,d1);
      divide(s,s,d1);
      divide(d,d,d1);
      flag = true;
    }
    else
      flag = false;

    // l = - u1 * ( c1 * u + c2 * v ) mod d */
    remainder(c1,c1,d);
    remainder(temp,c2,d);
    multiply(temp,v,temp);
    multiply(l,u,c1);
    add(l,l,temp);
    multiply(l,l,u1);
    l.negate();
    remainder(l,l,d);
    if (l.is_lt_zero())
      add(l,l,d);

    // aa = - u * ( n / d ) + l * ( a1 / d ) */
    divide(aa,n,d);
    multiply(aa,aa,u);
    divide(temp,a1,d);
    multiply(temp,temp,l);
    subtract(aa,temp,aa);
  }

  // partial reduction
  d.assign(a1);
  v3.assign(aa);
  nugcd(u,d,v1,v3,PEA_L);

  if (u.is_zero()) {
    //  u = 0; d = a1; v1 = 1; v3 = aa
    // b = a2 * d + n * u ) / a1  --->    b = a2 (-)
    // e = s * d + c2 * u ) / a1  --->   e = s  (-)

    // ff = ( b * v3 + n ) / d
    // b1 = 2 * b * v3 + n

    multiply(temp,a2,v3);
    add(ff,temp,n);
    add(b1,temp,ff);
    divide(ff,ff,d);

    // g = ( e * v1 - s ) / u    cannot be computed (u == 0)
    // q = 2 * e * v1 - s       --->   q = s (-)

    // g = ( c2 + s * v3 ) / d

    multiply(g,s,v3);
    add(g,c2,g);
    divide(g,g,d);

    if (flag) {
      multiply(s,s,d1);
      multiply(g,g,d1);
    }

    // a1 = d * b + e * u    --->   a1 = d * b
    multiply(a1,d,a2);

    // c1 = v3 * ff + g * v1  --->   c1 = v3 * ff + g
    multiply(c1,v3,ff);
    add(c1,c1,g);

    // b1 = b1 + q
    add(b1,b1,s);
  }
  else {
    // u != 0

    // b = a2 * d + n * u ) / a1
    multiply(temp,a2,d);
    multiply(b,n,u);
    add(b,b,temp);
    divide(b,b,a1);

    // e = s * d + c2 * u ) / a1
    multiply(temp,s,d);
    multiply(e,c2,u);
    add(e,e,temp);
    divide(e,e,a1);

    // ff = ( b * v3 + n ) / d
    // b1 = 2 * b * v3 + n
    multiply(q,b,v3);
    add(ff,q,n);
    add(b1,q,ff);
    divide(ff,ff,d);

    // g = ( e * v1 - s ) / u
    // q = 2 * e * v1 - s
    multiply(q,e,v1);
    subtract(g,q,s);
    add(q,q,g);
    divide(g,g,u);

    if ( flag ) {
      multiply(v1,v1,d1);
      multiply(u,u,d1);
      multiply(q,q,d1);
    }

    // a1 = d * b + e * u
    multiply(temp,d,b);
    multiply(a1,e,u);
    add(a1,a1,temp);

    // c1 = v3 * ff + g * v1
    multiply(temp,v3,ff);
    multiply(c1,g,v1);
    add(c1,c1,temp);

    // b1 = b1 + q
    add(b1,b1,q);
  }

  if (g1.is_pos_definite()) {
    f.a.assign(a1);
    f.b.assign(b1);
    f.c.assign(c1);
  }
  else {
    negate(f.a,a1);
    f.b.assign(b1);
    negate(f.c,c1);
  }
  f.QO = quadratic_order::add_to_list(f.QO,*g1.which_order());
  f.reduce();
}



void
quadratic_form::conjugate()
{
  b.negate();
}



void
get_conjugate(quadratic_form & f, const quadratic_form & g)
{
  f.assign(g);
  f.b.negate();
}



quadratic_form
get_conjugate(const quadratic_form & f)
{
  quadratic_form g;

  g.assign(f);
  g.b.negate();

  return g;
}



void
divide(quadratic_form & f, const quadratic_form & g1, const quadratic_form & g2)
{
  compose(f,g1,get_conjugate(g2));
}



void
divide_reduce(quadratic_form & f, const quadratic_form & g1, const quadratic_form & g2)
{
  compose_reduce(f,g1,get_conjugate(g2));
}



/* FIX - check irregular */
void
square(quadratic_form & f, const quadratic_form & g)
{
  bigint Delta,newb,d,w,temp,newa,newc;

  if (!g.is_regular() && (g.get_a() == 0)) {
    quadratic_form h;

    h.assign(g);
    h.reduce();
    square(newc,h.c);
    remainder(newc,newc,h.b);
    newa.assign_zero();
    newb.assign(h.b);
  }
  else {
    Delta.assign(g.discriminant());
    d.assign(xgcd_right(w,g.a,g.b));

    square(newb,g.b);
    subtract(newb,Delta,newb);
    shift_left(temp,d,1);
    divide(newb,newb,temp);
    multiply(newb,newb,w);
    add(newb,newb,g.b);

    divide(temp,g.a,d);
    square(newa,temp);

    shift_left(temp,newa,1);
    remainder(newb,newb,temp);

    square(newc,newb);
    subtract(newc,newc,Delta);
    shift_left(temp,newa,2);
    divide(newc,newc,temp);
  }

  f.a.assign(newa);
  f.b.assign(newb);
  f.c.assign(newc);
  f.QO = quadratic_order::add_to_list(f.QO,*g.which_order());
}



void
square_reduce(quadratic_form & f, const quadratic_form & g)
{
  square(f,g);
  f.reduce();
}



void
nudupl(quadratic_form & f, const quadratic_form & g)
{
  bigint a,b,c;
  bigint temp,u,d1,d,v1,v3,e,gg,PEA_L;
  bool flag;

  if (!g.is_pos_definite() && !g.is_neg_definite())
    lidia_error_handler("quadratic_form","nudupl() - form must be positive \
or negative definite");

  PEA_L.assign(g.which_order()->nu_bound());

  a.assign(abs(g.a));
  b.assign(g.b);
  c.assign(abs(g.c));

  // Euclidian pass
  d1.assign(xgcd_left(u,b,a));
  flag = (!d1.is_one());
  if (flag) {
    divide(a,a,d1);
    divide(b,b,d1);
  }

  multiply(v3,u,c);
  v3.negate();

  // partial euclidian algorithm
  d.assign(a);
  nugcd(u,d,v1,v3,PEA_L);

  // final squaring
  if (u.is_zero()) {
    multiply(gg,b,v3);
    add(gg,gg,c);

    if (flag) {
      multiply(b,b,d1);
      multiply(gg,gg,d1);
    }

    divide(gg,gg,d);

    square(a,d);
    square(c,v3);

    add(temp,d,v3);
    square(temp,temp);
    add(b,b,temp);
    subtract(b,b,a);
    subtract(b,b,c);

    add(c,c,gg);
  }
  else {
    // u != 0
    multiply(e,b,d);
    multiply(temp,c,u);
    add(e,e,temp);
    divide(e,e,a);

    multiply(temp,e,v1);
    subtract(gg,temp,b);
    add(b,temp,gg);
    divide(gg,gg,u);

    if (flag) {
      multiply(b,b,d1);
      multiply(u,u,d1);
      multiply(v1,v1,d1);
    }

    square(a,d);
    square(c,v3);

    add(temp,d,v3);
    square(temp,temp);
    add(b,b,temp);
    subtract(b,b,a);
    subtract(b,b,c);

    multiply(temp,e,u);
    add(a,a,temp);

    multiply(temp,gg,v1);
    add(c,c,temp);
  }

  if (g.is_pos_definite()) {
    f.a.assign(a);
    f.b.assign(b);
    f.c.assign(c);
  }
  else {
    negate(f.a,a);
    f.b.assign(b);
    negate(f.c,c);
  }
  f.QO = quadratic_order::add_to_list(f.QO,*g.which_order());
  f.reduce();
}


void
power(quadratic_form & f, const quadratic_form & g, const bigint & i)
{
  quadratic_form h;
  bigint j;

  h.assign(g);
  j.assign(i);
  if (j.is_lt_zero()) {
    h.conjugate();
    j.absolute_value();
  }
  f.assign_one(g.discriminant());
  while (j.is_gt_zero()) {
    if (j.is_odd())
      compose(f,f,h);
    j.divide_by_2();
    if (j.is_gt_zero())
      square(h,h);
  }
}



void
power(quadratic_form & f, const quadratic_form & g, const long i)
{
  quadratic_form h;
  register long j;

  h.assign(g);
  j = i;
  if (j < 0) {
    h.conjugate();
    j = -j;
  }
  f.assign_one(g.discriminant());
  while (j > 0) {
    if ((j & 1) == 1)
      compose(f,f,h);
    j >>= 1;
    if (j > 0)
      square(h,h);
  }
}



void
power_reduce(quadratic_form & f, const quadratic_form & g, const bigint & i)
{
  quadratic_form h;
  bigint j;

  h.assign(g);
  j.assign(i);
  if (j.is_lt_zero()) {
    h.conjugate();
    j.absolute_value();
  }
  f.assign_one(g.discriminant());
  while (j.is_gt_zero()) {
    if (j.is_odd())
      compose_reduce(f,f,h);
    j.divide_by_2();
    if (j.is_gt_zero())
      square_reduce(h,h);
  }
}



void
power_reduce(quadratic_form & f, const quadratic_form & g, const long i)
{
  quadratic_form h;
  register long j;

  h.assign(g);
  j = i;
  if (j < 0) {
    h.conjugate();
    j = -j;
  }
  f.assign_one(g.discriminant());
  while (j > 0) {
    if ((j & 1) == 1)
      compose_reduce(f,f,h);
    j >>= 1;
    if (j > 0)
      square_reduce(h,h);
  }
}



void
nupower(quadratic_form & f, const quadratic_form & g, const bigint & i)
{
  quadratic_form h;
  bigint j;

  if (!g.is_pos_definite() && !g.is_neg_definite())
    lidia_error_handler("quadratic_form","nupower() - form must be positive \
or negative definite");

  h.assign(g);
  j.assign(i);
  if (j.is_lt_zero()) {
    h.conjugate();
    j.absolute_value();
  }
  f.assign_one(g.discriminant());
  while (j.is_gt_zero()) {
    if (j.is_odd())
      nucomp(f,f,h);
    j.divide_by_2();
    if (j.is_gt_zero())
      nudupl(h,h);
  }
}



void
nupower(quadratic_form & f, const quadratic_form & g, const long i)
{
  quadratic_form h;
  register long j;

  if (!g.is_pos_definite() && !g.is_neg_definite())
    lidia_error_handler("quadratic_form","nupower() - form must be positive \
or negative definite");

  h.assign(g);
  j = i;
  if (j < 0) {
    h.conjugate();
    j = -j;
  }
  f.assign_one(g.discriminant());
  while (j > 0) {
    if ((j & 1) == 1)
      nucomp(f,f,h);
    j >>= 1;
    if (j > 0)
      nudupl(h,h);
  }
}



quadratic_form operator - (const quadratic_form & f)
{
  return get_conjugate(f);
}



quadratic_form operator * (const quadratic_form & f, const quadratic_form & g)
{
  quadratic_form h;

  compose_reduce(h,f,g);
  return h;
}



quadratic_form operator / (const quadratic_form & f, const quadratic_form & g)
{
  quadratic_form h;

  divide_reduce(h,f,g);
  return h;
}



quadratic_form & quadratic_form::operator *= (const quadratic_form & f)
{
  compose_reduce(*this,*this,f);
  return *this;
}



quadratic_form & quadratic_form::operator /= (const quadratic_form & f)
{
  compose_reduce(*this,*this,get_conjugate(f));
  return *this;
}



bool
quadratic_form::is_zero() const
{
  return ((a.is_zero()) && (b.is_zero()) && (c.is_zero()));
}



bool
quadratic_form::is_one() const
{
  quadratic_form U;
 
  U.assign_one(discriminant());
  return (is_equal(U));
}



bool
quadratic_form::is_equal(const quadratic_form & g) const
{
  return (!(a.compare(g.a)) && !(b.compare(g.b)) && !(c.compare(g.c)));
}



int
quadratic_form::compare(const quadratic_form & g) const
{
  int i = a.compare(g.a);
  if (i == 0)
    i = b.compare(g.b);
  if (i == 0)
    i = c.compare(g.c);

  return i;
}



int
quadratic_form::abs_compare(const quadratic_form & g) const
{
  int i = a.abs_compare(g.a);
  if (i == 0)
    i = b.abs_compare(g.b);
  if (i == 0)
    i = c.abs_compare(g.c);

  return i;
}



bool operator == (const quadratic_form & f, const quadratic_form & g)
{
  return f.is_equal(g);
}



bool operator != (const quadratic_form & f, const quadratic_form & g)
{
  return !f.is_equal(g);
}



bool operator <= (const quadratic_form & f, const quadratic_form & g)
{
  return (f.compare(g) <= 0);
}



bool operator < (const quadratic_form & f, const quadratic_form & g)
{
  return (f.compare(g) < 0);
}  



bool operator >= (quadratic_form & f, quadratic_form & g)
{
  return (f.compare(g) >= 0);
}



bool operator > (quadratic_form & f, quadratic_form & g)
{
  return (f.compare(g) > 0);
}



bool operator ! (const quadratic_form & f)
{
  return (f.is_zero());
}



void
swap(quadratic_form & f, quadratic_form & g)
{
  quadratic_form h;

  h.assign(f);
  f.assign(g);
  g.assign(h);
}




int
quadratic_form::definiteness() const
{
  if (!is_regular())
    return 2;

  if (discriminant().is_positive())
    return 0;

  if (a.is_positive())
    return 1;
  
  return -1;
}
  


bool
quadratic_form::is_pos_definite() const
{
  return (discriminant().is_negative() && a.is_positive() && is_regular());
}



bool
quadratic_form::is_pos_semidefinite() const
{
  return (discriminant().is_le_zero() && (a.is_positive() || c.is_positive()) && is_regular());
}



bool
quadratic_form::is_neg_definite() const
{
  return (discriminant().is_negative() && a.is_negative() && is_regular());
}



bool
quadratic_form::is_neg_semidefinite() const
{
  return (discriminant().is_le_zero() && (a.is_negative() || c.is_negative()) && is_regular());
}



bool
quadratic_form::is_indefinite() const
{
  return (discriminant().is_positive() && is_regular());
}



bool
quadratic_form::is_regular() const
{
  long val;
  bigint temp;

  val = power_test(temp,abs(discriminant()));
  return ((val & 1) == 1);
}



bigint
quadratic_form::content() const
{
  bigint temp;

  temp = gcd(a,b);
  return gcd(temp,c);
}



bool
quadratic_form::is_primitive() const
{
  bigint f;

  f = content();
  return (f.is_one());
}



bigint
quadratic_form::eval(const bigint & x, const bigint & y) const
{
  bigint temp1,temp2,temp3;

  square(temp2,x);
  multiply(temp3,temp2,a);
  multiply(temp2,x,y);
  multiply(temp2,temp2,b);
  add(temp1,temp2,temp3);
  square(temp2,y);
  multiply(temp3,temp2,c);
  add(temp1,temp1,temp3);

  return temp1;
}



bigint quadratic_form::operator () (const bigint &x, const bigint &y)
{
  bigint temp1,temp2,temp3;

  square(temp2,x);
  multiply(temp3,temp2,a);
  multiply(temp2,x,y);
  multiply(temp2,temp2,b);
  add(temp1,temp2,temp3);
  square(temp2,y);
  multiply(temp3,temp2,c);
  add(temp1,temp1,temp3);

  return temp1;
}



void
quadratic_form::transform(const matrix_GL2Z & U)
{
  bigint temp1,temp2,temp3;

  multiply(temp1,U.get_s(),U.get_u());
  multiply(temp1,temp1,a);	//asu
  
  multiply(temp2,U.get_t(),U.get_v());
  multiply(temp2,temp2,c);	//ctv
  add(temp1,temp1,temp2);
  temp1.multiply_by_2();	//2(asu+tvc)
  
  multiply(temp2,U.get_s(),U.get_v());
  multiply(temp3,U.get_t(),U.get_u());
  add(temp2,temp2,temp3);
  multiply(temp2,temp2,b);	//b(sv+tu)
  add (temp1,temp1,temp2);	//new b
  
  
  temp2.assign(eval(U.get_s(),U.get_t())); //new a
  temp3.assign(eval(U.get_u(),U.get_v())); //new c
  
  matrix_GL2Z V(U);
  if(V.det()==-1) {
    temp2.negate();
    temp1.negate();
    temp3.negate();
  }
  
  a.assign(temp2);
  b.assign(temp1);
  c.assign(temp3);
}



bool
prime_form(quadratic_form & f, const bigint & p, const bigint & newDelta)
{
  bigint b,c,temp;
  int kro,Dp,ip;
  quadratic_order *newQO;

  Dp = (int) remainder(newDelta,4);
  if (Dp < 0)
    Dp += 4;
  if (Dp > 1)
    lidia_error_handler("quadratic_form","prime_form() - newDelta must \
be congruent to 0 or 1 modulo 4");
  
  kro = kronecker(newDelta,p);
  if (kro < 0)
    return false;
  else {
    if (p == 2) {
      if (kro == 0) {
        Dp = (int) remainder(newDelta,16);
        if (Dp < 0)
          Dp += 16;
        if (Dp == 8)
          b.assign_zero();
        else
          b.assign(p);
      }
      else
        b.assign(1);
    }
    else {
      if (kro == 0) {
        remainder(temp,newDelta,p*p);
        if (temp.is_zero())
          b.assign(p);
        else
          b.assign_zero();
      }
      else {
        if (is_int(p)) {
          p.intify(ip);
          Dp = (int) remainder(newDelta,(long) ip);
          if (Dp < 0)
            Dp += ip;

          b.assign(ressol(Dp,ip));
        }
        else {
          remainder(temp,newDelta,p);
          if (temp.is_lt_zero())
            add(temp,temp,p);
          ressol(b,temp,p);
        }

        if (b.is_lt_zero())
          add(b,b,p);
      }

      if ((newDelta.is_odd()) != (b.is_odd()))
        subtract(b,p,b);
    }

    square(c,b);
    subtract(c,c,newDelta);
    shift_left(temp,p,2);
    divide(c,c,temp);
 
    f.a.assign(p);
    f.b.assign(b);
    f.c.assign(c);

    if (f.is_regular()) {
      if (quadratic_order::last_order())
        if (quadratic_order::last_order()->discriminant() == newDelta) {
          f.QO = quadratic_order::add_last(f.QO);
          return true;
        }
      newQO = new quadratic_order;
      newQO->assign(newDelta);
      f.QO = quadratic_order::add_dynamic_to_list(f.QO,*newQO);
    }

    return true;
  }
}




bool
quadratic_form::is_normal() const
{
  bigint temp,rootD;
  int dness;

  dness = definiteness();

  if (dness == 2)
    return is_reduced();
  if (dness == 1)
    return (b.compare(-a) > 0) && (b.compare(a) <= 0);
  else if (dness == -1)
    return (b.compare(-abs(a)) > 0) && (b.compare(abs(a)) <=0);
  else {
    sqrt(rootD,discriminant());
    if (a.compare(rootD) <= 0) {
      shift_left(temp,a,1);
      subtract(temp,rootD,temp);
      return (temp.compare(b) < 0) && (b.compare(rootD) <= 0);
    }
    else
      return (b.compare(-a) > 0) && (b.compare(a) <= 0);
  }
}



void
quadratic_form::normalize()
{
  bigint tmp1,tmp2,s;		//tmp1 becomes new b tmp2 new c
  quadratic_form f;

  if (is_normal())
    return;

  if (!is_regular())
    reduce_irregular();
  else if (is_neg_definite()) {
    f.assign(-a,b,-c);
    f.normalize();
    negate(a,f.a);
    b = f.b;
    negate(c,f.c);
  }
  else {
    s = norm_number();
    multiply (tmp1,s,a);
    tmp1.multiply_by_2();
    add(tmp1,b,tmp1);		//tmp1=b+2sa
  
    multiply(tmp2,a,s);
    add(tmp2,tmp2,b);
    multiply(tmp2,tmp2,s);
    add(tmp2,tmp2,c);		//tmp2=as^2 +bs +c
  
    b.assign(tmp1);
    c.assign(tmp2);
  }
}



void
quadratic_form::normalize(matrix_GL2Z & U )
{
  bigint tmp1,tmp2,s;		//tmp1 becomes new b tmp2 new c
  quadratic_form f;

  if (is_normal())
    return;

  if (!is_regular()) {
    matrix_GL2Z V((1),(0),(0),(1));
    reduce_irregular(V);
    multiply(U,U,V);
  }
  else if (is_neg_definite()) {
    matrix_GL2Z V((1),(0),(0),(-1));
    f.assign(-a,b,-c);
    multiply(U,U,V);
    f.normalize(U);
    multiply(U,U,V);
    negate(a,f.a);
    b = f.b;
    negate(c,f.c);
  }
  else {
    s=norm_number();
  
    matrix_GL2Z S((1),s,(0),(1));
  
    multiply (tmp1,s,a);
    tmp1.multiply_by_2();
    add(tmp1,b,tmp1);		//tmp1=b+2sa
  
    multiply(tmp2,a,s);
    add(tmp2,tmp2,b);
    multiply(tmp2,tmp2,s);
    add(tmp2,tmp2,c);		//tmp2=as^2 +bs +c
  
    b.assign(tmp1);
    c.assign(tmp2);
  
    multiply(U,U,S);
  }
}




bool
quadratic_form::is_reduced() const
{
  int dness;
  quadratic_form f;

  dness = definiteness();

  if (dness == 2)
    return is_reduced_irregular();
  else if (dness == 0)
    return is_reduced_indef();
  else if (dness == 1)
    return is_reduced_pos_def();
  else {
    f.assign(-a,b,-c);
    return f.is_reduced_pos_def();
  }
}




void
quadratic_form::reduce()
{
  quadratic_form f;

  if (is_reduced())
    return;

  if(!is_regular()) {
    reduce_irregular();
    return;
  }

  if (is_neg_definite()) {
    f.assign(-a,b,-c);
    f.reduce();
    negate(a,f.a);
    b.assign(f.b);
    negate(c,f.c);
    return;
  }

  normalize();
  while(!is_reduced())
    rho();
}





void
quadratic_form::reduce(matrix_GL2Z & U)
{
  quadratic_form f;

  if (is_reduced())
    return;

  if(!is_regular()) {
    reduce_irregular(U);
    return;
  }

  if (is_neg_definite()) {
    matrix_GL2Z V((1),(0),(0),(-1));
    f.assign(-a,b,-c);
    multiply(U,U,V);
    f.reduce(U);
    multiply(U,U,V);
    negate(a,f.a);
    b.assign(f.b);
    negate(c,f.c);
    return;
  }

  normalize(U);
  while(!is_reduced())
    rho(U);
}


void
quadratic_form::rho()
{
  bigint temp;
  quadratic_form f;

  if (is_regular()) {
    normalize();
    if (is_neg_definite()) {
      f.assign(-a,b,-c);
      f.rho();
      negate(a,f.a);
      b.assign(f.b);
      negate(c,f.c);
    }
    else {
      temp.assign(a);
      a.assign(c);
      c.assign(temp);
      b.negate();
      normalize();
    }
  }
}



void
quadratic_form::rho(matrix_GL2Z & U)
{
  bigint temp;
  quadratic_form f;
  matrix_GL2Z V((0),(-1),(1),(0)), W((1),(0),(0),(-1));

  if (is_regular()) {
    normalize(U);
    if (is_neg_definite()) {
      f.assign(-a,b,-c);
      multiply(U,U,W);
      f.rho(U);
      multiply(U,U,W);
      negate(a,f.a);
      b.assign(f.b);
      negate(c,f.c);
    }
    else {
      multiply(U,U,V);
      temp.assign(a);
      a.assign(c);
      c.assign(temp);
      b.negate();
      normalize(U);
    }
  }
}



void
quadratic_form::inverse_rho()
{
  bigint temp;
  quadratic_form f;

  if (is_regular()) {
    normalize();
    if (is_neg_definite()) {
      f.assign(-a,b,-c);
      f.inverse_rho();
      negate(a,f.a);
      b.assign(f.b);
      negate(c,f.c);
    }
    else {
      temp.assign(a);
      a.assign(c);
      c.assign(temp);
      b.negate();
      normalize();
    }
  }
}



void
quadratic_form::inverse_rho(matrix_GL2Z & U)
{
  bigint temp;
  quadratic_form f;
  matrix_GL2Z V((0),(1),(-1),(0));  

  if (is_regular()) {
    normalize(U);
    if (is_neg_definite()) {
      f.assign(-a,b,-c);
      f.inverse_rho();
      negate(a,f.a);
      b.assign(f.b);
      negate(c,f.c);
    }
    else {
      multiply(U,U,V);
      temp.assign(a);
      a.assign(c);
      c.assign(temp);
      b.negate();
      normalize(U);
    }
  }
}



bool
quadratic_form::is_equivalent(const quadratic_form & g, matrix_GL2Z & U) const
{ 
  int dness;
  quadratic_form hf(*this),hg(g); 
  matrix_GL2Z W((-1),(0), 
		(0),(1)); 
  matrix_GL2Z I((1),(0), 
		(0),(1)); 
  
  if (discriminant() != g.discriminant())
    return false;

  dness = definiteness();

  if (is_pos_definite() && g.is_pos_definite()) 
    return prop_equivalent_pos_def(g,U); 

  if (is_neg_definite() && g.is_neg_definite()) {
    matrix_GL2Z H(I);
    hf.transform(W);
    hg.transform(W);
    if (hf.prop_equivalent_pos_def(hg,H)) {
      multiply(H,W,H);
      multiply(H,H,inverse(W));
      multiply(U,U,H);
      return true;
    }
    else
      return false;
  }
  
  if (is_neg_definite() && g.is_pos_definite()) { 
    matrix_GL2Z H(I);
    hf.transform(W);
    if (hf.prop_equivalent_pos_def(hg,H)) {
      multiply(H,W,H);
      multiply(U,U,H);
      return true;
    }
    else
      return false;
  }
  
  if (is_pos_definite() && g.is_neg_definite()) {
    matrix_GL2Z H(I);
    hg.transform(W);
    if (hf.prop_equivalent_pos_def(hg,H)) {
      multiply(H,H,inverse(W));
      multiply(U,U,H);
      return true;
    }
    else
      return false;
  } 
  
  
  if (dness == 0) {
    matrix_GL2Z V((1), (0),
	    (0),(1));
    matrix_GL2Z S(V);
      
    quadratic_form rf(*this), rg(g),root,rff;
      
    rf.reduce(S);
    rg.reduce(V);
    rff.assign(-rf.a,rf.b,-rf.c);
      
    if (rf == rg) {
      S.invert();
      multiply(V,V,S);
      multiply(U,U,V);
      return true;
    }
    if (rff == rg) {
      S.invert();
      multiply(V,V,W);
      multiply(V,V,S);
      multiply(U,U,V);
      return true;
    }
      
    root.assign(rg);
    do {
      rg.rho(V);
      if (rf == rg) {
        S.invert();
        multiply(V,V,S);
        multiply(U,U,V);
        return true;
      }
      if (rff == rg) {
        S.invert();
        multiply(V,V,W);
        multiply(V,V,S);
        multiply(U,U,V);
        return true;
      }
    } while (root.compare(rg) != 0);
      
    return false;
  }
  
  
  if (dness == 2) {
    matrix_GL2Z Q(I),R(I);
    hf.reduce(Q);
    hg.reduce(R);
    if(!hf.compare(hg)) {
      R.invert();
      multiply(Q,Q,R);
      multiply(U,U,Q);
      return true;
    }
    else {
      hg.transform(W);
      multiply(R,R,W);
      hg.reduce(R);
      if(!hf.compare(hg)) {
        R.invert();
        multiply(Q,Q,R);
        multiply(U,U,Q);
        return true;
      }
      else
        return false;
    }
  }	 

  return false;
}



bool
quadratic_form::is_prop_equivalent(const quadratic_form & g) const
{
  int dness;

  if (discriminant() != g.discriminant())
    return false;

  dness = definiteness();
  if (dness == 1)
    return prop_equivalent_pos_def(g);
  else if (dness == 2)
    return prop_equivalent_irregular(g);
  else if (dness == 0)
    return prop_equivalent_indef(g);
  else 
    return prop_equivalent_neg_def(g);
}



bool
quadratic_form::is_principal() const
{
  quadratic_form UNIT;

  UNIT.assign_one(discriminant());
  if (is_neg_definite()) {
    UNIT.a.negate();
    UNIT.c.negate();
  }
  return is_equivalent(UNIT);
}



bool
quadratic_form::is_equivalent(const quadratic_form & g) const
{ 
  quadratic_form hf(*this),hg(g); 
  matrix_GL2Z W((-1),(0), 
		(0),(1)); 
  matrix_GL2Z I((1),(0), 
		(0),(1)); 
  
  if (discriminant() != g.discriminant())
    return false;

  if (is_pos_definite() && g.is_pos_definite()) 
    return prop_equivalent_pos_def(g); 

  if (is_neg_definite() && g.is_neg_definite()) 
    return prop_equivalent_neg_def(g);
  
  if (is_neg_definite() && g.is_pos_definite()) {
    hf.assign(-a,b,-c);
    return hf.prop_equivalent_pos_def(g);
  }
  
  if (is_pos_definite() && g.is_neg_definite()) {
    hg.assign(-g.a,g.b,-g.c);
    return prop_equivalent_pos_def(hg);
  }
  
  if (is_indefinite() && g.is_indefinite()) {  
    quadratic_form rf(*this), rg(g),root,rff;
      
    rf.reduce();
    rg.reduce();
    rff.assign(-rf.a,rf.b,-rf.c);
      
    if (!rf.compare(rg))
      return true;
    if (!rff.compare(rg))
      return true;
      
    root.assign(rg);
    do {
      rg.rho();
      if (!rf.compare(rg))
	return true;
      if (!rff.compare(rg))
	return true;
    } while (root.compare(rg));
      
    return false;
  }
  
  if (!is_regular() && !g.is_regular()) {
    hf.reduce();
    hg.reduce();
    if(!hf.compare(hg))
      return true;
    else {
      hg.assign(-hg.a,hg.b,-hg.c);
      hg.reduce();
      if(!hf.compare(hg))
        return true;
      return 0;
    }
  }	 

  return false;
}



bool
quadratic_form::is_prop_equivalent(const quadratic_form & g, matrix_GL2Z & U) const
{
  int dness;

  if (discriminant() != g.discriminant())
    return false;

  dness = definiteness();
  if (dness == 1)
    return prop_equivalent_pos_def(g,U);
  else if (dness == 2)
    return prop_equivalent_irregular(g,U);
  else if (dness == 0)
    return prop_equivalent_indef(g,U);
  else 
    return prop_equivalent_neg_def(g,U);
}



bool
quadratic_form::is_principal(matrix_GL2Z & U) const
{
  quadratic_form UNIT;

  UNIT.assign_one(discriminant());
  if (is_neg_definite()) {
    UNIT.a.negate();
    UNIT.c.negate();
  }
  return is_equivalent(UNIT,U);
}



bigint
quadratic_form::order_in_CL() const
{
  qi_class A;
  bigint ord;
  quadratic_form f,g;

  if (is_regular()) {
    ord.assign_zero();
    if (is_primitive()) {
      qi_class::set_current_order(*QO->get_qo());
      A.assign(*this);
      ord.assign(A.order_in_CL());
    }
  }
  else {
    f.assign(*this);
    f.reduce();
    g.assign(f);
    ord.assign_one();
    while (!f.is_one()) {
      compose_reduce(f,f,g);
      inc(ord);
    }
  }

  return ord;
}



bool
quadratic_form::DL(quadratic_form & G, bigint & x) const
{
  qi_class A,B;
  bool is_DL;

  is_DL = false;
  x.assign_zero();

  if (is_regular()) {
    if (discriminant() != G.discriminant())
      lidia_error_handler("quadratic_form","DL() - forms have different \
discriminants");

    if ((is_primitive()) && (G.is_primitive())) {
      qi_class::set_current_order(*QO->get_qo());
      A.assign(*this);
      B.assign(G);
      is_DL = A.DL(B,x);
    }
  }

  return is_DL;
}



base_vector <bigint>
subgroup(base_vector <quadratic_form> & G, base_vector <quadratic_form> & factor_base, bigint_matrix & U)
{
  lidia_size_t i,j,l;
  base_vector <qi_class> A,FB;
  base_vector <bigint> S;
  qi_class Amem;
  quadratic_form f;

  A.set_mode(EXPAND);
  FB.set_mode(EXPAND);
  factor_base.set_mode(EXPAND);
  S.set_mode(EXPAND);
  A.set_size(0);
  FB.set_size(0);
  factor_base.set_size(0);
  S.set_size(0);

  if (G[0].is_regular()) {
    qi_class::set_current_order(*G[0].which_order());

    l = G.size();
    j = 0;
    for (i=0; i<l; ++i) {
      if (G[0].discriminant() != G[i].discriminant())
        lidia_error_handler("quadratic_form","subgroup() - forms have  \
different discriminants");

      if (G[i].is_primitive()) {
        A[j] = qi_class(G[i]);
        ++j;
      }
    }

    if (A.size() > 0) {
      S = subgroup(A,FB,U);
      l = FB.size();
      for (i=0; i<l; ++i) {
        f = (quadratic_form) FB[i];
        if (G[0].is_neg_definite()) {
          f.a.negate();
          f.c.negate();
        }
        factor_base[i] = f;

      }
    }
  }

  return S;
}



bigfloat
quadratic_form::regulator()
{
  if (is_regular()) {
    return QO->get_qo()->regulator();
  }
  else
    return bigfloat(1);
}



/* FIX - class number of irregulars */
bigint
quadratic_form::class_number()
{
  if (is_regular()) {
    return QO->get_qo()->class_number();
  }
  else
    return bigint(0);
}



/* FIX - class number of irregulars */
base_vector <bigint>
quadratic_form::class_group()
{
  if (is_regular()) {
    return QO->get_qo()->class_group();
  }
  else
    return base_vector <bigint> ();
}



void
quadratic_form::fundamental_automorphism(matrix_GL2Z & U)
{
  matrix_GL2Z V((-1),(0),
		(0),(1));

  if (!is_indefinite())
    return;
  
  quadratic_form g(*this);
  g.reduce();
  
  quadratic_form h(g);
  quadratic_form i(g);
  i.transform(V);
  do {
    g.rho(U);
  }while ((g.compare(h)) && (g.compare(i)));
}



bool
quadratic_form::representations(bigint_matrix & Reps, const bigint & N)
{
  quadratic_form g,f,h;
  rational_factorization Nfact;
  bigint con,NN,temp,temp2,x,y,Delta,p;
  lidia_size_t num,i,j,k,cands,idx,nfacts,val,v;
  matrix_GL2Z U(1,0,0,1),V;
  base_vector<quadratic_form> plist;
  base_vector<int> elist;

  Reps.resize(1,1);

  if (is_zero())
    if (N.is_zero()) {
      Reps.resize(1,2);
      Reps.sto(0,0,bigint(0));
      Reps.sto(0,1,bigint(0));
      return true;
    }
    else
      return false;

  con = content();
  remainder(temp,N,con);
  if (!temp.is_zero()) {
    return false;
  }

  if (N.is_zero()) {
    Reps.resize(1,2);
    Reps.sto(0,0,bigint(0));
    Reps.sto(0,1,bigint(0));
    return true;
  }

  cout << "\nIN REPRESENTATIONS:  *this = " << *this << ", N = " << N << "\n" << flush;
  divide(NN,N,con);
  g.assign(*this);
  divide(g.a,g.a,con);
  divide(g.b,g.b,con);
  divide(g.c,g.c,con);
  cout << "primitive:  g = " << g << ", NN = " << NN << "\n" << flush;

  if (!g.is_regular())
    return g.comp_reps_irregular(Reps,NN);

  g.reduce(U);
  Delta.assign(g.discriminant());
  cout << "reduced:  g = " << g << "\n" << flush;
  cout << "U = \n" << U << "\n" << flush;
  cout << "Delta = " << Delta << "\n" << flush;

  Nfact.assign(abs(NN));
  Nfact.factor();

  plist.set_mode(EXPAND);
  plist.set_size(0);
  elist.set_mode(EXPAND);
  elist.set_size(0);
  num = 0;

  idx = 0;
  cands = 0;
  nfacts = Nfact.no_of_comp();
  f.assign_one(Delta);
  for (i=0; i<nfacts; ++i) {
    k = Nfact.exponent(i);
    cands += k;
   
    if (!prime_form(h,Nfact.base(i),Delta))
      return false;

    for (j=0; j<k; ++j) {
      compose(f,f,h);
      plist[idx] = h;
      elist[idx] = 1;
      ++idx;
    }
  }

  cout << "cands = " << cands << ", plist.size = " << plist.size() << "\n";
  cout << plist << "\n" << flush;
  // cout << elist << "\n" << flush; // MM

  idx = (1 << cands);
  for (j=1; j<=idx; ++j) {
    cout << "f = " << f << "\n" << flush;

    if (f.a == NN) {
      V = U;
      if (f.is_equivalent(g,V)) {
        x.assign(V.get_s());
        y.assign(V.get_t());
        Reps.resize(num+1,2);
        Reps.sto(num,0,x);
        Reps.sto(num,1,y);
        ++num;
      }
    }

    if (j < idx) {
      val = (j << 1);
      v = 0;
      while (!(val & 1)) {
        val >>= 1;
        ++v;
      }
      ++val;
      val >>= 1;
      --v;
      if (val & 1)
        elist[v] = -1;
      else
        elist[v] = 1;

      f.assign_one(Delta);
      for (i=0; i<cands; ++i) {
        if (elist[i] == 1)
          compose(f,f,get_conjugate(plist[i]));
        else
          compose(f,f,plist[i]);
      }
      // cout << elist << "\n" << flush; // MM
      cout << f << "\n" << flush;
    }
  }

  return (num > 0);
}



istream & operator >> (istream & in, quadratic_form & f)
{
  int n = 0, sz = 3;
  char c;
  bigint *ibuf;

  ibuf = new bigint[sz];
  in >> c;
  if (c != '(')
    error_handler("quadratic_form", "operator>>::( expected");
  in >> c;
  while (c != ')' && n != 3)
    {
      in.putback(c);
      in >> ibuf[n];
      n++;
      in >> c;
    }
  f.assign(ibuf[0], ibuf[1], ibuf[2]);
  delete[] ibuf;
  return in;
}



ostream & operator << (ostream & out, const quadratic_form & f)
{
  out << "( " << f.a << " " << f.b << " " << f.c << " )";
  return out;
}
