// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : quadratic_ideal.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


bool
quadratic_ideal::is_normal() const
{
  bigint Delta,rootD,temp;

  if (a.is_zero())
    return true;

  Delta.assign(discriminant());

  if (Delta.is_lt_zero())
    return (b.compare(-a) > 0) && (b.compare(a) <= 0);
  else {
    sqrt(rootD,abs(Delta));
    if (a.compare(rootD) <= 0) {
      shift_left(temp,a,1);
      subtract(temp,rootD,temp);
      if (temp.is_negative())
        inc(temp);
      return (temp.compare(b) < 0) && (b.compare(rootD) <= 0);
    }
    else
      return (b.compare(-a) > 0) && (b.compare(a) <= 0);
  }
}




void
quadratic_ideal::normalize()
{
  if (discriminant().is_lt_zero())
    normalize_imag();
  else
    normalize_real();
}



void
quadratic_ideal::normalize_imag()
{
  bigint a2,nq,temp;
  bigfloat temp2,temp3;

  if (!is_normal()) {
    /* nq = [(-b / 2a] */
    shift_left(a2,a,1);
    temp2.assign(-b,a2);
    add(temp2,temp2,bigfloat(0.5));
    temp3 = floor(temp2);
    temp3.bigintify(nq);

    multiply(temp,a2,nq);
    add(b,b,temp);
  }
}


void
quadratic_ideal::normalize_real()
{
  bigint abs_a,rootD,a2,nq,temp;
  bigfloat temp2,temp3;

  if (!is_normal()) {
    sqrt(rootD,discriminant());
    abs_a.assign(abs(a));
    if (abs_a < rootD) {
      /* nq = floor((rootD - b) / 2a) */
      shift_left(a2,abs_a,1);
      subtract(nq,rootD,b);
      temp2.assign(nq,a2);
      temp3 = floor(temp2);
      temp3.bigintify(nq);
    }
    else {
      /* nq = floor((a - b) / 2a) */
      shift_left(a2,abs_a,1);
      subtract(nq,abs_a,b);
      temp2.assign(nq,a2);
      temp3 = floor(temp2);
      temp3.bigintify(nq);
    }

    multiply(temp,a2,nq);
    add(b,b,temp);
  }
}




void
quadratic_ideal::reduce_imag()
{
  bigint Delta,c,temp,nq,a2;
  bigfloat temp2,temp3;

  Delta.assign(discriminant());

  /* c = (b^2 - D) / 4a */
  shift_left(a2,a,2);
  square(temp,b);
  subtract(temp,temp,Delta);
  divide(c,temp,a2);

  while (!((a.compare(c) < 0) || ((!a.compare(c)) && (b.is_ge_zero())))) {
    /* a = c */
    a.assign(c);

    /* nq = [b / 2a] */
    shift_left(a2,a,1);
    temp2.assign(b,a2);
    add(temp2,temp2,bigfloat(0.5));
    temp3 = floor(temp2);
    temp3.bigintify(nq);

    /* b = 2a(nq) - b */
    multiply(temp,a2,nq);
    subtract(b,temp,b);

    /* c = (b^2 - D) / 4a */
    a2.multiply_by_2();
    square(temp,b);
    subtract(temp,temp,Delta);
    divide(c,temp,a2);
  }
}



void
quadratic_ideal::reduce_real()
{
  bigint Delta,rootD,a2,nq,temp;
  bigfloat temp2,temp3;

  Delta.assign(discriminant());
  sqrt(rootD,Delta);

  shift_left(temp,a,1);
  subtract(temp,rootD,temp);
  if (temp.is_negative())
    inc(temp);
  temp.absolute_value();

  while (!((temp.compare(b) < 0) && (b.compare(rootD) <= 0))) {
    /* a = (D - b^2) / 4a */
    shift_left(a2,a,2);
    square(temp,b);
    subtract(temp,Delta,temp);
    divide(a,temp,a2);
    a.absolute_value();

    if (a <= rootD) {
      /* nq = floor((b + rootD) / 2a) */
      shift_left(a2,a,1);
      add(nq,rootD,b);
      temp2.assign(nq,a2);
      temp3 = floor(temp2);
      temp3.bigintify(nq);
    }
    else {
      /* nq = floor((b + a) / 2a) */
      shift_left(a2,a,1);
      add(nq,a,b);
      temp2.assign(nq,a2);
      temp3 = floor(temp2);
      temp3.bigintify(nq);
    }

    /* b = 2a(nq) - b */
    multiply(temp,a2,nq);
    subtract(b,temp,b);

    shift_left(temp,abs(a),1);
    subtract(temp,rootD,temp);
    if (temp.is_negative())
      inc(temp);
    temp.absolute_value();
  }
}




