/************************************************************************
 *
 * istream_sentry.cpp - test exercising basic_istream<charT>::sentry
 *
 * $Id$
 *
 ************************************************************************
 *
 * 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 2003-2006 Rogue Wave Software, Inc.
 * 
 **************************************************************************/

#include <istream>
#include <locale>        // for ctype
#include <streambuf>     // for streambuf

#include <rw_char.h>     // for UserChar, UserTraits
#include <rw_printf.h>   // for rw_printf()
#include <rw_driver.h>

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

    27.6.1.1.2   Class basic_istream::sentry   [lib.istream::sentry]

    namespace std {
        template <class charT, class traits = char_traits<charT> >
        class basic_istream<charT, traits>::sentry {
            typedef traits traits_type;
            bool ok_;                            // exposition only
        public:
            explicit
            sentry (basic_istream<charT, traits>& is, bool noskipws = false);
            ~sentry ();
            operator bool() const { return ok_; }
        private:
            sentry (const sentry&);              // not defined
            sentry& operator= (const sentry&);   // not defined
        };
    }

    -1- The class sentry defines a class that is responsible for doing
        exception safe prefix and suffix operations.

    explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);
    -2- Effects: If is.good() is true, prepares for formatted or unformatted
        input. First, if is.tie() is not a null pointer, the function calls
        is.tie()­>flush() to synchronize the output sequence with any
        associated external C stream. Except that this call can be suppressed
        if the put area of is.tie() is empty. Further an implementation is
        allowed to defer the call to flush until a call of
        is->rdbuf()­>underflow occurs. If no such call occurs before the
        sentry object is destroyed, the call to flush may be eliminated
        entirely(279). If noskipws is zero and is.flags() & ios_base::skipws
        is nonzero, the function extracts and discards each character as long
        as the next available input character c is a whitespace character.
    -3- Notes: The constructor explicit sentry(basic_istream<charT, traits>&
        is, bool noskipws = false) uses the currently imbued locale in is,
        to determine whether the next input character is whitespace or not.
    -4- To decide if the character cis a whitespace character, the constructor
        performs "as if" it executes the following code fragment:
        const ctype<charT>& ctype = use_facet<ctype<charT> >(is.getloc());
        if (ctype.is (ctype.space,c) != 0)
            // c is a whitespace character.
    -5- If, after any preparation is completed, is.good() is true,
        ok_ != false otherwise, ok_ == false. During preparation, the
        constructor may call setstate(failbit) (which may throw
        ios_base::failure (27.4.4.3))(280)
    __________________
    279) This will be possible only in functions that are part of the
         library. The semantics of the constructor used in user code is
         as specified.
    280) The sentry constructor and destructor can also perform additional
         implementation-dependent operations.

    -6- [Example: A typical implementation of the sentry constructor might
        include code such as:

        template <class charT, class traits = char_traits<charT> >
        basic_istream<charT,traits>::sentry(
            basic_istream<charT,traits>& is, bool noskipws = false) {
            ...
            int_type c;
            typedef ctype<charT> ctype_type;
            const ctype_type& ctype = use_facet<ctype_type>(is.getloc());
            while ((c = is.rdbuf()­>snextc()) != traits::eof()) {
                if (ctype.is(ctype.space,c)==0) {
                    is.rdbuf()­>sputbackc (c);
                    break;
                }
            }
            ...
        }
        --end example]

    ~sentry();
    -7- Effects: None.

    operator bool() const;
    -8- Effects: Returns ok_.

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

void
memfun_info (int line, const char *cname, const char *tname, const char *fname)
{
    // format the ISTREAM and SENTRY environment variables w/o writing
    // out any output
    rw_fprintf (0,
                "%{$ISTREAM!:@}",
                "%{?}istream%{:}%{?}wistream"
                "%{:}basic_istream<%s, %s>%{;}%{;}",
                'c' == *cname && 'c' == *tname, 
                'w' == *cname && 'c' == *tname,
                cname, tname);

    rw_fprintf (0,
                "%{$SENTRY!:@}",
                "%{$ISTREAM}::sentry");

    // pass fname through the "%{@}" directive to expand any embedded
    // %{$XYZ} directives
    rw_info (0, 0, line, "std::%{$SENTRY}::%{@}", fname);
}

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

