/*
 *   MIRACL arithmetic routines 0 - Add and subtract routines 
 *   mrarth0.c
 *
 *   Copyright (c) 1988-1995 Shamus Software Ltd.
 */
#include "miracl.h"

void mr_padd(_MIPD_ big x,big y,big z)
{ /*  add two  big numbers, z=x+y where *
   *  x and y are positive              */
    int i,lx,ly,lz,la;
    mr_small carry,psum;
    mr_small *gx,*gy,*gz; 
#ifdef MR_OS_THREADS
    miracl *mr_mip=get_mip();
#endif
    lx = (int)x->len;
    ly = (int)y->len;
    
    if (ly>lx)
    {
        lz=ly;
        la=lx;
        if (x!=z) copy(y,z); 
        else la=ly;  
    }
    else
    {
        lz=lx;
        la=ly;
        if (y!=z) copy(x,z);
        else la=lx;
    }
    carry=0;
    z->len=lz;
    gx=x->w; gy=y->w; gz=z->w;
    if (lz<mr_mip->nib || !mr_mip->check) z->len++;
    if (mr_mip->base==0) 
    {
        for (i=0;i<la;i++)
        { /* add by columns to length of the smaller number */
            psum=gx[i]+gy[i]+carry;
            if (psum>gx[i]) carry=0;
            else if (psum<gx[i]) carry=1;
            gz[i]=psum;
        }
        for (;i<lz && carry>0;i++ )
        { /* add by columns to the length of larger number (if there is a carry) */
            psum=gx[i]+gy[i]+carry;
            if (psum>gx[i]) carry=0;
            else if (psum<gx[i]) carry=1;
            gz[i]=psum;
        }
        if (carry)
        { /* carry left over - possible overflow */
            if (mr_mip->check && i>=mr_mip->nib)
            {
                mr_berror(_MIPP_ MR_ERR_OVERFLOW);
                return;
            }
            gz[i]=carry;
        }
    }
    else
    {
        for (i=0;i<la;i++)
        { /* add by columns */
            psum=gx[i]+gy[i]+carry;
            carry=0;
            if (psum>=mr_mip->base)
            { /* set carry */
                carry=1;
                psum-=mr_mip->base;
            }
            gz[i]=psum;
        }
        for (;i<lz && carry>0;i++)
        {
            psum=gx[i]+gy[i]+carry;
            carry=0;
            if (psum>=mr_mip->base)
            { /* set carry */
                carry=1;
                psum-=mr_mip->base;
            }
            gz[i]=psum;
        }
        if (carry)
        { /* carry left over - possible overflow */
            if (mr_mip->check && i>=mr_mip->nib)
            {
                mr_berror(_MIPP_ MR_ERR_OVERFLOW);
                return;
            }
            gz[i]=carry;
        }
    }

    if (gz[z->len-1]==0) z->len--;

}

void mr_psub(_MIPD_ big x,big y,big z)
{  /*  subtract two big numbers z=x-y      *
    *  where x and y are positive and x>y  */
    int i,lx,ly;
    mr_small borrow,pdiff;
    mr_small *gx,*gy,*gz;
#ifdef MR_OS_THREADS
    miracl *mr_mip=get_mip();
#endif
    lx = (int)x->len;
    ly = (int)y->len;
    if (ly>lx)
    {
        mr_berror(_MIPP_ MR_ERR_NEG_RESULT);
        return;
    }
    if (y!=z) copy(x,z);
    else ly=lx;
    z->len=lx;
    gx=x->w; gy=y->w; gz=z->w;
    borrow=0;
    if (mr_mip->base==0) for (i=0;i<ly || borrow>0;i++)
    { /* subtract by columns */
        if (i>lx)
        {
            mr_berror(_MIPP_ MR_ERR_NEG_RESULT);
            return;
        }
        pdiff=gx[i]-gy[i]-borrow;
        if (pdiff<gx[i]) borrow=0;
        else if (pdiff>gx[i]) borrow=1;
        gz[i]=pdiff;
    }
    else for (i=0;i<ly || borrow>0;i++)
    { /* subtract by columns */
        if (i>lx)
        {
            mr_berror(_MIPP_ MR_ERR_NEG_RESULT);
            return;
        }
        pdiff=gy[i]+borrow;
        borrow=0;
        if (gx[i]>=pdiff) pdiff=gx[i]-pdiff;
        else
        { /* set borrow */
            pdiff=mr_mip->base+gx[i]-pdiff;
            borrow=1;
        }
        gz[i]=pdiff;
    }
    mr_lzero(z);
}

