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

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


/**
** class field_ref
**/

const gf_p_base *field_ref::ptr_for_frame = NULL;
int field_ref::frame_counter = 0;


field_ref::field_ref(const field_ref &f)
{
    gf_p_base_ptr = f.gf_p_base_ptr;
    if (gf_p_base_ptr != NULL) gf_p_base_ptr->inc_ref_counter();
}

field_ref::field_ref(const gf_p_base & PB)
{
    gf_p_base_ptr = &PB;
    gf_p_base_ptr->inc_ref_counter();
}

field_ref::~field_ref()
{
    if (gf_p_base_ptr != NULL) gf_p_base_ptr->dec_ref_counter();
}

void field_ref::assign(const field_ref &f)
{
    if (gf_p_base_ptr != f.gf_p_base_ptr)
    {
	if (gf_p_base_ptr != NULL) gf_p_base_ptr->dec_ref_counter();
	gf_p_base_ptr = f.gf_p_base_ptr;
	if (gf_p_base_ptr != NULL) gf_p_base_ptr->inc_ref_counter();
    }
}

void field_ref::assign(const gf_p_base &pb)
{
    if (gf_p_base_ptr != &pb)
    {
	if (gf_p_base_ptr != NULL) gf_p_base_ptr->dec_ref_counter();
	gf_p_base_ptr = &pb;
	if (gf_p_base_ptr != NULL) gf_p_base_ptr->inc_ref_counter();
    }
}


// ****************** class gf_p_element ************************

/**
** constructors and destructor
**/

gf_p_element::gf_p_element()
{ 
    const gf_p_base *tmp = field_ref::current_field();
    if (tmp)
	set_base(*tmp);
}

gf_p_element::gf_p_element(const gf_p_base & PB)
{ 
    set_base(PB);
}

gf_p_element::gf_p_element(const gf_p_element & elem) :
	ffield(elem.ffield),
	pol_rep(elem.pol_rep)
{ }

gf_p_element::gf_p_element(int i)
{
    const gf_p_base *tmp = field_ref::current_field();
    if (!tmp)
	lidia_error_handler("gf_p_element","gf_p_element(int)::not allowed");
    set_base(*tmp);
    pol_rep.assign(i);
}

gf_p_element::~gf_p_element()
{ }

/**
** access functions
**/

const gf_p_base & gf_p_element::base() const
{ return ffield.base(); }

const Fp_polynomial & gf_p_element::polynomial_rep() const
{ return pol_rep; }


math_vector<multi_bigmod> gf_p_element::vector() const
{
    lidia_size_t i, n = base().field().degree();
    math_vector<multi_bigmod> vec(n,n);
    const bigint &p = base().field().characteristic();
    for (i = 0; i < n; i++)
	vec[i].assign(pol_rep[i], p);
    return vec;
}


/**
** assignments
**/

void gf_p_element::set_polynomial(const Fp_polynomial & pol)
{
    remainder(pol_rep, pol, base().pol_mod);
}

void gf_p_element::set_vector(const math_vector<multi_bigmod> & vector)
{ 
    lidia_size_t i, n = base().field().degree();
    if ( (vector.size()) !=  n )
	lidia_error_handler("gf_p_element", "set_vector: vector has wrong size");

    const bigint &p = base().field().characteristic();
    for (i = n-1; i >= 0; i--)
    {
	if (vector[i].modulus() != p)
	    lidia_error_handler("gf_p_element", "set_vector: wrong moduli");
	pol_rep.set_coefficient(vector[i].mantissa(), i);
    }
}


void gf_p_element::set_base(const gf_p_base &PB)
{
    ffield.assign(PB);
    pol_rep.set_modulus(PB.field().characteristic());
}

void gf_p_element::assign_zero()
{
    if (ffield.is_undefined())
	lidia_error_handler("gf_p_element","assign_zero()::no field defined");
    pol_rep.assign_zero();
}

void gf_p_element::assign_one()
{ 
    if (ffield.is_undefined())  
	lidia_error_handler("gf_p_element","assign_one()::no field defined");
    pol_rep.assign_one();
}

void gf_p_element::assign_zero(const gf_p_base &b)
{ 
    set_base(b);
    pol_rep.assign_zero();
}

void gf_p_element::assign_one(const gf_p_base &b)
{ 
    set_base(b);
    pol_rep.assign_one();
}