void
quadratic_ideal::rho_imag()
{
  bigint Delta,a2,nq,temp;
  bigfloat temp2,temp3;

  Delta.assign(discriminant());

  /* a = (b^2 - D) / 4a */
  shift_left(a2,a,2);
  square(temp,b);
  subtract(temp,temp,Delta);
  divide(a,temp,a2);

  /* nq = [b / 2a] */
  shift_left(a2,a,1);
  temp2.assign(b,a2);
  add(temp2,temp2,bigfloat(0.5));
  temp3 = floor(temp2);
  temp3.bigintify(nq);

  /* b = 2a(nq) - b */
  multiply(temp,a2,nq);
  subtract(b,temp,b);
}



void
quadratic_ideal::rho_real()
{
  bigint Delta,rootD,a2,nq,temp;
  bigfloat temp2,temp3;

  Delta.assign(discriminant());
  sqrt(rootD,Delta);

  /* a = (D - b^2) / 4a */
  shift_left(a2,a,2);
  square(temp,b);
  subtract(temp,Delta,temp);
  divide(a,temp,a2);
  a.absolute_value();

  if (a <= rootD) {
    /* nq = floor((b + rootD) / 2a) */
    shift_left(a2,a,1);
    add(nq,rootD,b);
    temp2.assign(nq,a2);
    temp3 = floor(temp2);
    temp3.bigintify(nq);
  }
  else {
    /* nq = floor((b + a) / 2a) */
    shift_left(a2,a,1);
    add(nq,a,b);
    temp2.assign(nq,a2);
    temp3 = floor(temp2);
    temp3.bigintify(nq);
  }

  /* b = 2a(nq) - b */
  multiply(temp,a2,nq);
  subtract(b,temp,b);
}




void
quadratic_ideal::inverse_rho_imag()
{
  bigint Delta,a2,nq,temp;
  bigfloat temp2,temp3;

  Delta.assign(discriminant());

  /* nq = [(b / 2a] */
  shift_left(a2,a,1);
  temp2.assign(b,a2);
  add(temp2,temp2,bigfloat(0.5));
  temp3 = floor(temp2);
  temp3.bigintify(nq);

  /* b = 2a(nq) - b */
  multiply(temp,a2,nq);
  subtract(b,temp,b);

  /* a = (b^2 - D) / 4a */
  a2.multiply_by_2();
  square(temp,b);
  subtract(temp,temp,Delta);
  divide(a,temp,a2);

  normalize_imag();
}



void
quadratic_ideal::inverse_rho_real()
{
  bigint Delta,rootD,a2,nq,temp;
  bigfloat temp2,temp3;

  Delta.assign(discriminant());
  sqrt(rootD,Delta);

  if (a <= rootD) {
    /* nq = floor((b + rootD) / 2a) */
    shift_left(a2,a,1);
    add(nq,b,rootD);
    temp2.assign(nq,a2);
    temp3 = floor(temp2);
    temp3.bigintify(nq);
  }
  else {
    /* nq = floor((b + a) / 2a) */
    shift_left(a2,a,1);
    add(nq,b,a);
    temp2.assign(nq,a2);
    temp3 = floor(temp2);
    temp3.bigintify(nq);
  }

  /* b = 2a(nq) - b */
  multiply(temp,a2,nq);
  subtract(b,temp,b);

  /* a = (D - b^2) / 4a */
  a2.multiply_by_2();
  square(temp,b);
  subtract(temp,Delta,temp);
  divide(a,temp,a2);

  a.absolute_value();
  normalize_real();
}




void
multiply_imag(quadratic_ideal & C, const quadratic_ideal & A, const quadratic_ideal & B)
{
  bigint Delta,newa,newb,dpr,v,d,w,ab2,temp;
  bigrational newq;

  Delta.assign(A.discriminant());

  /* solve dpr = v A.a + w B.a */
  dpr.assign(xgcd_left(v,A.a,B.a));

  /* C.b = v A.a (B.b - A.b) */
  multiply(newb,v,A.a);
  subtract(temp,B.b,A.b);
  multiply(newb,newb,temp);

  /* C.a = A.a B.a */
  multiply(newa,A.a,B.a);

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

    /* C.b = (C.b*v + w(Delta - A.b^2)/2) / d */
    multiply(newb,newb,v);

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

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

    /* C.a = C.a / (d^2) */
    square(temp,d);
    divide(newa,newa,temp);

    dpr.assign(d);
  }

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

  multiply(newq,A.q,B.q);
  multiply(newq,newq,bigrational(dpr));

  C.a = newa;
  C.b = newb;
  C.q = newq;
  C.QO = quadratic_order::add_to_list(C.QO,*A.which_order());
  C.normalize_imag();
}



