/*
    Copyright 2005-2015 Intel Corporation.  All Rights Reserved.

    This file is part of Threading Building Blocks. Threading Building Blocks is free software;
    you can redistribute it and/or modify it under the terms of the GNU General Public License
    version 2  as  published  by  the  Free Software Foundation.  Threading Building Blocks is
    distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    See  the GNU General Public License for more details.   You should have received a copy of
    the  GNU General Public License along with Threading Building Blocks; if not, write to the
    Free Software Foundation, Inc.,  51 Franklin St,  Fifth Floor,  Boston,  MA 02110-1301 USA

    As a special exception,  you may use this file  as part of a free software library without
    restriction.  Specifically,  if other files instantiate templates  or use macros or inline
    functions from this file, or you compile this file and link it with other files to produce
    an executable,  this file does not by itself cause the resulting executable to be covered
    by the GNU General Public License. This exception does not however invalidate any other
    reasons why the executable file might be covered by the GNU General Public License.
*/

#include "tbb/tbb_stddef.h"

#if __TBB_WIN8UI_SUPPORT
// TODO: figure out how the test can be enabled for win8ui
#define HARNESS_NO_PARSE_COMMAND_LINE 1
#include "harness.h"
int TestMain() {
    return Harness::Skipped;
}
#else

#include <stdio.h>
#include <stdlib.h>

#if !TBB_USE_EXCEPTIONS && _MSC_VER
    // Suppress "C++ exception handler used, but unwind semantics are not enabled" warning in STL headers
    #pragma warning (push)
    #pragma warning (disable: 4530)
#endif

#include <vector>
#include <string>
#include <utility>

#if !TBB_USE_EXCEPTIONS && _MSC_VER
    #pragma warning (pop)
#endif

#include "tbb/task_scheduler_init.h"

#define HARNESS_CUSTOM_MAIN 1
#define HARNESS_NO_PARSE_COMMAND_LINE 1
#define HARNESS_NO_MAIN_ARGS 0
#include "harness.h"

#if defined (_WIN32) || defined (_WIN64)
#define TEST_SYSTEM_COMMAND "test_tbb_version.exe @"
#define putenv _putenv
#else
#define TEST_SYSTEM_COMMAND "./test_tbb_version.exe @"
#endif

enum string_required {
    required,
    optional,
    optional_multiple
    };

typedef std::pair <std::string, string_required> string_pair;

void initialize_strings_vector(std::vector <string_pair>* vector);

const char stderr_stream[] = "version_test.err";
const char stdout_stream[] = "version_test.out";

