#ifndef _GF2N_H
#define _GF2N_H

#include <LiDIA/random.h>
#include <LiDIA/gf2nIO.h>
#include <LiDIA/lidia.h>
#include <LiDIA/bigint.h>
#include <LiDIA/rational_factorization.h>

const unsigned int FIXMUL = 9;  /* number of multiplication routines */
typedef unsigned short gf2n_bit16;
typedef unsigned char  gf2n_bit8;

class gf2n
{
private:

  static unsigned int anzBI;     /* number of udigits for each gf2n   */
  static unsigned int degree;    /* degree of extension over F_2 */
  static rational_factorization ord;  /* factorization of group order or 1 */
  static bool ord_is_fact;      /* true, if ord is set, false otherwise */
  static unsigned int *exponents; /* array of exponents with 1 coefficients */
                                  /* of generating polynomial, excluded 
				     degree and 0                          */
  static unsigned int exp1, exp2, exp3; /* for storing the exponents of    */
                                        /* 3/5-nomials */
  static unsigned int anz_exponents;  /* number of exponents */
  static unsigned int mulsel;         /* Multiplication-selector */
  static unsigned int invsel;         /* Inversion and Reduction selector */
  udigit *element;                    /* array representing element */

  
  /* ============================================================= */
  /*  PRIVATE FRIEND  & STATIC FUNCTIONS, USED IN ARITHMETIC       */
  /* ============================================================= */
 
  inline bool is_reduced() const
  {
    return ((element[anzBI-1] >> (degree % (8*sizeof(udigit)))) == (udigit) 0);
  }
  
  friend void karatsuba_mul (udigit *, udigit *, udigit*);
  friend void square(udigit*, udigit*); 
  friend void tri_partial_reduce1(udigit*);
  friend void pent_partial_reduce1(udigit*);
  friend void general_partial_reduce1(udigit*);
  friend void tri_partial_reduce2(udigit*);
  friend void pent_partial_reduce2(udigit*);
  friend void general_partial_reduce2(udigit*); 
  friend void tri_invert(udigit*, udigit*);
  friend void pent_invert(udigit*, udigit*);
  friend void general_invert(udigit*, udigit*);

  static void generate_mul_table();
  static void generate_square_table();
  static udigit *tmp_double;
  
  static char* read_modulus_from_database(unsigned int);


public:

  /* ===================================================== */
  /*            FRIENDS                                    */
  /* ===================================================== */

  friend class gf2nIO;

  /* ===================================================== */
  /*            INITIALISATION                             */
  /* ===================================================== */


  friend void gf2n_init(char *, gf2nIO::base = gf2nIO::Dec);

  friend void gf2n_init(unsigned int, gf2nIO::base = gf2nIO::Dec); 

  static unsigned int extension_degree() 
       { return degree; }
    

   /* ===================================================== */
   /*            CONSTRUCTORS, DESTRUCTOR                   */
   /* ===================================================== */



   gf2n();
   
   gf2n(const gf2n &);

   gf2n(unsigned long);

   gf2n(const bigint &);

   ~gf2n();

  /* ===================================================== */
  /*               ASSIGNMENTS                             */
  /* ===================================================== */
  
  
  gf2n & operator = (const gf2n &);
  gf2n & operator = (unsigned long);
  gf2n & operator = (const bigint &);
  void gf2n::assign_zero() ;
  void gf2n::assign_one() ;
  void gf2n::assign(const gf2n &);
  friend void swap (gf2n &, gf2n &);


  
  /* ===================================================== */
  /*            COMPARISONS                                */
  /* ===================================================== */

  friend bool operator == (const gf2n &, const gf2n &);
  
  friend bool operator != (const gf2n &, const gf2n &);
  
  bool is_zero() const;
  
  bool is_one() const;
  
  
  /* ===================================================== */
  /*               IO                                      */
  /* ===================================================== */
  
  
  friend istream & operator >> (istream &, gf2n &);
  
  friend ostream & operator << (ostream &, const gf2n &);
  
  
  /* ===================================================== */
  /*               ARITHMETIC                              */
  /* ===================================================== */
  
  
  friend gf2n operator * (const gf2n &, const gf2n &);
  
