#ifndef PYLOG_FORMATTER_H_SEEN
#define PYLOG_FORMATTER_H_SEEN

#include <cstring>
#include <cxxtest/Flags.h>

#ifndef _CXXTEST_HAVE_STD
#   define _CXXTEST_HAVE_STD
#endif // _CXXTEST_HAVE_STD

#include <cxxtest/ErrorFormatter.h>
#include <cxxtest/StdValueTraits.h>

#ifdef _CXXTEST_OLD_STD
#   include <iostream.h>
#else // !_CXXTEST_OLD_STD
#   include <iostream>
#endif // _CXXTEST_OLD_STD

namespace CxxTest 
{
class PylogFormatter : public TestListener
{
public:
    PylogFormatter( OutputStream *o, const char* name = "test" ) :
        _o(o),
        _base( _chompPath( name ) ),
        _runPassed(true),
        _suiteIndex(-1),
        _testIndex(-1)
    {}
    virtual ~PylogFormatter() { delete outputStream(); }

    virtual void enterWorld( const WorldDescription & /*desc*/ )
    {
        (*_o) << "**************************************************" << endl;
        _o->flush();
    }

    virtual void leaveWorld( const WorldDescription & desc )
    {
        using std::strlen;
        unsigned int skippedCount = 0;
        unsigned int failedCount = 0;
        unsigned int warnedCount = 0;
        unsigned int passedCount = 0;

        for ( unsigned int i = 0; i < desc.numSuites(); i++ ) {
            const SuiteDescription& suite = desc.suiteDescription(i);
            for ( unsigned int j = 0; j < suite.numTests(); j++ ) {
                const TestDescription& test = suite.testDescription(j);

                // Test Name
                (*_o) << _base.c_str() << "_";
                _padOut( i, 3 );
                (*_o) << "_";
                _padOut( j, 3);
                (*_o) << "    ";

                // Test Description
                (*_o) << test.suiteName() << "_|_" << test.testName();
                (*_o) << " ";

                unsigned const sent = strlen( test.suiteName() ) + strlen( test.testName() ) + 1;
                for ( unsigned z = sent; z < 56; z++ ) {
                    (*_o) << " ";
                }

                (*_o) << " : ";

                switch ( _status[i][j] ) {
                    case OK:
                        (*_o) << "PASS";
                        passedCount++;
                        break;
                    case SKIPPED:
                        (*_o) << "OMIT";
                        skippedCount++;
                        break;
                    case WARNING:
                        (*_o) << "WARNING";
                        warnedCount++;
                        break;
                    case ERROR:
                        (*_o) << "FAILURE";
                        failedCount++;
                        break;
                }

                (*_o) << endl;
                for ( CXXTEST_STD(vector)<CXXTEST_STD(string)>::iterator it = _messages[i][j].begin(); it < _messages[i][j].end(); ++it ) {
                    (*_o) << "        " << (*it).c_str() << endl;
                }
            }
        }

        (*_o) << "**************************************************" << endl;
        (*_o) << "Command line asked for " << desc.numTotalTests() << " of " << desc.numTotalTests() << " tests" << endl;
        (*_o) << "Of those: "
              << skippedCount << " Skipped, "
              << failedCount << " Failed, "
              << warnedCount << " Warned, "
              << passedCount << " Passed"
              << endl;
    }


    virtual void enterSuite( const SuiteDescription & desc )
    {
        (void)desc;
        _suiteIndex++;
        _testIndex = -1;
        while ( (_suiteIndex >= 0) && ((int)_status.size() <= _suiteIndex) ) {
            CXXTEST_STD(vector)<ErrorLevel> tmp;
            _status.push_back(tmp);
            CXXTEST_STD(vector)<CXXTEST_STD(vector)<CXXTEST_STD(string)> > tmp2;
            _messages.push_back(tmp2);
        }
    }

    virtual void leaveSuite( const SuiteDescription & desc )
    {
        (void)desc;
    }

    virtual void enterTest( const TestDescription & desc )
    {
        (void)desc;
        if ( _suiteIndex >= 0 && (int)_status.size() > _suiteIndex ) {
            _testIndex++;
            while ( (_testIndex >= 0) && ((int)_status[_suiteIndex].size() <= _testIndex) ) {
                ErrorLevel tmp = OK;
                _status[_suiteIndex].push_back(tmp);
                CXXTEST_STD(vector)<CXXTEST_STD(string)> tmp2;
                _messages[_suiteIndex].push_back(tmp2);
            }
        }
    }

    virtual void leaveTest( const TestDescription & desc )
    {
        (void)desc;
    }

    virtual void trace( const char * file, unsigned line,
                        const char * expression )
    {
        CXXTEST_STD(string)tmp(expression);
        _traceCurrent( file, line, tmp );
    }

    virtual void warning( const char * file, unsigned line,
                          const char * expression )
    {
        CXXTEST_STD(string)tmp(expression);
        _warnCurrent( file, line, tmp );
    }

