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

#if defined(HAVE_RANDOM)
#include <stdlib.h>
#else
#if !defined(__i386) || defined(SOLARIS)
#include <LiDIA/random.h>
#endif
#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
    #include <LiDIA:random.h>
#endif
#endif

#if defined(__GNUG__) && !defined(__mips) && !defined(SOLARIS)
extern "C"   int gettimeofday(struct timeval *tp, struct timezone * );
#endif



/**
** STATIC VARIABLES
* ---------------------------------------------------------------------- */

unsigned int gf2n::anzBI;
unsigned int gf2n::degree;
rational_factorization gf2n::ord;
bool gf2n::ord_is_fact;
unsigned int *gf2n::exponents;
unsigned int gf2n::anz_exponents;
unsigned int gf2n::mulsel;
unsigned int gf2n::invsel;


udigit * gf2n::tmp_double;   /* tmp variable of length 2*gf2n::anzBI, 
			  memory allocated in gf2n_init   */


/**
** CONSTRUCTORS and DESTRUCTOR
* ---------------------------------------------------------------------- */

gf2n::gf2n ()
{
  register unsigned int i;

  element = new udigit[gf2n::anzBI];
  if (element == NULL)
    lidia_error_handler ("gf2n","gf2n()::not enough memory \n");

  for (i = 0; i < gf2n::anzBI; i++)
    element[i] = (udigit) 0;
}


gf2n::gf2n (const gf2n & a)
{
  register unsigned int i;
  element = new udigit[gf2n::anzBI];
  if (element == NULL)
    lidia_error_handler ("gf2n","gf2n(const gf2n &)::not enough memory \n");
  
  for (i = 0; i < gf2n::anzBI; i++)
    element[i] = a.element[i];
}


gf2n::gf2n (unsigned long ui)
{
  register unsigned int i;
  
  if (degree < (8*sizeof(udigit)))
    if ((unsigned long) (1<<degree) <= ui)
      lidia_error_handler ("gf2n","gf2n(unsigned long)::input out of range ");
  
  element = new udigit[gf2n::anzBI];

  if (element == NULL)
    lidia_error_handler ("gf2n","gf2n(unsigned long)::not enough memory \n");

  for (i = 1; i < gf2n::anzBI; i++)
    element[i] = (udigit) 0;

  element[0] = (udigit) ui;    
}

gf2n::gf2n (const bigint & bi)
{
  register unsigned int i;
  bigint h;

  shift_left(h, bigint(1), degree);
  if (h <= bi || bi.is_negative())
    lidia_error_handler ("gf2n","gf2n(const bigint &)::input out of rnage ");
  
  element = new udigit[gf2n::anzBI];
  
  if (element == NULL)
    lidia_error_handler ("gf2n", "gf2n(const bigint &)::not enough memory \n");
  
  h.assign(bi);
  i = 0;

  while(!h.is_zero())
    {
      element[i++] = (udigit) h.least_significant_digit();
      shift_right(h, h, 8*sizeof(udigit));
    }

  while (i< gf2n::anzBI)
    element[i++] = (udigit) 0;
}


gf2n::~gf2n ()
{
  delete[] element;
}


/**
** ASSIGNMENTS
* ---------------------------------------------------------------------- */

gf2n & gf2n::operator = (const gf2n & a)
{
for (register unsigned int i = 0; i < gf2n::anzBI; i++)
    element[i] = a.element[i];
  return (*this);
}


gf2n & gf2n::operator = (unsigned long ui)
{
  if (degree < 8*sizeof(unsigned long))
    if ((unsigned long) (1<<degree) <= ui)
      lidia_error_handler ("gf2n","operator =(unsigned long)::input out of range");

  
  element[0] = (udigit) ui;	 
  for (register unsigned int i = 1; i < gf2n::anzBI; i++)
    element[i] = (udigit) 0;

return (*this);
}


gf2n & gf2n::operator = (const bigint & bi)
{
  register unsigned int i=0;
  bigint h;

  shift_left(h, bigint(1), degree);
  if (h <= bi || bi.is_negative())
    lidia_error_handler ("gf2n","operator =(const bigint &)::input out of range ");
  
  h.assign(bi);

  while(!h.is_zero())
    {
      element[i++] = (udigit) h.least_significant_digit();
      shift_right(h, h, 8*sizeof(udigit));
    }
  
  while (i< gf2n::anzBI)
    element[i++] = (udigit) 0;
  
  return (*this);
}


void gf2n::assign_zero()
{
  for (register unsigned int i = 0; i < gf2n::anzBI; i++)
    gf2n::element[i] = (udigit) 0;
}