void
multiply_real(quadratic_ideal & C, const quadratic_ideal & A, const quadratic_ideal & B)
{
  bigint Delta,newa,newb,dpr,v,d,w,ab2,temp;
  bigrational newq;

  Delta.assign(A.discriminant());

  /* solve dpr = v A.a + w B.a */
  dpr.assign(xgcd_left(v,A.a,B.a));

  /* C.b = v A.a (B.b - A.b) */
  multiply(newb,v,A.a);
  subtract(temp,B.b,A.b);
  multiply(newb,newb,temp);

  /* C.a = A.a B.a */
  multiply(newa,A.a,B.a);

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

    /* C.b = (C.b*v + w(D - A.b^2)/2) / d */
    multiply(newb,newb,v);

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

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

    /* C.a = C.a / (d^2) */
    square(temp,d);
    divide(newa,newa,temp);

    dpr.assign(d);
  }

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

  multiply(newq,A.q,B.q);
  multiply(newq,newq,bigrational(dpr));

  C.a = newa;
  C.b = newb;
  C.q = newq;
  C.QO = quadratic_order::add_to_list(C.QO,*A.which_order());
  C.normalize_real();
}



void
square_imag(quadratic_ideal & C, const quadratic_ideal & A)
{
  bigint Delta,newa,newb,d,w,temp;
  bigrational newq;

  Delta.assign(A.discriminant());

  /* solve d = v A.a + w A.b */
  d.assign(xgcd_right(w,A.a,A.b));

  /* C.b = A.b + w (D - A.b^2) / (2d) */
  square(newb,A.b);
  subtract(newb,Delta,newb);
  shift_left(temp,d,1);
  divide(newb,newb,temp);
  multiply(newb,newb,w);
  add(newb,newb,A.b);

  /* C.a = (A.a/d)^2 */
  divide(temp,A.a,d);
  square(newa,temp);

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

  square(newq,A.q);
  multiply(newq,newq,d);

  C.a = newa;
  C.b = newb;
  C.q = newq;
  C.QO = quadratic_order::add_to_list(C.QO,*A.which_order());
  C.normalize_imag();
}


void
square_real(quadratic_ideal & C, const quadratic_ideal & A)
{
  bigint Delta,newa,newb,d,w,temp;
  bigrational newq;

  Delta.assign(A.discriminant());

  /* solve d = v A.a + w A.b */
  d.assign(xgcd_right(w,A.a,A.b));

  /* C.b = A.b + w (D - A.b^2) / (2d) */
  square(newb,A.b);
  subtract(newb,Delta,newb);
  shift_left(temp,d,1);
  divide(newb,newb,temp);
  multiply(newb,newb,w);
  add(newb,newb,A.b);

  /* C.a = (A.a/d)^2 */
  divide(temp,A.a,d);
  square(newa,temp);

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

  square(newq,A.q);
  multiply(newq,newq,d);

  C.a = newa;
  C.b = newb;
  C.q = newq;
  C.QO = quadratic_order::add_to_list(C.QO,*A.which_order());
  C.normalize_real();
}


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



quadratic_ideal::quadratic_ideal(const quadratic_form & qf)
{ 
  if (!qf.is_regular())
    lidia_error_handler("quadratic_ideal","quadratic_ideal() - quadratic form \
is not regular");

  QO = NULL;
  a.assign(abs(qf.get_a()));
  b.assign(qf.get_b());
  q.assign_one();
  QO = quadratic_order::add_to_list(QO,*qf.which_order());
  normalize();
}



quadratic_ideal::quadratic_ideal(const qi_class & B)
{
  QO = NULL;
  a.assign(B.get_a());
  b.assign(B.get_b());
  q.assign_one();
  QO = quadratic_order::add_to_list(QO,*qi_class::get_current_order());
}



quadratic_ideal::quadratic_ideal(const qi_class_real & B)
{
  QO = NULL;
  a.assign(B.get_a());
  b.assign(B.get_b());
  q.assign_one();
  QO = quadratic_order::add_to_list(QO,*qi_class::get_current_order());
}



quadratic_ideal::quadratic_ideal(const quadratic_ideal & B)
{
  QO = NULL;
  a.assign(B.a);
  b.assign(B.b);
  q.assign(B.q);
  QO = quadratic_order::add_to_list(QO,*B.which_order());
}



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



