//-*-Mode:C++;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
// ex: set ft=cpp fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
//
// MIT License
//
// Copyright (c) 2009-2020 Michael Truog <mjtruog at protonmail dot com>
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
#include <stdint.h>
#include <string>
#include <boost/lexical_cast.hpp>
#include <gmp.h>
#include "piqpr8_gmp_verify.hpp"

class HexadecimalPiTestData
{
    public:
        static bool verify(std::string const & digit,
                           std::string const & pi)
        {
            switch (digit.size())
            {
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                    {
                        uint32_t const indexStart =
                            boost::lexical_cast<uint32_t>(digit);
                        if (indexStart > 65535)
                        {
                            return (verify(digit, pi,
                                           "74951",
                                           digits_74951_to_75000,
                                           50) &&
                                    verify(digit, pi,
                                           "99951",
                                           digits_99951_to_100000,
                                           50));
                        }
                        uint32_t const indexEnd = 
                            std::min<size_t>(65535, indexStart + pi.size() - 1);
                        uint32_t const count = indexEnd - indexStart + 1;
                        return (pi.compare(0, count, digits_1_to_65535,
                                           indexStart, count) == 0);
                    }
                case 6:     // 10^5
                case 7:     // 10^6
                    return (verify(digit, pi,
                                   "1000000",
                                   digits_1000000_to_1000013,
                                   14) &&
                            verify(digit, pi,
                                   "10000000",
                                   digits_10000000_to_10000013,
                                   14));
                case 8:     // 10^7
                    return (verify(digit, pi,
                                   "10000000",
                                   digits_10000000_to_10000013,
                                   14) &&
                            verify(digit, pi,
                                   "100000000",
                                   digits_100000000_to_100000013,
                                   14));
                case 9:     // 10^8
                    return (verify(digit, pi,
                                   "100000000",
                                   digits_100000000_to_100000013,
                                   14) &&
                            verify(digit, pi,
                                   "1000000000",
                                   digits_1000000000_to_1000000013,
                                   14));
                case 10:    // 10^9
                    return (verify(digit, pi,
                                   "1000000000",
                                   digits_1000000000_to_1000000013,
                                   14) &&
                            verify(digit, pi,
                                   "10000000000",
                                   digits_10000000000_to_10000000013,
                                   14));
                case 11:    // 10^10
                    return (verify(digit, pi,
                                   "10000000000",
                                   digits_10000000000_to_10000000013,
                                   14) &&
                            verify(digit, pi,
                                   "100000000000",
                                   digits_100000000000_to_100000000013,
                                   14));
                case 12:    // 10^11
                    return  verify(digit, pi,
                                   "100000000000",
                                   digits_100000000000_to_100000000013,
                                   14);
                case 13:    // 10^12
                    return  verify(digit, pi,
                                   "1250000000000",
                                   digits_1250000000000_to_1250000000013,
                                   14);
                case 15:    // 10^14
                    return  verify(digit, pi,
                                   "250000000000000",
                                   digits_250000000000000_to_250000000000013,
                                   14);
                default:
                    // any missing data is automatically valid
                    return true;
            }
        }
    private:
        static bool verify(std::string const & digit,
                           std::string const & pi,
                           char const * verifyDigit,
                           char const * verifyPi,
                           size_t length)
        {
            mpz_t indexStart;
            mpz_t indexStartOffset;
            mpz_t indexEnd;
            mpz_init(indexStart);
            mpz_init(indexStartOffset);
            mpz_init(indexEnd);
            if (mpz_set_str(indexStart, digit.c_str(), 10) < 0)
                return false;
            if (mpz_set_str(indexStartOffset, verifyDigit, 10) < 0)
                return false;
            if (mpz_cmp(indexStart, indexStartOffset) >= 0)
            {
                mpz_sub(indexStart, indexStart, indexStartOffset);
                if (mpz_cmp_ui(indexStart, length) > 0)
                    return true; // beyond verifyPi
                mpz_add_ui(indexEnd, indexStart, pi.size());
                if (mpz_fits_ulong_p(indexEnd) == 0)
                    return false;
                size_t const end = mpz_get_ui(indexEnd);
                // end >= pi.size()

                // index into verifyPi
                size_t const position = end - pi.size();
                size_t const count = std::min(pi.size(), length - position);
                return (pi.compare(0, count, verifyPi, position, count) == 0);
            }
            else
            {
                mpz_sub(indexStart, indexStart, indexStartOffset);
                mpz_add_ui(indexEnd, indexStart, pi.size());
                if (mpz_cmp_ui(indexEnd, 0) <= 0)
                    return true; // before verifyPi
                if (mpz_fits_ulong_p(indexEnd) == 0)
                    return false;
                size_t const end = mpz_get_ui(indexEnd);
                // end < pi.size()

                // index into pi
                size_t const position = pi.size() - end;
                size_t const count = std::min(end, length);
                return (pi.compare(position, count, verifyPi, 0, count) == 0);
            }
        }

        // data collected for verifying the algorithm

        // data collected from:
        // David H. Bailey, "The BBP Algorithm for Pi", 8 Sept 2006
        // http://crd.lbl.gov/~dhbailey/dhbpapers/bbp-alg.pdf
        static char const digits_1000000_to_1000013[];
        static char const digits_10000000_to_10000013[];
        static char const digits_100000000_to_100000013[];
        static char const digits_1000000000_to_1000000013[];
        static char const digits_10000000000_to_10000000013[];
        static char const digits_100000000000_to_100000000013[];
        static char const digits_1250000000000_to_1250000000013[];
        static char const digits_250000000000000_to_250000000000013[];

        // data collected from:
        // http://www.numberworld.org/digits/Pi/
        static char const digits_74951_to_75000[];
        static char const digits_99951_to_100000[];

        // data collected from:
        // http://www.jjj.de/hfloat/digits/pihex64k.gz
        static char const digits_1_to_65535[];

};

bool bbp_pi_verify(std::string const & digit, std::string const & pi)
{
    return HexadecimalPiTestData::verify(digit, pi);
}

char const HexadecimalPiTestData::digits_1000000_to_1000013[] =
    "26c65e52cb4593";
char const HexadecimalPiTestData::digits_10000000_to_10000013[] =
    "17af5863efed8d";
char const HexadecimalPiTestData::digits_100000000_to_100000013[] =
    "ecb840e21926ec";
char const HexadecimalPiTestData::digits_1000000000_to_1000000013[] =
    "85895585a0428b";
char const HexadecimalPiTestData::digits_10000000000_to_10000000013[] =
    "921c73c6838fb2";
char const HexadecimalPiTestData::digits_100000000000_to_100000000013[] =
    "9c381872d27596";
char const HexadecimalPiTestData::digits_1250000000000_to_1250000000013[] =
    "07e45733cc790b";
char const HexadecimalPiTestData::digits_250000000000000_to_250000000000013[] =
    "e6216b069cb6c1";
char const HexadecimalPiTestData::digits_74951_to_75000[] =
    "a3327fd065f0c4d23f82f48a26bb03abbd9f66e01b4f748a88";
char const HexadecimalPiTestData::digits_99951_to_100000[] =
    "443388751069558b3e62e612bc302ec487aa9a6ea22673c1a5";
char const HexadecimalPiTestData::digits_1_to_65535[] = ".\
[encoded data, 65535 characters]\
";