void gf2n::assign_one()
{
  for (register unsigned int i = 1; i < gf2n::anzBI; i++)
    gf2n::element[i] = (udigit) 0;

  gf2n::element[0] = (udigit) 1;  
}


void gf2n::assign(const gf2n &a)
{
  for (register unsigned int i = 0; i < gf2n::anzBI; i++)
      gf2n::element[i] = a.element[i];
}


void swap(gf2n & a, gf2n & b)
{
  gf2n c(a);
  
  a.assign(b);
  b.assign(c);
}



/**
** COMPARISONS
* ---------------------------------------------------------------------- */

bool operator == (const gf2n & a, const gf2n & b)
{
 register int i = gf2n::anzBI - 1;
 
 if (a.is_reduced() == false)
   partial_reduce2[gf2n::invsel](a.element);
 
 if (b.is_reduced() == false)
   partial_reduce2[gf2n::invsel](b.element);
 
 while (i >= 0 && a.element[i] == b.element[i])
    i--;

  if (i < 0)
    return true;
  else 
    return false;
}



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


bool gf2n::is_zero () const   
{
  int i = gf2n::anzBI - 1;
   
  if ((*this).is_reduced() == false)
    partial_reduce2[gf2n::invsel](element);
  
  while (i >= 0 && element[i] == (udigit) 0)
    i--;

  if (i < 0)
    return true;
  else
    return false;
}


bool gf2n::is_one ()  const
{
  int i = gf2n::anzBI - 1;
   
  if ((*this).is_reduced() == false)
    partial_reduce2[gf2n::invsel](element);
  
  while (i >= 0 && element[i] == (udigit) 0)
    i--;
  
  if ((i > 0) || (i < 0))
    return false;
  else
    if (element[i] == (udigit) 1)
      return true;
    else
      return false;
}



/* ====================================================================== */
/*                    ARITHMETIC                                         */
/* ====================================================================== */

gf2n operator * (const gf2n & a, const gf2n & b)
{
  gf2n c;
  multiply (c, a, b);
  return (c);
}

gf2n operator + (const gf2n & a, const gf2n & b)
{
  gf2n c;
  add (c, a, b);
  return (c);
}

gf2n operator - (const gf2n & a, const gf2n & b)
{
  gf2n c;
  subtract (c, a, b);
  return (c);
}

gf2n operator / (const gf2n & a, const gf2n & b)
{
  gf2n d(b);
  d.invert();
  multiply (d, a, d);
  return (d);
}


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

gf2n & gf2n::operator -= (const gf2n & y)
{
  add (*this, *this, y);
  return *this;
}

gf2n & gf2n::operator *= (const gf2n & y)
{
  multiply (*this, *this, y);
  return *this;
}

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



extern void (*uinvert[])(udigit*, udigit*);

/**
** INVERSE
* ---------------------------------------------------------------------- */

gf2n inverse (const gf2n & x)

{
  gf2n erg;

  if (x.is_one () == true)
    erg.assign_one ();
  else if (x.is_zero () == true)
    lidia_error_handler ("gf2n","inverse(const gf2n &)::can't invert 0 \n");
  else
    uinvert[gf2n::invsel] (erg.element, x.element);
  
  return erg;
}

/**
** INVERT II
* ---------------------------------------------------------------------- */

void gf2n::invert ()
{
  if ((*this).is_one () == true)
    return;

  if ((*this).is_zero() == true)
    lidia_error_handler ("gf2n","invert()::can't invert 0 \n");

  (*this).assign(inverse(*this));
}


/**
** POWER
* ---------------------------------------------------------------------- */

void power (gf2n & c, const gf2n & a, long i)

{
  int VORZ = 1;

  if (i < 0)
    {
      VORZ = -1;
      i = -i;
    }
    
  if (i == 0)
    {
      c.assign_one ();
      return;
    }

  if (a.is_zero () == true)
    if (VORZ == 1)
      {
	c.assign_zero ();
	return;
      }
    else
      lidia_error_handler ("gf2n","power(gf2n&, const gf2n&, long)::\
                           can't invert 0\n");
      
  gf2n res, mult;
  
  res.assign_one();
  mult.assign(a);

  while (1)
    {
      if ( i & 1)
	multiply(res, res, mult);
      if (i == 1)
	break; 
      i >>= 1;
      square(mult, mult);
    }
	
  if (VORZ == -1)
    invert (c, res);
  else
    c.assign(res);
}



