//
// LDASTools AL - A library collection to provide an abstraction layer
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools AL is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools AL is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include <list>
#if HAVE_RANDOM
#include <random>
#endif /* HAVE_RANDOM */
#include <string>
#include <utility>

#include "ldastoolsal/unittest.h"
#include "ldastoolsal/Profile.hh"
#include "ldastoolsal/util.hh"

LDASTools::Testing::UnitTest Test;

typedef std::list< std::pair< std::string, std::string > > data_container_type;
typedef data_container_type::value_type                    data_value_type;

#define RANDOM_SET_SIZE 1000000

data_container_type sample_set_lower;
data_container_type sample_set_mixed;
#if HAVE_RANDOM
data_container_type sample_set_random;
#endif /* HAVE_RANDOM */

static void build_test_data( );
static void test_correctness( );
#if HAVE_RANDOM
static void test_profiling( );
#endif /* HAVE_RANDOM */

int
main( int ArgC, char** ArgV )
{
    //---------------------------------------------------------------------
    // Initialize the test structure
    //---------------------------------------------------------------------
    Test.Init( ArgC, ArgV );
    build_test_data( );

    //---------------------------------------------------------------------
    // Run through a series of tests
    //---------------------------------------------------------------------

    test_correctness( );
#if HAVE_RANDOM
    test_profiling( );
#endif /* HAVE_RANDOM */

    //---------------------------------------------------------------------
    // Exit with the appropriate exit status
    //---------------------------------------------------------------------
    Test.Exit( );
    return 1;
}

#if HAVE_RANDOM
static int
random_character( )
{
    const int upper_case = 26;
    const int lower_case = 26;
    const int punctuation = 3;

    int letter = ( rand( ) % ( upper_case + lower_case + punctuation ) );

    if ( letter < upper_case )
    {
        letter = 'A' + letter;
    }
    else
    {
        letter -= upper_case;
        if ( letter < lower_case )
        {
            letter = 'a' + letter;
        }
        else
        {
            letter -= lower_case;

            switch ( letter )
            {
            case 0:
                letter = '_';
                break;
            case 1:
                letter = '-';
                break;
            case 2:
                letter = ':';
                break;
            default:
                letter = '|';
            }
        }
    }

    return letter;
}
#endif /* HAVE_RANDOM */

static void
build_test_data( )
{
    sample_set_lower.push_back( data_value_type( "one", "one" ) );
    sample_set_lower.push_back( data_value_type( "one", "two" ) );
    sample_set_lower.push_back( data_value_type( "two", "one" ) );

    sample_set_mixed.push_back( data_value_type( "one", "One" ) );
    sample_set_mixed.push_back( data_value_type( "one", "oNe" ) );
    sample_set_mixed.push_back( data_value_type( "One", "two" ) );
    sample_set_mixed.push_back( data_value_type( "two", "One" ) );
    sample_set_mixed.push_back( data_value_type( "one", "Two" ) );
    sample_set_mixed.push_back( data_value_type( "Two", "one" ) );
    sample_set_mixed.push_back(
        data_value_type( "H1:GDS-FAKE_STRAIN", "H1:GDS-FAKE_STRAIN" ) );

#if HAVE_RANDOM
    for ( int x = 0; x < RANDOM_SET_SIZE; ++x )
    {
        int word_size = ( std::rand( ) % 7 ) + 3;
        int same_size = ( std::rand( ) % ( word_size - 1 ) );

        std::string rhs;
        std::string lhs;

        //-------------------------------------------------------------------
        // Put in the matching characters of each string
        //-------------------------------------------------------------------
        for ( int x = 0; x < same_size; ++x )
        {
            int letter = random_character( );
            rhs.push_back( letter );
            lhs.push_back( letter );
        }
        //-------------------------------------------------------------------
        // Put in the random characters of each string
        //-------------------------------------------------------------------
        for ( int x = 0; x < ( word_size - same_size ); ++x )
        {
            rhs.push_back( random_character( ) );
            lhs.push_back( random_character( ) );
        }

        sample_set_random.push_back( data_value_type( lhs, rhs ) );
    }
#endif /* HAVE_RANDOM */
}

