/***************************************************************************
 *
 * 25.libc.cpp - test exercising 25.4 [lib.alg.c.library]
 *
 * $Id: 25.libc.cpp 510970 2007-02-23 14:57:45Z faridz $
 *
 ***************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  "License"); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 *
 * Copyright 1994-2006 Rogue Wave Software.
 * 
 ***************************************************************************/

#include <cstdlib>      // for bsearch, qsort 
#include <csignal>      // for signal
#include <csetjmp>      // for setjmp, longjmp

#include <alg_test.h>
#include <rw_value.h>   // for UserClass
#include <driver.h>     // for rw_test()

/**************************************************************************/

// used as a special value in comp below
static int global[] = { 0, 0 };


extern "C" {

    static std::jmp_buf jmp_env;

    // SIGABRT handler
    static void handle_ABRT (int)
    {
        // jump outta here to prevent abort() from trying too hard...
        std::longjmp (jmp_env, 1);
    }


    static int c_comp (const void *x, const void *y) 
    {
#ifndef _RWSTD_NO_EXCEPTIONS

        // verify that a thrown exception can be caught
        if (x >= global && x < global + sizeof global / sizeof *global)
            throw x;

#endif   // _RWSTD_NO_EXCEPTIONS

        return   (*_RWSTD_STATIC_CAST (const UserClass*, x)).data_.val_
               - (*_RWSTD_STATIC_CAST (const UserClass*, y)).data_.val_;
    }
}

extern "C++" {

    static int cxx_comp (const void *x, const void *y)
    {
#ifndef _RWSTD_NO_EXCEPTIONS

        // verify that a thrown exception can be caught
        if (x >= global && x < global + sizeof global / sizeof *global)
            throw x;

#endif   // _RWSTD_NO_EXCEPTIONS

        return   (*_RWSTD_STATIC_CAST (const UserClass*, x)).data_.val_
               - (*_RWSTD_STATIC_CAST (const UserClass*, y)).data_.val_;
    }
}

/**************************************************************************/

// exrcises std::qqsort (25.4.4)
static void
test_qsort (int          line,
            const char  *src,
            std::size_t  nsrc,
            bool         cxx) 
{
    UserClass* const xsrc = UserClass::from_char (src, nsrc);
    UserClass* const xsrc_end = xsrc + nsrc;
    RW_ASSERT (0 == nsrc || 0 != xsrc);

    if (cxx)
        std::qsort (xsrc, nsrc, sizeof *xsrc, cxx_comp);
    else 
        std::qsort (xsrc, nsrc, sizeof *xsrc, c_comp);

    bool success = is_sorted_lt (xsrc, xsrc_end);
    rw_assert (success, 0, line, 
               "line %d: extern \"C%{?}++%{;}\" qsort (\"%s\", ...) ==> "
               "\"%{X=*.*}\" not sorted",
               __LINE__, cxx, src, int (nsrc), -1, xsrc);

    delete[] xsrc;
}

/**************************************************************************/

// exrcises std::qsort (25.4.4 Note)
static void
test_qsort_exception (int          line,
                      int         *src,
                      std::size_t  nsrc,
                      bool         cxx) 
{
#ifndef _RWSTD_NO_EXCEPTIONS

    // install a SIGABRT handler in case libc can't throw
    // exceptions and aborts instead (e.g., glibc/gcc)
    std::signal (SIGABRT, handle_ABRT);

    bool success = false;
    try {
        if (0 == setjmp (jmp_env)) {
            if (cxx)
                std::qsort (src, nsrc, sizeof *src, cxx_comp);
            else 
                std::qsort (src, nsrc, sizeof *src, c_comp);
        }
        else {
            // prevent double assertion
            success = true;
            rw_assert (false, 0, line,
                       "line %d: extern \"C%{?}++%{;}\" qsort() aborted "
                       "on exception", __LINE__, cxx);
        }
    }
    catch (const void* x) {
        success = x >= src && x < src + nsrc;
    }
    catch (...) {
    }

    rw_assert (success, 0, line, 
               "line %d: extern \"C%{?}++%{;}\" qsort() failed to propagate "
               "exception", __LINE__, cxx);

#else   // if defined (_RWSTD_NO_EXCEPTIONS)

    _RWSTD_UNUSED (line);
    _RWSTD_UNUSED (src);
    _RWSTD_UNUSED (nsrc);
    _RWSTD_UNUSED (key);
    _RWSTD_UNUSED (cxx);

#endif   // _RWSTD_NO_EXCEPTIONS
}

