//-----------------------------------------------------------------------------
// File: XbRandName.cpp
//
// Desc: Generates a random and somewhat readable name. Useful for fake
//       player names, session names, etc.
//
//       Based on "Random Number Generators; Good ones are hard to find", 
//       Stephen K. Park and Keith W. Miller, Communications of the ACM,
//       October 1988, Volume 31, Number 10, page 1192-1201.
//
// Hist: 10.10.01 - New for Nov release
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#include "XbRandName.h"
#include <cassert>




//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
const DWORD MAX_RAND_NAME = 128;

// These values based on original RNG article (see above)
const LONG MODULUS = 2147483647L;
const LONG MULTIPLIER = 48271L;
const LONG Q = MODULUS / MULTIPLIER;
const LONG R = MODULUS % MULTIPLIER;




//-----------------------------------------------------------------------------
// Name: CXBRandName()
// Desc: Create rand name generator
//-----------------------------------------------------------------------------
CXBRandName::CXBRandName( DWORD dwSeed )
{
    SetSeed( dwSeed );
}




//-----------------------------------------------------------------------------
// Name: ~CXBRandName()
// Desc: Destroy rand name generator
//-----------------------------------------------------------------------------
CXBRandName::~CXBRandName()
{
}




//-----------------------------------------------------------------------------
// Name: SetSeed()
// Desc: Reseed the generator
//-----------------------------------------------------------------------------
VOID CXBRandName::SetSeed( DWORD dwSeed )
{
    m_nValue = LONG( dwSeed );
    if( m_nValue <= 0 )
        m_nValue += MODULUS;
    assert( m_nValue > 0 && m_nValue <= MODULUS );
}




//-----------------------------------------------------------------------------
// Name: GetName()
// Desc: Generate a random name
//-----------------------------------------------------------------------------
VOID CXBRandName::GetName( WCHAR* strName, DWORD dwSize ) const
{
    assert( dwSize < MAX_RAND_NAME );

    // Name consists of two to five parts.
    //
    // 1) consonant or consonant group (e.g. th, qu, st) [optional]
    // 2) vowel or vowel group (e.g. ea, ee, au)
    // 3) consonant or consonant group
    // 4) vowel or vowel group [optional]
    // 5) consonant or consonant group [optional]

    WCHAR strRandom[ MAX_RAND_NAME ];
    strRandom[ 0 ] = 0;
    if( ( GetRand() % 2 == 0 ) )
        AppendConsonant( strRandom, TRUE );
    AppendVowel( strRandom );
    AppendConsonant( strRandom, FALSE );
    if( ( GetRand() % 2 == 0 ) )
    {
        AppendVowel( strRandom );
        if( ( GetRand() % 2 == 0 ) )
            AppendConsonant( strRandom, FALSE );
    }

    // Make the first letter upper case
    *strRandom = towupper( *strRandom );

    lstrcpynW( strName, strRandom, dwSize );
}




//-----------------------------------------------------------------------------
// Name: GetRand()
// Desc: Generates a pseudo random number. This implementation is based on
//       the portable implementation defined in the C Standard. We use
//       this instead of CRT rand() to avoid interfering with the random
//       number sequence generated by rand().
//-----------------------------------------------------------------------------
DWORD CXBRandName::GetRand() const
{
    m_nValue = ( MULTIPLIER * ( m_nValue % Q ) ) - ( R * ( m_nValue / Q ) );
    if( m_nValue <= 0 )
        m_nValue += MODULUS;
    assert( m_nValue > 0 && m_nValue <= MODULUS );
    return DWORD( m_nValue );
}



//-----------------------------------------------------------------------------
// Name: GetRandVowel()
// Desc: Get a random vowel
//-----------------------------------------------------------------------------
WCHAR CXBRandName::GetRandVowel() const // private
{
    for(;;)
    {
        WCHAR c = WCHAR( L'a' + ( GetRand() % 26 ) );
        if( wcschr( L"aeiou", c ) != NULL )
            return c;
    }
}




//-----------------------------------------------------------------------------
// Name: GetRandConsonant()
// Desc: Get a random consonant
//-----------------------------------------------------------------------------
WCHAR CXBRandName::GetRandConsonant() const // private
{
    for(;;)
    {
        WCHAR c = WCHAR( L'a' + ( GetRand() % 26 ) );
        if( wcschr( L"aeiou", c ) == NULL )
            return c;
    }
}




//-----------------------------------------------------------------------------
// Name: AppendConsonant()
// Desc: Append consonant or consonant group to string
//-----------------------------------------------------------------------------
VOID CXBRandName::AppendConsonant( WCHAR* strRandom, BOOL bLeading ) const // private
{
    if( ( GetRand() % 2 == 0 ) )
    {
        WCHAR strChar[ 2 ] = { GetRandConsonant(), 0 };
        lstrcatW( strRandom, strChar );
    }
    else
    {
        WCHAR* strLeadConGroup[32] = 
        {
            L"bl", L"br", L"cl", L"cr", L"dr", L"fl", L"fr", L"gh", L"gl", L"gn", 
            L"gr", L"kl", L"kn", L"kr", L"ph", L"pl", L"pr", L"ps", L"qu", L"sc", 
            L"sk", L"sl", L"sn", L"sp", L"st", L"sw", L"th", L"tr", L"vh", L"vl", 
            L"wh", L"zh"
        };
        WCHAR* strTrailConGroup[32] = 
        {
            L"rt", L"ng", L"bs", L"cs", L"ds", L"gs", L"hs", L"sh", L"ss", L"ks",
            L"ms", L"ns", L"ps", L"rs", L"ts", L"gh", L"ph", L"sk", L"st", L"tt",
            L"nd", L"nk", L"nt", L"nx", L"pp", L"rd", L"rg", L"rk", L"rn", L"rv",
            L"th", L"ys"
        };
        if( bLeading )
            lstrcatW( strRandom, strLeadConGroup[ GetRand() % 32 ] );
        else
            lstrcatW( strRandom, strTrailConGroup[ GetRand() % 32 ] );
    }
}




//-----------------------------------------------------------------------------
// Name: AppendVowel()
// Desc: Append vowel or vowel group to string
//-----------------------------------------------------------------------------
VOID CXBRandName::AppendVowel( WCHAR* strRandom ) const // private
{
    if( ( GetRand() % 2 == 0 ) )
    {
        WCHAR strChar[ 2 ] = { GetRandVowel(), 0 };
        lstrcatW( strRandom, strChar );
    }
    else
    {
        WCHAR* strVowelGroup[10] =
        {
            L"ai", L"au", L"ay", L"ea", L"ee", L"ie", L"oa", L"oi", L"oo", L"ou"
        };
        lstrcatW( strRandom, strVowelGroup[ GetRand() % 10 ] );
    }
}
