//
// LiDIA - a library for computational number theory
//   Copyright (c) 1996 by the LiDIA Group
//
// File        : galois_field.c
// Author      : Detlef Anton (DA), Thomas Pfahler (TPf)
// Last change : DA, Jan 10 1996, initial version
//               TPf, Jul 1 1996, added reference counting, minor changes
//

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


/**
** private funktions
**/

void galois_field::inc_ref_counter() const
{
    LIDIA_MUT(galois_field, this)->ref_counter++;
    debug_handler_c("galois_field", "inc_ref_counter()", 0,
	cout<<ref_counter<<" references to "<<*this<<endl; );
}

void galois_field::dec_ref_counter() const
{
    LIDIA_MUT(galois_field, this)->ref_counter--;
    debug_handler_c("galois_field", "dec_ref_counter()", 0,
	cout<<ref_counter<<" references to "<<*this<<endl;);
    if (ref_counter < 0)
	lidia_error_handler_c("galois_field", "dec_ref_counter :: ref_counter < 0",
	cout<<"class galois_field, element "<<*this<<" :\n";);
}

void galois_field::init_p_pow_n_minus_1() const
{ 
    if ( !(LIDIA_MUT(galois_field, this)->p_pow_n_minus_1.is_prime_factorization()) ) 
    { 
	/*   p-1 divides p^n-1 for n>1   */
	if (n > 1)  LIDIA_MUT(galois_field, this)->p_pow_n_minus_1.refine(p-1);

	LIDIA_MUT(galois_field, this)->p_pow_n_minus_1.factor();
	if ( !(LIDIA_MUT(galois_field, this)->p_pow_n_minus_1.is_prime_factorization()) )
	    lidia_error_handler("galois_field", "init_p_pow_n_minus_1: factorization failed");
    }
}

/**
** constructors and destructor
**/

galois_field::galois_field() : n(0), ref_counter(0)
{ }
 
galois_field::galois_field(const bigint & characteristic, lidia_size_t deg) : ref_counter(0)
{ 
    if ( characteristic < 2 )
	lidia_error_handler("galois_field", "galois_field: characteristic must be greater then one");
    if ( deg < 1 )
	lidia_error_handler("galois_field", "galois_field: degree must be greater then zero");
  
    p.assign(characteristic);
    n = deg;
    power(p_pow_n, p, n);
    bigint c(p_pow_n);
    dec(c);
    p_pow_n_minus_1.assign(c);
}


galois_field::galois_field(const bigint & characteristic, lidia_size_t deg,
       const rational_factorization &fact) : ref_counter(0)
{ 
    if ( characteristic < 2 )
	lidia_error_handler("galois_field", "galois_field: characteristic must be greater then one");
    if ( deg < 1 )
	lidia_error_handler("galois_field", "galois_field: degree must be greater then zero");

    p.assign(characteristic);
    n = deg;
    p_pow_n_minus_1.assign(fact);
    power(p_pow_n, p, n);
}


galois_field::galois_field(const rational_factorization & fact) : ref_counter(0)
{
    bigint factor;
    register lidia_size_t i, end;
    p_pow_n.assign(1);
    end = fact.no_of_comp();
    for (i=0; i<end; i++)
    {
	power(factor, fact.base(i), (long) fact.exponent(i));
	multiply(p_pow_n, p_pow_n, factor);
    }
    p_pow_n++;
    bigint ah;
    long tmp = 1, tmp2 = 1;
    int pr;
    if (is_prime(p_pow_n, 8))
	ah.assign(p_pow_n);
    else
    { 
	tmp = power_test(ah, p_pow_n);
	if (tmp != -1)
	{ 
	    pr = is_prime(ah, 8);
	    while ((!pr) && (tmp2 != -1))
	    { 
		tmp2 = power_test(factor, ah);
		if (tmp2 != -1)
		{ 
		    tmp *= tmp2;
		    ah.assign(factor);
		    pr = is_prime(ah,8);
		}
	    }
	}
	else
	    lidia_error_handler("galois_field", "galois_field: bad factorization");
    }
    p.assign(ah);
    n = (int)tmp;
    p_pow_n_minus_1.assign(fact);
}


galois_field::galois_field(const galois_field & K) : 
	p(K.p), n(K.n), 
	p_pow_n(K.p_pow_n), 
	p_pow_n_minus_1(K.p_pow_n_minus_1),
	ref_counter(0)
{ }

galois_field::~galois_field()
{ 
    debug_handler_c("galois_field", "destructor", 0,
	cout<<*this<<endl<<"has "<<ref_counter<<" references"<<endl;);
    if (ref_counter != 0)
	lidia_error_handler_c("galois_field","destructor::element still in use",
	cout<<"class galois_field, element "<<*this<<" :\n";);
}

/**
** access functions
**/

const bigint & galois_field::characteristic() const
{ 
    return p; 
}

const lidia_size_t & galois_field::degree() const
{ 
    return n; 
}

const bigint & galois_field::number_of_elements() const
{ 
    return p_pow_n;
}

const rational_factorization & galois_field::factorization_of_mult_order() const
{ 
    init_p_pow_n_minus_1();
    return p_pow_n_minus_1;
}

/**
** assignment
**/

void galois_field::assign(const galois_field & K)
{ 
    if (ref_counter != 0)
	lidia_error_handler("galois_field","assign( galois_field& )::field still in use");
    p.assign(K.p);
    n = K.n;
    p_pow_n_minus_1.assign(K.p_pow_n_minus_1);
    p_pow_n.assign(K.p_pow_n);
}

/**
** input / output
**/

istream & operator >> (istream & in, galois_field & K)
{
    if (K.ref_counter != 0)
	lidia_error_handler("galois_field","assign( galois_field& )::field still in use");
  char c;
  in >> c;
  while ( c == ' ' ) in >> c;
  if ( c != '(' )
  { lidia_error_handler("galois_field", ">>: '(' expected");
  }
  in >> K.p;
  in >> c;
  while ( c == ' ' ) in >> c;
  if ( c != ',' )
  { lidia_error_handler("galois_field", ">>: ',' expected");
  }
  in >> K.n;
  in >> c;
  while ( c == ' ' ) in >> c;
  if ( c != ')' )
  { lidia_error_handler("galois_field", ">>: ')' expected");
  }
  power(K.p_pow_n, K.p, K.n);
  K.p_pow_n_minus_1.assign(K.p-1);
  K.init_p_pow_n_minus_1();
  return in;
}


ostream & operator << (ostream & out, const galois_field & K)
{ out << "(" << K.p << ", " << K.n << ")";
  return out;
}

bigint galois_field::greatest_divisor_of_1_coprime_to_2(const bigint & a, const bigint & b)
{
    bigint al(a);
    bigint bl(b);
    while ( !(bl.is_one()) )
    {
	bl.assign(gcd(al, bl));
	divide(al, al, bl);
    }
    return al;
}