/**************************************************************************/

// exrcises std::bsearch (25.4.3)
static void
test_bsearch (int          line,
              const char  *src,
              std::size_t  nsrc,
              const char   key_val,
              std::size_t  res,
              bool         cxx) 
{
    UserClass* const xsrc = UserClass::from_char (src, nsrc,
                                                  true); // must be sorted
    RW_ASSERT (0 == nsrc || 0 != xsrc);

    UserClass key;
    key.data_.val_ = key_val;

    const void* result = cxx ?
        std::bsearch (&key, xsrc, nsrc, sizeof *xsrc, cxx_comp)
      : std::bsearch (&key, xsrc, nsrc, sizeof *xsrc, c_comp);

    const UserClass* exp_res = res == _RWSTD_SIZE_MAX ? 0 : xsrc + res;

    bool success = result == exp_res;
    rw_assert (success, 0, line, 
               "line %d: extern \"C%{?}++%{;}\" bsearch (\"%s\", %#c, ...) "
               "== %p, got %p, difference is %td elements",
               __LINE__, cxx, src, key_val, result, exp_res,
               _RWSTD_STATIC_CAST (const UserClass*, result) - exp_res);

    delete[] xsrc;
}

/**************************************************************************/

// exrcises std::bsearch (25.4.4 Note)
static void
test_bsearch_exception (int         line,
                        const int  *src,
                        std::size_t nsrc,
                        const int  *key,
                        bool        cxx) 
{
#ifndef _RWSTD_NO_EXCEPTIONS

    // install a SIGABRT handler in case libc can't throw
    // exceptions and aborts instead (e.g., glibc/gcc)
    std::signal (SIGABRT, handle_ABRT);

    bool success = false;
    try {
        if (0 == setjmp (jmp_env)) {
            if (cxx)
                std::bsearch (key, src, nsrc, sizeof *src, cxx_comp);
            else 
                std::bsearch (key, src, nsrc, sizeof *src, c_comp);
        }
        else {
            // prevent double assertion
            success = true;
            rw_assert (false, 0, line,
                       "line %d: extern \"C%{?}++%{;}\" bsearch() aborted "
                       "on exception", __LINE__, cxx);
        }
    }
    catch (const void* x) {
        success = x >= src && x < src + nsrc;
    }
    catch (...) {
    }

    rw_assert (success, 0, line, 
               "line %d: extern \"C%{?}++%{;}\" bsearch() failed to propagate "
               "exception", __LINE__, cxx);

#else   // if defined (_RWSTD_NO_EXCEPTIONS)

    _RWSTD_UNUSED (line);
    _RWSTD_UNUSED (src);
    _RWSTD_UNUSED (nsrc);
    _RWSTD_UNUSED (key);
    _RWSTD_UNUSED (cxx);

#endif   // _RWSTD_NO_EXCEPTIONS
}

/**************************************************************************/

static int rw_opt_no_c;                   // --no-c
static int rw_opt_no_cxx;                 // --no-cpp
static int rw_opt_no_bsearch;             // --no-bsearch
static int rw_opt_no_qsort;               // --no-qsort
static int rw_opt_no_exceptions;          // --no-exceptions

/**************************************************************************/

