#include "stdafx.h"
#include "KeyArray.h"

IoAKey IoAKeyArray::m_NullKey( NULL, NULL );  // define and initialise static data member

int IoAKeyArray::Allocate( int nKeys, int nData )
{
    if ( nKeys <= 0 || nData <= 0 ) return FALSE;

    Free( );

    m_pKeys = new IoAKey[ nKeys ];

    m_pData = new char[ nData ];

    if ( ! IsAllocated( ) )
    {
        Free( );
        
        return FALSE;
    }

    m_nKeys = nKeys;
    m_nData = nData;

    return TRUE;
}

void IoAKeyArray::Free( )
{
    delete [ ] m_pKeys;

    delete [ ] m_pData;

    m_pKeys = NULL;
    m_nKeys = 0;

    m_pData = NULL;
    m_nData = 0;
}

int IoAKeyArray::Read( const char *KeyFile )
{
    Free( );

    if ( KeyFile )
    {
        int Success = FALSE;

        HANDLE Handle = ::CreateFile( KeyFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );

        if ( Handle != INVALID_HANDLE_VALUE )
        {
            DWORD FileSize = ::GetFileSize( Handle, NULL );

            if ( FileSize != 0xffffffff )
            {
                char *KeyString = new char[ FileSize + 1 ];

                if ( KeyString )
                {
                    DWORD nBytes;

                    if ( ::ReadFile( Handle, KeyString, FileSize, &nBytes, NULL ) && nBytes == FileSize )
                    {
                        KeyString[ FileSize ] = '\0';

                        if ( Load( KeyString ) ) Success = TRUE;
                    }

                    delete KeyString;
                }
            }

            ::CloseHandle( Handle );
        }

        if ( ! Success ) return FALSE;
    }

    return TRUE;
}

#pragma warning( push )
#pragma warning( disable: 4996 )

int IoAKeyArray::Load( int argc, char* argv[ ] )
{
    Free( );

	// Count number of characters to store command line arguments
	int CharCount = 0;

	for ( int i = 1; i < argc; i++ )
	{
		CharCount += ::strlen( argv[ i ] );
	}

	if ( CharCount )
	{
		int Success = FALSE;

		// Allocate space to store command line arguments
		char *Buffer = new char[ CharCount + argc ];

		if ( Buffer )
		{
			// Copy data
			::strcpy( Buffer, argv[ 1 ] );

			for ( int i = 2; i < argc; i++ )
			{
				::strcat( Buffer, " "       );
				::strcat( Buffer, argv[ i ] );
			}

			if ( Load( Buffer ) ) Success = TRUE;

			delete [ ] Buffer;
		}

		if ( ! Success ) return FALSE;
	}

	return TRUE;
}

#pragma warning( pop )

