using System;
//using System.Collections.Generic;
//using System.Text;

namespace MyCompany.Server
{
    /*Třída pro implementaci některých matematických operací:
     *      - sčítání
     *      - odčítání (jen pomocné)
     *      - násobení
     *      - Montgomeryho algoritmus pro násobení s modulo operací
     */

    class IntMath
    {
        //Proměná určující, zda jsou porovnávaná čísla stejné
        private bool stejne;
        private bool rotace;

        private void setRotace(bool rotace)
        {
            this.rotace = rotace;
        }
        private bool getRotace()
        {
            return this.rotace;
        }

        private void setStejne(bool stejne)
        {
            this.stejne = stejne;
        }
        private bool getStejne()
        {
            return this.stejne;
        }

        //----------------------------------------------------------------------------
        //Metoda sloužící pro součet dvou velkých čísel
        //----------------------------------------------------------------------------
        public int[] SoucetInt(int[] a, int[] b)
        {
            int c = 0;                           //proměnná značící přenos
            int pom = 0;

            //Zjištění délek jednotlivých čísel
            int delkaA = a.Length;
            int delkaB = b.Length;

            int difference = 0;

            //Proměnná do které se uloží výsledek operace sčítání
            int[] vysledek;

            //Inicializujeme proměnnou 'vysledek' na velikost, která je rovna 
            //   delšímu z čísel
            if (delkaA > delkaB)
            {
                vysledek = new int[delkaA + 1];
            }
            else
            {
                vysledek = new int[delkaB + 1];
            }

            //Vynulujeme proměnnou 'vysledek'
            for (int i = 0; i <= vysledek.Length - 1; i++)
            {
                vysledek[i] = 0;
            }

            //Jestlize je jedno číslo uložené v 'a' větší nebo rovno číslu v 'b'..
            if (delkaA >= delkaB)
            {
                difference = delkaA - delkaB;

                //Součet jednotlivých číslic
                for (int i = delkaA - 1; i >= 0; i--)
                {
                    if (i - difference >= 0)
                    {
                        //pomB = b[i - difference] - '0';
                        pom = a[i] + b[i - difference] + c;
                    }
                    else
                    {
                        //Proměnná 'b[i]' je rovna nule
                        pom = a[i] + c;
                    }

                    if (pom >= 10)
                    {
                        c = 1;
                        pom = pom - 10;
                    }
                    else
                    {
                        c = 0;
                    }
                    vysledek[i + 1] = pom;
                }
            }
            else
            {
                difference = delkaB - delkaA;
                //Součet jednotlivých číslic
                for (int i = delkaB - 1; i >= 0; i--)
                {
                    if (i - difference >= 0)
                    {
                        pom = a[i - difference] + b[i] + c;
                    }
                    else
                    {
                        //Proměnná 'a[i]' je rovna nule
                        pom = b[i] + c;
                    }

                    if (pom >= 10)
                    {
                        c = 1;
                        pom = pom - 10;
                    }
                    else
                    {
                        c = 0;
                    }
                    vysledek[i + 1] = pom;
                }
            }

            if (c == 1)
            {
                //Jestliže je nastavená proměnná 'c' (proměnná značící přenos), 
                //   ulož ji na nultou pozici proměnné 'vysledek'
                //   proměnnou 'vysledek' poté navrať jako výsledke součtu
                vysledek[0] = c;
                return vysledek;
            }

            //Jestliže není proměnná 'c' nastavená na jedničku, odtsraň
            //   počáteční nulu a ulož výsledné číslo do proměnné 'vysledekPom',
            //   kterou poté navrať jako výsledek operace součtu
            int[] vysledekPom = RedukceInt(vysledek);

            return vysledekPom;
        }

