//////////////////////////////////////////////////////////////////////////////
//
// Asura_CfgFileReader.cpp
//
// Copyright (c) 2003 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////
#include "Core/Asura.h"

// This file's header.
#include "Asura_CfgFileReader.h"

// Stdlib independent.
#include <wchar.h>

// Engine independent.
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Interface/Asura_CommandConsole.h"

// C L A S S E S /////////////////////////////////////////////////////////////

class Asura_CfgFileReader::ListNode
{

    public:

        ListNode( Asura_UnicodeChar* wszRawLine, Asura_UnicodeChar* wszName, Asura_UnicodeChar* wszValue, u_int uLineNum );
        ~ListNode( void );

        const ListNode* Find( const Asura_UnicodeChar* wszName ) const;
        void InsertAtEnd( ListNode* pxNewNode );

        const Asura_UnicodeChar* GetRawLine( void ) const { return m_wszRawLine; }
        const Asura_UnicodeChar* GetName( void ) const { return m_wszName;  }
        const Asura_UnicodeChar* GetValue( void ) const { return m_wszValue; }
        u_int GetLineNum( void ) const { return m_uLineNum; }
        bool IsUsed( void ) const { return m_bIsUsed; }
        const ListNode* GetNext( void ) const { return m_pxNext;  }

    private:

        Asura_UnicodeChar* m_wszRawLine;
        Asura_UnicodeChar* m_wszName;
        Asura_UnicodeChar* m_wszValue;
        u_int              m_uLineNum;
        mutable bool       m_bIsUsed;
        ListNode*          m_pxNext;

};

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

//
// Asura_CfgFileReader.
//

Asura_CfgFileReader::Asura_CfgFileReader( void )
{
    m_pxListHead = NULL;
}

Asura_CfgFileReader::~Asura_CfgFileReader( void )
{
    DeallocateList();
}

void Asura_CfgFileReader::DeallocateList( void )
{
    delete m_pxListHead;
}

void Asura_CfgFileReader::ItBegin( void ) const
{
    m_pxItNode = m_pxListHead;
}

bool Asura_CfgFileReader::ItDone( void ) const
{
    return !m_pxItNode;
}

u_int Asura_CfgFileReader::ItGetLineNum( void ) const
{
    return m_pxItNode->GetLineNum();
}

const Asura_UnicodeChar* Asura_CfgFileReader::ItGetName( void ) const
{
    return m_pxItNode->GetName();
}

const Asura_UnicodeChar* Asura_CfgFileReader::ItGetRawLine( void ) const
{
    return m_pxItNode->GetRawLine();
}

const Asura_UnicodeChar* Asura_CfgFileReader::ItGetValue( void ) const
{
    return m_pxItNode->GetValue();
} 

bool Asura_CfgFileReader::ItHasBeenUsed( void ) const
{
    return m_pxItNode->IsUsed();
}

void Asura_CfgFileReader::ItNext( void ) const
{
    m_pxItNode = m_pxItNode->GetNext();
}

bool Asura_CfgFileReader::ExecuteFile( const Asura_UnicodeChar* wszFilename )
{
    Asura_CfgFileReader* pxRdr = Platform_Create();
    
    bool bOK = ( pxRdr && pxRdr->Open(wszFilename) );

    if( bOK )
    {
        bOK = pxRdr->Parse();
        pxRdr->Close();

		if (!bOK)
		{
			Asura_CommandConsole::Printf(L"Failed to parse config \'%s\'", wszFilename);
		}
    }
	else
	{
		Asura_CommandConsole::Printf(L"Failed to load config \'%s\'", wszFilename);
	}

    if( bOK )
    {
		Asura_CommandConsole::Printf(L"Loaded and parsed config \'%s\'", wszFilename);
		pxRdr->Execute();
	}

	delete pxRdr;

    return bOK;
}

bool Asura_CfgFileReader::Parse( void )
{
    const u_int LINE_BFR_SIZE = 512;

    Asura_UnicodeChar wszLineBfr[LINE_BFR_SIZE];
    Asura_UnicodeChar wszNameAndValueBfr[LINE_BFR_SIZE];

    bool bError = false;

    READRES eRes;
    for( u_int uLineNum = 1; READRES_OK == (eRes = ReadLine(wszLineBfr, LINE_BFR_SIZE)); ++uLineNum )
    {
        Asura_UnicodeChar* wszStart = wszLineBfr;
        if( !*wszStart ) { continue; }

        // Ignore comments.
        {
            Asura_UnicodeChar* wszComment = wcsstr( wszStart, L"//" );
            if( wszComment ) { *wszComment = L'\0'; }
        }

        // Ignore leading or trailing whitespace.
        wszStart = Asura_StringUtil::StripWhitespace( wszStart, Asura_StringUtil::STRIP_BOTH_ENDS );
        if( !wszStart ) { continue; }

        // Setup separate name and value buffer. We'll work in here to split
        // them up.
        wcscpy( wszNameAndValueBfr, wszStart );
        Asura_UnicodeChar* wszName = wszNameAndValueBfr;

        Asura_UnicodeChar* wszValue;
        if( *wszName == '\"' )
        {
            wszValue = wcschr( wszName + 1, L'\"' );
            if( wszValue )
            {
                ++wszValue;
                
                // Must be whitespace separating them.
                if( !*wszValue || !iswspace(*wszValue) ) { wszValue = NULL; }
            }
            else
            {
                continue;
            }
        }
        else
        {
            // Name and value are separate by whitespace.
            wszValue = wcschr( wszName, L' ' );
            if( !wszValue ) { wszValue = wcschr( wszName, L'\t' ); }
        }

        if( wszValue )
        {
            *wszValue++ = L'\0';
            wszValue = Asura_StringUtil::StripWhitespace( wszValue, Asura_StringUtil::STRIP_BOTH_ENDS );
            wszValue = Asura_StringUtil::StripQuotes( wszValue );
        }

        if( *wszName == '\"' )
        {
            wszName = Asura_StringUtil::StripQuotes( wszName );
            wszName = Asura_StringUtil::StripWhitespace( wszName, Asura_StringUtil::STRIP_BOTH_ENDS );
            if( !wszName ) { continue; }
        }

        // Ok, store this pair.
        ListNode* pxNode = new ListNode( wszLineBfr, wszName, wszValue, uLineNum );
        if( pxNode )
        {
            if( m_pxListHead )
            {
                m_pxListHead->InsertAtEnd( pxNode );
            }
            else
            {
                m_pxListHead = pxNode;
            }
        }
        else
        {
            bError = true;
            break;
        }
    }

    if( bError || READRES_ERR == eRes )
    {
        DeallocateList();
        return false;
    }
    else
    {
        return true;
    }
}


