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

#ifndef LIDIA_GF_P_ELEMENT__H
#define LIDIA_GF_P_ELEMENT__H

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


class gf_p_base;
template< class T > class gf_element;




class field_ref
{
// This class is "a clever pointer" to an object of class gf_p_base
// because it takes care about incrementing/decrementing reference counters.
// It additionally provides a frame for computations within a finite field
// defined by an instance of class gf_p_base.
// It is heavily used in class gf_p_element and in
// class gf_polynomial (= polynomial<gf_p_element>)

    const gf_p_base *gf_p_base_ptr;

 public:
    field_ref() : gf_p_base_ptr(NULL)
    { }
    
    field_ref(const field_ref &f);
    
    field_ref(const gf_p_base & PB);

    ~field_ref();
    
    void assign(const field_ref &f);
    void assign(const gf_p_base &pb);

    const gf_p_base &base() const
    {   if (gf_p_base_ptr == NULL)
	    lidia_error_handler("field_ref/gf_p_base", "no field defined");
	return *gf_p_base_ptr;   }

    bool operator == (const field_ref &f) const
	{ return gf_p_base_ptr == f.gf_p_base_ptr; }
    bool operator != (const field_ref &f) const
	{ return gf_p_base_ptr != f.gf_p_base_ptr; }
    bool is_undefined() const
	{ return (gf_p_base_ptr == NULL); }

    void swap(field_ref &f)
    {   const gf_p_base *tmp = gf_p_base_ptr;
	gf_p_base_ptr = f.gf_p_base_ptr;
	f.gf_p_base_ptr = tmp;   }


private:
    static const gf_p_base *ptr_for_frame;
    static int frame_counter;
public:
    static const gf_p_base *current_field()
	{ return ptr_for_frame; }  //returns NULL if no field defined

    static void enter_frame(const gf_p_base &b)
    {   if (frame_counter++ == 0)
	    ptr_for_frame = &b;
	else if (ptr_for_frame != &b) lidia_error_handler("field_ref/gf_p_base",
			"enter_frame::different fields");   }

    static void exit_frame()
    {   if (--frame_counter == 0)
	    ptr_for_frame = NULL;
	if (frame_counter<0) lidia_error_handler("field_ref/gf_p_base",
			"exit_frame()::frame_counter < 0");   }
};






class gf_p_element : public gf_element<gf_p_element >
{
    
  /**
  ** the C++ type we use to represent an Element 
  ** in a Polynomial Base of a Galois field
  **/

    field_ref      ffield;
    Fp_polynomial  pol_rep;

    static const gf_p_base *get_field_from_frame();
//    friend gf_element<gf_p_element >;
 
  public:

  /**
  ** constructors and destructor
  **/

    gf_p_element();
    gf_p_element(const gf_p_base & PB);
    gf_p_element(const gf_p_element & elem);
    gf_p_element(int);	//not allowed in general -> runtime error
    ~gf_p_element();

  /**
  ** access functions
  **/

    const gf_p_base &            base()   const;
    const Fp_polynomial &        polynomial_rep() const;
    math_vector<multi_bigmod>    vector() const;

  /**
  ** assignments
  **/

    void set_polynomial(const Fp_polynomial & pol);
    void set_vector(const math_vector<multi_bigmod> & vector);
    void set_base(const gf_p_base & p);
    void assign_zero();
    void assign_zero(const gf_p_base &);
    void assign_one();
    void assign_one(const gf_p_base &);
    const gf_p_element & operator = (const gf_p_element & elem);
    const gf_p_element & operator = (int i);	
	//not allowed in general -> runtime error
	
    void assign(const gf_p_element & a);

  /**
  ** arithmetical operations
  **/
  friend gf_p_element operator - (const gf_p_element & a);
  friend gf_p_element operator + (const gf_p_element & a, const gf_p_element & b);
  friend gf_p_element operator + (const multi_bigmod & a, const gf_p_element & b);
  friend gf_p_element operator + (const gf_p_element & a, const multi_bigmod & b);
  friend gf_p_element operator - (const gf_p_element & a, const gf_p_element & b);
  friend gf_p_element operator - (const multi_bigmod & a, const gf_p_element & b);
  friend gf_p_element operator - (const gf_p_element & a, const multi_bigmod & b);
  friend gf_p_element operator * (const gf_p_element & a, const gf_p_element & b);
  friend gf_p_element operator * (const multi_bigmod & a, const gf_p_element & b);
  friend gf_p_element operator * (const gf_p_element & a, const multi_bigmod & b);
  friend gf_p_element operator / (const gf_p_element & a, const gf_p_element & b);
  friend gf_p_element operator / (const multi_bigmod & a, const gf_p_element & b);
  friend gf_p_element operator / (const gf_p_element & a, const multi_bigmod & b);