void
quadratic_ideal::assign_zero()
{
  if (quadratic_order::last_order())
    assign_zero(*quadratic_order::last_order());
  else
    lidia_error_handler("quadratic_ideal","assign_zero() - no quadratic \
orders have been used yet");
}



void
quadratic_ideal::assign_one()
{
  if (quadratic_order::last_order())
    assign_one(*quadratic_order::last_order());
  else
    lidia_error_handler("quadratic_ideal","assign_one() - no quadratic \
orders have been used yet");
}



void
quadratic_ideal::assign_principal(const bigint & x, const bigint & y)
{
  if (quadratic_order::last_order())
    assign_principal(x,y,*quadratic_order::last_order());
  else
    lidia_error_handler("quadratic_ideal","assign_principal() - no quadratic \
orders have been used yet");
}



bool
quadratic_ideal::assign(const bigint & a2, const bigint & b2, const bigrational & q2)
{
  if (!quadratic_order::last_order())
    lidia_error_handler("quadratic_ideal","assign() - no quadratic \
orders have been used yet");

  return assign(a2,b2,q2,*quadratic_order::last_order());
}



bool
quadratic_ideal::assign(const long a2, const long b2, const bigrational & q2)
{
  if (!quadratic_order::last_order())
    lidia_error_handler("quadratic_ideal","assign() - no quadratic \
orders have been used yet");

  return assign(a2,b2,q2,*quadratic_order::last_order());
}



void
quadratic_ideal::assign_zero(quadratic_order & QO2)
{
  a.assign_zero();
  b.assign_zero();
  q.assign_zero();
  QO = quadratic_order::add_to_list(QO,QO2);
}



void
quadratic_ideal::assign_one(quadratic_order & QO2)
{
  a.assign_one();
  b.assign(QO2.discriminant());
  q.assign_one();
  QO = quadratic_order::add_to_list(QO,QO2);

  normalize();
}



void
quadratic_ideal::assign_principal(const bigint & x, const bigint & y, quadratic_order & QO2)
{
  bigint x2,y2,Delta,n,m,k,l,temp,temp2;

  QO = quadratic_order::add_to_list(QO,QO2);
  Delta = QO2.discriminant();

  /* use (x + y \sqrt(Delta))/2 form */
  y2.assign(y);
  shift_left(x2,x,1);
  if (Delta.is_odd())
    add(x2,x2,y);

  /* compute norm of (x2 + y2 sqrt(Delta))/2 */
  square(n,x2);
  square(temp,y2);
  multiply(temp,temp,Delta);
  subtract(n,n,temp);
  shift_right(n,n,2);
  n.absolute_value();

  /* solve m = k y2 + l (x2 + y2 Delta) / 2 */
  multiply(temp2,y2,Delta);
  add(temp2,temp2,x2);
  temp2.divide_by_2();
  m.assign(xgcd(k,l,y2,temp2));

  /* a = n / m^2 */
  square(temp,m);
  divide(a,n,temp);

  /* (b = kx2 + l(x2+y2)D/2 ) / m */
  add(temp2,x2,y2);
  multiply(temp2,temp2,l);
  multiply(b,temp2,Delta);
  b.divide_by_2();
  multiply(temp,k,x2);
  add(b,b,temp);
  divide(b,b,m);
  shift_left(temp,a,1);
  remainder(b,b,temp);

  /* q = m */
  q.assign(m);

  normalize();
}



bool
quadratic_ideal::assign(const bigint & a2, const bigint & b2, const bigrational & q2, quadratic_order & QO2)
{
  bigint c,temp;

  if (a2.is_zero())
    if (b2.is_zero()) {
      a.assign_zero();
      b.assign_zero();
      q.assign(q2);
      QO = quadratic_order::add_to_list(QO,QO2);
      return true;
    }
    else
      return false;
  else {
    square(c,b2);
    subtract(c,c,QO2.discriminant());
    shift_left(temp,a2,2);
    remainder(c,c,temp);
    if (c.is_zero()) {
      a.assign(a2);
      b.assign(b2);
      q.assign(q2);
      QO = quadratic_order::add_to_list(QO,QO2);
      normalize();
      return true;
    }
    else
      return false;
  }
}



bool
quadratic_ideal::assign(const long a2, const long b2, const bigrational & q2, quadratic_order & QO2)
{
  bigint c,temp;

  if (!a2)
    if (!b2) {
      a.assign_zero();
      b.assign_zero();
      q.assign(q2);
      QO = quadratic_order::add_to_list(QO,QO2);
      return true;
    }
    else
      return false;
  else {
    square(c,bigint(b2));
    subtract(c,c,QO2.discriminant());
    shift_left(temp,bigint(a2),2);
    remainder(c,c,temp);
    if (c.is_zero()) {
      a.assign(a2);
      b.assign(b2);
      q.assign(q2);
      QO = quadratic_order::add_to_list(QO,QO2);
      normalize();
      return true;
    }
    else
      return false;
  }
}



