// FinFld.js
// Finite Field computations, written in JavaScript
// Author: Robert Campbell, <rcampbel@umbc.edu>
// Date: 29 Jan, 2010
// Freely available for use, abuse and modification (see license at bottom)

///////////////////////////////////////////////////////////////
// finFld object
// Defines a finite field with presentation (driving polynomial)
///////////////////////////////////////////////////////////////

// Set up finite field or ring, including reduction table (aka mult tensor)
// Polynomial is assumed monic (even for char > 2)
// Lazy confirmation that it is a field (irred polynomial) 
//   and factoring of order of multiplicative group (char^deg - 1)
// reduc[i] = x^(deg+i) expressed as sum of {1,x,x^2,...,x^(deg-1)}
function finFld(char, poly, xvar){
   var i,j;
   this.char = char;
   this.poly = poly;
   this.deg = poly.length;
   this.xvar = xvar;   // Variable, used to display
   this.ordfact = [];  // Lazy factorization of (char^deg - 1)
   this.reduc = [];
   this.reduc[0] = [];
   for (j=0; j<this.deg; j++){
      this.reduc[0][j] = (((-poly[j])%char)+char)%char;  // -p[j] (mod char)
   }
   // reduc[i] = shift(reduc[i-1]) + lead*(reduc[0]), 
   //   where lead is leading coeff of reduc[i-1]
   for (i=1; i<this.deg; i++){
      this.reduc[i] = [];
      this.reduc[i][0] = 0;
      for (j=1; j<this.deg; j++){
         this.reduc[i][j] = this.reduc[i-1][j-1];
      }
      for (j=0; j<this.deg; j++){
         this.reduc[i][j] = ((this.reduc[i][j] + (this.reduc[i-1][this.deg-1])*(this.reduc[0][j])) % char);
      }
   }
   // Various member functions
   this.format = function format_finFld(){
         return "GF(" + this.char + "^" + this.deg + ", [" + printpoly((this.poly).concat(1),this.xvar) + "])";
      };
   this.repr = function repr_finFld(){
         return "finFld(" + this.char + ",[" + this.poly + "], " + this.xvar + ")";
      };
   this.dump = function dump_finFld(){
         var thedump = "";
         thedump += "char=" + this.char + "\n";
         thedump += "poly=[" + this.poly + "]\n";
         thedump += "deg=" + this.deg + "\n";
         thedump += "reduc= \n";
         for (var i=0; i<this.deg; i++){
            thedump += "   " + i + ": [" + this.reduc[i] + "]\n";}
         return thedump;
      };
}

// Element of a finite field or ring
//    and its functions and operators
function finFldElt(thefield,thepoly){
   var i;
   this.field = thefield;
   if (thepoly.length > thefield.deg){return "Error: finFldElt too large";}
   this.poly = thepoly;
   for (i=thepoly.length; i<thefield.deg; i++){
      this.poly[i] = 0;
   }
   // Add two finite field elements (from the same finite field)
   this.add = function finFldElt_add(elt){
         var thesum = new finFldElt(this.field,this.poly);
         for (var i=0; i<this.field.deg; i++){
	    thesum.poly[i] = (thesum.poly[i] + elt.poly[i]) % this.field.char;
	 }
         return thesum;
      };
   // The negative of a finite field element
   this.neg = function finFldElt_neg(){
         var theneg = new finFldElt(this.field,this.poly);
	 for (var i=0; i<this.field.deg; i++){
	    theneg.poly[i] = (((-(theneg.poly[i])) % this.field.char) + this.field.char) % this.field.char;
	 }
         return theneg;
      };
   // Multiply two finite field elements (from the same finite field)
   this.mult = function finFldElt_mult(elt){
         var i, j, temp;
         var theprod = new finFldElt(this.field,[]);
         for (i=0; i<this.field.deg; i++){
            temp = 0;
            for (j=0; j<=i; j++){
               temp += this.poly[j]*elt.poly[i-j];
            }
            theprod.poly[i] = temp;
         }
	 for (i=this.field.deg; i<=(2*this.field.deg-2); i++){
            // Compute coeff of x^i before reduction
            temp = 0;
            for (j=(i-(this.field.deg-1)); j<this.field.deg; j++){
               temp += this.poly[j]*elt.poly[i-j];
            }
            temp = ((temp % this.field.char) + this.field.char) % this.field.char;
            // Reduce mod field polynomial, so x^i --> field.reduc[i]
            for (j=0; j<this.field.deg; j++){
               theprod.poly[j] += temp*(this.field.reduc[i-this.field.deg][j]);
            }
         }
	 for (i=0; i<this.field.deg; i++){
            theprod.poly[i] = ((theprod.poly[i] % this.field.char) + this.field.char) % this.field.char;
         }
         return theprod;
      };
   // Efficient replacement for mult - equivalent to "*="
   this.multeq = function finFldElt_multeq(){
         return "multeq: Not Implemented\n";
      };
   // Compute a power of a finite field element
   this.powmod = function finFldElt_pow(e){
         var i=0;
         var accum = new finFldElt(this.field,[1]);
         var basepow2 = new finFldElt(this.field,this.poly);
         while ((e >> i)>0) {
            // Need to use multeq here
            if(((e >> i) & 1) == 1){accum = accum.mult(basepow2);};
            basepow2 = basepow2.mult(basepow2);
            i++;
         }
         return accum;
      };
   // Simple text/string format - return as string form of a list
   this.format = function format_finFldElt(){
         return "(" + printpoly(this.poly,this.field.xvar) + ")";
      };
   // Format appropriate for input
   this.repr = function format_finFldElt(){
         return "finFldElt([" + this.poly + "])";
      };
   // Debugging dump format - formatted print of all class variables
   this.dump = function dump_finFldElt(){
         var thedump = "";
         thedump += "poly=[" + this.poly + "]\n";
         thedump += "field: -----------------------\n" + this.field.dump() + "\n";
         return thedump;   
      };
}