        //----------------------------------------------------------------------------
        //Metoda sloužící pro odečtení dvou velkých čísel
        //----------------------------------------------------------------------------
        public int[] RozdilInt(int[] a, int[] b)
        {
            int c = 0;                           //proměnná značící přenos
            int pom = 0;
            int difference = 0;

            //Proměnná 'rotace' je nastavena na true, jakmile dojde k záměně čísel
            //   ('a' se uloží do 'b' a naopak)
            //   - značí, že výsledné číslo je záporné
            setRotace(false);

            //Zjištění délek jednotlivých čísel
            int delkaA = a.Length;
            int delkaB = b.Length;

            int[] vysledek;

            //Inicializujeme proměnnou 'vysledek' na velikost, která je rovna 
            //   delšímu z čísel
            if (delkaA > delkaB)
            {
                vysledek = new int[delkaA + 1];
            }
            else
            {
                vysledek = new int[delkaB + 1];
            }

            //Vynulujeme proměnnou 'vysledek'
            for (int i = 0; i <= vysledek.Length - 1; i++)
            {
                vysledek[i] = 0;
            }

            //Jestlize je jedno číslo delší než jiné .. (odečti to delší od toho kratšího)
            if (delkaA > delkaB)
            {
                //O kolik se liší jednotlivá čísla
                difference = delkaA - delkaB;

                //Rozdíl jednotlivých číslic
                for (int i = delkaA - 1; i >= 0; i--)
                {
                    if (i - difference >= 0)
                    {
                        pom = a[i] - b[i - difference] + c;
                    }
                    else
                    {
                        //proměnná 'b[i]' je rovna nule
                        pom = a[i] + c;
                    }

                    if (pom >= 0)
                    {
                        c = 0;
                    }
                    else
                    {
                        c = -1;
                        pom += 10;
                    }
                    vysledek[i + 1] = pom;
                }
            }
            else if (delkaB > delkaA)
            {
                difference = delkaB - delkaA;
                //Rozdíl jednotlivých číslic
                for (int i = delkaB - 1; i >= 0; i--)
                {
                    if (i - difference >= 0)
                    {
                        pom = b[i] - a[i - difference] + c;
                    }
                    else
                    {
                        //proměnná 'a[i]' je rovan nule
                        pom = b[i] + c;
                    }

                    if (pom >= 0)
                    {
                        c = 0;
                    }
                    else
                    {
                        c = -1;
                        pom += 10;
                    }
                    vysledek[i + 1] = pom;
                }
            }
            else if (delkaB == delkaA)
            {
                
                
                //Pokud mají čísla stejnou délku, ale číslo v proměnné 'b' je větší,
                //   tak je navzájem zaměň ('a' ulož do 'b' a naopak)
                if (b == UrciVetsiInt(a, b))
                {
                    int[] tmp;

                    tmp = b;
                    b = a;
                    a = tmp;
                    setRotace(true);
                }

                //Proveď rozdíl těchto dvou čísel
                for (int i = delkaA - 1; i >= 0; i--)
                {
                    pom = a[i] - b[i] + c;

                    if (pom >= 0)
                    {
                        c = 0;
                    }
                    else
                    {
                        c = -1;
                        pom += 10;
                    }
                    vysledek[i + 1] = pom;
                }
            }
            return RedukceInt(vysledek);
        }

        //----------------------------------------------------------------------------
        //Metoda sloužící pro porovnání čísel
        //----------------------------------------------------------------------------
        public int[] UrciVetsiInt(int[] a, int[] b)
        {
            //Úprava velikosti vstupního čísla 'a'
            int[] aReduced;
            if (a[0] == 0)
            {
                aReduced = RedukceInt(a);
            }
            else
            {
                aReduced = new int[a.Length];
                aReduced = a;
            }

            //Úprava velikosti vstupního čísla 'b'
            int[] bReduced;
            if (b[0] == 0)
            {
                bReduced = RedukceInt(b);
            }
            else
            {
                bReduced = new int[b.Length];
                bReduced = b;
            }

            //Zjisti délku jednotlivých čísel
            int delkaA = aReduced.Length;
            int delkaB = bReduced.Length;

            //Porovnáni velikosti čísel na základě jejich délky, jestliže jsou
            //   stejně dlouhá, budou se porovnávat jednotlivé znaky   
            if (delkaA > delkaB)
            {
                //Jestliže je číslo v proměnné 'a' delší, vrať jeho hodnotu
                return a;
            }
            else if (delkaB > delkaA)
            {
                //Jestliže je číslo v proměnné 'b' delší, vrať jeho hodnotu
                return b;
            }
            else
            {
                for (int i = 0; i < delkaA; i++)
                {
                    if (aReduced[i] > bReduced[i])
                    {
                        //Jestliže je číslice na i-té pozici v 'a' větší nežli
                        //   číslice na i-té pozici v 'b', vrať číslo 'a'
                        return a;
                    }
                    else if (aReduced[i] < bReduced[i])
                    {
                        //Jestliže je číslice na i-té pozici v 'b' větší nežli
                        //   číslice na i-té pozici v 'a', vrať číslo 'b'
                        return b;
                    }
                    if ((delkaA - 1) == i)
                    {
                        //Jakmile se projdou všechny číslice, jsou obě stejná
                        //   a je jedno které z těchto dvou se navrátí
                        return a;
                    }
                }
                //Na toto místo by algoritmus neměl nikdy dojít ..
                return a;
            }
        }