HARNESS_EXPORT
int main(int argc, char *argv[] ) {
    const size_t psBuffer_len = 2048;
    char psBuffer[psBuffer_len];
/* We first introduced runtime version identification in 3014 */
#if TBB_INTERFACE_VERSION>=3014
    // For now, just test that run-time TBB version matches the compile-time version,
    // since otherwise the subsequent test of "TBB: INTERFACE VERSION" string will fail anyway.
    // We need something more clever in future.
    if ( tbb::TBB_runtime_interface_version()!=TBB_INTERFACE_VERSION ){
        snprintf( psBuffer, psBuffer_len,
                  "%s %s %d %s %d.",
                  "Running with the library of different version than the test was compiled against.",
                  "Expected",
                  TBB_INTERFACE_VERSION,
                  "- got",
                  tbb::TBB_runtime_interface_version()
                  );
        ASSERT( tbb::TBB_runtime_interface_version()==TBB_INTERFACE_VERSION, psBuffer );
    }
#endif
#if __TBB_MIC_OFFLOAD
    // Skip the test in offload mode.
    // Run the test in 'true' native mode (because 'system()' works in 'true' native mode).
    (argc, argv);
    REPORT("skip\n");
#elif __TBB_MPI_INTEROP || __bg__
    (void) argc; // unused
    (void) argv; // unused
    REPORT("skip\n");
#else
    __TBB_TRY {
        FILE *stream_out;
        FILE *stream_err;

        if(argc>1 && argv[1][0] == '@' ) {
            stream_err = freopen( stderr_stream, "w", stderr );
            if( stream_err == NULL ){
                REPORT( "Internal test error (freopen)\n" );
                exit( 1 );
            }
            stream_out = freopen( stdout_stream, "w", stdout );
            if( stream_out == NULL ){
                REPORT( "Internal test error (freopen)\n" );
                exit( 1 );
            }
            {
                tbb::task_scheduler_init init(1);
            }
            fclose( stream_out );
            fclose( stream_err );
            exit(0);
        }
        //1st step check that output is empty if TBB_VERSION is not defined.
        if ( getenv("TBB_VERSION") ){
            REPORT( "TBB_VERSION defined, skipping step 1 (empty output check)\n" );
        }else{
            if( ( system(TEST_SYSTEM_COMMAND) ) != 0 ){
                REPORT( "Error (step 1): Internal test error\n" );
                exit( 1 );
            }
            //Checking output streams - they should be empty
            stream_err = fopen( stderr_stream, "r" );
            if( stream_err == NULL ){
                REPORT( "Error (step 1):Internal test error (stderr open)\n" );
                exit( 1 );
            }
            while( !feof( stream_err ) ) {
                if( fgets( psBuffer, psBuffer_len, stream_err ) != NULL ){
                    REPORT( "Error (step 1): stderr should be empty\n" );
                    exit( 1 );
                }
            }
            fclose( stream_err );
            stream_out = fopen( stdout_stream, "r" );
            if( stream_out == NULL ){
                REPORT( "Error (step 1):Internal test error (stdout open)\n" );
                exit( 1 );
            }
            while( !feof( stream_out ) ) {
                if( fgets( psBuffer, psBuffer_len, stream_out ) != NULL ){
                    REPORT( "Error (step 1): stdout should be empty\n" );
                    exit( 1 );
                }
            }
            fclose( stream_out );
        }

        //Setting TBB_VERSION in case it is not set
        if ( !getenv("TBB_VERSION") ){
            putenv(const_cast<char*>("TBB_VERSION=1"));
        }

        if( ( system(TEST_SYSTEM_COMMAND) ) != 0 ){
            REPORT( "Error (step 2):Internal test error\n" );
            exit( 1 );
        }
        //Checking pipe - it should contain version data
        std::vector <string_pair> strings_vector;
        std::vector <string_pair>::iterator strings_iterator;

        initialize_strings_vector( &strings_vector );
        strings_iterator = strings_vector.begin();

        stream_out = fopen( stdout_stream, "r" );
        if( stream_out == NULL ){
            REPORT( "Error (step 2):Internal test error (stdout open)\n" );
            exit( 1 );
        }
        while( !feof( stream_out ) ) {
            if( fgets( psBuffer, psBuffer_len, stream_out ) != NULL ){
                REPORT( "Error (step 2): stdout should be empty\n" );
                exit( 1 );
            }
        }
        fclose( stream_out );

        stream_err = fopen( stderr_stream, "r" );
        if( stream_err == NULL ){
            REPORT( "Error (step 1):Internal test error (stderr open)\n" );
            exit( 1 );
        }
        
        while( !feof( stream_err ) ) {
            if( fgets( psBuffer, psBuffer_len, stream_err ) != NULL ){
                if (strstr( psBuffer, "TBBmalloc: " )) {
                    // TBB allocator might or might not be here, ignore it
                    continue;
                }
                bool match_found = false;
                do{
                    if ( strings_iterator == strings_vector.end() ){
                        REPORT( "Error: version string dictionary ended prematurely.\n" );
                        REPORT( "No match for: \t%s", psBuffer );
                        exit( 1 );
                    }
                    if ( strstr( psBuffer, strings_iterator->first.c_str() ) == NULL ){ // mismatch
                        if( strings_iterator->second == required ){
                            REPORT( "Error: version strings do not match.\n" );
                            REPORT( "Expected \"%s\" not found in:\n\t%s", strings_iterator->first.c_str(), psBuffer );
                            exit( 1 );
                        }
                        ++strings_iterator;
                    }else{
                        match_found = true;
                        if( strings_iterator->second != optional_multiple )
                            ++strings_iterator;
                    }
                }while( !match_found );
            }
        }
        fclose( stream_err );
    } __TBB_CATCH(...) {
        ASSERT( 0,"unexpected exception" );
    }
    REPORT("done\n");
#endif //__TBB_MIC_OFFLOAD, __TBB_MPI_INTEROP etc
    return 0;
}