void gf_p_element::assign(const gf_p_element & a)
{ 
    ffield.assign(a.ffield);
    pol_rep.assign(a.pol_rep);
}

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

const gf_p_element & gf_p_element::operator = (int i)
{
    const gf_p_base *tmp = field_ref::current_field();
    if (!tmp)
	lidia_error_handler("gf_p_element","operator=(int)::not allowed");
    set_base(*tmp);
    pol_rep.assign(i);
    return *this;
}

/**
** arithmetical operations
**/

gf_p_element operator - (const gf_p_element & a)
{
    gf_p_element h(a);
    h.negate();
    return h;
}

gf_p_element operator + (const gf_p_element & a, const gf_p_element & b)
{ 
    gf_p_element h;
    add(h, a, b);
    return h;
}

gf_p_element operator + (const multi_bigmod & a, const gf_p_element & b)
{ 
    gf_p_element h;
    add(h, a, b);
    return h;
}

gf_p_element operator + (const gf_p_element & a, const multi_bigmod & b)
{ 
    gf_p_element h;
    add(h, a, b);
    return h;
}

gf_p_element operator - (const gf_p_element & a, const gf_p_element & b)
{ 
    gf_p_element h;
    subtract(h, a, b);
    return h;
}

gf_p_element operator - (const multi_bigmod & a, const gf_p_element & b)
{ 
    gf_p_element h;
    subtract(h, a, b);
    return h;
}

gf_p_element operator - (const gf_p_element & a, const multi_bigmod & b)
{ 
    gf_p_element h;
    subtract(h, a, b);
    return h;
}

gf_p_element operator * (const gf_p_element & a, const gf_p_element & b)
{ 
    gf_p_element h;
    multiply(h, a, b);
    return h;
}

gf_p_element operator * (const multi_bigmod & a, const gf_p_element & b)
{ 
    gf_p_element h;
    multiply(h, a, b);
    return h;
}

gf_p_element operator * (const gf_p_element & a, const multi_bigmod & b)
{ 
    gf_p_element h;
    multiply(h, a, b);
    return h;
}

gf_p_element operator / (const gf_p_element & a, const gf_p_element & b)
{ 
    gf_p_element h;
    divide(h, a, b);
    return h;
}

gf_p_element operator / (const multi_bigmod & a, const gf_p_element & b)
{ 
    gf_p_element h;
    divide(h, a, b);
    return h;
}

gf_p_element operator / (const gf_p_element & a, const multi_bigmod & b)
{ 
    gf_p_element h;
    divide(h, a, b);
    return h;
}

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

gf_p_element & gf_p_element::operator += (const multi_bigmod & a)
{ 
    add(*this, *this, a);
    return *this;
}

gf_p_element & gf_p_element::operator -= (const gf_p_element & a)
{ 
    subtract(*this, *this, a);
    return *this;
}

gf_p_element & gf_p_element::operator -= (const multi_bigmod & a)
{ 
    subtract(*this, *this, a);
    return *this;
}

gf_p_element & gf_p_element::operator *= (const gf_p_element & a)
{ 
    multiply(*this, *this, a);
    return *this;
}

gf_p_element & gf_p_element::operator *= (const multi_bigmod & a)
{ 
    multiply(*this, *this, a);
    return *this;
}

gf_p_element & gf_p_element::operator /= (const gf_p_element & a)
{ 
    divide(*this, *this, a);
    return *this;
}

gf_p_element & gf_p_element::operator /= (const multi_bigmod & a)
{ 
    divide(*this, *this, a);
    return *this;
}


/**
** procedural versions
**/

void add(gf_p_element & c, const gf_p_element & a, const gf_p_element & b)
{ 
    if ( a.ffield != b.ffield )
	lidia_error_handler("gf_p_element", "add: different bases");
	
    c.ffield.assign(a.ffield);
    add(c.pol_rep, a.pol_rep, b.pol_rep);
}

void add(gf_p_element & c, const multi_bigmod & a, const gf_p_element & b)
{ 
    add(c, b, a);
}

void add(gf_p_element & c, const gf_p_element & a, const multi_bigmod & b)
{ 
    if (b.modulus() != a.base().field().characteristic())
	lidia_error_handler("gf_p_element", "add: different moduli");
	
    c.ffield.assign(a.ffield);
    add(c.pol_rep, a.pol_rep, b.mantissa());
}