        //----------------------------------------------------------------------------
        //Metoda pro odstranění nul ze začátku čísla
        //----------------------------------------------------------------------------
        public int[] RedukceInt(int[] a)
        {
            int pocetNul = 0;
            int[] aReduced;
            int difference = 0;

            //Jestliže je první znak nula..
            if (a[0] == 0)
            {
                //..spočítej počet nul na začátku čísla
                for (int i = 0; i < a.Length - 1; i++)
                {
                    if ((i == 0 && a[0] == 0) || (a[0] == 0 && a[i] == 0))
                    {
                        pocetNul++;
                    }
                    else
                    {
                        break;
                    }
                }
                //Proměnná 'aReduced' bude mít stejnou délku jako proměnná 'a'
                //   s odečtením nul na začátku (proměnná 'pocetNul')
                aReduced = new int[a.Length - pocetNul];
                difference = a.Length - aReduced.Length;
                //Překopíruj proměnnou 'a' do 'aReduced', kromě nul na začátku proměnné 'a'
                for (int i = aReduced.Length - 1; i >= 0; i--)
                {
                    aReduced[i] = a[i + difference];
                }
            }
            else
            {
                //Když není první znak nula, tak vrať jako výsledek proměnnou 'a'
                aReduced = new int[a.Length];
                aReduced = a;
            }
            return aReduced;
        }

        //----------------------------------------------------------------------------
        //Metoda pro násobení dvou velkých čísel
        //----------------------------------------------------------------------------
        public int[] NasobeniInt(int[] a, int[] b)
        {
            int c = 0;                      //proměnná značící přenos
            int pom = 0;                    //(u*v)_10

            int delkaA = a.Length;          //n+1 čísel
            int delkaB = b.Length;          //t+1 čísel

            int[] vysledek = new int[delkaA + delkaB];

            //Vynulujeme výstupní proměnnou
            for (int i = 0; i <= (delkaA + delkaB - 1); i++)
            {
                vysledek[i] = 0;
            }

            //Násobení na principu "tužka a papír"
            for (int i = delkaB - 1; i >= 0; i--)
            {
                c = 0;
                for (int j = delkaA - 1; j >= 0; j--)
                {
                    //vynásobené číslo se uloží do proměnné 'pom'
                    //   (u*v)_10 = w_ij + (a_j * b_i) + c 
                    pom = vysledek[i + j + 1] + a[j] * b[i] + c;

                    //v proměnné 'pom' je uložena proměnná 'c' na pozici 0 - přenos
                    //   a výsledek po násobení, se kterým se bude dále pracovat (pozice 1)
                    c = pom / 10;
                    vysledek[i + j + 1] = pom % 10;
                }
                vysledek[i] = c;
            }

            return vysledek;
        }

