//
// Program to generate code for reduction modulo an irreducible trinomial or pentanomial
// over the field GF(2^m), m a prime
//
// cl /O2 /GX irp.cpp
//
// code is generated and can be cut-and-pasted into the function 
// reduce2(.) in the MIRACL module mrecgf2m.c
//
// To find a suitable irreducible polynomial, see the program findbase.cpp
//


#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    int i,j,W,M,A,B,C,k1,k2,k3,k4,rs1,rs2,rs3,rs4,ls1,ls2,ls3,ls4;
    int ind[64],shift[64][8],xor,nxor,nsh;

	argc--; argv++;

    if (argc<3 || argc>5)
    {
        cout << "incorrect usage" << endl;
        cout << "Program generates reduction code for given irreducible polynomial" << endl;
        cout << "of the form x^M+x^A+1 or x^M+x^A+x^B+x^C+1 for odd M" << endl;
        cout << "for a computer with wordlength W" << endl;
        cout << "irp <W> <M> <A>" << endl;
        cout << "OR" << endl;
        cout << "irp <W> <M> <A> <B> <C>" << endl;
        cout << "The code that is generated can be copied into the" << endl;
        cout << "function reduce2 in the module mrecgf2m.c" << endl;
        return 0;
    }

    A=B=C=0;
    W=atoi(argv[0]);
    M=atoi(argv[1]);
	A=atoi(argv[2]);
    if (W<0 || W%8!=0 || A<1 || M-A < W) exit(0);

    if (argc==5)
    {
        B=atoi(argv[3]);
        C=atoi(argv[4]);
        if (B<1 || C<1 || B>A || C>B) exit(0);
    }

    k1=1+M/W;
    
    rs1=M%W;
    ls1=W-rs1;

    k2=1+(M-A)/W; 

    rs2=(M-A)%W;
    ls2=W-rs2;

    if (B)
    {
        k3=1+(M-B)/W;
        rs3=(M-B)%W;
        ls3=W-rs3;

        k4=1+(M-C)/W;
        rs4=(M-C)%W;
        ls4=W-rs4;
    }

    for (i=0;i<64;i++) ind[i]=0;
  
    if (rs1==0) 
    {
        shift[k1-1][ind[k1-1]]=0;
        ind[k1-1]++;
    }
    else
    {
        shift[k1-1][ind[k1-1]]=rs1;
        ind[k1-1]++;
        shift[k1][ind[k1]]=-ls1;
        ind[k1]++;
    }
    
    if (rs2==0) 
    {
        shift[k2-1][ind[k2-1]]=0;
        ind[k2-1]++;
    }
    else
    {
        shift[k2-1][ind[k2-1]]=rs2;
        ind[k2-1]++;
        shift[k2][ind[k2]]=-ls2;
        ind[k2]++;
    }

    if (B)
    {
        if (rs3==0) 
        {
            shift[k3-1][ind[k3-1]]=0;
            ind[k3-1]++;
        }
        else
        {
            shift[k3-1][ind[k3-1]]=rs3;
            ind[k3-1]++;
            shift[k3][ind[k3]]=-ls3;
            ind[k3]++;
        }
        if (rs4==0) 
        {
            shift[k4-1][ind[k4-1]]=0;
            ind[k4-1]++;
        }
        else
        {
            shift[k4-1][ind[k4-1]]=rs4;
            ind[k4-1]++;
            shift[k4][ind[k4]]=-ls4;
            ind[k4]++;
        }
    }

    if (B) printf("    if (M==%d && A==%d && B==%d && C==%d)\n",M,A,B,C);
    else   printf("    if (M==%d && A==%d)\n",M,A);
  
    printf("    {\n        for (i=xl-1;i>=%d;i--)\n        {\n",k1);
    printf("            w=gx[i]; gx[i]=0;\n");

    nxor=0; nsh=0;
    for (i=0;i<64;i++)
    {
        if (ind[i]==0) continue;
        nxor++;
        printf("            gx[i-%d]^=",i);
        xor=0;
        for (j=0;j<ind[i];j++)
        {
            if (xor) printf("^");
            if (shift[i][j]==0) {printf("w"); xor++;}
            if (shift[i][j]>0) {printf("(w>>%d)",shift[i][j]); xor++; nsh++; }
            if (shift[i][j]<0) {printf("(w<<%d)",-shift[i][j]); xor++; nsh++;}
        }

        nxor+=(xor-1);
        printf(";\n");  
    }
    printf("        }   /* XORs= %d shifts= %d */\n",nxor,nsh);
    printf("        top=gx[%d]>>%d;\n",k1-1,rs1);
    printf("        gx[0]^=top;\n");
    printf("        top<<=%d;\n",rs1);

    for (i=0;i<64;i++) ind[i]=0;

    if (rs2==0) 
    {
        shift[k1-k2][ind[k1-k2]]=0;
        ind[k1-k2]++;
    }
    else
    {
        shift[k1-k2][ind[k1-k2]]=rs2;
        ind[k1-k2]++;
        if (k1>k2)
        {
            shift[k1-k2-1][ind[k1-k2-1]]=-ls2;
            ind[k1-k2-1]++;
        }
    }

    if (B)
    {
        if (rs3==0) 
        {
            shift[k1-k3][ind[k1-k3]]=0;
            ind[k1-k3]++;
        }
        else
        {
            shift[k1-k3][ind[k1-k3]]=rs3;
            ind[k1-k3]++;
            if (k1>k3)
            {
                shift[k1-k3-1][ind[k1-k3-1]]=-ls3;
                ind[k1-k3-1]++;
            }
        }
        if (rs4==0) 
        {
            shift[k1-k4][ind[k1-k4]]=0;
            ind[k1-k4]++;
        }
        else
        {
            shift[k1-k4][ind[k1-k4]]=rs4;
            ind[k1-k4]++;
            if (k1>k4)
            {
                shift[k1-k4-1][ind[k1-k4-1]]=-ls4;
                ind[k1-k4-1]++;
            }
        }
    }

    for (i=0;i<64;i++)
    {
        if (ind[i]==0) continue;
        printf("        gx[%d]^=",i);
        xor=0;
        for (j=0;j<ind[i];j++)
        {
            if (xor) printf("^");
            if (shift[i][j]==0) {printf("top");  xor++;}
            if (shift[i][j]>0) {printf("(top>>%d)",shift[i][j]); xor++;}
            if (shift[i][j]<0) {printf("(top<<%d)",-shift[i][j]); xor++;}
        }
        printf(";\n");
    }

    printf("        gx[%d]^=top;\n",k1-1);
    printf("        x->len=%d;\n",k1); 
    printf("        if (gx[%d]==0) mr_lzero(x);\n",k1-1);
    printf("        return;\n    }\n");
	return 0;
}
