

#include <LiDIA/multi_bigmod.h>


  //
  // ***** define pointer to modulus with value zero *****
  //

  residue_class<bigint>* multi_bigmod::zero = nil;


  //
  // ****** modulus handling *****
  //

  void multi_bigmod::set_modulus ( const bigint & m )
   {
     if (m.is_zero())
        lidia_error_handler ("multi_bigmod", "set_modulus::zero modulus");
     else
      {
	if (Mp != nil)
	   (base_bigmod::L)->clear(Mp);

	if (m.is_positive())
	   Mp = (base_bigmod::L)->insert(m);
	else
	   Mp = (base_bigmod::L)->insert(-m);
      }
   }

  void multi_bigmod::initialize ()
   {
     if (base_bigmod::L == nil)
        base_bigmod::L = new residue_class_list<bigint>;

     if (zero == nil)
      {
	bigint z;
	z.assign_zero();
	zero = (base_bigmod::L)->insert(z);
      }
   }


  //
  // ***** constructors / destructor
  //
 	
  multi_bigmod::
  multi_bigmod ()
   {
     initialize();
     Mp = (base_bigmod::L)->set_to(zero);
   }
 	
  multi_bigmod::
  multi_bigmod ( int i, const bigint & m )
   {
     initialize();
     Mp = nil;
     set_modulus(m);
     I.assign(i);
     this->normalize();
   }
 	
  multi_bigmod::
  multi_bigmod ( long l, const bigint & m )
   {
     initialize();
     Mp = nil;
     set_modulus(m);
     I.assign(l);
     this->normalize();
   }
 	
  multi_bigmod::
  multi_bigmod ( unsigned long ul, const bigint & m )
   {
     initialize();
     Mp = nil;
     set_modulus(m);
     I = ul;
     this->normalize();
   }
 	
  multi_bigmod::
  multi_bigmod ( double d, const bigint & m )
   {
     initialize();
     Mp = nil;
     set_modulus(m);
     I = d;
     this->normalize();
   }
 	
  multi_bigmod::
  multi_bigmod ( const bigint & i, const bigint & m )
   {
     initialize();
     Mp = nil;
     set_modulus(m);
     I.assign(i);
     this->normalize();
   }
 	
  multi_bigmod::
  multi_bigmod ( const multi_bigmod & a )
   {
     I.assign(a.I);
     Mp = (base_bigmod::L)->set_to(a.Mp);
   }

  multi_bigmod::
  ~multi_bigmod ()
   {
     (base_bigmod::L)->clear(Mp);
   }



  //
  // ***** assignments *****
  //

  const multi_bigmod & 
  multi_bigmod::operator = ( const multi_bigmod & a )
   {
     I.assign(a.I); 
     this->assign_modulus(a.Mp); 
     return *this;
   }

  const multi_bigmod & 
  multi_bigmod::operator = ( const bigmod & a )
   {
     I.assign(a.I);
     (base_bigmod::L)->clear(Mp);
     Mp = (base_bigmod::L)->set_to(bigmod::Mp);
     return *this;
   }


  //
  // ***** member functions *****
  //

  void 
  multi_bigmod::randomize (const bigint & m)
   {
     set_modulus(m);
     I.assign(::randomize(Mp->get_mod()));
     if (I == Mp->get_mod())
        I.assign_zero();
   }


  //
  // ***** comparisons *****
  //

  bool operator == ( const multi_bigmod & a, const bigmod & b )
   {
     if ( (a.I.compare(b.I) == 0) && (a.Mp == bigmod::Mp) )
        return true;
     else
        return false;
   }

  bool operator == ( const bigmod & a, const multi_bigmod & b )
   {
     if ( (a.I.compare(b.I) == 0) && (bigmod::Mp == b.Mp) )
        return true;
     else
        return false;
   }

  bool operator != ( const multi_bigmod & a, const bigmod & b )
   {
     if ( (a.I.compare(b.I) != 0) || (a.Mp != bigmod::Mp) )
        return true;
     else
        return false;
   }

  bool operator != ( const bigmod & a, const multi_bigmod & b )
   {
     if ( (a.I.compare(b.I) != 0) || (bigmod::Mp != b.Mp) )
        return true;
     else
        return false;
   }


  //
  // ***** operator overloading *****
  //

  multi_bigmod operator - ( const multi_bigmod & a )
   {
      multi_bigmod c(a);
      if (!c.I.is_zero())
       {
	 c.I.negate();
	 add(c.I, c.I, (c.Mp)->get_mod());
       }
      return c;
   }



  //
  // ***** arithmetical procedures *****
  //

  void add ( multi_bigmod & c, const multi_bigmod & a, const multi_bigmod & b )
   {
      if ( a.Mp == b.Mp )
       {
         add ( c, a, b, (a.Mp)->get_mod() ) ;
	 c.assign_modulus (a.Mp);
       }
      else
         lidia_error_handler ( "multi_bigmod", "add::different moduli" ) ;
   }

  void add ( multi_bigmod & c, const bigint & a, const multi_bigmod & b )
   {
     add ( c, a, b, (b.Mp)->get_mod() ) ;
     c.assign_modulus (b.Mp);
   }

  void add ( multi_bigmod & c, const multi_bigmod & a, const bigint & b )
   {
     add ( c, a, b, (a.Mp)->get_mod() ) ;
     c.assign_modulus (a.Mp);
   }

  void add ( multi_bigmod & c, long a, const multi_bigmod & b )
   {
     add ( c, a, b, (b.Mp)->get_mod() ) ;
     c.assign_modulus (b.Mp);
   }

  void add ( multi_bigmod & c, const multi_bigmod & a, long b )
   {
     add ( c, a, b, (a.Mp)->get_mod() ) ;
     c.assign_modulus (a.Mp);
   }	

  void add ( multi_bigmod & c, const bigmod & a, const multi_bigmod & b )
   {
     if ( bigmod::Mp == b.Mp )
      {
        add ( c, a, b, bigmod::M ) ;
	c.assign_modulus (bigmod::Mp);
      }
     else
        lidia_error_handler ( "multi_bigmod", "add::different moduli" ) ;
   }

  void add ( multi_bigmod & c, const multi_bigmod & a, const bigmod & b )
   {
     if ( bigmod::Mp == a.Mp )
      {
        add ( c, a, b, bigmod::M ) ;
	c.assign_modulus (bigmod::Mp);
      }
     else
        lidia_error_handler ( "multi_bigmod", "add::different moduli" ) ;
   }



  void subtract ( multi_bigmod & c, const multi_bigmod & a, const multi_bigmod & b )
   {
      if ( a.Mp == b.Mp )
       {
         subtract ( c, a, b, (a.Mp)->get_mod() ) ;
	 c.assign_modulus (a.Mp);
       }
      else
         lidia_error_handler ( "multi_bigmod", "subtract::different moduli" ) ;
   }

  void subtract ( multi_bigmod & c, const bigint & a, const multi_bigmod & b )
   {
     subtract ( c, a, b, (b.Mp)->get_mod() ) ;
     c.assign_modulus (b.Mp);
   }

  void subtract ( multi_bigmod & c, const multi_bigmod & a, const bigint & b )
   {
     subtract ( c, a, b, (a.Mp)->get_mod() ) ;
     c.assign_modulus (a.Mp);
   }

  void subtract ( multi_bigmod & c, long a, const multi_bigmod & b )
   {
     subtract ( c, a, b, (b.Mp)->get_mod() ) ;
     c.assign_modulus (b.Mp);
   }

  void subtract ( multi_bigmod & c, const multi_bigmod & a, long b )
   {
     subtract ( c, a, b, (a.Mp)->get_mod() ) ;
     c.assign_modulus (a.Mp);
   }	

  void subtract ( multi_bigmod & c, const bigmod & a, const multi_bigmod & b )
   {
     if ( bigmod::Mp == b.Mp )
      {
        subtract ( c, a, b, bigmod::M ) ;
	c.assign_modulus (bigmod::Mp);
      }
     else
        lidia_error_handler ( "multi_bigmod", "subtract::different moduli" ) ;
   }

  void subtract ( multi_bigmod & c, const multi_bigmod & a, const bigmod & b )
   {
     if ( bigmod::Mp == a.Mp )
      {
        subtract ( c, a, b, bigmod::M ) ;
	c.assign_modulus (bigmod::Mp);
      }
     else
        lidia_error_handler ( "multi_bigmod", "subtract::different moduli" ) ;
   }



  void multiply ( multi_bigmod & c, const multi_bigmod & a, const multi_bigmod & b )
   {
      if ( a.Mp == b.Mp )
       {
         multiply ( c, a, b, (a.Mp)->get_mod() ) ;
	 c.assign_modulus (a.Mp);
       }
      else
         lidia_error_handler ( "multi_bigmod", "multiply::different moduli" ) ;
   }

  void multiply ( multi_bigmod & c, const bigint & a, const multi_bigmod & b )
   {
     multiply ( c, a, b, (b.Mp)->get_mod() ) ;
     c.assign_modulus (b.Mp);
   }

  void multiply ( multi_bigmod & c, const multi_bigmod & a, const bigint & b )
   {
     multiply ( c, a, b, (a.Mp)->get_mod() ) ;
     c.assign_modulus (a.Mp);
   }

  void multiply ( multi_bigmod & c, long a, const multi_bigmod & b )
   {
     multiply ( c, a, b, (b.Mp)->get_mod() ) ;
     c.assign_modulus (b.Mp);
   }

  void multiply ( multi_bigmod & c, const multi_bigmod & a, long b )
   {
     multiply ( c, a, b, (a.Mp)->get_mod() ) ;
     c.assign_modulus (a.Mp);
   }	

  void multiply ( multi_bigmod & c, const bigmod & a, const multi_bigmod & b )
   {
     if ( bigmod::Mp == b.Mp )
      {
        multiply ( c, a, b, bigmod::M ) ;
	c.assign_modulus (bigmod::Mp);
      }
     else
        lidia_error_handler ( "multi_bigmod", "multiply::different moduli" ) ;
   }

  void multiply ( multi_bigmod & c, const multi_bigmod & a, const bigmod & b )
   {
     if ( bigmod::Mp == a.Mp )
      {
        multiply ( c, a, b, bigmod::M ) ;
	c.assign_modulus (bigmod::Mp);
      }
     else
        lidia_error_handler ( "multi_bigmod", "multiply::different moduli" ) ;
   }




  void divide ( multi_bigmod & c, const multi_bigmod & a, const multi_bigmod & b )
   {
      if ( a.Mp == b.Mp )
       {
         divide ( c, a, b, (a.Mp)->get_mod() ) ;
	 c.assign_modulus (a.Mp);
       }
      else
         lidia_error_handler ( "multi_bigmod", "divide::different moduli" ) ;
   }

  void divide ( multi_bigmod & c, const bigint & a, const multi_bigmod & b )
   {
     divide ( c, a, b, (b.Mp)->get_mod() ) ;
     c.assign_modulus (b.Mp);
   }

  void divide ( multi_bigmod & c, const multi_bigmod & a, const bigint & b )
   {
     divide ( c, a, b, (a.Mp)->get_mod() ) ;
     c.assign_modulus (a.Mp);
   }

  void divide ( multi_bigmod & c, long a, const multi_bigmod & b )
   {
     divide ( c, a, b, (b.Mp)->get_mod() ) ;
     c.assign_modulus (b.Mp);
   }

  void divide ( multi_bigmod & c, const multi_bigmod & a, long b )
   {
     divide ( c, a, b, (a.Mp)->get_mod() ) ;
     c.assign_modulus (a.Mp);
   }	

  void divide ( multi_bigmod & c, const bigmod & a, const multi_bigmod & b )
   {
     if ( bigmod::Mp == b.Mp )
      {
        divide ( c, a, b, bigmod::M ) ;
	c.assign_modulus (bigmod::Mp);
      }
     else
        lidia_error_handler ( "multi_bigmod", "divide::different moduli" ) ;
   }

  void divide ( multi_bigmod & c, const multi_bigmod & a, const bigmod & b )
   {
     if ( bigmod::Mp == a.Mp )
      {
        divide ( c, a, b, bigmod::M ) ;
	c.assign_modulus (bigmod::Mp);
      }
     else
        lidia_error_handler ( "multi_bigmod", "add::different moduli" ) ;
   }



  void power ( multi_bigmod & c, const multi_bigmod & a, const bigint & b )
   {
     power(c,a,b,(a.Mp)->get_mod());
     c.assign_modulus (a.Mp);
   }

  void power ( multi_bigmod & c, const multi_bigmod & a, long b )
   {
     power(c,a,b,(a.Mp)->get_mod());
     c.assign_modulus (a.Mp);
   }



  //
  // ***** functions *****
  //

  multi_bigmod randomize ( const multi_bigmod & a )
   {
     multi_bigmod c;
     c.I.assign(::randomize(a.I));
     c.assign_modulus (a.Mp);
     return c;
   }

  void normalize ( multi_bigmod & a, const multi_bigmod & b )
   {
     bigint q, r;
     div_rem(q, r, b.I, (b.Mp)->get_mod());
     a.I.assign(r);
     a.assign_modulus(b.Mp);
   }

  void square ( multi_bigmod & a, const multi_bigmod & b )
   {
     square(a.I, b.I);
     remainder(a.I, a.I, (b.Mp)->get_mod());
     a.assign_modulus(b.Mp);
   }

  void swap ( multi_bigmod & a, multi_bigmod & b )
   {
     swap(b.I, a.I);

     residue_class<bigint> *h;
     h = b.Mp;
     b.Mp = a.Mp;
     a.Mp = h;
   }



  //
  // ***** input / output *****
  //

  void multi_bigmod::
  read (istream & in)
    {
      bigint m;
      char c;


      // read white spaces

      in >> c;
      while ( c == ' ' ) in >> c;

      // read '('

      if ( c != '(' )
       {
	 in.putback(c);
	 in >> I;
	 this->normalize();
       }
      else
       {
	 // read mantissa
	 in >> I;
	 
         // read white spaces

	 in >> c;
	 while ( c == ' ' ) in >> c;

	 // read ','

	 if ( c != ',' )
	  {
	    lidia_error_handler ("multi_bigmod::read(istream & in)",
		   	         "',' expected.");
          }
	 else
	  {
	    // read modulus
	    in >> m;
	    (base_bigmod::L)->clear(Mp);
	    Mp = (base_bigmod::L)->insert(m);

	    this->normalize();

	    // read white spaces

	    in >> c;
	    while ( c == ' ' ) in >> c;

	    // read ')'

	    if ( c != ')' )
	     {
	       lidia_error_handler ("multi_bigmod::read(istream & in)",
				    "')' expected.");
	     }
          }
       }
    }

   void multi_bigmod::
   print (ostream & out) const
    {
      out << "(" << I << "," << Mp->get_mod() << ")";
    }

   istream & operator >> (istream & in, multi_bigmod & a)
    {
      a.read(in);
      return(in);
    }

   ostream & operator << (ostream & out, const multi_bigmod & a)
    {
      a.print(out);
      return out;
    }


   int
   string_to_multi_bigmod(char *s, multi_bigmod & a)
    {
      long l = strlen(s);
      long  n, i;
      char *h;
      bigint m;


      if (l == 0)
       {
	 lidia_error_handler ("multi_bigmod::string_to_multi_bigmod(...)",
			      "Length of input string is zero.");
       }
      else if (s[0] != '(')
       {
	 string_to_bigint(s, a.I);
	 a.normalize();
       }
      else
       {
	 // count the number n of ',' in s

	 n = 0;
	 i = 0;
	 while (i < l)
	  {
	    if (s[i] == ',')
	       n++;
	    i++;
	  }

	 if (n == 0 || !(n&1) || s[l-1] != ')' )
	    lidia_error_handler ("multi_bigmod::string_to_multi_bigmod(...)",
				 "Invalid multi_bigmod format.");
	 else
	  {
	    n = (n>>1)+1;

	    // find s[i], the n-th ',' in s
	    i = 0;
	    while (n > 0)
	     {
	       if (s[i] == ',')
	          n--;
	       i++;
	     }
	    i--;
	    
	    // extract mantissa

	    s[i] = '\0';
	    h = &(s[1]);
	    string_to_bigint(h,a.I);
	    s[i] = ',';	    

	    // extract modulus

	    s[l-1] = '\0';
	    h = &(s[i+1]);
	    string_to_bigint(h,m);
	    s[l-1] = ')';

	    a.set_modulus(m);
	    a.normalize();
	  }
       }

      return (strlen(s));
    }


   int
   multi_bigmod_to_string(const multi_bigmod & a, char * &s)
    {
      char *man, *mod;
      long  l;      

      man = new char[a.I.bit_length()/3 + 10];
      mod = new char[((a.Mp)->get_mod()).bit_length()/3 + 10];

      bigint_to_string(a.I,man);
      bigint_to_string((a.Mp)->get_mod(),mod);

      if (s)
         delete [] s;

      s = new char [strlen(man) + strlen(mod) + 4];

      s[0] = '(';
      s[1] = '\0';
      s = strcat (s,man);
      l = strlen(s);
      s[l] = ',';
      s[l+1] = '\0';
      s = strcat (s,mod);
      l = strlen(s);
      s[l] = ')';
      s[l+1] = '\0';

      return (strlen(s));
    }


   //
   // using fread/fwrite
   //

   void multi_bigmod::
   read_from_file(FILE * fp)
    {
      bigint m;

      I.read_from_file(fp);
      if (feof(fp))
        {
	  lidia_error_handler ("multi_bigmod::read_from_file(FILE*)",
			       "Invalid multi_bigmod format.");
	}
      else
       {
	 m.read_from_file(fp);
	 this->set_modulus(m);
	 this->normalize();
       }
    }

   void multi_bigmod::
   write_to_file(FILE * fp)
    {
      I.write_to_file(fp);
      (Mp->get_mod()).write_to_file(fp);
    }


   //
   // using fscanf/fprintf
   //

   void multi_bigmod::
   scan_from_file(FILE * fp)
    {
      char c;
      bigint m;
      
      fscanf (fp,"%c",&c);
      if (feof(fp))
       {
	 I.assign_zero();
       }
      else if (c != '(')
       {
	 lidia_error_handler ("multi_bigmod::scan_from_file(FILE * fp)",
			      "'(' expected.");
       }
      else
       {
	 I.scan_from_file(fp);
	 fscanf (fp,"%c",&c);
	 if (c != ',')
            lidia_error_handler ("multi_bigmod::scan_from_file(FILE * fp)",
				 "',' expected.");
	 else
	  {
	    m.scan_from_file(fp);
	    this->set_modulus(m);
	    this->normalize();

	    fscanf (fp,"%c",&c);
	    if (c != ')')
               lidia_error_handler ("multi_bigmod::scan_from_file(FILE * fp)",
		   		    "')' expected.");
	  }
       }
    }

   void multi_bigmod::
   print_to_file(FILE * fp)
    {
      fprintf (fp,"(");
      I.print_to_file(fp);
      fprintf (fp,",");
      (Mp->get_mod()).print_to_file(fp);
      fprintf (fp,")");
    }