template <class charT>
struct Ctype: std::ctype<charT>
{
    typedef std::ctype<charT> Base;

    const charT ws_;   // whitespace character

    std::ctype_base::mask table_ [256];

    Ctype (unsigned ref, charT white)
        : Base (ref), ws_ (white) { }

    // virtuals overridden below used by ctype<wchar_t>
    // ctype<char> uses the table() member instead

    /* virtual */ bool
    do_is (std::ctype_base::mask m, charT c) const {
        if (m & std::ctype_base::space)
            return ws_ == c;
        return Base::is (m, c);
    }

    /* virtual */ const charT*
    do_is (const charT *from, const charT *to,
           std::ctype_base::mask *m) const {
        return Base::is (from, to, m);
    }

    /* virtual */ const charT*
    do_scan_is (std::ctype_base::mask m,
                 const charT *from, const charT *to) const {
        for (; from != to && !do_is (m, *from); ++from);
        return from;
    }

    /* virtual */ const charT*
    do_scan_not (std::ctype_base::mask m,
                 const charT *from, const charT *to) const {
        for (; from != to && do_is (m, *from); ++from);
        return from;
    }
};


// specialized for ctype<char>
_RWSTD_SPECIALIZED_CLASS
Ctype<char>::Ctype (unsigned ref, char white)
    : Base (table_, false, ref), ws_ (white)
{
    for (unsigned i = 0; i != sizeof table_ / sizeof *table_; ++i)
        table_ [i] = std::ctype_base::mask ();

    typedef unsigned char UChar;

    table_ [UChar (ws_)] = std::ctype_base::space;
}


template <class charT, class Traits>
struct Streambuf: std::basic_streambuf<charT, Traits>
{
    typedef std::basic_streambuf<charT, Traits> Base;

    int nsyncs_;

    Streambuf (const charT *gbeg, const charT *gend)
        : Base (), nsyncs_ (0) {
        this->setg (_RWSTD_CONST_CAST (charT*, gbeg),
                    _RWSTD_CONST_CAST (charT*, gbeg),
                    _RWSTD_CONST_CAST (charT*, gend));
    }

    const charT* pubeback () const {
        return this->eback ();
    }

    const charT* pubgptr () const {
        return this->gptr ();
    }

    const charT* pubegptr () const {
        return this->egptr ();
    }

    /* virtual */ int sync () {
        ++nsyncs_;
        return Base::sync ();
    }
};