void power(gf2n & c, const gf2n & a, const bigint & bi)
{
  int vorz_exp = 1, i;
  gf2n res;
  
  if (bi.is_negative ())
    vorz_exp = -1;
  
  if (a.is_zero () == true)
    if (vorz_exp == 1)
      {
	c.assign_zero ();
	return;
      }
    else
      lidia_error_handler ("gf2n","power(gf2n&, const gf2n&, const bigint&)::\
                          can't invert 0\n");
      
  if (bi.is_zero ())
    {
    c.assign_one ();
    return;
    }

  res.assign_one();

  for (i = bi.bit_length()-1; i>=0; i--)
    {
      square(res, res);
      if (bi.bit(i) == 1)
	multiply(res, res, a);
    }
  
  if (vorz_exp == -1)
    invert(c, res);
  else c.assign(res);
}



/**
** SQRT
* ---------------------------------------------------------------------- */

void sqrt (gf2n & c, const gf2n & a)
{
  c.assign(a);

  for (register unsigned int i = 1; i < gf2n::degree; i++)
    square (c, c);
}

gf2n sqrt (const gf2n & a)
{
  gf2n c;
  sqrt(c, a);
  return c;
}

/**
** RANDOM
* ---------------------------------------------------------------------- */

void randomize(gf2n & a)
{
static bool initialized = false;

if (initialized == false)
  {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    srandom((unsigned int)((&tv)->tv_usec));
    initialized = true;
  }

for (register unsigned int i=0; i< gf2n::anzBI; i++)
  a.element[i] = (udigit) random();

a.element[gf2n::anzBI-1] &= ~((~0) << (gf2n::degree % (8*sizeof(udigit))));
}


bigint compute_order (const gf2n & a)
{
  bigint q;
  unsigned int  max_index, i;    
  gf2n apow, res;

  if (a.is_zero())
    lidia_error_handler("gf2n","compute_order(const gf2n&)::zero is no element of multiplicative group");

  if (a.is_one())
    return bigint(1);
  
static sort_vector < bigint > div;

  if (gf2n::ord_is_fact == false)
    {
      if (gf2n::degree > 150 && !(gf2n::degree & 1))
	{
	  i = gf2n::degree;

	  while(!(i & 1) && i > 50)
	    i >>= 1;

	  shift_left(q, bigint(1), i);
	  multiply(gf2n::ord, factor(q-bigint(1)), factor(q+bigint(1)));
	  i <<= 1;
	  while(i < gf2n::degree)
	    {
	      square(q, q);
	      multiply(gf2n::ord, gf2n::ord, factor(q+bigint(1)));
	      i <<= 1;
	    }
	}
      else
	{
	  shift_left(q, bigint(1), gf2n::degree);
	  dec(q);
	  gf2n::ord  = factor (q);
	}
      if (gf2n::ord.is_prime_factorization() == false)
	lidia_error_handler("gf2n","compute_order::can't factor group order");

      gf2n::ord_is_fact = true;
      div = divisors (gf2n::ord);
    }

  max_index = div.size () - 1;
  res.assign (a);


  for (i = 1; i <= max_index; i++)
    {
      subtract(q, div[i], div[i-1]);
      power(apow, a, q);
      multiply(res, res, apow);
      
      if (res.is_one())
	return div[i];
    }
  
  lidia_error_handler("gf2n","compute_order():: error in divisors of group order");
  return (bigint(-1));
}

/**
** gf2n - HASH
**/

udigit hash (const gf2n & a)
{
  return (udigit) a.element[0];
}


/**
** GENERATOR
* ---------------------------------------------------------------------- */

gf2n get_generator ()
{
  gf2n a, apow, ahelp;
  bigint *v, h1, gord, h;
  bool found = false;
  unsigned int i, length;
 
  shift_left(gord, bigint(1), gf2n::degree);   /* order of mult. group */
  dec(gord); 

  if (gf2n::ord_is_fact == false)              /* factor order, if necessary */
    {
      gf2n::ord  = factor(gord);
      gf2n::ord_is_fact = true;
    }

  if (is_prime(gord, 8))
    {
      do
	{
	  randomize(a);
	}
      while(a.is_one() || a.is_zero());
      return a;
    }

  length = gf2n::ord.no_of_comp();
  v = new bigint[length];  
   
  /* compute in v the difference between ord/p_i for prime factors p_i
     of ord                                                        */

  divide(v[0], gord, gf2n::ord.base(length-1));
  h.assign(v[0]);
  i = 1;
  
  while (i < length)
    {
     divide(h1, gord, gf2n::ord.base(length-1-i)); 
     subtract(v[i++], h1, h);
     h.assign(h1);
   }

  
  while(found == false)              /* check for generator  */
    {
      randomize(a);
      power(apow, a, v[0]);
      if (apow.is_one())
	continue;

      i=1;
      while(i < length)
	{
	  power(ahelp, a, v[i++]);
	  multiply(apow, apow, ahelp);
	  if (apow.is_one())
	    break;
	}
      if ((i == length) && (apow.is_one() == false))
	found = true;
    }
  delete[] v;
  return a;
}