    virtual void failedTest( const char * file, unsigned line,
                             const char * expression )
    {
        CXXTEST_STD(string)tmp(expression);
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssert( const char * file, unsigned line,
                               const char * expression )
    {
        CXXTEST_STD(string)tmp(expression);
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssertEquals( const char * file, unsigned line,
                                     const char * xStr, const char * yStr,
                                     const char * x, const char * y )
    {
        CXXTEST_STD(string)tmp;
        tmp += "Expected (";
        tmp += xStr;
        tmp += " == ";
        tmp += yStr;
        tmp += "), found (";
        tmp += x;
        tmp += " != ";
        tmp += y;
        tmp += ")";
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssertSameData( const char * file, unsigned line,
                                       const char * /*xStr*/, const char * /*yStr*/,
                                       const char * /*sizeStr*/, const void * /*x*/,
                                       const void * /*y*/, unsigned /*size*/ )
    {
        CXXTEST_STD(string)tmp("TODO - fill in error details");
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssertDelta( const char * file, unsigned line,
                                    const char * /*xStr*/, const char * /*yStr*/,
                                    const char * /*dStr*/, const char * /*x*/,
                                    const char * /*y*/, const char * /*d*/ )
    {
        CXXTEST_STD(string)tmp("TODO - fill in error details");
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssertDiffers( const char * file, unsigned line,
                                      const char * xStr, const char * yStr,
                                      const char * value )
    {
        CXXTEST_STD(string)tmp;
        tmp += "Expected (";
        tmp += xStr;
        tmp += " != ";
        tmp += yStr;
        tmp += "), found (";
        tmp += value;
        tmp += ")";
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssertLessThan( const char * file, unsigned line,
                                       const char * xStr, const char * yStr,
                                       const char * x, const char * y )
    {
        CXXTEST_STD(string)tmp;
        tmp += "Expected (";
        tmp += xStr;
        tmp += " < ";
        tmp += yStr;
        tmp += "), found (";
        tmp += x;
        tmp += " >= ";
        tmp += y;
        tmp += ")";
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssertLessThanEquals( const char * file, unsigned line,
                                             const char * xStr, const char * yStr,
                                             const char * x, const char * y )
    {
        CXXTEST_STD(string)tmp;
        tmp += "Expected (";
        tmp += xStr;
        tmp += " <= ";
        tmp += yStr;
        tmp += "), found (";
        tmp += x;
        tmp += " > ";
        tmp += y;
        tmp += ")";
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssertPredicate( const char * file, unsigned line,
                                        const char * /*predicate*/, const char * /*xStr*/, const char * /*x*/ )
    {
        CXXTEST_STD(string)tmp("TODO - fill in error details");
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssertRelation( const char * file, unsigned line,
                                       const char * /*relation*/, const char * /*xStr*/, const char * /*yStr*/,
                                       const char * /*x*/, const char * /*y*/ )
    {
        CXXTEST_STD(string)tmp("TODO - fill in error details");
        _failCurrent( file, line, tmp);
    }

    virtual void failedAssertThrows( const char * file, unsigned line,
                                     const char * /*expression*/, const char * /*type*/,
                                     bool /*otherThrown*/ )
    {
        CXXTEST_STD(string)tmp("TODO - fill in error details");
        _failCurrent( file, line, tmp );
    }

    virtual void failedAssertThrowsNot( const char * file, unsigned line,
                                        const char * expression )
    {
        CXXTEST_STD(string)tmp(expression);
        _failCurrent( file, line, tmp );
    }

protected:

    enum ErrorLevel {
        OK,
        SKIPPED,
        WARNING,
        ERROR
    };

    OutputStream *outputStream() const
    {
        return _o;
    }

    void _traceCurrent( const char* /*file*/, unsigned /*line*/, const CXXTEST_STD(string)& errMsg ) {
        _runPassed = false;
        if ( _suiteIndex < (int)_status.size() ) {
            if ( _testIndex < (int)_status[_suiteIndex].size() ) {
                _messages[_suiteIndex][_testIndex].push_back( errMsg );
            }
        }
    }

    void _warnCurrent( const char* /*file*/, unsigned /*line*/, const CXXTEST_STD(string)& errMsg ) {
        _runPassed = false;
        if ( _suiteIndex < (int)_status.size() ) {
            if ( _testIndex < (int)_status[_suiteIndex].size() ) {
                if ( _status[_suiteIndex][_testIndex] != ERROR ) {
                    _status[_suiteIndex][_testIndex] = WARNING;
                }

                _messages[_suiteIndex][_testIndex].push_back( errMsg );
            }
        }
    }

    void _failCurrent( const char* /*file*/, unsigned /*line*/, const CXXTEST_STD(string)& errMsg ) {
        _runPassed = false;
        if ( _suiteIndex < (int)_status.size() ) {
            if ( _testIndex < (int)_status[_suiteIndex].size() ) {
                _status[_suiteIndex][_testIndex] = ERROR;

                _messages[_suiteIndex][_testIndex].push_back( errMsg );
            }
        }
    }

    void _padOut( unsigned int num, int digits )
    {
        int match = 1;
        for ( int j = 1; j < digits; j++ ) {
            match *= 10;
        }

        for ( unsigned int i = match; i > 1 && i > num; i /= 10 )
        {
            (*_o) << "0";
        }
        (*_o) << num;
    }

private:
    static void endl( OutputStream &o )
    {
        OutputStream::endl( o );
    }

    static CXXTEST_STD(string) _chompPath( const char* str )
    {
        CXXTEST_STD(string) tmp( str );
        if ( tmp.length() > 2 && tmp[0] == '.' && tmp[1] == '/' ) {
            tmp = tmp.substr( 2 );
        }
        return tmp;
    }

    OutputStream *_o;
    CXXTEST_STD(string) _base;
    CXXTEST_STD(vector)< CXXTEST_STD(vector)<ErrorLevel> > _status;
    CXXTEST_STD(vector)< CXXTEST_STD(vector)< CXXTEST_STD(vector)<CXXTEST_STD(string)> > > _messages;

    bool _runPassed;
    int _suiteIndex;
    int _testIndex;
};

} // namespace CxxTest

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :

#endif // PYLOG_FORMATTER_H_SEEN