template <class charT, class Traits>
void test_ctor (const charT*, const Traits*,
                const char *cname, const char *tname)
{
    typedef std::basic_istream<charT, Traits>   Istream;
    typedef typename Istream::sentry            Sentry;

    memfun_info (__LINE__, cname, tname, "sentry (%{$ISTREAM}&, bool)");

    const charT cbuf[] = { 'a', 'b', 'c', 'd', 'e', ' ', 'f', '\0' };

    const std::ios_base::iostate states[] = {
        std::ios_base::badbit,
        std::ios_base::eofbit,
        std::ios_base::failbit,
        std::ios_base::goodbit,
        std::ios_base::badbit | std::ios_base::eofbit,
        std::ios_base::badbit | std::ios_base::failbit,
        std::ios_base::eofbit | std::ios_base::failbit,
        std::ios_base::badbit | std::ios_base::eofbit | std::ios_base::failbit
    };

    //////////////////////////////////////////////////////////////
    // exercise 27.6.1.1.2, p1:
    //     -  is.good() is true
    //     -  is.tie() is not null
    //     =  the function calls is.tie().flush()

    unsigned iter = 0;     // iteration counter

    for (unsigned i = 0; i != sizeof states / sizeof *states; ++i) {
        for (unsigned j = 0; j != 2; ++j /* noskipws */) {
            Streambuf<charT, Traits>
                sb (cbuf, cbuf + sizeof cbuf / sizeof *cbuf);

            Istream is (&sb);

            // flush() is called iff
            // all of the following conditions hold
            const bool flush_called = is.good () && 0 != is.tie ();

            const Sentry guard (is, 0 != j);

            _RWSTD_UNUSED (guard);

            rw_assert (flush_called == sb.nsyncs_, 0, __LINE__,
                       "%u. basic_istream<%s, %s>::sentry::sentry"
                       "(basic_istream &is, bool noskipws = %d); "
                       "expected to call is.flush () %d times, got %d"
                       "initial is.state () = %{Is}, is.flags() & "
                       "ios::skipws = %d",
                       iter, cname, tname, 0 != j, flush_called, sb.nsyncs_,
                       states [i], is.flags () & std::ios_base::skipws);

            ++iter;
        }
    }

    //////////////////////////////////////////////////////////////
    // exercise 27.6.1.1.2, p1:
    //     -  is.good() is true
    //     -  noskipws is zero
    //     -  is.flags() & ios_base::skipws
    //     =  the function extracts and discards each character as long
    //        as the next available input character c is a whitespace
    //        character.

    for (unsigned i = 0; i != sizeof states / sizeof *states; ++i) {
        for (unsigned j = 0; j != 2; ++j /* noskipws */) {
            for (unsigned k = 0; k != 2; ++k /* ios_base::skipws */) {
                for (charT wc = charT ('a'); wc != charT ('c'); ++wc) {

                    const Ctype<charT> ctp (1, wc);

                    Streambuf<charT, Traits>
                        sb (cbuf, cbuf + sizeof cbuf / sizeof *cbuf);

                    Istream is (&sb);

                    is.setstate (states [i]);

                    if (k)
                        is.setf (std::ios_base::skipws);
                    else
                        is.unsetf (std::ios_base::skipws);

                    const std::locale loc = 
                        is.imbue (std::locale (is.getloc (), &ctp));

                    // imbue the previous locale into the stream
                    // buffer to verify that the sentry ctor uses
                    // the locale imbued in the stream object and
                    // not the one in the stream buffer
                    sb.pubimbue (loc);

                    // a whitespace character is extracted iff
                    // all of the following conditions hold
                    const bool extract =
                           is.good ()
                        && 0 == j
                        && is.flags () & std::ios_base::skipws
                        && cbuf [0] == wc;

                    const Sentry guard (is, 0 != j);

                    _RWSTD_UNUSED (guard);

                    rw_assert (cbuf + extract == sb.pubgptr (), 0, __LINE__, 
                               "%u. %{$SENTRY}::sentry"
                               "(%{$ISTREAM} &is, bool noskipws "
                               "= %b); expected to extract %d "
                               "whitespace chars ('%c') from %{*Ac}, "
                               "extracted %u; initial is.state () = "
                               "%{Is}, is.flags() & ios::skipws = %d",
                               iter, j, extract + 0, char (wc),
                               int (sizeof (*cbuf)), cbuf,
                               sb.pubgptr () - sb.pubeback (),
                               states [i], k);

                    // verify that the ctor doesn't affect gcount()
                    rw_assert (0 == is.gcount (), 0, __LINE__, 
                               "%u. %{$SENTRY}::sentry"
                               "(%{$ISTREAM} &is = %{*Ac}, bool noskipws "
                               "= %b); changed is.gcount() from 0 to %i",
                               iter, int (sizeof (*cbuf)), cbuf, j,
                               is.gcount ());

                    ++iter;
                }
            }
        }
    }
}

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

