/*
 *   MIRACL small number theoretic routines 
 *   mrsmall.c
 *
 *   Copyright (c) 1988-1997 Shamus Software Ltd.
 */

#include "miracl.h"

#ifdef MR_FP
#include <math.h>
#endif

#ifdef MR_WIN64
#include <intrin.h>
#endif


mr_small smul(mr_small x,mr_small y,mr_small n)
{ /* returns x*y mod n */
    mr_small r;

#ifdef MR_ITANIUM
    mr_small tm;
#endif
#ifdef MR_WIN64
    mr_small tm;
#endif
#ifdef MR_FP
    mr_small dres;
#endif
#ifndef MR_NOFULLWIDTH
    if (n==0)
    { /* Assume n=2^MIRACL */
        muldvd(x,y,(mr_small)0,&r);
        return r;
    }
#endif
    x=MR_REMAIN(x,n);
    y=MR_REMAIN(y,n);
    muldiv(x,y,(mr_small)0,n,&r);
    return r;
}

mr_small invers(mr_small x,mr_small y)
{ /* returns inverse of x mod y */
    mr_small r,s,q,t,p;
#ifdef MR_FP
    mr_small dres;
#endif

    BOOL pos;
    if (y!=0) x=MR_REMAIN(x,y);
    r=1;
    s=0;
    p=y;
    pos=TRUE;
#ifndef MR_NOFULLWIDTH
    if (p==0)
    { /* if modulus is 0, assume its actually 2^MIRACL */
        if (x==1) return (mr_small)1;
        t=r; r=s; s=t;
        p=x;  
        q=muldvm((mr_small)1,(mr_small)0,p,&t);
        t=r+s*q;  r=s; s=t;
        t=0-p*q;  x=p; p=t; 
    }
#endif
    while (p!=0)
    { /* main euclidean loop */
        q=MR_DIV(x,p);
        t=r+s*q;  r=s; s=t;
        t=x-p*q;  x=p; p=t;
        pos=!pos;
    }
    if (!pos) r=y-r;
    return r;
}

int jac(mr_small x,mr_small n)
{ /* finds (x/n) as (-1)^m */ 
    int m,k,n8,u4;
    mr_small t;
#ifdef MR_FP
    mr_small dres;
#endif
    if (x==0)
    {
        if (n==1) return 1;
        else return 0;
    } 
    if (MR_REMAIN(n,2)==0) return 0;
    x=MR_REMAIN(x,n);   
    m=0;
    while(n>1)
    { /* main loop */
        if (x==0) return 0;

/* extract powers of 2 */
        for (k=0;MR_REMAIN(x,2)==0;k++) x=MR_DIV(x,2);
        n8=(int)MR_REMAIN(n,8);
        if (k%2==1) m+=(n8*n8-1)/8;

/* quadratic reciprocity */
        u4=(int)MR_REMAIN(x,4);
        m+=(n8-1)*(u4-1)/4;
        t=n;  t=MR_REMAIN(t,x);
        n=x;  x=t;
        m%=2;
    }
    if (m==0) return 1;
    else      return (-1);
} 

#ifndef MR_STATIC

mr_small spmd(mr_small x,mr_small n,mr_small m) 
{ /*  returns x^n mod m  */
    mr_small r,sx;
#ifdef MR_FP
    mr_small dres;
#endif
    x=MR_REMAIN(x,m);
    r=0;
    if (x==0) return r;
    r=1;
    if (n==0) return r;
    sx=x;
    forever
    {
        if (MR_REMAIN(n,2)!=0) muldiv(r,sx,(mr_small)0,m,&r);
        n=MR_DIV(n,2);
        if (n==0) return r;
        muldiv(sx,sx,(mr_small)0,m,&sx);
    }
}

mr_small sqrmp(mr_small x,mr_small m)
{ /* square root mod a small prime by Shanks method  *
   * returns 0 if root does not exist or m not prime */
    mr_small z,y,v,w,t,q;
#ifdef MR_FP
    mr_small dres;
#endif
    int i,e,n,r;
    BOOL pp;
    x=MR_REMAIN(x,m);
    if (x==0) return 0;
    if (x==1) return 1;
    if (spmd(x,(mr_small)((m-1)/2),m)!=1) return 0;    /* Legendre symbol not 1   */
    if (MR_REMAIN(m,4)==3) return spmd(x,(mr_small)((m+1)/4),m);  /* easy case for m=4.k+3   */
    if (MR_REMAIN(m,8)==5)
    { /* also relatively easy */
        t=spmd(x,(mr_small)((m-1)/4),m);
        if (t==1) return spmd(x,(mr_small)((m+3)/8),m);
        if (t==(mr_small)(m-1))
        {
            muldiv((mr_small)4,x,(mr_small)0,m,&t);
            t=spmd(t,(mr_small)((m+3)/8),m);
            muldiv(t,(mr_small)((m+1)/2),(mr_small)0,m,&t);
            return t;
        }
        return 0;
    }
    q=m-1;
    e=0;
    while (MR_REMAIN(q,2)==0) 
    {
        q=MR_DIV(q,2);
        e++;
    }
    if (e==0) return 0;      /* even m */
    for (r=2;;r++)
    { /* find suitable z */
        z=spmd((mr_small)r,q,m);
        if (z==1) continue;
        t=z;
        pp=FALSE;
        for (i=1;i<e;i++)
        { /* check for composite m */
            if (t==(m-1)) pp=TRUE;
            muldiv(t,t,(mr_small)0,m,&t);
            if (t==1 && !pp) return 0;
        }
        if (t==(m-1)) break;
        if (!pp) return 0;   /* m is not prime */
    }
    y=z;
    r=e;
    v=spmd(x,(mr_small)((q+1)/2),m);
    w=spmd(x,q,m);
    while (w!=1)
    {
        t=w;
        for (n=0;t!=1;n++) muldiv(t,t,(mr_small)0,m,&t);
        if (n>=r) return 0;
        y=spmd(y,mr_shiftbits(1,r-n-1),m);
        muldiv(v,y,(mr_small)0,m,&v);
        muldiv(y,y,(mr_small)0,m,&y);
        muldiv(w,y,(mr_small)0,m,&w);
        r=n;
    }
    return v;
}

#endif