        //----------------------------------------------------------------------------
        //Montgomeryho algoritmus pro násobení s modulem
        //----------------------------------------------------------------------------
        public int[] MontgomeryInt(int[] x, int[] y, int[] mMod, int mAP)
        {
            int[] xPom = new int[1] { 0 };              //pomocná proměnná pro uložení jednoho znaku
            int pomX = 0;                               //to samé co proměnná xPom jen v intu
            int pomALast = 0;                           //poslední znak v A

            //Inicializace proměnných
            int[] u = new int[1] { 0 };
            int[] xiy = new int[mMod.Length + 2];
            int[] uim = new int[mMod.Length + 2];
            int[] ATemp;
            int[] A = new int[mMod.Length + 2];

            //Předpočítáme si některé proměnné
            int difference = mMod.Length - x.Length;    //Udává o kolik znaků se liší jednotlivá slova
            int pomY = y[y.Length - 1];                 //V proměnné je uložen poslední číslice v číslu 'y'

            //Vynulujeme výstupní proměnnou
            for (int i = A.Length - 1; i >= 0; i--)
            {
                A[i] = 0;
            }

            //Výpočet Montgomeryho algoritmu (násobení)
            for (int i = mMod.Length - 1; i >= 0; i--)
            {
                //Spočteme proměnnou 'xiy'
                //   Budeme brát postupně jednotlivé číslice ve vstupním čísle 'xi'
                //   a vynásobíme jej se vstupním slovem 'y'
                if (i - difference >= 0)
                {
                    pomX = x[i - difference];

                    if (pomX == 0)
                    {
                        //Jestliže je proměnná 'pomX' rovna nule, jen znuluj
                        //   hodnoty v 'xiy'
                        for (int j = xiy.Length - 1; j >= 0; j--)
                        {
                            xiy[j] = 0;
                        }
                    }
                    else if (pomX == 1)
                    {
                        //Jestliže je proměnná 'pomX' rovna jedné, jen překopíruj
                        //   hodnoty v 'y' do 'xiy'
                        int differenceXiY = xiy.Length - y.Length;
                        for (int j = xiy.Length - 1; j >= 0; j--)
                        {
                            if ((j - differenceXiY) >= 0)
                            {
                                xiy[j] = y[j - differenceXiY];
                            }
                            else
                            {
                                xiy[j] = 0;
                            }
                        }
                    }
                    else
                    {
                        xPom[0] = x[i - difference];
                        xiy = NasobeniInt(xPom, y);
                    }
                }
                else
                {
                    pomX = 0;
                    for (int j = xiy.Length - 1; j >= 0; j--)
                    {
                        xiy[j] = 0;
                    }
                }

                //2.1
                // u = (y_last + xi * yi) mod 10
                u[0] = ((pomALast + pomX * pomY) * mAP) % 10;

                //2.2  
                //Spočteme proměnnou 'uim'
                if (u[0] == 0)
                {
                    //Jestliže je proměnná 'u' rovna nule, jen znuluj
                    //   hodnoty v 'u'
                    for (int j = uim.Length - 1; j >= 0; j--)
                    {
                        uim[j] = 0;
                    }
                }
                else if (u[0] == 1)
                {
                    //Jestliže je proměnná 'u' rovna jedné, jen překopíruj
                    //   hodnoty v 'mMod' do 'uim'
                    int differenceUiM = uim.Length - mMod.Length;
                    for (int j = uim.Length - 1; j >= 0; j--)
                    {
                        if ((j - differenceUiM) >= 0)
                        {
                            uim[j] = mMod[j - differenceUiM];
                        }
                        else
                        {
                            uim[j] = 0;
                        }
                    }
                }
                else
                {
                    uim = NasobeniInt(u, mMod);
                }
                //ATemp = A + xiy + uim
                ATemp = SoucetInt(SoucetInt(A, xiy), uim);

                int differeceA = ATemp.Length - A.Length;
                //Vynulujeme proměnou A
                for (int j = 0; j <= A.Length - 1; j++)
                {
                    A[j] = 0;
                }
                //Do proměnné 'A' se uloží hodnoty z 'ATemp' kromě posledního čísla
                for (int j = ATemp.Length - 1; j > 0; j--)
                {
                    A[j - differeceA] = ATemp[j - 1];
                }
                //Proměnná 'ATemp' se přepíše nulami
                for (int j = 0; j <= ATemp.Length - 1; j++)
                {
                    ATemp[j] = 0;
                }

                //Do proměnné 'pomALast' se uloží poslední znak z 'A'
                pomALast = A[A.Length - 1];
            }
            //Jestliže je číslo v 'A' větší nežli 'mMod', proveď
            //   A = A - mMod
            if (UrciVetsiInt(A, mMod) == A)
            {
                A = RozdilInt(A, mMod);
            }
            //Odstraň nuly na začátku čísla 'A'
            int[] reducedA = RedukceInt(A);
            return reducedA;
        }
    }
}
