#include <LiDIA/matrix_GL2Z.h>

matrix_GL2Z::matrix_GL2Z()
{
  s.assign(1);
  u.assign(0);
  t.assign(0);
  v.assign(1);
  determinant = 1;
}


matrix_GL2Z::matrix_GL2Z(const bigint & a, const bigint & c,
	                 const bigint & b, const bigint & d)
{
  bigint d_2, sv, tu;
  s.assign(a);
  u.assign(c);
  t.assign(b);
  v.assign(d);
  multiply(sv, s, v);
  multiply(tu, t, u);
  subtract(d_2, sv, tu);
  if (d_2.intify(determinant) || (abs(determinant) != 1))
    {
    error_handler("matrix_GL2Z", "abs(det)!=1::not a valid GL2Z matrix");
  };
}

matrix_GL2Z::matrix_GL2Z(const matrix_GL2Z & a)
{
  s.assign(a.s);
  t.assign(a.t);
  u.assign(a.u);
  v.assign(a.v);
  determinant = a.determinant;
}

matrix_GL2Z::~matrix_GL2Z()
{
}

const bigint &  matrix_GL2Z::get_s() const
{ 
  return s; 
}

const bigint &  matrix_GL2Z::get_t() const 
{ 
  return t; 
}

const bigint &  matrix_GL2Z::get_u() const 
{ 
  return u; 
}

const bigint  &  matrix_GL2Z::get_v() const
{ 
  return v; 
}

int matrix_GL2Z::det()
{
  if (determinant == 0)
  {
    bigint d, sv, tu;
    multiply(sv, s, v);
    multiply(tu, t, u);
    subtract(d, sv, tu);
    if (!d.intify(determinant) || abs(determinant) != 1)
      error_handler("matrix_GL2Z", "abs(det)!=1::not a valid GL2Z matrix");
  }
  return determinant;
}

void matrix_GL2Z::invert()
{
  bigint d, sv, tu;
  multiply(sv, s, v);
  multiply(tu, t, u);
  subtract(d, sv, tu);
  d.intify(determinant);

  if (determinant == 0)
    error_handler("matrix_GL2Z", "det=0::cannot invert");
  else if (determinant == 1)
    {
      swap(s, v);
      t.negate();
      u.negate();
    }
  else if (determinant == -1)
    {
      swap(s, v);
      s.negate();
      v.negate();
    }
}

void matrix_GL2Z::es(const bigint & a)
{
  u=a*s+u;
  v=a*t+v;
}

void matrix_GL2Z::te()
{
  swap(s,u);
  u.negate();
  swap(t,v);
  v.negate();
}
void matrix_GL2Z::assign_zero()
{
  s.assign_zero();
  u.assign_zero();
  t.assign_zero();
  v.assign_zero();
  determinant = 0;
}

void matrix_GL2Z::assign_one()
{
  s.assign_one();
  u.assign_zero();
  t.assign_zero();
  v.assign_one();
  determinant = 1;
}

matrix_GL2Z & matrix_GL2Z::operator = (const matrix_GL2Z & a)
{
  s.assign(a.s);
  t.assign(a.t);
  u.assign(a.u);
  v.assign(a.v);
  determinant = a.determinant;
  return *this;
}

bigint matrix_GL2Z::operator()(int i, int j) const
{
  
  if(i==0 && j==0)
    return s;
  if(i==0 && j==1)
    return u;
  if(i==1 && j==0)
    return t;
  if(i==1 && j==1)
    return v;
  error_handler("matrix_GL2Z", "matrix_GL2Z indices not in range");
}

int operator == (const matrix_GL2Z & a, const matrix_GL2Z & b)
{
  if(a.get_s().compare(b.get_s()))
     return 0;
  if(a.get_t().compare(b.get_t()))
     return 0;
  if(a.get_u().compare(b.get_u()))
     return 0;
  if(a.get_v().compare(b.get_v()))
     return 0;

  return 1;
}
void multiply(matrix_GL2Z & c, const matrix_GL2Z & a, const matrix_GL2Z & b)
{
  bigint tmp1, tmp2, cs, ct, cu, cv;

  multiply(tmp1, a.s, b.s);
  multiply(tmp2, a.u, b.t);
  add(cs, tmp1, tmp2);

  multiply(tmp1, a.s, b.u);
  multiply(tmp2, a.u, b.v);
  add(cu, tmp1, tmp2);

  multiply(tmp1, a.t, b.s);
  multiply(tmp2, a.v, b.t);
  add(ct, tmp1, tmp2);

  multiply(tmp1, a.t, b.u);
  multiply(tmp2, a.v, b.v);
  add(cv, tmp1, tmp2);

  c.s.assign(cs);
  c.t.assign(ct);
  c.u.assign(cu);
  c.v.assign(cv);

  c.determinant = a.determinant * b.determinant;
}

void divide(matrix_GL2Z & c, const matrix_GL2Z & a, const matrix_GL2Z & b)
{
  matrix_GL2Z inv_b(b);
  inv_b.invert();
  multiply(c, a, inv_b);
}

matrix_GL2Z inverse(const matrix_GL2Z & a)
{
  matrix_GL2Z inv_a(a);
  inv_a.invert();
  return inv_a;
}

matrix_GL2Z operator * (const matrix_GL2Z & a, const matrix_GL2Z & b)
{
  matrix_GL2Z U;
  multiply(U,a,b);
  return U;
}

matrix_GL2Z operator /(const matrix_GL2Z & a, const matrix_GL2Z & b)
{
  matrix_GL2Z U;
  divide(U,a,b);
  return U;
}


ostream & operator << (ostream & out, const matrix_GL2Z & a)
{
  char ss[1024], st[1024], su[1024], sv[1024];
  int ls, lt, lu, lv, max, i;

  ls = bigint_to_string(a.s, ss);
  max = ls;
  lt = bigint_to_string(a.t, st);
  max = (max > lt) ? max : lt;
  lu = bigint_to_string(a.u, su);
  max = (max > lu) ? max : lu;
  lv = bigint_to_string(a.v, sv);
  max = (max > lv) ? max : lv;

  out << "   [";
  for (i = ls; i < max; i++)
    out << " ";
  out << a.s;
  out << " ";
  for (i = lu; i < max; i++)
    out << " ";
  out << a.u << "]\n";

  out << "   [";
  for (i = lt; i < max; i++)
    out << " ";
  out << a.t;
  out << " ";
  for (i = lv; i < max; i++)
    out << " ";
  out << a.v << "]\n";

  return out;
}

istream & operator >> (istream & in, matrix_GL2Z & A)
{	
  debug_handler("matrix_GL2Z", "operator>>");
  char c;

  in >> c;
  if (c != '[')
    error_handler("matrix_GL2Z", "operator>>::[ expected");
  in >> A.s;
  in >> A.u;
  do{
    in >> c;
  }while (isspace(c));
  if (c != ']')
    error_handler("matrix_GL2Z", "operator>>::] expected");
  do{
    in >> c;
  }while (isspace(c));
  if (c != '[')
    error_handler("matrix_GL2Z", "operator>>::[ expected");
  in >> A.t;
  in >> A.v;
  do{
    in >> c;
  }while (isspace(c));
  if (c != ']')
    error_handler("matrix_GL2Z", "operator>>::] expected");

  bigint d_2, sv, tu;
  multiply(sv, A.s, A.v);
  multiply(tu, A.t, A.u);
  subtract(d_2, sv, tu);
  if (d_2.intify(A.determinant) || (abs(A.determinant) != 1))
    error_handler("matrix_GL2Z", "abs(det)!=1::not a valid GL2Z matrix");

  return in;
}