void
quadratic_ideal::assign(const quadratic_form & qf)
{ 
  if (!qf.is_regular())
    lidia_error_handler("quadratic_ideal","assign() - quadratic form \
is not regular");

  a.assign(abs(qf.get_a()));
  b.assign(qf.get_b());
  q.assign_one();
  QO = quadratic_order::add_to_list(QO,*qf.which_order());
  normalize();
}



void
quadratic_ideal::assign(const qi_class & B)
{
  a.assign(B.get_a());
  b.assign(B.get_b());
  q.assign_one();
  QO = quadratic_order::add_to_list(QO,*qi_class::get_current_order());
}



void
quadratic_ideal::assign(const qi_class_real & B)
{
  a.assign(B.get_a());
  b.assign(B.get_b());
  q.assign_one();
  QO = quadratic_order::add_to_list(QO,*qi_class::get_current_order());
}



void
quadratic_ideal::assign(const quadratic_ideal & B)
{
  a.assign(B.a);
  b.assign(B.b);
  q.assign(B.q);
  QO = quadratic_order::add_to_list(QO,*B.which_order());
}



bool
quadratic_ideal::assign_order(quadratic_order & QO2)
{
  bigint c,temp;

  if (is_zero()) {
    QO = quadratic_order::add_to_list(QO,QO2);
    return true;
  }
  else {
    square(c,b);
    subtract(c,c,QO2.discriminant());
    shift_left(temp,a,2);
    remainder(c,c,temp);
    if (c.is_zero()) {
      QO = quadratic_order::add_to_list(QO,QO2);
      return true;
    }
    else
      return false;
  }
}



quadratic_ideal & quadratic_ideal::operator = (const quadratic_ideal & A)
{
  assign(A);
  return *this;
}



bigint
quadratic_ideal::get_c() const
{
  bigint Delta,c,temp;

  Delta.assign(discriminant());

  if (a.is_zero())
    c.assign_zero();
  else {
    /* c = (b^2 - D) / 4a */
    square(c,b);
    subtract(c,c,Delta);
    shift_left(temp,a,2);
    divide(c,c,temp);
  }

  return c;
}



quadratic_order *
quadratic_ideal::which_order() const
{
  return QO->get_qo();
}



quadratic_order *
which_order(const quadratic_ideal & A)
{
  return A.QO->get_qo();
}