static void
test_correctness( )
{
    bool pass = true;

    for ( data_container_type::const_iterator cur = sample_set_lower.begin( ),
                                              last = sample_set_lower.end( );
          cur != last;
          ++cur )
    {
        int expect = cur->first.compare( cur->second );

        //-------------------------------------------------------------------
        // Ensure the output is what is to be expected
        //-------------------------------------------------------------------
        expect = ( ( expect == 0 ) ? 0 : ( ( expect < 0 ) ? -1 : 1 ) );

        int nc1 = LDASTools::AL::cmp_nocase( cur->first, cur->second );
        int nc2 = LDASTools::AL::cmp_nocase( cur->first.c_str( ),
                                             cur->second.c_str( ) );

        bool lpass = ( ( expect == nc1 ) && ( expect == nc2 ) );

        //-------------------------------------------------------------------
        // Report the findings
        //-------------------------------------------------------------------
        Test.Message( 30 ) << ( ( lpass ) ? "PASS" : "FAIL" )
                           << ": Comparing (lower): " << cur->first
                           << " to: " << cur->second << " [" << expect << ":"
                           << nc1 << ":" << nc2 << "]" << std::endl;
        if ( !lpass )
        {
            pass = lpass;
        }
    }

    LDASTools::AL::CaseInsensitiveCmp c;
    for ( data_container_type::const_iterator cur = sample_set_mixed.begin( ),
                                              last = sample_set_mixed.end( );
          cur != last;
          ++cur )
    {
        bool lpass = ( LDASTools::AL::cmp_nocase( cur->first, cur->second ) ==
                       LDASTools::AL::cmp_nocase( cur->first.c_str( ),
                                                  cur->second.c_str( ) ) );

        Test.Message( 30 ) << ( ( lpass ) ? "PASS" : "FAIL" )
                           << ": Comparing (mixed): " << cur->first
                           << " to: " << cur->second << " ["
                           << LDASTools::AL::cmp_nocase( cur->first,
                                                         cur->second )
                           << ":"
                           << LDASTools::AL::cmp_nocase( cur->first.c_str( ),
                                                         cur->second.c_str( ) )
                           << "=?=" << c( cur->first, cur->second ) << "]"
                           << std::endl;
        if ( !lpass )
        {
            pass = lpass;
        }
    }

#if HAVE_RANDOM
    for ( data_container_type::const_iterator cur = sample_set_random.begin( ),
                                              last = sample_set_random.end( );
          cur != last;
          ++cur )
    {
        bool lpass = ( LDASTools::AL::cmp_nocase( cur->first, cur->second ) ==
                       LDASTools::AL::cmp_nocase( cur->first.c_str( ),
                                                  cur->second.c_str( ) ) );

        Test.Message( 30 ) << ( ( lpass ) ? "PASS" : "FAIL" )
                           << ": Comparing (mixed): " << cur->first
                           << " to: " << cur->second << std::endl;
        if ( !lpass )
        {
            pass = lpass;
        }
    }

#endif /* HAVE_RANDOM */

    Test.Check( pass ) << "Testing comparison correctness" << std::endl;
}

#if HAVE_RANDOM
static void
test_profiling( )
{
    //---------------------------------------------------------------------
    // Profile the std::string variant
    //---------------------------------------------------------------------
    int sum = 0;
    {
        LDASTools::AL::Profile p( "cmp_no_case( string variant)" );

        for ( data_container_type::const_iterator
                  cur = sample_set_random.begin( ),
                  last = sample_set_random.end( );
              cur != last;
              ++cur )
        {
            int val = LDASTools::AL::cmp_nocase( cur->first, cur->second );
            sum += val;
        }
        Test.Message( 10 ) << "Profiling: " << p( __FILE__, __LINE__, "INFO" )
                           << std::endl;
    }
    {
        LDASTools::AL::Profile p( "cmp_no_case( const char* variant)" );

        for ( data_container_type::const_iterator
                  cur = sample_set_random.begin( ),
                  last = sample_set_random.end( );
              cur != last;
              ++cur )
        {
            int val = LDASTools::AL::cmp_nocase( cur->first.c_str( ),
                                                 cur->second.c_str( ) );
            sum += val;
        }
        Test.Message( 10 ) << "Profiling: " << p( __FILE__, __LINE__, "INFO" )
                           << std::endl;
    }

    //---------------------------------------------------------------------
    // Make sure there is a purpose for sum
    //---------------------------------------------------------------------
    Test.Message( 300 ) << " Code to ensure comparison code not optimised out."
                        << " sum: " << sum << std::endl;
}
#endif /* HAVE_RANDOM */