void subtract(gf_p_element & c, const gf_p_element & a, const gf_p_element & b)
{ 
    if ( a.ffield != b.ffield )
	lidia_error_handler("gf_p_element", "subtract: different bases");

    c.ffield.assign(a.ffield);
    subtract(c.pol_rep, a.pol_rep, b.pol_rep);
}

void subtract(gf_p_element & c, const multi_bigmod & a, const gf_p_element & b)
{ 
    if (a.modulus() != b.base().field().characteristic())
	lidia_error_handler("gf_p_element", "subtract: different moduli");
	
    c.ffield.assign(b.ffield);
    negate(c.pol_rep, b.pol_rep);
    add(c.pol_rep, c.pol_rep, a.mantissa());
}

void subtract(gf_p_element & c, const gf_p_element & a, const multi_bigmod & b)
{ 
    if (b.modulus() != a.base().field().characteristic())
	lidia_error_handler("gf_p_element", "subtract: different moduli");
	
    c.ffield.assign(a.ffield);
    subtract(c.pol_rep, a.pol_rep, b.mantissa());
}

void multiply(gf_p_element & c, const gf_p_element & a, const gf_p_element & b)
{ 
    if ( a.ffield != b.ffield )
	lidia_error_handler("gf_p_element", "multiply: different bases");

    c.ffield.assign(a.ffield);
    multiply(c.pol_rep, a.pol_rep, b.pol_rep, a.base().pol_mod);
}

void multiply(gf_p_element & c, const multi_bigmod & a, const gf_p_element & b)
{ 
    multiply(c, b, a);
}

void multiply(gf_p_element & c, const gf_p_element & a, const multi_bigmod & b)
{ 
    if (b.modulus() != a.base().field().characteristic())
	lidia_error_handler("gf_p_element", "multiply: different moduli");

    c.ffield.assign(a.ffield);
    multiply_by_scalar(c.pol_rep, a.pol_rep, b.mantissa());
}

void divide(gf_p_element & c, const gf_p_element & a, const gf_p_element & b)
{ 
    if ( a.ffield != b.ffield )
	lidia_error_handler("gf_p_element", "divide: different bases");

    if (b.is_zero())
	lidia_error_handler("gf_p_element", "divide: b = 0");

    multiply(c, a, inverse(b));
}

void divide(gf_p_element & c, const multi_bigmod & a, const gf_p_element & b)
{ 
    const bigint &p = b.base().field().characteristic();
    
    if (a.modulus() != p)
	lidia_error_handler("gf_p_element", "divide: different moduli");
		    
    if (b.is_zero())
	lidia_error_handler("gf_p_element", "divide: b = 0");

    gf_p_element h(b.base());
    h.pol_rep.set_modulus(p);
    h.pol_rep.set_coefficient(a.mantissa(),0);
    multiply(c, h, inverse(b));
}

void divide(gf_p_element & c, const gf_p_element & a, const multi_bigmod & b)
{ 
    if (b.modulus() != a.base().field().characteristic())
	lidia_error_handler("gf_p_element", "divide: different moduli");

    if (b.is_zero())
	lidia_error_handler("gf_p_element", "divide: b = 0");

    c.ffield.assign(a.ffield);
    multi_bigmod h(b);
    h.invert();
    multiply(c, a, h);
}

void gf_p_element::negate()
{ 
    pol_rep.negate();
}

void negate(gf_p_element & a, const gf_p_element & b)
{ 
    a.assign(b);
    a.negate();
}

void gf_p_element::invert()
{ 
    if (is_zero())
	lidia_error_handler("gf_p_element", "invert: element is zero");

    Fp_polynomial s,t,d;
    xgcd(d, s, t, pol_rep, base().irred_polynomial());
		// a s + b t = d = gcd(a,b)

//this should never happen :
    if (!d.is_one()) lidia_error_handler("gf_p_element",
				"invert()::cannot compute inverse");

    pol_rep.assign(s);
}

void invert(gf_p_element & a, const gf_p_element & b)
{ 
    a.assign(b); 
    a.invert();
}
 
gf_p_element inverse(const gf_p_element & a)
{ 
    gf_p_element h(a);
    h.invert(); 
    return h;
}

void square(gf_p_element & a, const gf_p_element & b)
{ 
    a.ffield.assign(b.ffield);
    square(a.pol_rep, b.pol_rep, b.base().pol_mod);
}