bigint
quadratic_ideal::discriminant() const
{
  if (!QO)
    lidia_error_handler("quadratic_ideal", "discriminant() - no quadratic \
order has been defined for this ideal");

  if (!QO->get_qo())
    lidia_error_handler("quadratic_ideal", "discriminant() - the quadratic \
order of this ideal has been deleted");

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



/* FIX */
void
add(quadratic_ideal & C, const quadratic_ideal & A, const quadratic_ideal & B)
{
  C.assign(A);
  warning_handler_c("quadratic_ideal","add() - not implemented", {C.assign(A); return;});

  if (A.discriminant() != B.discriminant())
    lidia_error_handler("quadratic_ideal","add() - ideals are associated with \
different quadratic orders");

}



void
intersect(quadratic_ideal & C, const quadratic_ideal & A, const quadratic_ideal & B)
{
  if (A.discriminant() != B.discriminant())
    lidia_error_handler("quadratic_ideal","intersect() - ideals are \
associated with different quadratic orders");

  multiply(C,A,B);
  C /= (A+B);
}




void
multiply(quadratic_ideal & C, const quadratic_ideal & A, const quadratic_ideal & B)
{
  if (A.discriminant() != B.discriminant())
    lidia_error_handler("quadratic_ideal","multiply() - ideals are associated \
with different quadratic orders");

  if (A.discriminant() < 0)
    multiply_imag(C,A,B);
  else
    multiply_real(C,A,B);
}




void
divide(quadratic_ideal & C, const quadratic_ideal & A, const quadratic_ideal & B)
{
  quadratic_ideal temp;

  temp = inverse(B);
  multiply(C,A,temp);
}



void
quadratic_ideal::conjugate()
{
  b.negate();
  normalize();
}



void
get_conjugate(quadratic_ideal & A, const quadratic_ideal & B)
{
  A.assign(B);
  A.b.negate();
  A.normalize();
}



quadratic_ideal
get_conjugate(const quadratic_ideal & B)
{
  quadratic_ideal A;

  A.assign(B);
  A.b.negate();
  A.normalize();

  return A;
}


void
quadratic_ideal::invert()
{
  bigrational n;

  if (!is_invertible()) {
    b.negate();
    normalize();
  }
  else {
    n.assign(norm());
    b.negate();
    normalize();
    divide(q,q,n);
  }
}



void
inverse(quadratic_ideal & A, const quadratic_ideal & B)
{
  bigrational n;

  if (B.is_zero())
    A.assign_zero(*B.which_order());
  else {
    if (!B.is_invertible()) {
      A.assign(B);
      A.b.negate();
      A.normalize();
    }
    else {
      n.assign(B.norm());
      A.assign(B);
      A.b.negate();
      A.normalize();
      divide(A.q,A.q,n);
    }
  }
}



quadratic_ideal
inverse(const quadratic_ideal & B)
{
  quadratic_ideal A;
  bigrational n;

  if (B.is_zero())
    A.assign_zero(*B.which_order());
  else {
    if (!B.is_invertible()) {
      A.assign(B);
      A.b.negate();
      A.normalize();
    }
    else {
      n.assign(B.norm());
      A.assign(B);
      A.b.negate();
      A.normalize();
      divide(A.q,A.q,n);
    }
  }

  return A;
}



void
square(quadratic_ideal & C, const quadratic_ideal & A)
{
  if (A.discriminant().is_lt_zero())
    square_imag(C,A);
  else
    square_real(C,A);
}



void
power(quadratic_ideal & C, const quadratic_ideal & A, const bigint & i)
{
  quadratic_ideal B;
  bigint j;

  B.assign(A);
  j.assign(i);
  if (j.is_lt_zero()) {
    B.invert();
    j.absolute_value();
  }
  C.assign_one(*A.which_order()); 
  while (j.is_gt_zero()) {
    if (j.is_odd())
      multiply(C,C,B);
    j.divide_by_2();
    if (j.is_gt_zero())
      square(B,B);
  }
}



void
power(quadratic_ideal & C, const quadratic_ideal & A, const long i)
{
  quadratic_ideal B;
  register long j;

  B.assign(A);
  j = i;
  if (j < 0) {
    B.invert();
    j = -j;
  }
  C.assign_one(*A.which_order()); 
  while (j > 0) {
    if ((j & 1) == 1)
      multiply(C,C,B);
    j >>= 1;
    if (j > 0)
      square(B,B);
  }
}



quadratic_ideal operator - (const quadratic_ideal & A)
{
  return inverse(A);
}



quadratic_ideal operator + (const quadratic_ideal & A, const quadratic_ideal & B)
{
  quadratic_ideal C;

  add(C,A,B);
  return C;
}



quadratic_ideal operator & (const quadratic_ideal & A, const quadratic_ideal & B)
{
  quadratic_ideal C;

  intersect(C,A,B);
  return C;
}



quadratic_ideal operator * (const quadratic_ideal & A, const quadratic_ideal & B)
{
  quadratic_ideal C;

  multiply(C,A,B);
  return C;
}



quadratic_ideal operator / (const quadratic_ideal & A, const quadratic_ideal & B)
{
  quadratic_ideal C;

  divide(C,A,B);
  return C;
}



quadratic_ideal & quadratic_ideal::operator += (const quadratic_ideal & A)
{
  add(*this,*this,A);
  return *this;
}



quadratic_ideal & quadratic_ideal::operator &= (const quadratic_ideal & A)
{
  intersect(*this,*this,A);
  return *this;
}



quadratic_ideal & quadratic_ideal::operator *= (const quadratic_ideal & A)
{
  multiply(*this,*this,A);
  return *this;
}



quadratic_ideal & quadratic_ideal::operator /= (const quadratic_ideal & A)
{
  multiply(*this,*this,inverse(A));
  return *this;
}




bool
quadratic_ideal::is_zero() const
{
  return ((a.is_zero()) && (q.is_zero()));
}



bool
quadratic_ideal::is_one() const
{
  return ((a.is_one()) && (q.is_one()));
}



bool
quadratic_ideal::is_equal(const quadratic_ideal & B) const
{
  if (discriminant() != B.discriminant())
    lidia_error_handler("quadratic_ideal","is_equal() - ideals are associated \
with different quadratic orders");

  return (!(a.compare(B.a)) && !(b.compare(B.b)) && !(q.compare(B.q)));
}


bool
quadratic_ideal::is_subset(const quadratic_ideal & B) const
{
  bool is_sub;
  bigrational temp;

  if (discriminant() != B.discriminant())
    lidia_error_handler("quadratic_ideal","is_subset() - ideals are \
associated with different quadratic orders");

  divide(temp,norm(),B.norm());
  is_sub = (is_bigint(temp));

  return is_sub;
}


bool
quadratic_ideal::is_proper_subset(const quadratic_ideal & B) const
{
  return ((this->is_subset(B)) && (!this->is_equal(B)));
}



bool operator == (const quadratic_ideal & A, const quadratic_ideal & B)
{
  return A.is_equal(B);
}



bool operator != (const quadratic_ideal & A, const quadratic_ideal & B)
{
  return !A.is_equal(B);
}



bool operator <= (const quadratic_ideal & A, const quadratic_ideal & B)
{
  return (A.is_subset(B));
}



bool operator < (const quadratic_ideal & A, const quadratic_ideal & B)
{
  return (A.is_proper_subset(B));
}



bool operator >= (const quadratic_ideal & A, const quadratic_ideal & B)
{
  return (B.is_subset(A));
}



bool operator > (const quadratic_ideal & A, const quadratic_ideal & B)
{
  return (B.is_proper_subset(A));
}



bool operator ! (const quadratic_ideal & A)
{
  return (A.is_zero());
}



void
swap(quadratic_ideal & A, quadratic_ideal & B)
{
  quadratic_ideal C;

  C.assign(A);
  A.assign(B);
  B.assign(C);
}



bigrational
quadratic_ideal::smallest_rational() const
{
  bigrational temp;

  multiply(temp,q,bigrational(a));
  return temp;
}



bool
quadratic_ideal::is_integral() const
{
  return is_bigint(q);
}




bigint
quadratic_ideal::conductor() const
{
  bigint temp;

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



bool
quadratic_ideal::is_invertible() const
{
  bigint f;

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




bigrational
quadratic_ideal::norm() const
{
  bigrational nm;
  bigint temp;

  multiply(temp,a,conductor());
  square(nm,q);
  multiply(nm,nm,bigrational(temp));

  return nm;
}



quadratic_order
quadratic_ideal::ring_of_multipliers()
{
  bigint f,newDelta;
  quadratic_order newQO;

  f = conductor();
  newDelta = (QO->get_qo()->discriminant()) / (f*f); 

  newQO.assign(newDelta);

  return newQO;
}



bool
prime_ideal(quadratic_ideal & A, const bigint & p)
{
  bool OK;

  OK = false;
  if (quadratic_order::last_order())
    OK = prime_ideal(A,p,*quadratic_order::last_order());
  else
    lidia_error_handler("quadratic_ideal","prime_ideal() - no quadratic \
have been used yet");

  return OK;
}



bool
prime_ideal(quadratic_ideal & A, const bigint & p, quadratic_order & QO2)
{
  bigint Delta,b,temp;
  int kro,Dp,ip;

  Delta.assign(QO2.discriminant());
  kro = kronecker(Delta,p);
  if (kro < 0)
    return false;
  else {
    if (p == 2) {
      if (kro == 0) {
        Dp = (int) remainder(Delta,16);
        if (Dp == 8)
          b.assign_zero();
        else
          b.assign(p);
      }
      else
        b.assign(1);
    }
    else {
      if (kro == 0) {
        remainder(temp,Delta,p*p);
        if (temp.is_zero())
          b.assign(p);
        else
          b.assign_zero();
      }
      else {
        if (is_int(p)) {
          p.intify(ip);
          Dp = (int) remainder(Delta,(long) ip);
          if (Dp < 0)
            Dp += ip;

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

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

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

    A.assign(p,b,1,QO2);
    return true;
  }
}



bool
quadratic_ideal::is_reduced() const
{
  bigint Delta,rootD,c,temp;

  Delta.assign(discriminant());

  if (a.is_zero())
    return true;

  if (Delta.is_lt_zero()) {
    c.assign(get_c());
    return (a.compare(c) < 0) || ((!a.compare(c)) && (b.is_ge_zero()));
  }
  else {
    sqrt(rootD,Delta);
    shift_left(temp,a,1);
    subtract(temp,rootD,temp);
    if (temp.is_negative())
      inc(temp);
    temp.absolute_value();
    return (temp.compare(b) < 0) && (b.compare(rootD) <= 0);
  }
}



void
quadratic_ideal::reduce()
{
  q.assign_one();
  if (discriminant().is_lt_zero())
    reduce_imag();
  else
    reduce_real();
}



void
quadratic_ideal::rho()
{
  if (discriminant().is_lt_zero())
    rho_imag();
  else
    rho_real();
}



void
apply_rho(quadratic_ideal & A, const quadratic_ideal & B)
{
  A.assign(B);
  A.rho();
}



quadratic_ideal
apply_rho(const quadratic_ideal & A)
{
  quadratic_ideal B;

  B.assign(A);
  B.rho();
  return B;
}



void
quadratic_ideal::inverse_rho()
{
  if (discriminant().is_lt_zero())
    inverse_rho_imag();
  else
    inverse_rho_real();
}



void
apply_inverse_rho(quadratic_ideal & A, const quadratic_ideal & B)
{
  A.assign(B);
  A.inverse_rho();
}



quadratic_ideal
apply_inverse_rho(const quadratic_ideal & A)
{
  quadratic_ideal B;

  B.assign(A);
  B.inverse_rho();
  return B;
}



bool
quadratic_ideal::is_equivalent(const quadratic_ideal & B) const
{
  qi_class A,Bc;
  bool equiv;

  if (discriminant() != B.discriminant())
    lidia_error_handler("quadratic_ideal","is_equivalent() - ideals are \
associated with different quadratic orders");

  equiv = false;
  if ((is_invertible()) && (B.is_invertible())) {
    qi_class::set_current_order(*which_order());
    A.assign(*this);
    Bc.assign(B);
    equiv = A.is_equivalent(Bc);
  }

  return equiv;
}



bool
quadratic_ideal::is_principal() const
{
  qi_class A;
  bool prin;

  prin = false;
  if (is_invertible()) {
    qi_class::set_current_order(*which_order());
    A.assign(*this);
    prin = A.is_principal();
  }

  return prin;
}



bigint
quadratic_ideal::order_in_CL() const
{
  qi_class A;
  bigint ord;
  
  ord.assign_zero();

  if (is_invertible()) {
    qi_class::set_current_order(*which_order());
    A.assign(*this);
    ord.assign(A.order_in_CL());
  }

  return ord;
}



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

  if (discriminant() != G.discriminant())
    lidia_error_handler("quadratic_ideal","DL() - ideals are associated with \
different quadratic orders");

  is_DL = false;
  x.assign_zero();

  if ((is_invertible()) && (G.is_invertible())) {
    qi_class::set_current_order(*which_order());
    A.assign(*this);
    B.assign(G);
    is_DL = A.DL(B,x);
  }

  return is_DL;
}



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

  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);

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

  l = G.size();
  j = 0;
  for (i=0; i<l; ++i) {
    if (qi_class::discriminant() != G[i].discriminant())
      lidia_error_handler("quadratic_ideal","subgroup() - ideals are \
associated with different quadratic orders");

    if (G[i].is_invertible()) {
      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)
      factor_base[i] = (quadratic_ideal) FB[i];
  }

  return S;
}



bigfloat
quadratic_ideal::regulator()
{
  if (!QO)
    lidia_error_handler("quadratic_ideal", "regulator() - no quadratic \
order has been defined for this ideal");

  if (!QO->get_qo())
    lidia_error_handler("quadratic_ideal", "regulator() - the quadratic \
order of this ideal has been deleted");

  return QO->get_qo()->regulator();
}



bigint
quadratic_ideal::class_number()
{
  if (!QO)
    lidia_error_handler("quadratic_ideal", "class_number() - no quadratic \
order has been defined for this ideal");

  if (!QO->get_qo())
    lidia_error_handler("quadratic_ideal", "class_number() - the quadratic \
order of this ideal has been deleted");

  return QO->get_qo()->class_number();
}



base_vector <bigint>
quadratic_ideal::class_group()
{
  if (!QO)
    lidia_error_handler("quadratic_ideal", "class_group() - no quadratic \
order has been defined for this ideal");

  if (!QO->get_qo())
    lidia_error_handler("quadratic_ideal", "class_group() - the quadratic \
order of this ideal has been deleted");

  return QO->get_qo()->class_group();
}




istream & operator >> (istream & in, quadratic_ideal & A)
{
  int n = 0, sz = 2;
  char c;
  bigint *ibuf;
  bigrational qbuf;
  quadratic_order *QO;

  ibuf = new bigint[sz];
  QO = new quadratic_order;

  in >> c;
  if (c != '(')
    lidia_error_handler("qi_class", "operator>>::( expected");
  in >> c;
  while (c != ')' && n != 2)
    {
      in.putback(c);
      if (n == 0)
        in >> qbuf;
      if (n < 3)
        in >> ibuf[n-1];
      else
        in >> *QO;

      n++;
      in >> c;
    }

  A.assign(ibuf[0], ibuf[1], qbuf, *QO);

  delete[] ibuf;

  return in;
}



ostream & operator << (ostream & out, const quadratic_ideal & A)
{
  out << "((" << A.q << "), " << A.a << ", " << A.b << ")";
  return out;
}