  gf_p_element & operator += (const gf_p_element & a);
  gf_p_element & operator += (const multi_bigmod & a);
  gf_p_element & operator -= (const gf_p_element & a);
  gf_p_element & operator -= (const multi_bigmod & a);
  gf_p_element & operator *= (const gf_p_element & a);
  gf_p_element & operator *= (const multi_bigmod & a);
  gf_p_element & operator /= (const gf_p_element & a);
  gf_p_element & operator /= (const multi_bigmod & a);

  /**
  ** procedural versions
  **/

  friend void add     (gf_p_element & c, const gf_p_element & a, const gf_p_element & b);
  friend void add     (gf_p_element & c, const multi_bigmod & a, const gf_p_element & b);
  friend void add     (gf_p_element & c, const gf_p_element & a, const multi_bigmod & b);
  friend void subtract(gf_p_element & c, const gf_p_element & a, const gf_p_element & b);
  friend void subtract(gf_p_element & c, const multi_bigmod & a, const gf_p_element & b);
  friend void subtract(gf_p_element & c, const gf_p_element & a, const multi_bigmod & b);
  friend void multiply(gf_p_element & c, const gf_p_element & a, const gf_p_element & b);
  friend void multiply(gf_p_element & c, const multi_bigmod & a, const gf_p_element & b);
  friend void multiply(gf_p_element & c, const gf_p_element & a, const multi_bigmod & b);
  friend void divide  (gf_p_element & c, const gf_p_element & a, const gf_p_element & b);
  friend void divide  (gf_p_element & c, const multi_bigmod & a, const gf_p_element & b);
  friend void divide  (gf_p_element & c, const gf_p_element & a, const multi_bigmod & b);

         void         negate();
  friend void         negate(gf_p_element & a, const gf_p_element & b);
         void         invert();
  friend void         invert(gf_p_element & a, const gf_p_element & b);
  friend gf_p_element inverse(const gf_p_element & a);
  friend void         square(gf_p_element & a, const gf_p_element & b);
         void         power(const gf_p_element & a, const bigint & e);
  friend void         power(gf_p_element & c, const gf_p_element & a, const bigint & e);
  friend void         pth_power(gf_p_element & c, const gf_p_element & a, lidia_size_t e);

  /**
  ** comparisons
  **/

  friend bool operator == (const gf_p_element & a, const gf_p_element & b);
  friend bool operator != (const gf_p_element & a, const gf_p_element & b);
  
  friend bool operator == (const gf_p_element & a, int i);
    //not allowed in general -> runtime error
  friend bool operator != (const gf_p_element & a, int i);
    //not allowed in general -> runtime error
  
  bool is_zero() const;
  bool is_one()  const;

  /**
  ** basic functions
  **/

         void randomize();
  friend void swap(gf_p_element & a, gf_p_element & b);

  /**
  ** high level functions
  **/

         bigint compute_order()               const;
  friend bigint compute_order(const gf_p_element & a);
         multi_bigmod compute_trace()                     const;
  friend multi_bigmod compute_trace(const gf_p_element & a);
         multi_bigmod compute_norm()                      const;
  friend multi_bigmod compute_norm(const gf_p_element & a);
         bool   is_primitive_element()            const;
  friend bool   is_primitive_element(const gf_p_element & a);
         bool   is_free_element()                 const;
  friend bool   is_free_element(const gf_p_element & a);

  /**
  ** functions implemented in gf_n_element
  **/

  friend void transform_n_to_p(gf_p_element & a, const gf_n_element & b);
  friend void transform_p_to_n(gf_n_element & a, const gf_p_element & b);

  /** 
  ** input / output - functions implemented in base class gf_element
  **/

    friend istream & operator >> (istream & in, gf_p_element & a);
    friend ostream & operator << (ostream & out, const gf_p_element & a);
};




#endif


