// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2012 The Bitcoin developers
// Copyright (c) 2011-2013 PPCoin developers
// Copyright (c) 2013 Primecoin developers
// Distributed under conditional MIT/X11 software license,
// see the accompanying file COPYING

#include "prime.h"
#include "json/json_spirit_value.h"
//#include <boost/algorithm/string/replace.hpp>
//#include <boost/filesystem.hpp>
//#include <boost/filesystem/fstream.hpp>

using namespace std;
using namespace boost;

CBlockIndex* pindexBest = NULL;
int64 nHPSTimerStart = 0;

void BitcoinMiner(CBlockProvider *block_provider, unsigned int thread_id)
{
    printf("PrimecoinMiner started\n");
    SetThreadPriority(THREAD_PRIORITY_LOWEST);
    RenameThread("primecoin-miner");

    unsigned int nPrimorialMultiplier = nPrimorialHashFactor;
    double dTimeExpected = 0;   // time expected to prime chain (micro-second)
    int64 nSieveGenTime = 0; // how many milliseconds sieve generation took
    bool fIncrementPrimorial = true; // increase or decrease primorial factor

    CBlock *pblock = NULL;
	uint256 old_hash;
	unsigned int old_nonce = 0;

    try { loop {
        //
        // Create new block
        //
        CBlockIndex* pindexPrev = pindexBest;

        if ((pblock = block_provider->getBlock(thread_id, pblock == NULL ? 0 : pblock->nTime)) == NULL) { //server not reachable?
          MilliSleep(20000);
          continue;
        } else if (old_hash == pblock->GetHeaderHash()) {
          if (old_nonce >= 0xffff0000) {
		    MilliSleep(100);
			//TODO: FORCE a new getblock!
			if (fDebug && GetBoolArg("-printmining"))
				printf("Nothing to do --- uh ih uh ah ah bing bang!!\n");
            continue;
		  } else
		    pblock->nNonce = old_nonce;
        } else {
            old_hash = pblock->GetHeaderHash();
			old_nonce = 0;
        }

        // Search
        //
        int64 nStart = GetTime();
        bool fNewBlock = true;
        unsigned int nTriedMultiplier = 0;

        // Primecoin: try to find hash divisible by primorial
        unsigned int nHashFactor = PrimorialFast(nPrimorialHashFactor);

        // Based on mustyoshi's patch from https://bitcointalk.org/index.php?topic=251850.msg2689981#msg2689981
        uint256 phash;
        mpz_class mpzHash;
        loop {
            // Fast loop
            if (pblock->nNonce >= 0xffff0000)
                break;

            // Check that the hash meets the minimum
            phash = pblock->GetHeaderHash();
            if (phash < hashBlockHeaderLimit) {
                pblock->nNonce++;
                continue;
            }

            // Check that the hash is divisible by the fixed primorial
            mpz_set_uint256(mpzHash.get_mpz_t(), phash);
            if (!mpz_divisible_ui_p(mpzHash.get_mpz_t(), nHashFactor)) {
                pblock->nNonce++;
                continue;
            }

            // Use the hash that passed the tests
            break;
        }
        if (pblock->nNonce >= 0xffff0000) {
			old_nonce = 0xffff0000;
            continue;
		}
        // Primecoin: primorial fixed multiplier
        mpz_class mpzPrimorial;
        unsigned int nRoundTests = 0;
        unsigned int nRoundPrimesHit = 0;
        int64 nPrimeTimerStart = GetTimeMicros();
        Primorial(nPrimorialMultiplier, mpzPrimorial);

        loop
        {
            unsigned int nTests = 0;
            unsigned int nPrimesHit = 0;
            unsigned int nChainsHit = 0;

            // Primecoin: adjust round primorial so that the generated prime candidates meet the minimum
            mpz_class mpzMultiplierMin = mpzPrimeMin * nHashFactor / mpzHash + 1;
            while (mpzPrimorial < mpzMultiplierMin)
            {
                if (!PrimeTableGetNextPrime(nPrimorialMultiplier))
                    error("PrimecoinMiner() : primorial minimum overflow");
                Primorial(nPrimorialMultiplier, mpzPrimorial);
            }
            mpz_class mpzFixedMultiplier;
            if (mpzPrimorial > nHashFactor) {
                mpzFixedMultiplier = mpzPrimorial / nHashFactor;
            } else {
                mpzFixedMultiplier = 1;
            }

            // Primecoin: mine for prime chain
            unsigned int nProbableChainLength;
            if (MineProbablePrimeChain(*pblock, mpzFixedMultiplier, fNewBlock, nTriedMultiplier, nProbableChainLength, nTests, nPrimesHit, nChainsHit, mpzHash, nPrimorialMultiplier, nSieveGenTime, pindexPrev, block_provider != NULL))
            {
                SetThreadPriority(THREAD_PRIORITY_NORMAL);
				block_provider->submitBlock(pblock);
                SetThreadPriority(THREAD_PRIORITY_LOWEST);
				old_nonce = pblock->nNonce + 1;
                break;
            }

            ///
            /// ENABLE the following code, if you need data for the perftool
            ///
            /*if (nProbableChainLength > 0 && nTests > 10)
            {
              static CCriticalSection cs;
              {
                LOCK(cs);

                std::ofstream output_file("miner_data");
                std::ofstream output_file_block("miner_data.blk", std::ofstream::out | std::ofstream::binary);

                ::Serialize(output_file_block, *pblock, 0, 0); //writeblock

                output_file << mpzFixedMultiplier.get_str(10) << std::endl;
                output_file << fNewBlock << std::endl;
                output_file << nTriedMultiplier << std::endl;
                output_file << nPrimorialMultiplier << std::endl;
                output_file << mpzHash.get_str(10) << std::endl;

                output_file.close();
                output_file_block.close();
              }
            }*/

            nRoundTests += nTests;
            nRoundPrimesHit += nPrimesHit;

            // Meter primes/sec
            static volatile int64 nPrimeCounter;
            static volatile int64 nTestCounter;
            static volatile int64 nChainCounter;
            static double dChainExpected;
            int64 nMillisNow = GetTimeMillis();
            if (nHPSTimerStart == 0)
            {
                nHPSTimerStart = nMillisNow;
                nPrimeCounter = 0;
                nTestCounter = 0;
                nChainCounter = 0;
                dChainExpected = 0;
            }
            else
            {
#ifdef __GNUC__
                // Use atomic increment
                __sync_add_and_fetch(&nPrimeCounter, nPrimesHit);
                __sync_add_and_fetch(&nTestCounter, nTests);
                __sync_add_and_fetch(&nChainCounter, nChainsHit);
#else
                nPrimeCounter += nPrimesHit;
                nTestCounter += nTests;
                nChainCounter += nChainsHit;
#endif
            }
            if (nMillisNow - nHPSTimerStart > 60000)
            {
                static CCriticalSection cs;
                {
                    LOCK(cs);
                    if (nMillisNow - nHPSTimerStart > 60000)
                    {
                        double dPrimesPerMinute = 60000.0 * nPrimeCounter / (nMillisNow - nHPSTimerStart);
                        double dPrimesPerSec = dPrimesPerMinute / 60.0;
                        double dTestsPerSec = 1000.0 * nTestCounter / (nMillisNow - nHPSTimerStart);
                        double dChainsPerMinute = 60000.0 * nChainCounter / (nMillisNow - nHPSTimerStart);
                        double dChainsPerDay = 86400000.0 * dChainExpected / (GetTimeMillis() - nHPSTimerStart);
                        nHPSTimerStart = nMillisNow;
                        nPrimeCounter = 0;
                        nTestCounter = 0;
                        nChainCounter = 0;
                        dChainExpected = 0;
                        static int64 nLogTime = 0;
                        if (nMillisNow - nLogTime > 59000)
                        {
                            nLogTime = nMillisNow;
                            printf("[STATS] %s | %4.0f primes/s, %4.0f tests/s, %4.0f %d-chains/h, %3.3f chains/d\n", DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nLogTime / 1000).c_str(), dPrimesPerSec, dTestsPerSec, dChainsPerMinute * 60.0, nStatsChainLength, dChainsPerDay);
                        }
                    }
                }
            }

			old_nonce = pblock->nNonce;

            // Check for stop or if block needs to be rebuilt
            boost::this_thread::interruption_point();
            if (pblock->nNonce >= 0xffff0000)
                break;
            if (pindexPrev != pindexBest/* || (block_provider != NULL && GetTime() - nStart > 200)*/)
                break;
			if (thread_id == 0 && block_provider != NULL && (GetTime() - nStart) > 300) { //5 minutes no update? something's wrong -> reconnect!
				block_provider->forceReconnect();
				nStart = GetTime();
			}
            if (fNewBlock) //aka: sieve's done, we need a updated nonce
            {
                // Primecoin: a sieve+primality round completes
                // Primecoin: estimate time to block
                const double dTimeExpectedPrev = dTimeExpected;
                unsigned int nCalcRoundTests = max(1u, nRoundTests);
                // Make sure the estimated time is very high if only 0 primes were found
                if (nRoundPrimesHit == 0)
                    nCalcRoundTests *= 1000;
                int64 nRoundTime = (GetTimeMicros() - nPrimeTimerStart);
                dTimeExpected = (double) nRoundTime / nCalcRoundTests;
                double dRoundChainExpected = (double) nRoundTests;
                for (unsigned int n = 0, nTargetLength = TargetGetLength(pblock->nBits); n < nTargetLength; n++)
                {
                    double dPrimeProbability = EstimateCandidatePrimeProbability(nPrimorialMultiplier, n);
                    dTimeExpected = dTimeExpected / max(0.01, dPrimeProbability);
                    dRoundChainExpected *= dPrimeProbability;
                }
                dChainExpected += dRoundChainExpected;
                if (fDebug && GetBoolArg("-printmining"))
                {
                    double dPrimeProbabilityBegin = EstimateCandidatePrimeProbability(nPrimorialMultiplier, 0);
                    unsigned int nTargetLength = TargetGetLength(pblock->nBits);
                    double dPrimeProbabilityEnd = EstimateCandidatePrimeProbability(nPrimorialMultiplier, nTargetLength - 1);
                    printf("PrimecoinMiner() : Round primorial=%u tests=%u primes=%u time=%uus pprob=%1.6f pprob2=%1.6f tochain=%6.3fd expect=%3.9f\n", nPrimorialMultiplier, nRoundTests, nRoundPrimesHit, (unsigned int) nRoundTime, dPrimeProbabilityBegin, dPrimeProbabilityEnd, ((dTimeExpected/1000000.0))/86400.0, dRoundChainExpected);
                }

                // Primecoin: update time and nonce
                //pblock->nTime = max(pblock->nTime, (unsigned int) GetAdjustedTime());
                pblock->nTime = max(pblock->nTime, block_provider->GetAdjustedTimeWithOffset(thread_id));
                pblock->nNonce++;
                loop {
                    // Fast loop
                    if (pblock->nNonce >= 0xffff0000)
                        break;

                    // Check that the hash meets the minimum
                    phash = pblock->GetHeaderHash();
                    if (phash < hashBlockHeaderLimit) {
                        pblock->nNonce++;
                        continue;
                    }

                    // Check that the hash is divisible by the fixed primorial
                    mpz_set_uint256(mpzHash.get_mpz_t(), phash);
                    if (!mpz_divisible_ui_p(mpzHash.get_mpz_t(), nHashFactor)) {
                        pblock->nNonce++;
                        continue;
                    }

                    // Use the hash that passed the tests
                    break;
                }
                if (pblock->nNonce >= 0xffff0000)
                    break;

                // Primecoin: reset sieve+primality round timer
                nPrimeTimerStart = GetTimeMicros();
                if (dTimeExpected > dTimeExpectedPrev)
                    fIncrementPrimorial = !fIncrementPrimorial;

                // Primecoin: primorial always needs to be incremented if only 0 primes were found
                if (nRoundPrimesHit == 0)
                    fIncrementPrimorial = true;

                nRoundTests = 0;
                nRoundPrimesHit = 0;

                // Primecoin: dynamic adjustment of primorial multiplier
                if (fIncrementPrimorial)
                {
                    if (!PrimeTableGetNextPrime(nPrimorialMultiplier))
                        error("PrimecoinMiner() : primorial increment overflow");
                }
                else if (nPrimorialMultiplier > nPrimorialHashFactor)
                {
                    if (!PrimeTableGetPreviousPrime(nPrimorialMultiplier))
                        error("PrimecoinMiner() : primorial decrement overflow");
                }
                Primorial(nPrimorialMultiplier, mpzPrimorial);
            }
        }
    } }
    catch (boost::thread_interrupted)
    {
        printf("PrimecoinMiner terminated\n");
        throw;
    }
}