bool Asura_CfgFileReader::Execute()
{
    // Push remaining lines through command console.
    for( ItBegin(); !ItDone(); ItNext() )
    {
        if( !ItHasBeenUsed() )
        {
            bool bReturnValue;
            Asura_CommandConsole::Execute(ItGetRawLine(), &bReturnValue);
        }
    }

    return true;
}

Asura_CfgFileReader::GETRES Asura_CfgFileReader::GetBool( const Asura_UnicodeChar* wszName, bool* pbValue, u_int* puLineNum ) const
{
    if( !m_pxListHead ) { return GETRES_MISSING; }

    const ListNode* pxNode = m_pxListHead->Find( wszName );
    if( !pxNode ) { return GETRES_MISSING; }

    if( puLineNum ) { *puLineNum = pxNode->GetLineNum(); }

    if( !Asura_StringUtil::GetBoolValue(pxNode->GetValue(), pbValue) ) { return GETRES_INVALID_FORMAT; }

    return GETRES_OK;
}

Asura_CfgFileReader::GETRES Asura_CfgFileReader::GetFloat( const Asura_UnicodeChar* wszName, float* pfValue, u_int* puLineNum ) const
{
    if( !m_pxListHead ) { return GETRES_MISSING; }

    const ListNode* pxNode = m_pxListHead->Find( wszName );
    if( !pxNode ) { return GETRES_MISSING; }

    if( puLineNum ) { *puLineNum = pxNode->GetLineNum(); }

    if( !Asura_StringUtil::GetFloatValue(pxNode->GetValue(), pfValue) ) { return GETRES_INVALID_FORMAT; }

    return GETRES_OK;
}

Asura_CfgFileReader::GETRES Asura_CfgFileReader::GetInt( const Asura_UnicodeChar* wszName, int* piValue, u_int* puLineNum ) const
{
    if( !m_pxListHead ) { return GETRES_MISSING; }

    const ListNode* pxNode = m_pxListHead->Find( wszName );
    if( !pxNode ) { return GETRES_MISSING; }

    if( puLineNum ) { *puLineNum = pxNode->GetLineNum(); }

    if( !Asura_StringUtil::GetIntValue(pxNode->GetValue(), piValue) ) { return GETRES_INVALID_FORMAT; }

    return GETRES_OK;
}

Asura_CfgFileReader::GETRES Asura_CfgFileReader::GetStr( const Asura_UnicodeChar* wszName, const Asura_UnicodeChar** pwszValue, u_int* puLineNum ) const
{
    if( !m_pxListHead ) { return GETRES_MISSING; }

    const ListNode* pxNode = m_pxListHead->Find( wszName );
    if( !pxNode ) { return GETRES_MISSING; }

    if( puLineNum ) { *puLineNum = pxNode->GetLineNum(); }

    *pwszValue = pxNode->GetValue();
    return GETRES_OK;
}

//
// Asura_CfgFileReader::ListNode.
//

Asura_CfgFileReader::ListNode::ListNode( Asura_UnicodeChar* wszRawLine, Asura_UnicodeChar* wszName, Asura_UnicodeChar* wszValue, u_int uLineNum )
{
    Asura_Assert( wszRawLine && wszName, "" );

    m_wszRawLine = Asura_StringUtil::Duplicate( wszRawLine );
    m_wszName    = Asura_StringUtil::Duplicate( wszName );
    m_wszValue   = Asura_StringUtil::Duplicate( wszValue ? wszValue : L"" );
    m_uLineNum   = uLineNum;
    m_bIsUsed    = false;
    m_pxNext     = NULL;
}

Asura_CfgFileReader::ListNode::~ListNode( void )
{
    if( m_pxNext ) { delete m_pxNext; }

    delete[] m_wszValue;
    delete[] m_wszName;
    delete[] m_wszRawLine;
}

const Asura_CfgFileReader::ListNode* Asura_CfgFileReader::ListNode::Find( const Asura_UnicodeChar* wszName ) const
{
    if( !wcsicmp(m_wszName, wszName) )
    {
        m_bIsUsed = true;
        return this;
    }
    else
    {
        return( m_pxNext ? m_pxNext->Find(wszName) : NULL );
    }
}

void Asura_CfgFileReader::ListNode::InsertAtEnd( ListNode* pxNewNode )
{
    if( m_pxNext )
    {
        m_pxNext->InsertAtEnd( pxNewNode );
    }
    else
    {
        m_pxNext = pxNewNode;
    }
}

