#include "lcrypt/vlong_math.h"

/* *******************************************************************************************************
   сложение двух длинных чисел
   Параметры:
       a - 1-е слагаемое (массив, little-endian)
       a_size - размер a
       b - 2-е слагаемое (массив, little-endian)
       с - возвращает сумма (массив, little-endian)
            может быть = a, если a не нужно
   Результат
       c_size
***********************************************************************************************************/
ind_t vlong_add(const long_t *a, ind_t a_size, const long_t* b, ind_t b_size, long_t *c)
{
    const long_t *a1;//, *b1;
    ind_t i, min_size, max_size;
    tmp_t temp;
    int carry = 0;

    if (a_size >= b_size)
    {
        a1 = a; max_size = a_size;
        min_size = b_size;
    }
    else
    {
        a1 = b; max_size = b_size;
        min_size = a_size;
    }

    for (i=0; i < min_size; i++)
    {
        temp = a[i] + b[i] + carry;
        if (temp >= BASE)
        {
            c[i] = (long_t)(temp - BASE);
            carry = 1;
        }
        else
        {
            c[i] = (long_t)temp;
            carry = 0;
        }
    }

    for (i=min_size; i < max_size; i++)
    {
        temp = a1[i] + carry;
        if (temp >= BASE)
        {
            c[i] = (long_t)(temp - BASE);
            carry = 1;
        }
        else
        {
            c[i] = (long_t)temp;
            carry = 0;
        }
    }

    if (carry)
    {
        c[i] = carry;
        return max_size + 1;
    }
    else
        return max_size;
}




/* *******************************************************************************************************
   Умножение длинного числа на простое
   Параметры:
       a - 1-ый множитель (массив, little-endian)
       a_size - размер a
       b - 2-ой множитель (простое число типа long_t ( должно быть в пределах BASE??? - посмотреть)
       с - возвращает произведение (массив, little-endian)
            может быть = a, если a не нужно
   Результат
       c_size
***********************************************************************************************************/
ind_t vlong_smul(const long_t *a, ind_t a_size, long_t b, long_t *c)
{
    ind_t i;
    tmp_t temp;
    int carry = 0;

    for (i=0; i < a_size; i++)
    {
        temp = (tmp_t)a[i]*b + carry;
        carry = temp/BASE;
        c[i] = (long_t)(temp - carry*BASE);
    }

    if (carry)
    {
        c[i] = carry;
        return a_size + 1;
    }
    else
        return a_size;
}




/* *******************************************************************************************************
   Умножение длинных чисел
   Параметры:
       a - 1-ый множитель (массив, little-endian)
       a_size - размер a
       b - 2-ой множитель (массив, little-endian)
       с - возвращает произведение (массив, little-endian)
            должен быть отдельный массив! - нельзя использовать a или b!!!
   Результат
       c_size
***********************************************************************************************************/
ind_t vlong_mul(const long_t *a, ind_t a_size, const long_t *b, ind_t b_size, long_t *c)
{
    ind_t i,j;
    int carry = 0;
    tmp_t temp;

    for (i=0; i < (a_size + b_size); i++)
        c[i] = 0;

    for (i=0; i < a_size; i++)
    {
        carry = 0;

        for (j=0; j < b_size; j++)
        {
            temp = (tmp_t)a[i]*b[j] + c[i+j] + carry;
            carry = temp/BASE;
            c[i+j] = (long_t)(temp - carry*BASE);
        }
        c[i+j] = carry;
    }

    i = a_size + b_size - 1;
    if (c[i] == 0 )
        i--;
    return i+1;
}


/* *******************************************************************************************************
    Деление длинного целого на простое число
   Параметры:
       a - делимое (массив, little-endian)
       a_size - размер a
       b - делитель (массив, little-endian)
       q - возвращает частное (мб NULL если нужен только остаток)
       q_size - возвращает размер частного (мб NULL)
       r1 - возвращает остаток (указатель на число slong_t)
   Результат
        q_size
***********************************************************************************************************/

ind_t vlong_sdiv(const long_t *a, ind_t a_size, slong_t b, long_t *q, slong_t* r1)
{
    tmp_t temp;
    slong_t r;
    ind_t i;
    r = 0;

    for (i= a_size; i >= 0; i--)
    {
        temp = (tmp_t)r*BASE + a[i];

        if (q!=NULL)
            q[i] = (long_t)(temp/ b);
        r = temp - q[i]*b;
    }

    (*r1) = r;
    i = a_size - 1;
    while ((i>0)&&(q[i]==0))
        i--;
    return (i+1);
}