template <class charT, class Traits>
void test_ok (const charT*, const Traits*,
              const char *cname, const char *tname)
{
    typedef std::basic_istream<charT, Traits>   Istream;
    typedef typename Istream::sentry            Sentry;

    memfun_info (__LINE__, cname, tname, "operator bool () const");

    const charT cbuf[] = { 'a', 'b', 'c', 'd', 'e', ' ', 'f', '\0' };

    const std::ios_base::iostate states[] = {
        std::ios_base::badbit,
        std::ios_base::eofbit,
        std::ios_base::failbit,
        std::ios_base::goodbit,
        std::ios_base::badbit | std::ios_base::eofbit,
        std::ios_base::badbit | std::ios_base::failbit,
        std::ios_base::eofbit | std::ios_base::failbit,
        std::ios_base::badbit | std::ios_base::eofbit | std::ios_base::failbit
    };

    //////////////////////////////////////////////////////////////
    // exercise 27.6.1.1.2, p5:
    //     -  is.good() is true
    //     -  noskipws is zero
    //     -  is.flags() & ios_base::skipws
    //     -  the function extracts and discards each character as long
    //        as the next available input character c is a whitespace
    //        character
    //     =  if, after any preparation is completed, is.good() is true,
    //        ok_ != false otherwise, ok_ == false.

    unsigned iter = 0;     // iteration counter

    for (unsigned i = 0; i != sizeof states / sizeof *states; ++i) {
        for (unsigned j = 0; j != 2; ++j /* noskipws */) {
            for (unsigned k = 0; k != 2; ++k /* ios_base::skipws */) {
                for (charT wc = charT ('a'); wc != charT ('c'); ++wc) {

                    const Ctype<charT> ctp (1, wc);

                    Streambuf<charT, Traits>
                        sb (cbuf, cbuf + sizeof cbuf / sizeof *cbuf);

                    Istream is (&sb);

                    is.setstate (states [i]);

                    if (k)
                        is.setf (std::ios_base::skipws);
                    else
                        is.unsetf (std::ios_base::skipws);

                    const std::locale loc = 
                        is.imbue (std::locale (is.getloc (), &ctp));

                    // imbue the previous locale into the stream
                    // buffer to verify that the sentry ctor uses
                    // the locale imbued in the stream object and
                    // not the one in the stream buffer
                    sb.pubimbue (loc);

                    const Sentry guard (is, 0 != j);

                    _RWSTD_UNUSED (guard);

                    // verify that both are either false or true
                    const bool success = !is.good () == !guard;

                    rw_assert (success, 0, __LINE__,
                               "%u. %{$SENTRY}"
                               "(%{$ISTREAM} &is, bool noskipws "
                               "= %d).operator bool() == %d; initial "
                               "is.state() = %{Is}, is.flags() & "
                               "ios::skipws = %d",
                               iter, j, is.good (),
                               states [i], k);

                    ++iter;
                }
            }
        }
    }
}

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

static int opt_char;
static int opt_wchar;
static int opt_char_traits;
static int opt_user_traits;


static int
run_test (int, char**)
{
#define TEST(what, charT, Traits) \
    test_ ## what ((charT*)0, (Traits*)0, #charT, #Traits)

    using namespace std;

    if (rw_note (0 <= opt_char && 0 <= opt_char_traits, 0, __LINE__,
                 "istream::sentry tests disabled")) {
        TEST (ctor, char, char_traits<char>);
        TEST (ok, char, char_traits<char>);
    }

    if (rw_note (0 <= opt_char && 0 <= opt_user_traits, 0, __LINE__,
                 "basic_istream<char, UserTraits<char>::sentry "
                 "tests disabled")) {
        TEST (ctor, char, UserTraits<char>);
        TEST (ok, char, UserTraits<char>);
    }

#ifndef _RWSTD_NO_WCHAR_T

    if (rw_note (0 <= opt_wchar && 0 <= opt_char_traits, 0, __LINE__,
                 "wistream::sentry tests disabled")) {
        TEST (ctor, wchar_t, char_traits<wchar_t>);
        TEST (ok, wchar_t, char_traits<wchar_t>);
    }

    if (rw_note (0 <= opt_wchar && 0 <= opt_user_traits, 0, __LINE__,
                 "basic_istream<wchar_t, UserTraits<wchar_t>::sentry "
                 "tests disabled")) {
        TEST (ctor, wchar_t, UserTraits<wchar_t>);
        TEST (ok, wchar_t, UserTraits<wchar_t>);
    }

#endif   // _RWSTD_NO_WCHAR_T

    return 0;

}

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

int main (int argc, char *argv[])
{
    return rw_test (argc, argv, __FILE__,
                    "istream.sentry",
                    0 /* no comment */,
                    run_test,
                    "|-char~ "
                    "|-wchar_t~ "
                    "|-char_traits~ "
                    "|-UserTraits~ ",
                    &opt_char,
                    &opt_wchar,
                    &opt_char_traits,
                    &opt_user_traits,
                    (void*)0   /* sentinel */);
}
