//
// LiDIA - a library for computational number theory
//   Copyright (c) 1996 by the LiDIA Group
//
// File        : gf_p_base.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:gf_p_base.h>
#else
#include <LiDIA/gf_p_base.h>
#endif

void gf_p_base::inc_ref_counter() const
{ 
    LIDIA_MUT(gf_p_base, this)->ref_counter++;
//#####cout<<"gf_p_base: "<<*this<<"  *"<<ref_counter<<endl;
}

void gf_p_base::dec_ref_counter() const
{
     LIDIA_MUT(gf_p_base, this)->ref_counter--;
//#####cout<<"gf_p_base: "<<*this<<"  *"<<ref_counter<<endl;

    if (ref_counter < 0)
        lidia_error_handler_c("gf_p_base","dec_ref_counter :: ref_counter < 0)",
	cout<<"class gf_p_base, element "<<*this<<" :\n";);
}

/**
** constructors and destructor
**/

gf_p_base::gf_p_base() : gf_ptr(NULL), pol_mod(), ref_counter(0)
{ }

gf_p_base::gf_p_base(const galois_field & K) : gf_ptr(&K), ref_counter(0)
{ 
    gf_ptr->inc_ref_counter();

    Fp_polynomial tmp;
    build_irred(tmp, K.characteristic(), K.degree());
    pol_mod.build(tmp);
}

gf_p_base::gf_p_base(const galois_field & K, const Fp_polynomial & pol) : 
	gf_ptr(& K), pol_mod(pol), ref_counter(0)
{ 
    gf_ptr->inc_ref_counter();

    if ( pol.degree() != K.degree() )
	lidia_error_handler("gf_p_base", "gf_p_base: degree of polynomial is not correct");
    if ( pol.modulus() != K.characteristic() )
	lidia_error_handler("gf_p_base", "gf_p_base: modulus of polynomial is not correct");
    if ( !pol.is_monic() )
	lidia_error_handler("gf_p_base", "gf_p_base: polynomial is not monic");
}

gf_p_base::gf_p_base(const gf_p_base & PB) :
	gf_ptr(PB.gf_ptr), pol_mod(PB.pol_mod.modulus()), ref_counter(0)
{
    gf_ptr->inc_ref_counter();
}

gf_p_base::~gf_p_base()
{ 
    if (ref_counter != 0)
	lidia_error_handler_c("gf_p_base","destructor :: element still in use",
	cout<<"class gf_p_base, element "<<*this<<" :\n";);
    gf_ptr->dec_ref_counter();
}

/**
** access functions
**/

const galois_field& gf_p_base::field() const
{ 
    if (!gf_ptr)
	lidia_error_handler("gf_p_base","field()::no field defined");
    return *gf_ptr;
}

const Fp_polynomial & gf_p_base::irred_polynomial() const
{ return pol_mod.modulus(); } 

/**
** assignments
**/

const gf_p_base & gf_p_base::operator = (const gf_p_base & PB)
{ 
    if (this != &PB)
	assign(PB);
    return *this;
}

void gf_p_base::assign(const gf_p_base & PB)
{
    if (PB.gf_ptr == NULL)
	lidia_error_handler("gf_p_base","assign(gf_p_base&)::argument is not initialized");
    if (ref_counter != 0)
	lidia_error_handler("gf_p_base",
	"assign(gf_p_base&)::element still in use");
    if (gf_ptr != PB.gf_ptr)
    {
	if (gf_ptr != NULL) gf_ptr->dec_ref_counter();
	gf_ptr = PB.gf_ptr;
	gf_ptr->inc_ref_counter();
    }
    pol_mod.build(PB.pol_mod.modulus());
}

/**
** high level functions
**/

gf_p_element gf_p_base::gen_primitive_elem() const
{
    if (!gf_ptr)
	lidia_error_handler("gf_p_base",
	"gen_primitive_elem()::no field	defined");

    bigint a, b, e, f, g, q, A, B, Bl;
    gf_p_element x   (*this);
    gf_p_element y   (*this);
    gf_p_element z   (*this);
    gf_p_element h   (*this);
    gf_p_element pow (*this);

    do
	x.randomize();
    while ( x.is_zero() );
    e.assign(x.compute_order());

    q.assign(gf_ptr->number_of_elements());
    q--;
    if ( e == q )
	return x;	/*   e.g. if (gf_ptr->number_of_elements() == 2)   */

    for (;;)
    { 
	do
	{
	    y.randomize();
	    power(pow, y, e);
	}
	while (pow.is_one() || pow.is_zero());
	f.assign(y.compute_order());
	if ( f == q )
	    return y; 

	g.assign(lcm(e, f));
	divide(a, f, gcd(e, f));
	A.assign(galois_field::greatest_divisor_of_1_coprime_to_2(e, a));
	divide(a, e, gcd(e, f));
	Bl.assign(galois_field::greatest_divisor_of_1_coprime_to_2(f, a));
	divide(B, Bl, gcd(A, Bl));
	divide(a, e, A);
	divide(b, f, B);
	power(h, y, b);
	power(z, x, a);
	multiply(z, z, h);
	x.assign(z);
	e.assign(g);
	if ( e == q )
	    return x; 
    }
    
// this point will never be reached...
    lidia_error_handler("gf_p_base", "gen_primitive_elem: no primitive element found");
    return x;
}

gf_p_element gf_p_base::gen_free_elem() const
{ 
    gf_p_element x(*this);
    do 
	x.randomize();
    while (!x.is_free_element());
    return x;
}

gf_p_element gf_p_base::gen_free_primitive_elem() const
{ 
    gf_p_element h(*this);
    h.assign(gen_primitive_elem());
    while (!(h.is_free_element()))
	h.assign(gen_primitive_elem());
    return h;
}

/**
** input / output
**/

istream & operator >> (istream & in, gf_p_base & PB)
{
  if (PB.ref_counter != 0)
    lidia_error_handler("gf_p_base", "operator >>: element still in use");

  if (PB.gf_ptr == NULL)
  { lidia_error_handler("gf_p_base", "operator >>: no field defined");
  }
  Fp_polynomial tmp;
  in >> tmp;
  if (!tmp.is_monic() || tmp.degree()!=PB.gf_ptr->degree() ||
	tmp.modulus()!=PB.gf_ptr->characteristic())
    lidia_error_handler("gf_p_base", "operator >>: bad input polynomial");
  PB.pol_mod.build(tmp);
  return in;
}

ostream & operator << (ostream & out, const gf_p_base & PB)
{ out << PB.pol_mod.modulus() << endl;
  return out;
}

