#ifndef INCLUDED_BOBCAT_BIGINT_
#define INCLUDED_BOBCAT_BIGINT_

#include <iosfwd>
#include <vector>

#include <openssl/bn.h>
#include <bobcat/exception>

namespace FBB
{

class BigInt
{
    friend std::ostream &operator<<(std::ostream &out, BigInt const &bn);

    BIGNUM *d_bn;

    enum
    {
        WordMask = (1 << BN_BYTES) - 1
    };

    public:
        using Word =  BN_ULONG;

        enum Msb
        {
            MSB_UNKNOWN = -1,
            MSB_IS_ONE,
            TOP_TWO_BITS_ONE
        };

        enum Lsb
        {
            EVEN,
            ODD,
        };

        enum PrimeType
        {
            ANY = false,
            SAFE = true
        };

        enum Little
        {};

        BigInt();                               //                  1
        BigInt(BigInt const &other);            //                  2
        BigInt(BigInt &&tmp);                   //                  3

        template<typename Type>                 // promotion OK     4.f
        BigInt(Type value);

        explicit BigInt(BIGNUM const &bignum);  //                  5
        explicit BigInt(BIGNUM const *bignum);  //                  6

                                                //                  7.f
        explicit BigInt(BIGNUM *bignum);        // .f   (avoids selection of
                                                //       the template)

        BigInt(char const *bigEndian, size_t length, bool negative = false);
                                                            //      8
        explicit BigInt(std::string const &bigEndian, bool negative = false);
                                                            //      9

        BigInt(size_t length, char const *littleEndian, bool negative = false);
                                                            //      10

        BigInt(Little endian,  std::string littleEndian,
                       bool negative = false);              //      11


        ~BigInt();

        BigInt &operator=(BigInt const &other); // opis.cc
        BigInt &operator=(BigInt &&tmp);        // opmovis.cc

        BigInt operator-() const;
        BigInt &negate();
        BigInt negatec() const;

        BigInt &setNegative(bool negative);
        BigInt setNegativec(bool negative) const;

        bool isNegative() const;                        // .f


        BigInt &tildeBits();
        BigInt tildeBitsc() const;

        BigInt &tildeInt();
        BigInt tildeIntc() const;

        BigInt &operator--();                   // opdec.f
        BigInt operator--(int);

        BigInt &operator++();                   // opinc.f
        BigInt operator++(int);


        class Bit;

        Bit operator[](size_t idx);             // opindex.f
                                                //  non-const BigInts:
                                                //  distinguishes lhs/rhs

        int operator[](size_t idx) const;       // opindexc.f
                                                //  only rhs for const BigInts

        class Bit
        {
            friend Bit BigInt::operator[](size_t idx);
            friend std::ostream &operator<<(std::ostream &out,
                                                            Bit const &bit);
            BigInt &d_bi;
            size_t d_idx;

            public:
                operator bool() const;
                Bit &operator=(bool rhs);       // assign   a bit
                Bit &operator&=(bool rhs);      // bit_and  a bit
                Bit &operator|=(bool rhs);      // bit_or   a bit
                Bit &operator^=(bool rhs);      // bit_xor  a bit

            private:
                Bit(BigInt &bi, size_t idx);
        };

        char *bigEndian() const;
        char *littleEndian() const;

        BigInt &operator+=(BigInt const &rhs);                  // opaddis.f
        BigInt &addMod(BigInt const &rhs, BigInt const &mod);   // .f
        BigInt addModc(BigInt const &rhs, BigInt const &mod) const;

        BigInt &operator-=(BigInt const &rhs);                  // opsubis.f
        BigInt &subMod(BigInt const &rhs, BigInt const &mod);   // .f
        BigInt subModc(BigInt const &rhs, BigInt const &mod) const;

        BigInt &operator*=(BigInt const &rhs);
        BigInt &mulMod(BigInt const &rhs, BigInt const &mod);   // .f
        BigInt mulModc(BigInt const &rhs, BigInt const &mod) const;

        BigInt &operator%=(BigInt const &rhs);
        BigInt &operator/=(BigInt const &rhs);      // integer division,

                                                    // integer division, also
                                                    // returning remainder
        BigInt &div(BigInt *remainder, BigInt const &rhs);
        BigInt divc(BigInt *remainder, BigInt const &rhs) const;

        BigInt &sqr();
        BigInt sqrc() const;

        BigInt &sqrMod(BigInt const &mod);                      // .f
        BigInt sqrModc(BigInt const &mod) const;

        BigInt &operator&=(BigInt const &rhs);
        BigInt &operator|=(BigInt const &rhs);
        BigInt &operator^=(BigInt const &rhs);

        bool isZero() const;                                    // .f
        bool isOne() const;                                     // .f
        bool isOdd() const;                                     // .f

        unsigned long ulong() const;                            // .f
        BIGNUM const &bignum() const;                           // .f

        size_t sizeInBytes() const;                             // .f
        size_t size() const;                                    // .f

        size_t nWords() const;                                  // .f
        static size_t constexpr sizeOfWord();                   // .f

        Word at(size_t index) const;
        void setWord(size_t index, Word value);

        int compare(BigInt const &other) const;                 // .f
        int uCompare(BigInt const &other) const;                // .f

        BigInt &exp(BigInt const &exponent);
        BigInt expc(BigInt const &exponent) const;
        BigInt &expMod(BigInt const &exponent, BigInt const &mod);
        BigInt expModc(BigInt const &exponent, BigInt const &mod) const;