static void
test_qsort (bool cxx) 
{
    rw_info (0, 0, 0,
             "extern \"C%{?}++%{;}\" qsort (void*, size_t, size_t, "
             "int (*compar)(const void*, const void*))", cxx);

#define TEST(src)                                              \
    test_qsort (__LINE__, src, sizeof src - 1, cxx)

    TEST ("a");

    TEST ("ba");
    TEST ("cba");
    TEST ("dcba");
    TEST ("edcba");
    TEST ("fedcba");
    TEST ("gfedcba");
    TEST ("hgfedcba");
    TEST ("ihgfedcba");
    TEST ("jihgfedcba");

    TEST ("ab");
    TEST ("abc");
    TEST ("abcd");
    TEST ("abcde");
    TEST ("abcdef");
    TEST ("abcdefg");
    TEST ("abcdefgh");
    TEST ("abcdefghi");
    TEST ("abcdefghij");

    TEST ("aa");
    TEST ("aabb");
    TEST ("bbccaa");
    TEST ("ddbbccaa");
    TEST ("ddeebbccaa");

    TEST ("aaaaaaaaaa");
    TEST ("ababababab");
    TEST ("bababababa");

#undef TEST

    if (rw_opt_no_exceptions) {
        rw_note (0, 0, 0, "extern \"C%{?}++%{;}\" qsort() exceptions tests "
                 "disabled", cxx);
    }
    else {
        test_qsort_exception (__LINE__, global, 
                              sizeof global / sizeof *global, cxx); 
    }
}

/**************************************************************************/

static void
test_bsearch (bool cxx) 
{
    rw_info (0, 0, 0,
             "extern \"C%{?}++%{;}\" bsearch (const void*, const void*, "
             "size_t, size_t, int (*compar)(const void*, const void*))",
             cxx);

#define TEST(src, key, res)                              \
     test_bsearch (__LINE__, src, sizeof src - 1, key,   \
                   std::size_t (res), cxx)

    TEST ("",  'a', -1);
    TEST ("a", 'a',  0);
    TEST ("a", 'b', -1);
    TEST ("b", 'a', -1);

    TEST ("ab", 'a',  0);
    TEST ("ac", 'a',  0);
    TEST ("ab", 'b',  1);
    TEST ("bc", 'a', -1);

    TEST ("acegi", 'i',  4);
    TEST ("acegi", 'b', -1);
    TEST ("acegi", 'g',  3);
    TEST ("acegi", 'f', -1);
    TEST ("acegi", 'e',  2);
    TEST ("acegi", 'j', -1);
    TEST ("acegi", 'c',  1);
    TEST ("bdfhj", 'a', -1);

    TEST ("abcdefghij", 'a',  0);
    TEST ("abcdefghij", 'c',  2);
    TEST ("abcdefghij", 'f',  5);
    TEST ("abcdefghij", 'h',  7);
    TEST ("abcdefghij", 'j',  9);

#undef TEST

    if (rw_opt_no_exceptions) {
        rw_note (0, 0, 0, "extern \"C%{?}++%{;}\" bsearch() exceptions "
                 "tests disabled", cxx);
    }
    else {
        test_bsearch_exception (__LINE__, global, 
                                sizeof global / sizeof *global, global, cxx); 
    }
}

/**************************************************************************/

static void
test_libc (bool cxx)
{
    if (rw_opt_no_qsort) 
        rw_note (0, 0, 0, "qsort test disabled");
    else 
        test_qsort (cxx);

    if (rw_opt_no_bsearch) 
        rw_note (0, 0, 0, "bsearch test disabled");
    else 
        test_bsearch (cxx);
}

/**************************************************************************/

static int
run_test (int, char*[])
{
    if (rw_opt_no_c) 
        rw_note (0, 0, 0, "tests of extern \"C\" cfunctions disabled");
    else 
        test_libc (false);

    if (rw_opt_no_cxx) 
        rw_note (0, 0, 0, "tests of extern \"C++\" functions disabled");
    else 
        test_libc (true);

    return 0;
}

/**************************************************************************/

int main (int argc, char *argv[])
{
    return rw_test (argc, argv, __FILE__,
                    "lib.alg.c.library",
                    0 /* no comment */,
                    run_test,
                    "|-no-extern_c# "
                    "|-no-extern_cxx# "
                    "|-no-bsearch# "
                    "|-no-qsort# "
                    "|-no-exceptions",
                    &rw_opt_no_c,
                    &rw_opt_no_cxx,
                    &rw_opt_no_bsearch,
                    &rw_opt_no_qsort,
                    &rw_opt_no_exceptions);
}