function printpoly(coeffs,xvar){
   var i;
   var thestr = "";
   var wroteany = false;
   for (i=0; i<coeffs.length; i++){
      if (coeffs[i]!=0){
         if (i==0){
            thestr += coeffs[0];
            wroteany = true;
         } else if (i==1) {
            if (wroteany || (coeffs[1] < 0)) {          // x is not written +x
               if (coeffs[1] < 0) {
                  if (wroteany) { thestr += " - ";}
                  else { thestr += "-";}              // Note spacing: (-x) vs (1 - x)
               } else {  thestr += " + ";}}
            if ((coeffs[1]!=1) && (coeffs[1]!=-1)){  // 1*x is written as x
               thestr += Math.abs(coeffs[1]);}
            thestr += xvar;                           // x^1 is written as x
            wroteany = true;
         } else {                                    // i > 1 (quadratic or greater terms)
            if (wroteany || (coeffs[i] < 0)) {          // x is not written +x
               if (coeffs[i] < 0) {
                  if (wroteany) { thestr += " - ";}
                  else { thestr += "-";}              // Note spacing: (-x) vs (1 - x)
               } else {  thestr += " + ";}}
            if ((coeffs[i]!=1) && (coeffs[i]!=-1)) {  // 1*x^i is written as x^i
               thestr += Math.abs(coeffs[i]);}
            thestr += xvar + "^" + i;
            wroteany = true;
         }
      }
   }
   if (!wroteany) { thestr += "0";}
   return thestr;
}


///////////////////////////////////////////////////////////////
// Examples:
// (ref: Primitive Polynomials over Finite Fields, T. Hansen & G. L. Mullins, Math Comp, Oct 1992)
// GF32 = new finFld(2,[1,0,1,0,0],'x')        # Define GF(2^5) = Z_2[x]/<x^5 + x^2 + 1>
// GF256 = new finFld(2,[1,0,1,1,1,0,0,0],'a') # Define GF(2^8) = Z_2[a]/<a^8 + a^4 + a^3 + a^2 + 1>
// GF27 = new finFld(3,[1,2,0],'x')            # Define GF(3^3) = Z_3[x]/<x^3 + 2x + 2>
// GF6561 = new finFld(3,[2,0,0,1,0,0,0,0],'x')# Define GF(3^8) = Z_3[x]/<x^8 + x^3 + 2>
// GF25 = new finFld(5,[2,1],'x')              # Define GF(5^2) = Z_5[x]/<x^2 + x + 2>
// GF125 = new finFld(5,[2,3,0],'b')           # Define GF(5^3) = Z_5[b]/<b^3 + 3b + 2>
// GF2197 = new finFld(13,[6,1,0],'x')         # Define GF(13^3) = Z_13[x]/<x^3 + x + 6>
///////////////////////////////////////////////////////////////
// ONCLICK="var newfld = new finFld(parseInt(form.mod.value,10),eval(form.poly.value),'a');
//   form.thefld.value = newfld.format();  
//   var x = new finFldElt(newfld,[0,1]);
//   form.theelt.value = x.format();"
///////////////////////////////////////////////////////////////
// var gf27 = new finFld(3,[1,2,0],'x');
// var x = new finFldElt(gf27,[0,1,0]);
// var ffelt = new finFldElt(gf27,[2,1,1]);
// alert(x.add(ffelt).format());  // Show sum of x and 2+x+x^2
///////////////////////////////////////////////////////////////
// Work Needed (29 Jan 2010)
//   finFldElt_inv(a) - need to implement polynomial xgcd
//   isirred(poly) - need to implement NumbThy/factorall
//   finFldExt_minpoly(a) - need to implement matrix solve
///////////////////////////////////////////////////////////////

// (this is the Simplified BSD License, aka FreeBSD license)
// Copyright 2001-2010 Robert Campbell. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    1. Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//    2. Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in 
//       the documentation and/or other materials provided with the distribution.


