//------------------------------------------------------------------------------
/*
    Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================

RandomNumbers::RandomNumbers ()
    : m_initialized (false)
{
}

RandomNumbers::~RandomNumbers ()
{
}

bool RandomNumbers::initialize ()
{
    assert (!m_initialized);

    bool success = platformAddEntropy ();

    if (success)
        m_initialized = true;

    return success;
}

void RandomNumbers::fillBytes (void* destinationBuffer, int numberOfBytes)
{
    // VFALCO NOTE this assert is here to remind us that the code is not yet
    //         thread safe.
    assert (m_initialized);

    // VFALCO NOTE When a spinlock is available in beast, use it here.
    if (! m_initialized)
    {
        if (! initialize ())
        {
            char const* message = "Unable to add system entropy";
            Log::out() << message;
            throw std::runtime_error (message);
        }
    }

#ifdef PURIFY
    memset (destinationBuffer, 0, numberOfBytes);
#endif

    if (RAND_bytes (reinterpret_cast <unsigned char*> (destinationBuffer), numberOfBytes) != 1)
    {
        assert (false);

        throw std::runtime_error ("Entropy pool not seeded");
    }
}

RandomNumbers& RandomNumbers::getInstance ()
{
    static RandomNumbers instance;

    return instance;
}

//------------------------------------------------------------------------------

#if BEAST_WIN32

// Get entropy from the Windows crypto provider
bool RandomNumbers::platformAddEntropy ()
{
    char name[512], rand[128];
    DWORD count = 500;
    HCRYPTPROV cryptoHandle;

    if (!CryptGetDefaultProviderA (PROV_RSA_FULL, NULL, CRYPT_MACHINE_DEFAULT, name, &count))
    {
#ifdef BEAST_DEBUG
        Log::out() << "Unable to get default crypto provider";
#endif
        return false;
    }

    if (!CryptAcquireContextA (&cryptoHandle, NULL, name, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
    {
#ifdef BEAST_DEBUG
        Log::out() << "Unable to acquire crypto provider";
#endif
        return false;
    }

    if (!CryptGenRandom (cryptoHandle, 128, reinterpret_cast<BYTE*> (rand)))
    {
#ifdef BEAST_DEBUG
        Log::out() << "Unable to get entropy from crypto provider";
#endif
        CryptReleaseContext (cryptoHandle, 0);
        return false;
    }

    CryptReleaseContext (cryptoHandle, 0);
    RAND_seed (rand, 128);

    return true;
}

#else

bool RandomNumbers::platformAddEntropy ()
{
    char rand[128];
    std::ifstream reader;

    reader.open ("/dev/urandom", std::ios::in | std::ios::binary);

    if (!reader.is_open ())
    {
#ifdef BEAST_DEBUG
        Log::out() << "Unable to open random source";
#endif
        return false;
    }

    reader.read (rand, 128);

    int bytesRead = reader.gcount ();

    if (bytesRead == 0)
    {
#ifdef BEAST_DEBUG
        Log::out() << "Unable to read from random source";
#endif
        return false;
    }

    RAND_seed (rand, bytesRead);
    return bytesRead >= 64;
}

#endif

//------------------------------------------------------------------------------

//
// "Never go to sea with two chronometers; take one or three."
// Our three time sources are:
//  - System clock
//  - Median of other nodes's clocks
//  - The user (asking the user to fix the system clock if the first two disagree)
//

void RandomNumbers::platformAddPerformanceMonitorEntropy ()
{
    // VFALCO TODO Remove all this fancy stuff
    struct
    {
        int64 operator () () const
        {
            return time (NULL);
        }
    } GetTime;

    struct
    {
        void operator () ()
        {
            struct
            {
                // VFALCO TODO clean this up
                int64 operator () () const
                {
                    int64 nCounter = 0;
#if BEAST_WIN32
                    QueryPerformanceCounter ((LARGE_INTEGER*)&nCounter);
#else
                    timeval t;
                    gettimeofday (&t, NULL);
                    nCounter = t.tv_sec * 1000000 + t.tv_usec;
#endif
                    return nCounter;
                }
            } GetPerformanceCounter;

            // Seed with CPU performance counter
            int64 nCounter = GetPerformanceCounter ();
            RAND_add (&nCounter, sizeof (nCounter), 1.5);
            memset (&nCounter, 0, sizeof (nCounter));
        }
    } RandAddSeed;

    RandAddSeed ();

    // This can take up to 2 seconds, so only do it every 10 minutes
    static int64 nLastPerfmon;

    if (GetTime () < nLastPerfmon + 10 * 60)
        return;

    nLastPerfmon = GetTime ();

#if BEAST_WIN32
    // Don't need this on Linux, OpenSSL automatically uses /dev/urandom
    // Seed with the entire set of perfmon data
    unsigned char pdata[250000];
    memset (pdata, 0, sizeof (pdata));
    unsigned long nSize = sizeof (pdata);
    long ret = RegQueryValueExA (HKEY_PERFORMANCE_DATA, "Global", NULL, NULL, pdata, &nSize);
    RegCloseKey (HKEY_PERFORMANCE_DATA);

    if (ret == ERROR_SUCCESS)
    {
        RAND_add (pdata, nSize, nSize / 100.0);
        memset (pdata, 0, nSize);
        //printf("%s RandAddSeed() %d bytes\n", DateTimeStrFormat("%x %H:%M", GetTime()).c_str(), nSize);
    }

#endif
}