        BigInt &gcd(BigInt const &rhs);
        BigInt gcdc(BigInt const &rhs) const;

        BigInt &inverseMod(BigInt const &mod);
        BigInt inverseModc(BigInt const &mod) const;

        BigInt &isqrt();
        BigInt isqrtc() const;

        static long long diophantus(long long *factor1, long long *factor2,
                                    long long const &value1,
                                    long long const &value2);
        static BigInt diophantus(BigInt *factor1, BigInt *factor2,
                                 BigInt const &value1, BigInt const &value2);

        static BigInt rand(size_t bitsSize,
                           Msb msb = MSB_IS_ONE, Lsb lsb = ODD);

        static BigInt randRange(BigInt const &max);

        static BigInt setBigEndian(std::string const &bytes);

        static BigInt pseudoRand(size_t bitsSize,                   // .f
                           Msb msb = MSB_IS_ONE, Lsb lsb = ODD);
        static BigInt pseudoRandRange(BigInt const &max);           // .f

        static BigInt prime(size_t nBits,
                            BigInt const *add = 0, BigInt const *rem = 0,
                            PrimeType primeType = ANY);


        static BigInt fromText(std::string const &text, int mode = 0);

        BigInt &clearBit(size_t index);
        BigInt clearBit(size_t index) const;

        bool hasBit(size_t index) const;                        // .f

        BigInt &maskBits(size_t lowerNBits);
        BigInt maskBitsc(size_t lowerNBits) const;

        BigInt &setBit(size_t index);
        BigInt setBitc(size_t index) const;

        BigInt &setBit(size_t index, bool value);
        BigInt setBitc(size_t index, bool value) const;

        BigInt &lshift();
        BigInt lshiftc() const;

        BigInt &lshift(size_t nBits);
        BigInt lshiftc(size_t nBits) const;

        BigInt &operator<<=(size_t nBits);                  // opshlis.f

        BigInt &rshift();
        BigInt rshiftc() const;

        BigInt &rshift(size_t nBits);
        BigInt rshiftc(size_t nBits) const;

        BigInt &operator>>=(size_t nBits);                  // opshris.f

        void swap(BigInt &other);

    private:
        void mod_inverse(BigInt *ret, BigInt const &mod) const;

        std::ostream &insertInto(std::ostream &out) const;
        static char *bn2oct(BIGNUM const *bn);

        void copy(BIGNUM *lhs, BIGNUM const &rhs);

        void nWordsCheck(size_t index) const;

        BigInt &checked1(
                int (*BN_op)(BIGNUM *,
                             BIGNUM const *, BIGNUM const *),
                BigInt const &rhs, char const *op);

        BigInt &checked2(int (*BN_op)(BIGNUM *,
                                           BIGNUM const *, BIGNUM const *,
                                           BIGNUM const *,
                                           BN_CTX *),
                              BigInt const &rhs, BigInt const &mod,
                              char const *op);

        void checked3(BIGNUM *div, BIGNUM *rem,
                                   BigInt const &rhs, char const *op) const;

        BigInt &checked4(int (*BN_op)(BIGNUM *,
                                     BIGNUM const *, BIGNUM const *,
                                     BN_CTX *),
                        BigInt const &rhs, char const *op);

        static void primeCallback(int reason, int primeNr, void *primeBase);
        static bool addDigit(char ch, BigInt &ret, BigInt const &radix,
                                                   int (*pConv)(int));
};

#include "bigint4.f"
#include "bigint7.f"


#include "nwords.f"
#include "addmod.f"
#include "bignum.f"
#include "compare.f"
#include "hasbit.f"
#include "isnegative.f"
#include "isodd.f"
#include "isone.f"
#include "iszero.f"
#include "mulmod.f"
#include "opaddis.f"
#include "opbool.f"
#include "opdec.f"
#include "opinc.f"
#include "opindex.f"
#include "opindexc.f"
#include "opshlis.f"
#include "opshris.f"
#include "opsubis.f"
#include "pseudorand.f"
#include "pseudorandrange.f"
#include "size.f"
#include "sizeinbytes.f"
#include "sizeofword.f"
#include "sqrmod.f"
#include "submod.f"
#include "ulong.f"
#include "ucompare.f"

// Free functions

BigInt operator*(BigInt const &lhs, BigInt const &rhs);
BigInt operator/(BigInt const &lhs, BigInt const &rhs);
BigInt operator%(BigInt const &lhs, BigInt const &rhs);
BigInt operator+(BigInt const &lhs, BigInt const &rhs);
BigInt operator-(BigInt const &lhs, BigInt const &rhs);
BigInt operator>>(BigInt const &lhs, size_t rhs);
BigInt operator<<(BigInt const &lhs, size_t rhs);
BigInt operator|(BigInt const &lhs, BigInt const &rhs);
BigInt operator&(BigInt const &lhs, BigInt const &rhs);
BigInt operator^(BigInt const &lhs, BigInt const &rhs);

BigInt gcd(BigInt const &lhs, BigInt const &rhs);
BigInt inverseMod(BigInt const &lhs, BigInt const &mod);

std::istream &operator>>(std::istream &out, BigInt &bn);

int isoctdigit(int ch);

    // convert to spaceship operator use?

#include "opisequal.f"              // ==
#include "opisunequal.f"            // !=

#include "opgreater.f"              // >
#include "opgreaterequal.f"         // >=
#include "opless.f"                 // <
#include "oplessequal.f"            // <=

#include "opinsert.f"               // ostream <<

}   // namespace FBB


#endif