static void mr_select(_MIPD_ big x,int d,big y,big z)
{ /* perform required add or subtract operation */
    int sx,sy,sz,jf,xgty;
#ifdef MR_FLASH
    if (mr_notint(x) || mr_notint(y))
    {
        mr_berror(_MIPP_ MR_ERR_INT_OP);
        return;
    }
#endif
    sx=exsign(x);
    sy=exsign(y);
    sz=0;
    x->len&=MR_OBITS;  /* force operands to be positive */
    y->len&=MR_OBITS;
    xgty=mr_compare(x,y);
    jf=(1+sx)+(1+d*sy)/2;
    switch (jf)
    { /* branch according to signs of operands */
    case 0:
        if (xgty>=0)
            mr_padd(_MIPP_ x,y,z);
        else
            mr_padd(_MIPP_ y,x,z);
        sz=MINUS;
        break;
    case 1:
        if (xgty<=0)
        {
            mr_psub(_MIPP_ y,x,z);
            sz=PLUS;
        }
        else
        {
            mr_psub(_MIPP_ x,y,z);
            sz=MINUS;
        }
        break;
    case 2:
        if (xgty>=0)
        {
            mr_psub(_MIPP_ x,y,z);
            sz=PLUS;
        }
        else
        {
            mr_psub(_MIPP_ y,x,z);
            sz=MINUS;
        }
        break;
    case 3:
        if (xgty>=0)
            mr_padd(_MIPP_ x,y,z);
        else
            mr_padd(_MIPP_ y,x,z);
        sz=PLUS;
        break;
    }
    if (sz<0) z->len^=MR_MSBIT;         /* set sign of result         */
    if (x!=z && sx<0) x->len^=MR_MSBIT; /* restore signs to operands  */
    if (y!=z && y!=x && sy<0) y->len^=MR_MSBIT;
}

void add(_MIPD_ big x,big y,big z)
{  /* add two signed big numbers together z=x+y */
#ifdef MR_OS_THREADS
    miracl *mr_mip=get_mip();
#endif
    if (mr_mip->ERNUM) return;

    MR_IN(27)

    mr_select(_MIPP_ x,PLUS,y,z);

    MR_OUT
}

void subtract(_MIPD_ big x,big y,big z)
{ /* subtract two big signed numbers z=x-y */
#ifdef MR_OS_THREADS
    miracl *mr_mip=get_mip();
#endif
    if (mr_mip->ERNUM) return;

    MR_IN(28)

    mr_select(_MIPP_ x,MINUS,y,z);

    MR_OUT
}

void incr(_MIPD_ big x,int n,big z)
{  /* add int to big number: z=x+n */
#ifdef MR_OS_THREADS
    miracl *mr_mip=get_mip();
#endif
    if (mr_mip->ERNUM) return;

    MR_IN(7)

    convert(_MIPP_ n,mr_mip->w0);
    mr_select(_MIPP_ x,PLUS,mr_mip->w0,z);

    MR_OUT
}

void decr(_MIPD_ big x,int n,big z)
{  /* subtract int from big number: z=x-n */   
#ifdef MR_OS_THREADS
    miracl *mr_mip=get_mip();
#endif
    if (mr_mip->ERNUM) return;

    MR_IN(8)

    convert(_MIPP_ n,mr_mip->w0);
    mr_select(_MIPP_ x,MINUS,mr_mip->w0,z);

    MR_OUT
}