  friend gf2n operator + (const gf2n &, const gf2n &);
  
  friend gf2n operator - (const gf2n &, const gf2n &);
  
  friend gf2n operator / (const gf2n &, const gf2n &);
  
  
  gf2n & operator += (const gf2n &);
  
  gf2n & operator -= (const gf2n &);
  
  gf2n & operator *= (const gf2n &);
  
  gf2n & operator /= (const gf2n &);

  friend void add(gf2n &, const gf2n &, const gf2n &);

  friend void subtract(gf2n &, const gf2n &, const gf2n &);

  friend void multiply(gf2n &, const gf2n &, const gf2n &);
  
  friend void square(gf2n &, const gf2n &);
  
  friend void divide(gf2n &, const gf2n &, const gf2n &);
  
  friend void power(gf2n &, const gf2n &, long); 
  
  friend void power(gf2n &, const gf2n &, const bigint &); 
  
  friend void sqrt(gf2n &, const gf2n &);
  
  friend gf2n sqrt(const gf2n &);

  friend void randomize(gf2n &);

  void gf2n::invert();
  
  friend void invert (gf2n &, const gf2n &);
  
  friend gf2n inverse(const gf2n &);
  
  friend bigint compute_order(const gf2n &);
  
  friend gf2n get_generator();
  
  friend udigit hash(const gf2n &);
  
};



/**
** INLINE PROCEDURAL VERSIONS
* ====================================================================== */


extern void ( *gf2nmul[] ) (udigit*, udigit*, udigit*);
extern void ( *partial_reduce1[]) (udigit *);
extern void ( *partial_reduce2[]) (udigit *);
extern void ( *uinvert[]) (udigit *, udigit *);



/**
** MULTIPLICATION
* ---------------------------------------------------------------------- */

inline void multiply(gf2n & c, const gf2n & a, const gf2n & b)
{
  register unsigned int i;

  for (i=0; i < 2*gf2n::anzBI; i++)  
     gf2n::tmp_double[i] = (udigit) 0; 

  if (a.element == b.element)
    {
      square(gf2n::tmp_double, a.element);
      partial_reduce1[gf2n::invsel](gf2n::tmp_double);
    }
  else
    {
      gf2nmul[gf2n::mulsel](gf2n::tmp_double, a.element, b.element);
      partial_reduce1[gf2n::invsel](gf2n::tmp_double);
    } 
  for (i=0; i < gf2n::anzBI; i++)
    c.element[i] = gf2n::tmp_double[i];
}

/**
** SQUARING
* ---------------------------------------------------------------------- */

inline void square(gf2n & c, const gf2n & a)
{
  square(gf2n::tmp_double, a.element);
  partial_reduce1[gf2n::invsel](gf2n::tmp_double);
  for (register unsigned i=0; i < gf2n::anzBI; i++)
    c.element[i] = gf2n::tmp_double[i];
}


/**
 **   INVERSION & DIVISION
 * ---------------------------------------------------------------------- */

inline void invert (gf2n & b, const gf2n & a)
{
 if (a.is_one () == true)
   {
     b.assign_one ();
   }
 else
   {
     if (a.is_zero () == true)
       lidia_error_handler("gf2n","invert(gf2n&, const gf2n&)::zero not invertible\n");
 
     uinvert[gf2n::invsel](b.element, a.element);
   }
}




inline void divide(gf2n & c, const gf2n & a, const gf2n & b)
{
  gf2n d;
  ::invert(d, b); // MM
  multiply(c, a, d);
}



/**
** ADDITION
* ---------------------------------------------------------------------- */

inline void add(gf2n & c, const gf2n & a, const gf2n & b)
{
  register unsigned int i;
  register udigit *cp, *ap, *bp;

  for (i=0, ap=a.element, bp=b.element, cp=c.element; 
       i < gf2n::anzBI; i++, cp++, ap++, bp++) 
    *cp = (*ap) ^ (*bp);
}

/**
** SUBTRACTION
* ---------------------------------------------------------------------- */

inline void subtract(gf2n & c, const gf2n & a, const gf2n & b)
{
   add(c, a, b);
}


#endif