/* *******************************************************************************************************
    Деление двух длинных чисел
   Параметры:
       a - делимое (массив, little-endian)
       a_size - размер a
       b - делитель (массив, little-endian)
       b_size - размер b
       q - возвращает частное (мб NULL если нужен только остаток)
       q_size - возвращает размер частного (мб NULL)
       u - возвращает остаток (обязательно не NULL, т.к. используется для вычислений
            может быть равен a, если a не надо сохранить
       pu_size - возвращает размер u

   Результат
        q_size, если указатель q и q_size != NULL, иначе 0
***********************************************************************************************************/
ind_t vlong_div(const long_t *a, ind_t a_size, long_t* b, ind_t b_size, long_t *q, ind_t *q_size, long_t *u, ind_t *pu_size)
{
    ind_t n, m, u_size;
    ind_t uJ, vJ, i;
    tmp_t temp1, temp2, temp;

    long_t scale;
    tmp_t qGuess;
    tmp_t r;
    int borrow, carry;

    n = b_size;
    u_size = a_size + 1;
    m = a_size - b_size;


    if (a_size < b_size)
    {
        if ((u!=NULL)&&(u!=a))
        {
            for (i=0; i < a_size; i++)
                u[i] = a[i];
        }
        if (pu_size!=NULL)
            (*pu_size) = a_size;

        return 0;
    }

    for (i=0; i < u_size-1; i++)
        u[i] = a[i];
    u[u_size-1] = 0;

    //нормализация
    scale = (long_t)((tmp_t)BASE/ (b[n-1] + 1));
    if (scale > 1)
    {
        u_size = vlong_smul(u, u_size, scale, u);
        b_size = vlong_smul(b, b_size, scale, b);
    }

    for (vJ = m, uJ = n + vJ; vJ >= 0; --vJ, --uJ)
    {
        long_t *    shift;
        qGuess = ((tmp_t)u[uJ] * BASE + u[uJ - 1]) / b[n-1];
        r = ((tmp_t)u[uJ]*BASE + u[uJ-1]) % b[n-1];

        //пока на будут выполнены условия (2) -> уменьшать частное
        while (r < BASE)
        {
            temp2 = (tmp_t)b[n-2]*qGuess;
            temp1 = (tmp_t)r*BASE + u[uJ-2];

            if ((temp2 > temp1) || (qGuess==BASE))
            {
                --qGuess;
                r+=b[n-1];
            }
            else break;
        }

        //qGuess - правильное частное или на 1 больше  й
        //вычесть делитель b, умноженный на qGuess из делимого u, начиная с vJ + i
        carry = 0; borrow = 0;
            shift = u + vJ;

        for (i=0; i<n; i++)
        {
            temp1 = (tmp_t)b[i]*qGuess + carry;
            carry = temp1/ BASE;
            temp1 -= carry*BASE;

            //сразу же вычесть из U
            temp2 = (tmp_t)    shift[i] - temp1 + borrow;
            if (temp2 < 0)
            {
                shift[i] = (long_t)(temp2 + BASE);
                borrow = -1;
            }
            else
            {
                shift[i] = (long_t)(temp2);
                borrow = 0;
            }
        }

        //если B*qGuess удлинилось => после умножения остался неиспользованный carry
        // => вычесть
        temp2 = (tmp_t)    shift[i] - carry + borrow;
        if (temp2 < 0)
        {
            shift[i] = (long_t)(temp2 + BASE);
            borrow = -1;
        }
        else
        {
            shift[i] = (long_t)temp2;
            borrow = 0;
        }

        //Прошло ли вычитание нормально?
        if (borrow == 0) //частное угадано
        {
            if (q!=NULL)
                q[vJ] = (long_t)qGuess;
        }
        else //нет - последний перенос при вычитании borrow = -1 => qGuess на 1 больше
        {
            if (q!=NULL)
                q[vJ] = (long_t)(qGuess-1);

            //добавить одно, вычтенно сверх необходимого B к U
            carry = 0;
            for (i=0; i < n; i++)
            {
                temp = (tmp_t)    shift[i] +(tmp_t)b[i] + carry;
                if (temp >= BASE)
                {
                    temp-=BASE;
                    shift[i] = (long_t)temp;
                    carry = 1;
                }
                else
                {
                    shift[i] = (long_t)temp;
                    carry = 0;
                }
            }
                shift[i] = (long_t)((tmp_t)    shift[i] + carry - BASE);
        }

        //обновляем размер U
        i = u_size - 1;
        while ((i>0) && (u[i]==0))
            i--;
        u_size = i+1;
    }

    //деление завершено

    //размер частного m+1, но м.б. первая цифра - 0
    if ((q!=NULL)&&(q_size!=NULL))
    {
        while ((m>0) && (q[m]==0))
            m--;
        (*q_size) = m+1;
    }

    if (scale > 1)
    {
        slong_t junk;
        b_size = vlong_sdiv(b, b_size, scale, b, &junk);
        u_size = vlong_sdiv(u, u_size, scale, u, &junk);
    }
    (*pu_size) = u_size;
    if (q_size!=NULL)
        return (*q_size);
    else
        return 0;
}






/*************************************************************************************************
   возведение длинного числа в степень в виде 32-разр по модулю n (длинное число)
   Параметры:
       a - исходное число  (массив, little-endian)
       a_size - размер a
       e - степень, в которую будет возведено число
       n - модуль по которому берется результат (массив, little-endian)
            может быть NULL если нужно просто возвести в степень
       n_size - размер n
       pow - релультат возведения в степень (массив, little-endian)
       t, buf - вспомогательные массивы (должны быть размера хотя бы a_size*2)
            a и t могут быть одним буфером, если не надо сохранить a
   Результат
        размер pow
****************************************************************************************************/

ind_t vlong_fast_spow_mod(const long_t *a, ind_t a_size, uint32_t e, long_t *n, ind_t n_size, long_t *pow, long_t *t, long_t *buf)
{
    ind_t i;
    ind_t pow_size = 1;
    ind_t t_size = a_size;

    if (a!=t)
    {
        for (i=0; i < a_size; i++)
            t[i] = a[i];
    }

    for (i=0; i < n_size; i++)
        pow[i] = 0;
    pow[0]=1;

    while (e)
    {
        if (e&1)
        {
            pow_size = vlong_mul(pow, pow_size, t, t_size, buf);
            for (i=0; i < pow_size; i++)
                pow[i] = buf[i];
            if (n!=NULL)
                vlong_div(pow, pow_size, n, n_size, 0, 0, pow, &pow_size);
        }
        t_size = vlong_mul(t, t_size, t, t_size, buf);
        for (i=0; i < t_size; i++)
            t[i] = buf[i];
        if (n!=NULL)
            vlong_div(t, t_size, n, n_size, buf, 0, t, &t_size);
        e >>= 1;
//        WdtClr();
    }
    return pow_size;
}
