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

void mecgen16(ec_curve & me, ec_point_M & mp, bigint & factor, int m)
{
   ec_curve we( bigmod(-8), bigmod(-32) );
   ec_point_W wq(we, bigmod(12), bigmod(40));

   bigmod h1, h2;
   bigmod alpha, la, lb, lc, ld;
   
   multiply(wq, wq, m, factor);
  
   subtract (h2, wq.X(), 9);
   
   factor = h2.invert(1);
   if ( !factor.is_one() ) return;
   
   add(h1, wq.Y(), 25);
   multiply(h2, h2, h1);
   inc(h2);
   
   factor = h2.invert(1);
   if ( !factor.is_one() ) return;
   
   alpha.assign(h2);

   square(h2, h2);
   multiply(h2, h2, 8);
   dec(h2);
   
   factor = h2.invert(1);
   if ( !factor.is_one() ) return;
   
   multiply(h1, alpha, 4);
   inc(h1);
   multiply(h1, h1, alpha);
   multiply(h1, h1, 2);
   multiply(ld, h1, h2);
   
   h1.assign(ld);
   factor = h1.invert(1);
   if ( !factor.is_one() ) return;
   
   multiply(h2, ld, 2);
   dec(h2);
   multiply(h1, h1, h2);
   subtract(h2, ld, 1);
   multiply(lc, h1, h2);
   
   multiply(lb, lc, ld);
   
   invert( h2, bigmod(4) );
   
   multiply(h1, ld, 2);
   dec(h1);
   
   multiply(h1, h1, h2);
   h1.negate();

   la = h1;

   subtract(h2, ld, 1);
   square(h2, h2);
   
   factor = h2.invert(1);
   if ( !factor.is_one() ) return;
   
   h1 = ld;
   square(h1, h1);
   
   add(la, la, ld);
   subtract(la, la, h1);
   multiply(la, la, h2);
   
   mp.assign(la, bigmod(1));

   subtract(h1, ld, 1);
   multiply(h1, h1, ld);
   h1.multiply_by_2();

   factor= h1.invert(1);
   
   h2.assign(ld);
   h2.multiply_by_2();
   dec(h2);
   square(h2, h2);
   multiply(h2, h2, h1);
   
   square(h2, h2);
   
   subtract(h2, h2, 2);

   me.assign(h2, bigmod(1));

}


void 
add(ec_point_M & erg, const ec_point_M & mp1, const ec_point_M & mp2, const ec_point_M & diff)
{
   bigmod t1, t2, h3, h4;
   
   subtract(t1, mp1.x, mp1.z);
   add(h3, mp2.x, mp2.z);
   multiply(t1, t1, h3);

   add(t2, mp1.x, mp1.z);
   subtract(h3, mp2.x, mp2.z);
   multiply(t2, t2, h3);
   
   add(h3, t1, t2);
   square(h3, h3);
   multiply(h4, h3, diff.z);

   subtract(h3, t1, t2);
   square(h3, h3);
   multiply(h3, h3, diff.x);
   
   erg.assign(h4, h3);
}


void
multiply_by_2(ec_point_M & erg, const ec_point_M &mp, const bigmod & a_2)
{
   bigmod h1, h2, h3;

   add(h1, mp.x, mp.z);
   square(h1, h1);

   subtract(h2, mp.x, mp.z);
   square(h2, h2);
   
   multiply(h3, h1, h2);
   erg.x.assign(h3);
   
   subtract(h1, h1, h2);
   multiply(h3, h1, a_2);
   add(h3, h3, h2);
   multiply(h3, h1, h3);
   erg.z.assign(h3);
}

#ifndef __GNUG__

void 
multiply(ec_point_M & erg, const ec_point_M & mp, const bigmod & a_2, unsigned int p, unsigned int m)
{
   int i;
   unsigned int r;
   unsigned int bit ;

   ec_point_M P1 (mp);
   ec_point_M P2, Erg1;

   multiply_by_2(P2, mp, a_2);

   r = integer_log(m) - 1;               //  r = erstes gesetztes Bit in m 

   bit = 0x00000001 << r ;               //  bit = 2^r  

   if ( r==31 )                           
      p = -m ;                           //  p = 2^32 - m  =  -m  
   else
   {
      p = 0x00000001 << (r+1) ;
      p = p - m ;                        //  p = 2^(r+1) - m      
   }   
 
   for (i = r-1; i > 0; i--) 
   {
     add(Erg1, P1, P2, mp);            //  Erg1 = P1 + P2 mit P1-P2 = P 
      
     bit >>= 1;
     if (bit & p)                      //  i.th Bit of p is 1
     {  
       multiply_by_2(P1, P1, a_2);
       P2.assign(Erg1);
     }
     else                              // i.th Bit of p is 0 
     { 
       multiply_by_2(P2, P2, a_2);
       P1.assign(Erg1);
     }
   }
   
   add(erg, P1, P2, mp);
}

#else