void gf_p_element::power(const gf_p_element & a, const bigint & e)
{ 
    ::power(*this, a, e);
}

void power(gf_p_element & c, const gf_p_element & a, const bigint & e)
{ 
    if (&c != &a)
	c.set_base(a.base());		//must be set !
    element_power(c, a, e);
}

void pth_power(gf_p_element & c, const gf_p_element & a, lidia_size_t e)
{ 
    bigint q;
    const bigint &p = a.base().field().characteristic();
    power(q, p, e);
    power(c, a, q);
}

/**
** comparisons
**/

bool operator == (const gf_p_element & a, const gf_p_element & b)
{ 
    return ( ( a.ffield == b.ffield ) && ( a.pol_rep == b.pol_rep ) );
}

bool operator != (const gf_p_element & a, const gf_p_element & b)
{   return !( a == b );   }

bool operator == (const gf_p_element & a, int i)
{
    const gf_p_base *tmp = field_ref::current_field();
    if (!tmp || i<0 || i>1)
	lidia_error_handler("gf_p_element","operator==(int)::not allowed");
    switch(i)
    {
	case(0) : return a.is_zero();
	case(1) : return a.is_one();
    }
}

bool operator != (const gf_p_element & a, int i)
{   return !( a == i );   }

bool gf_p_element::is_zero() const
{
    return pol_rep.is_zero();
}

bool gf_p_element::is_one() const
{
    return pol_rep.is_one();
}

/**
** basic functions
**/

void gf_p_element::randomize()
{ 
    lidia_size_t n = base().field().degree();
    lidia_size_t i;
    for (i = n-1; i >= 0; i--)
	pol_rep.set_coefficient(::randomize(pol_rep.modulus()), i);
    pol_rep.remove_leading_zeros();
}

void swap(gf_p_element & a, gf_p_element & b)
{
    a.ffield.swap(b.ffield);
    swap(a.pol_rep, b.pol_rep);
}

/**
** high level functions
**/

bigint gf_p_element::compute_order() const
{ 
    return element_order(*this);
}

bigint compute_order(const gf_p_element & a)
{ 
    return a.compute_order();
}

multi_bigmod gf_p_element::compute_trace() const
{ 
    gf_p_element sum(*this), apk(*this);
    register lidia_size_t k, n = base().field().degree();
    const bigint &p = base().field().characteristic();
    for (k = 1; k < n; k++)
    { 
	::power(apk, apk, p);
	add(sum, sum, apk);
    }
    
//this should never happen:
    if (!(sum.pol_rep.degree() < 1))
	lidia_error_handler("gf_p_element", "compute_trace: trace not in gf(p)");
    
    multi_bigmod m(sum.pol_rep[0], p);
    return m;
}

multi_bigmod compute_trace(const gf_p_element & a)
{ 
    return a.compute_trace();
}

multi_bigmod gf_p_element::compute_norm() const
{ 
    gf_p_element h(base());
    h.assign(element_norm(*this));

//this should never happen:
    if (!(h.pol_rep.degree() < 1))
	lidia_error_handler("gf_p_element", "compute_norm: norm not in gf(p)");

    multi_bigmod m(h.pol_rep[0], base().field().characteristic());
    return m;
}

multi_bigmod compute_norm(const gf_p_element & a)
{ 
    return a.compute_norm();
}

bool gf_p_element::is_primitive_element() const
{ 
    return element_is_primitive_element(*this);
}

bool is_primitive_element(const gf_p_element & a)
{ 
    return a.is_primitive_element();
}
 
bool gf_p_element::is_free_element() const
{ 
    return element_is_free_element(*this);
}

bool is_free_element(const gf_p_element & a)
{ 
    return a.is_free_element();
}

istream & operator >> (istream & in, gf_p_element & a)
{
    char c;
    in >> c;
    if (c != '(')
	lidia_error_handler("gf_p_element","operator >>::bad input");
    in >> a.pol_rep;
    if (a.ffield.is_undefined() ||
	a.base().field().characteristic() != a.pol_rep.modulus()  ||
	a.base().field().degree() <= a.pol_rep.degree())
	lidia_error_handler("gf_p_element","operator >>::bad input");
    in >> c;
    if (c != ')')
	lidia_error_handler("gf_p_element","operator >>::bad input");
    return in;
}

ostream & operator << (ostream & out, const gf_p_element & a)
{
    out << "(" << a.pol_rep << ")";
    return out;
}