int IoAKeyArray::Load( const char *KeyString )
{
    Free( );

    if ( KeyString )
    {
        enum { eWhiteSpace, eEqualsWhiteSpace, eToken, eComment, eEndOfData };

        int KeyCount, TokenDataCount, StateAfterComment;

        // Scan input string to determine number of keys and token characters
        for ( int i = KeyCount = TokenDataCount = 0, NameToken = FALSE, State = eWhiteSpace; State != eEndOfData; i++ )
        {
            switch( State )
            {
                case eWhiteSpace:
                {
                    if ( IsWhiteSpace( KeyString[ i ] ) )
                    {
//                        State = eWhiteSpace;  // no change of state
                    }
                    else if ( IsStartOfComment( KeyString[ i ] ) )  // start of comment
                    {
                        StateAfterComment = eWhiteSpace;

                        State = eComment;
                    }
                    else if ( IsEqualsSign( KeyString[ i ] ) )  // equals sign separates key token from first value token
                    {
                        State = eEqualsWhiteSpace;
                    }
                    else if ( IsNull( KeyString[ i ] ) )  // end of data
                    {
                        State = eEndOfData;
                    }
                    else  // start of token (except first value token)
                    {
                        TokenDataCount += 1;  // first token character

                        // Flag token as it may turn out to be a name token
                        NameToken = TRUE;

                        State = eToken;
                    }
                }
                break;

                case eEqualsWhiteSpace:
                {
                    if ( IsWhiteSpace( KeyString[ i ] ) )
                    {
//                        State = eEqualsWhiteSpace;  // no change of state
                    }
                    else if ( IsStartOfComment( KeyString[ i ] ) )  // start of comment
                    {
                        StateAfterComment = eEqualsWhiteSpace;

                        State = eComment;
                    }
                    else if ( IsEqualsSign( KeyString[ i ] ) )
                    {
                        return FALSE;  // error: consecutive equals signs
                    }
                    else if ( IsNull( KeyString[ i ] ) )  // end of data
                    {
                        return FALSE;  // error: missing value token
                    }
                    else  // start of first value token
                    {
                        // Error: missing name token
                        if ( ! NameToken )
                        {
                            return FALSE;
                        }

                        // Presence of name token signals start of new key
                        KeyCount += 1;

                        // Clear pointer to name token (it will be set when next tokens are encountered)
                        NameToken = FALSE;

                        TokenDataCount += 1;  // first token character

                        State = eToken;
                    }
                }
                break;

                case eToken:
                {
                    if ( IsWhiteSpace( KeyString[ i ] ) )
                    {
                        State = eWhiteSpace;
                    }
                    else if ( IsStartOfComment( KeyString[ i ] ) )  // start of comment
                    {
                        StateAfterComment = eWhiteSpace;

                        State = eComment;
                    }
                    else if ( IsEqualsSign( KeyString[ i ] ) )  // equals sign separates key token from first value token
                    {
                        State = eEqualsWhiteSpace;
                    }
                    else if ( IsNull( KeyString[ i ] ) )  // end of data
                    {
                        State = eEndOfData;
                    }
                    else
                    {
                        TokenDataCount += 1;  // next token character

//                        State = eToken;  // no change of state
                    }

                    if ( State != eToken )  // end of token
                    {
                        TokenDataCount += 1;  // count terminating character (blank or null)
                    }
                }
                break;

                case eComment:
                {
                    if ( IsNewLine( KeyString[ i ] ) )
                    {
                        State = StateAfterComment;
                    }
                    else if ( IsNull( KeyString[ i ] ) )
                    {
                        // Error: missing value token
                        if ( StateAfterComment == eEqualsWhiteSpace )
                        {
                            return FALSE;
                        }

                        State = eEndOfData;
                    }
                    else
                    {
//                        State = eComment;  // no change of state
                    }
                }
            }
        }

        // Allocate memory to hold key data (if any)
        if ( KeyCount )
        {
            if ( ! Allocate( KeyCount, TokenDataCount ) )
            {
                return FALSE;
            }

            // Scan input string to load key data into memory
            for ( int i = KeyCount = TokenDataCount = 0, NameToken = -1, State = eWhiteSpace; State != eEndOfData; i++ )
            {
                switch( State )
                {
                    case eWhiteSpace:
                    {
                        if ( IsWhiteSpace( KeyString[ i ] ) )
                        {
//                            State = eWhiteSpace;  // no change of state
                        }
                        else if ( IsStartOfComment( KeyString[ i ] ) )  // start of comment
                        {
                            StateAfterComment = eWhiteSpace;

                            State = eComment;
                        }
                        else if ( IsEqualsSign( KeyString[ i ] ) )  // equals sign separates key token from first value token
                        {
                            State = eEqualsWhiteSpace;
                        }
                        else if ( IsNull( KeyString[ i ] ) )  // end of data
                        {
                            State = eEndOfData;
                        }
                        else  // start of token (except first value token)
                        {
                            // Keep location of token as it may turn out to be a name token
                            NameToken = TokenDataCount;

                            m_pData[ TokenDataCount++ ] = KeyString[ i ];  // first token character

                            State = eToken;
                        }
                    }
                    break;

                    case eEqualsWhiteSpace:
                    {
                        if ( IsWhiteSpace( KeyString[ i ] ) )
                        {
//                            State = eEqualsWhiteSpace;  // no change of state
                        }
                        else if ( IsStartOfComment( KeyString[ i ] ) )  // start of comment
                        {
                            StateAfterComment = eEqualsWhiteSpace;

                            State = eComment;
                        }
                        else if ( IsEqualsSign( KeyString[ i ] ) )
                        {
                        }
                        else if ( IsNull( KeyString[ i ] ) )  // end of data
                        {
                        }
                        else  // start of first value token
                        {
                            // Terminate previous value token (if there is one)
                            if ( NameToken > 0 ) m_pData[ NameToken - 1 ] = '\0';

                            // Terminate name token
                            m_pData[ TokenDataCount - 1 ] = '\0';

                            // Store pointers to key name and value
                            m_pKeys[ KeyCount++ ].Define( m_pData + NameToken, m_pData + TokenDataCount );

                            //Reset location of name token (it will be set when next tokens are encountered)
                            NameToken = -1;

                            m_pData[ TokenDataCount++ ] = KeyString[ i ];  // first token character

                            State = eToken;
                        }
                    }
                    break;

                    case eToken:
                    {
                        if ( IsWhiteSpace( KeyString[ i ] ) )
                        {
                            State = eWhiteSpace;
                        }
                        else if ( IsStartOfComment( KeyString[ i ] ) )  // start of comment
                        {
                            StateAfterComment = eWhiteSpace;

                            State = eComment;
                        }
                        else if ( IsEqualsSign( KeyString[ i ] ) )  // equals sign separates key token from first value token
                        {
                            State = eEqualsWhiteSpace;
                        }
                        else if ( IsNull( KeyString[ i ] ) )  // end of data
                        {
                            State = eEndOfData;
                        }
                        else
                        {
                            m_pData[ TokenDataCount++ ] = KeyString[ i ];  // next token character

//                            State = eToken;  // no change of state
                        }

                        if ( State != eToken )  // end of token
                        {
                            m_pData[ TokenDataCount++ ] = ' ';  // terminating character (blank for now)
                        }
                    }
                    break;

                    case eComment:
                    {
                        if ( IsNewLine( KeyString[ i ] ) )
                        {
                            State = StateAfterComment;
                        }
                        else if ( IsNull( KeyString[ i ] ) )
                        {
                            State = eEndOfData;
                        }
                        else
                        {
//                            State = eComment;  // no change of state
                        }
                    }
                }
            }

            // Null-terminate final token (if there is one)
            if ( TokenDataCount )
            {
                m_pData[ TokenDataCount - 1 ] = '\0';
            }
        }
    }

    return TRUE;
}

int IoAKeyArray::KeyCount( const char *Name ) const
{
    int nKeys = 0;

    for ( int i = 0; i < m_nKeys; i++ )
    {
        if ( m_pKeys[ i ].IsCalled( Name ) )
        {
            nKeys += 1;
        }
    }

    return nKeys;
}

const IoAKey& IoAKeyArray::Key( const char *Name, int Index ) const
{
    int nKeys = 0;

    for ( int i = 0; i < m_nKeys; i++ )
    {
        if ( m_pKeys[ i ].IsCalled( Name ) )
        {
            if ( Index < 0 || Index == nKeys )
            {
                return m_pKeys[ i ];
            }

            nKeys += 1;
        }
    }

    return m_NullKey;
}