void 
multiply(ec_point_M & erg, const ec_point_M & mp, const bigmod & a_2, unsigned int p, unsigned int m)
{
  unsigned int d, r, e, h;
  float alpha = 1.618034;
  ec_point_M A(mp), B, C, H, T; 
  
  while ( !(m&1) )
  {
    m >>= 1;
    multiply_by_2(A, A, a_2);
  }
  
  while ( !(m%3) )
  {
    m /= 3;
    
    multiply_by_2(H, A, a_2);
    add(A, A, H, A);
  }

  d = m; 
  
  while ( d != 1 )
  {
    if ( p > d )  p = d;
    
    r = (unsigned int) ( (float)p / alpha + 0.5);
    r = ( d / p ) * r;
    
    e = 2 * r - d;
    d = 2 * d - 3 * r;
    
    C.assign(A);
    multiply_by_2(A, A, a_2);
    add(B, A, C, C);
    
    while ( d != e )
    {
      if ( d < e )
      {
        h = d; d = e; e = h;
        H.assign(A);
	A.assign(B);
	B.assign(H);
      }
         
      if ( (d-e) < (e/4) )
      {
        h = (int) (d+e)/3 ;

        if ( (d + e) == (3 * h) )
        {
          d -= h;
          e -= h;

          add(T, A, B, C);
          add(H, T, A, B);
          add(B, T, B, A);
	  A.assign(H);
	  
          goto end_while;
        }
        else  
        {
          if ( !((d - e) % 6) )
          {
            d = ( d - e ) / 2;

            add(B, A, B, C);
            multiply_by_2(A, A, a_2);

            goto end_while;
          }
          
          d = d - e;

          add(T, A, B, C);
          C.assign(B);
	  B.assign(T);

          goto end_while;
        }
      }
          
      if ( d <= ( 4 * e ) )
      {
        d = d - e;

        add(H, A, B, C);
	C.assign(B);
	B.assign(H);

        goto end_while;
      }
      
          
      if ( !((d-e)&1) )
      {
        d = ( d - e ) / 2;

        add(B, A, B, C);
        multiply_by_2(A, A, a_2);

        goto end_while;
      }

      
      if ( !(d&1) )
      {
        d >>= 1;

        add(C, A, C, B);
        multiply_by_2(A, A, a_2);

        goto end_while;
      }

      
      if ( !(d%3) )
      {
        d = d / 3 - e ;

        multiply_by_2(H, A, a_2);
        add(T, A, B, C);
        add(A, H, A, A);
        add(H, H, T, C);

	C.assign(B);
	B.assign(H);

        goto end_while;
      }

      
      if ( !((d+e) % 3 ) )
      {
        d = ( d + e ) / 3 - e;

        add(H, A, B, C);
        add(B, H, A, B);

        multiply_by_2(T, A, a_2);
        add(A, A, T, A);

        goto end_while;
      }
      
      
      if ( !(( d-e) % 3 ) )
      {
        d = ( d - e ) / 3;

        add(H, A, B, C);
        add(C, A, C, B);
	B.assign(H);

        multiply_by_2(T, A, a_2);          /*  X3(A)  */
        add(A, A, T, A);

        goto end_while;
      }
      
      
      if ( !(e&1) )
      {
        e >>= 1 ;

        add(C, C, B, A);
        multiply_by_2(B, B, a_2);

        goto end_while;
      }
      
    end_while:
      ;
    }
    
    add(A, A, B, C);
  }

  erg.assign(A);
}

#endif


                         // Weierstrass parametrization


/**
** comparisons
**/

bool operator == (const ec_point_W & wp1, const ec_point_W & wp2)
{
   if ( (wp1.is_0 == wp2.is_0) &&
        (wp1.is_0 || (wp1.x == wp2.x && wp1.y == wp2.y) ))
      return 1;
   else
      return 0;
}


/**
** Procedural versions
**/


bigint trans(ec_curve & ell_curve, ec_point_W & wp, ec_point_M & mp)
{
   bigint factor;
   bigmod la, inv_3, inv_t, h1, h2, h3;
   
   h2 = mp.X();
   
   square(h2, h2);
   
   multiply(h1, mp.X(), ell_curve.A());
   add(inv_t, h1, h2);
   inc(inv_t);
   multiply(inv_t, inv_t, mp.X());

   factor = inv_t.invert(1);
   if ( !factor.is_one() ) return factor;

   wp.assign_one();
   wp.y.assign(inv_t);

   invert(inv_3, bigmod(3) );
   
   multiply(h3, inv_3, ell_curve.A());
   add(h3, h3, mp.X());
   multiply(h3, h3, inv_t);
   
   wp.x.assign(h3);

   h2 = ell_curve.A();
   square(h2, h2);
   multiply(h1, h2, inv_3);
   h3.assign_one();
   subtract(h3, h3, h1);

   h1 = inv_t;
   square(h1, h1);
   multiply(h3, h3, h1);

   la = h3;

   multiply(h1, h1, inv_t);
   multiply(h1, h1, inv_3);
   
   multiply(h3, inv_3, inv_3);
   multiply(h2, h2, h3);
   multiply(h2, h2, 2);
   dec(h2);
   multiply(h2, h2, ell_curve.A());

   multiply(h2, h2, h1);
   
   ell_curve.assign(la, h2);
   wp.curve = (ec_curve *) &ell_curve;
   return ( bigint(1) );
}