// Fill dictionary with version strings for platforms 
void initialize_strings_vector(std::vector <string_pair>* vector)
{
    vector->push_back(string_pair("TBB: VERSION\t\t4.3", required));          // check TBB_VERSION
    vector->push_back(string_pair("TBB: INTERFACE VERSION\t8006", required)); // check TBB_INTERFACE_VERSION
    vector->push_back(string_pair("TBB: BUILD_DATE", required));
    vector->push_back(string_pair("TBB: BUILD_HOST", required));
    vector->push_back(string_pair("TBB: BUILD_OS", required));
#if _WIN32||_WIN64
#if !__MINGW32__
    vector->push_back(string_pair("TBB: BUILD_CL", required));
#endif
    vector->push_back(string_pair("TBB: BUILD_COMPILER", required));
#elif __APPLE__
    vector->push_back(string_pair("TBB: BUILD_KERNEL", required));
    vector->push_back(string_pair("TBB: BUILD_CLANG", required));
    vector->push_back(string_pair("TBB: BUILD_XCODE", optional));
    vector->push_back(string_pair("TBB: BUILD_COMPILER", optional)); //if( getenv("COMPILER_VERSION") )
#elif __sun
    vector->push_back(string_pair("TBB: BUILD_KERNEL", required));
    vector->push_back(string_pair("TBB: BUILD_SUNCC", required));
    vector->push_back(string_pair("TBB: BUILD_COMPILER", optional)); //if( getenv("COMPILER_VERSION") )
#else // We use version_info_linux.sh for unsupported OSes
#if !__ANDROID__
    vector->push_back(string_pair("TBB: BUILD_KERNEL", required));
#endif
    vector->push_back(string_pair("TBB: BUILD_GCC", required));
    vector->push_back(string_pair("TBB: BUILD_COMPILER", optional)); //if( getenv("COMPILER_VERSION") )
#if __ANDROID__
    vector->push_back(string_pair("TBB: BUILD_NDK", optional));
#else
    vector->push_back(string_pair("TBB: BUILD_LIBC", required));
#endif // !__ANDROID__
    vector->push_back(string_pair("TBB: BUILD_LD", required));
#endif // OS
    vector->push_back(string_pair("TBB: BUILD_TARGET", required));
    vector->push_back(string_pair("TBB: BUILD_COMMAND", required));
    vector->push_back(string_pair("TBB: TBB_USE_DEBUG", required));
    vector->push_back(string_pair("TBB: TBB_USE_ASSERT", required));
#if __TBB_CPF_BUILD
    vector->push_back(string_pair("TBB: TBB_PREVIEW_BINARY", required));
#endif
    vector->push_back(string_pair("TBB: DO_ITT_NOTIFY", required));
    vector->push_back(string_pair("TBB: ITT", optional)); //#ifdef DO_ITT_NOTIFY
    vector->push_back(string_pair("TBB: ALLOCATOR", required));
#if _WIN32||_WIN64
    vector->push_back(string_pair("TBB: Processor groups", required));
    vector->push_back(string_pair("TBB: ----- Group", optional_multiple));
#endif
    vector->push_back(string_pair("TBB: RML", optional));
    vector->push_back(string_pair("TBB: Intel(R) RML library built:", optional));
    vector->push_back(string_pair("TBB: Intel(R) RML library version:", optional));
    vector->push_back(string_pair("TBB: Tools support", required));
    return;
}
#endif /* __TBB_WIN8UI_SUPPORT */
