/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Test of shader macro parsing.
 *//*
 * LEGAL:   COPYRIGHT (C) 2009 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <iostream>
#include <sstream>
#include <string>
using std::string;

// Compatibility.
#define UX(COND) (COND)
#define EX(COND) (COND)
#define Exception string

/*****************************************************************************
 * @return Size of an opened file or -1 if error.
 *****************************************************************************/
long
FileSize( FILE* file )
{
    int fd = fileno( file );        // fileno() shouldn't ever fail
    struct stat s;
    if ( fstat( fd, &s ) == 0 )
        return s.st_size;
    else
        return -1;
}

/*****************************************************************************
 * Read a file into a C++ string.
 *****************************************************************************/
bool
ReadFile( const string& fname, string& obuf )
{
    obuf.erase();                                               // clear buffer
    FILE* file = fopen( fname.c_str(), "rb" );
    if ( file )
    {
        long fileSize = FileSize( file );                       // returns -1 if error
        if ( fileSize > 0 )
        {
            char* buf = new char[ fileSize + 10 ];
            uint bytesRead = fread( buf, 1, fileSize, file );   // should return 0..fileSize
            obuf.assign( buf, bytesRead );                      // assign bytes actually read
            delete[] buf;
        }
        fclose( file );
    }
    return obuf.size() > 0;
}

/*****************************************************************************
 * Recursively expand macros in shader source.
 *****************************************************************************/
string
ExpandMacros( const string& shaderSrc )
{
    const string& input = shaderSrc;
    std::istringstream inputStream( input );
    std::ostringstream outputStream;
    string field;

    // Scan for macro commands.
    while ( inputStream.good() )
    {
        field.erase(); inputStream >> field;
        if ( field == "@include" )
        {
            field.erase(); inputStream >> field;
            string includeRaw;
            if ( UX( not ReadFile( field, includeRaw ) ) )
            {
                std::ostringstream msg;
                msg << "ERROR[shader @include]: cannot read " << field << std::endl;
                throw Exception( msg.str() );
            }

            // Recurse!
            string include = ExpandMacros( includeRaw );
            outputStream << include;  // expand include file
        }
        else  // not a macro keyword
        {
            outputStream << field;
        }

        // Preserve whitespace in source code.
        while ( inputStream.good() )
        {
            const char c = inputStream.peek();
            if ( EX( c == ' ' or c == '\t' or c == '\n' ) )
            {
                outputStream << c;  // copy to output
                inputStream.get();  // discard next whitespace char
            }
            else
            {
                break;  // non-whitespace
            }
        }
    }

    // Yuck but cannot force ostringstream() ctor to use specified string
    // [ostringstream(const string&) implies a copy).
    return outputStream.str();
}

/*****************************************************************************
 * Simple test.
 *****************************************************************************/
void
SimpleTest( void )
{
    const string shaderSrcOrig = 
"// test \n"
"main() \n"
"{ \n"
"@include inc.glsl \n"
"} \n";
    string shaderSrcExpanded;

    std::cout << "--------------------------------------------------" << std::endl
              << "Original shader src:" << std::endl
              << shaderSrcOrig;

    shaderSrcExpanded = ExpandMacros( shaderSrcOrig );

    std::cout << "--------------------------------------------------" << std::endl
              << "Expanded shader src:" << std::endl
              << shaderSrcExpanded;
}

/*****************************************************************************
 * Nested test.
 *****************************************************************************/
void
NestedTest( void )
{
    const string shaderSrcOrig = 
"// test \n"
"main() \n"
"{ \n"
"@include nest1.glsl \n"
"} \n";
    string shaderSrcExpanded;

    std::cout << "--------------------------------------------------" << std::endl
              << "Original shader src:" << std::endl
              << shaderSrcOrig;

    shaderSrcExpanded = ExpandMacros( shaderSrcOrig );

    std::cout << "--------------------------------------------------" << std::endl
              << "Expanded shader src:" << std::endl
              << shaderSrcExpanded;
}

/*****************************************************************************
 * main.
 *****************************************************************************/
int
main( int argc, char** argv )
{
    std::cout << "==================================================" << std::endl << std::endl;
    SimpleTest();

    std::cout << "==================================================" << std::endl << std::endl;
    NestedTest();

    return 0;
}