void 
multiply_by_2(ec_point_W &erg, const ec_point_W &wp, bigint &factor)
{
   bigmod h, h1, h2;
   
   if ( wp.is_0 ) 
   {
      factor.assign_one();
      erg.assign_zero();
      return;
   }

   h2.assign(wp.y);
   h2.multiply_by_2();
   factor = h2.invert(1);
   if ( !factor.is_one() )  return;

   h1.assign(wp.x);
   square(h1, h1);
   multiply(h1, h1, 3);
   add(h1, h1, (*(wp.Curve())).A());
   
   multiply(h, h1, h2);
   h1.assign(h);

   square(h1, h1);
   h2.assign(wp.x);
   h2.multiply_by_2();
   subtract(h2, h1, h2);
   subtract(h1, wp.x, h2);
   multiply(h1, h, h1);
   subtract(h, h1, wp.y);
   
   erg.assign(h2, h);
}

void
addPQ(ec_point_W & erg, const ec_point_W & wp1, const ec_point_W & wp2, bigint & factor)
{
   bigmod h, h1, h2;
   
   subtract(h2, wp2.x, wp1.x);

   factor = h2.invert(1);
   if ( !factor.is_one() ) return;

   subtract(h1, wp2.y, wp1.y);
   
   multiply(h, h1, h2);
   h1.assign(h);
   square(h1, h1);
   subtract(h1, h1, wp1.x);
   subtract(h1, h1, wp2.x);
   
   subtract(h2, wp1.x, h1);
   multiply(h2, h2, h);
   subtract(h2, h2, wp1.y);
   
   erg.assign(h1, h2);
}

void  
add(ec_point_W & erg, const ec_point_W & wp1, const ec_point_W & wp2, bigint & factor)
{
   if ( wp1.is_0 )
   {
      factor.assign_one();
      erg.assign(wp2);
   }
   else
   if ( wp2.is_0 )
   {
      erg.assign(wp1);
      factor.assign_one();
   }
   else
   if ( wp1 == wp2 )
   {
      bigmod h, h1, h2;
      
      h2.assign(wp2.y);
      h2.multiply_by_2();                   

      factor = h2.invert(1);                        // h2 = (2y1)^(-1)
      if ( !factor.is_one() )  return;
      
      h1.assign(wp2.x);
      square(h1, h1);
      multiply(h1, h1, 3);
      add(h1, h1, (*(wp2.Curve())).A());           // h1 = 3x1^2 + a 
      
      multiply(h, h1, h2);                         // h = (3x1^2+a)/(2y1) 
      h1.assign(h);
      square(h1, h1);                              // h1 = h^2
      h2.assign(wp2.x);
      h2.multiply_by_2();
      subtract(h2, h1, h2);

      subtract(h1, wp2.x, h2);
      multiply(h1, h, h1);
      subtract(h, h1, wp2.y);
      
      erg.assign(h2, h);
   }
   else
   if ( wp1.is_negative(wp2) )
   {
      factor.assign_one();
      erg.assign_zero();
   }
   else
   {
      bigmod h, h1, h2;
      
      subtract(h2, wp2.x, wp1.x);
      
      factor = h2.invert(1);
      if ( !factor.is_one() ) return;
      
      subtract(h1, wp2.y, wp1.y);
      
      multiply(h, h1, h2);
      h1.assign(h);
      square(h1, h1);
      subtract(h1, h1, wp1.x);
      subtract(h1, h1, wp2.x);
      
      subtract(h2, wp1.x, h1);
      multiply(h2, h2, h);
      subtract(h2, h2, wp1.y);
      
      erg.assign(h1, h2);
   }
}


void
multiply(ec_point_W & erg, const ec_point_W & wp, int m, bigint & factor)
{
   ec_point_W Z;
   
   if ( m < 5 )
   {
      Z.assign(wp);
      for (; m > 1; m--)
      {
	 add(Z, Z, wp, factor);
	 if ( !factor.is_one() )   return;
      }
   }
   else
   {
      ec_point_W X(wp);
      Z.assign_zero();

      while( m > 0 )
      {
         while ( !(m & 1) )              /* while m even:  */
	 {                               /* doubling point X */
	    multiply_by_2(X, X, factor);
	    if ( !factor.is_one() )   return;
	    m >>= 1;
	 }
	 
	 add(Z, X, Z, factor);
	 if ( !factor.is_one() )   return;
	 m--;
      }
   }
   
   erg.assign(Z);
}






