//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
// 
// This program is free software.  The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
// 
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please see the GNU General Public License
// for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
// 
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
// 
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
///////////////////////////////////////////////////////////////////////////////
// displayencoder.cpp
//

//=========================================================================
// INCLUDES
//=========================================================================

#include "stdcore.h"
#include "displayencoder.h"
#include "charutil.h"
#include "debug.h"
#include "twlocale.h"
#include "stringutil.h"
#include "errorutil.h"
#include "ntmbs.h"
#include "codeconvert.h"

//=========================================================================
// STANDARD LIBRARY INCLUDES
//=========================================================================

#include <iomanip>
#include <iterator>

//=========================================================================
// DEFINES AND MACROS
//=========================================================================

// uncomment this to test schema
// #define TSS_DO_SCHEMA_VALIDATION

//////////////////////////////////////////////////////////////////////////////
// ENCODER UTILITIES
//////////////////////////////////////////////////////////////////////////////


inline bool IsSingleTCHAR(  TSTRING::const_iterator first, 
                            TSTRING::const_iterator last )
{
    return( first + 1 == last );
}

//////////////////////////////////////////////////////////////////////////////
// CHAR ENCODER INTERFACE
//////////////////////////////////////////////////////////////////////////////

// all derived classes should encode a char to "EscapeChar() + Identifier() + Encode( char ) [ + Identifier() ]"

class iCharEncoder
{
    public:
        virtual bool    NeedsEncoding(  TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const = 0;
        // Determines if character identified by [first,last) needs encoding.
        // Returns true if it does.

        virtual TSTRING EncodeRoundtrip(TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const = 0;
        // Encodes character identified by [first,last) in such a way that it 
        // can be decoded by Decode().  Returns encoded character sequence.

        virtual TSTRING EncodePretty(   TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const = 0;
        // Encodes character identified by [first,last) in a manner that is not roundtrip,
        // but looks good.  Returns encoded character sequence.

        virtual TSTRING Decode(         TSTRING::const_iterator* pcur, 
                                  const TSTRING::const_iterator end  ) const = 0;
        // Decodes character sequence beginning with '*pcur' and ending before 'end'.
        // Returns decoded character or sequence of characters.  Advances *pcur beyond
        // the last character decoded.


        virtual TCHAR   Identifier() const = 0;        

        static TCHAR    EscapeChar() { return char_escape; }

    protected:    

        static TCHAR        char_escape;
};


class cNonNarrowableCharEncoder : public iCharEncoder
{
    public:
        virtual bool    NeedsEncoding(  TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING EncodeRoundtrip(TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING EncodePretty(   TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING Decode(         TSTRING::const_iterator* cur, 
                                  const TSTRING::const_iterator end  ) const;

        virtual TCHAR   Identifier() const;
    private:
        static TCHAR char_identifier;
        static TCHAR char_replace;    
};


class cNonPrintableCharEncoder : public iCharEncoder
{
    public:
        cNonPrintableCharEncoder( bool f_allowWS )
            : m_allowWS( f_allowWS ) {};

        virtual bool    NeedsEncoding(  TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING EncodeRoundtrip(TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING EncodePretty(   TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING Decode(         TSTRING::const_iterator* cur, 
                                  const TSTRING::const_iterator end  ) const;

        virtual TCHAR   Identifier() const;
    private:
        static TCHAR char_identifier;
        static TCHAR char_replace;

        bool m_allowWS;
};

class cQuoteCharEncoder : public iCharEncoder
{
    public:
        virtual bool    NeedsEncoding(  TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING EncodeRoundtrip(TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING EncodePretty(   TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING Decode(         TSTRING::const_iterator* cur, 
                                  const TSTRING::const_iterator end  ) const;

        virtual TCHAR   Identifier() const;
    private:
        static TCHAR char_test;
        static TCHAR char_identifier;
        static TCHAR char_replace;
};


class cBackslashCharEncoder : public iCharEncoder
{
    public:
        virtual bool    NeedsEncoding(  TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING EncodeRoundtrip(TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING EncodePretty(   TSTRING::const_iterator first, 
                                        TSTRING::const_iterator last ) const;

        virtual TSTRING Decode(         TSTRING::const_iterator* cur, 
                                  const TSTRING::const_iterator end  ) const;

        virtual TCHAR   Identifier() const;
    private:
        static TCHAR char_test;
        static TCHAR char_identifier;
        static TCHAR char_replace;
};


//////////////////////////////////////////////////////////////////////////////
// CHARACTER SPECIALIZATIONS
//////////////////////////////////////////////////////////////////////////////

TCHAR   iCharEncoder::char_escape = _T('\\');

TCHAR   cNonNarrowableCharEncoder::char_identifier = _T('x');
TCHAR   cNonPrintableCharEncoder::char_identifier = _T('x');
TCHAR   cQuoteCharEncoder::char_identifier = _T('\"');
TCHAR   cBackslashCharEncoder::char_identifier = _T('\\');

TCHAR   cBackslashCharEncoder::char_test = cBackslashCharEncoder::char_identifier;
TCHAR   cQuoteCharEncoder::char_test = cQuoteCharEncoder::char_identifier;

TCHAR   cBackslashCharEncoder::char_replace = cBackslashCharEncoder::char_identifier;
TCHAR   cQuoteCharEncoder::char_replace = cQuoteCharEncoder::char_identifier;

TCHAR   cNonNarrowableCharEncoder::char_replace = _T('?');
TCHAR   cNonPrintableCharEncoder::char_replace = _T('?');

//////////////////////////////////////////////////////////////////////////////
// TESTS
//////////////////////////////////////////////////////////////////////////////

bool cNonNarrowableCharEncoder::NeedsEncoding( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{

#ifdef _UNICODE

    ASSERT( IsSingleTCHAR( first, last ) );

    char amb[ MB_LEN_MAX ];
    TCHAR awch[] = { *first, 0 }; 

    // TODO:BAM -- this is not really correct!  Convert will not honor nSource!!  
    // it looks for the first null char!
    try
    {
        int ret = iCodeConverter::GetInstance()->Convert( amb, sizeof( amb ), awch, 1 );
        return( -1 == ret );
    }
    catch( eError& )
    {
        return true;
    }

#else

    return false; // all chars are narrow

#endif

}



bool cNonPrintableCharEncoder::NeedsEncoding( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    // TODO:BAM -- handle this with mb chars
    // std::isprint<wchar_t> does a wctob() on the wchar!!?!?!
    // what's up with that?  Maybe ignore this all together and
    // just do a isprint like KAI does?

    // HYPOTHESIS: all mb characters are printable.  only sb ASCII
    // chars that would have C isprint() return false actually aren't printable
    // So escape chars, and tabs and such are only in sb chars that C isprint() would check.
    // HMMMM: true in all locales, though? (LC_CTYPE is checked for C isprint(), though...)

    // Sooooo... it should be something like
    //
    //  #ifdef _UNICODE
    //      char nch = wctob( ch );
    //      return( nch != EOF && ! isprint( nch ) );
    //  #else
    //      return( ! isprint( ch ) );
    //  #endif
    // 

    // assuming all unprintable chars are one TCHAR long
    if( ! IsSingleTCHAR( first, last ) )
        return false;

    if( m_allowWS && cCharEncoderUtil::IsWhiteSpace( *first ) )
        return false;

    return cCharEncoderUtil::IsPrintable( *first );
}

bool cQuoteCharEncoder::NeedsEncoding( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    return( 
            IsSingleTCHAR( first, last )
            &&
            ( *first == char_test )
          );
}

bool cBackslashCharEncoder::NeedsEncoding( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    return( 
            IsSingleTCHAR( first, last )
            &&
            ( *first == char_test )
          );
}

//////////////////////////////////////////////////////////////////////////////
// ROUNDTRIP ENCODINGS
//////////////////////////////////////////////////////////////////////////////


TSTRING cNonNarrowableCharEncoder::EncodeRoundtrip( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    TSTRING str;

    str += char_escape;
    str += char_identifier;
    str += cCharEncoderUtil::CharStringToHexValue( TSTRING( first, last ) );

    return str;
}


TSTRING cNonPrintableCharEncoder::EncodeRoundtrip( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    ASSERT( IsSingleTCHAR( first, last ) ); // non-prints are single char (see NOTE above)

    TSTRING str;

    str += char_escape;
    str += char_identifier;
    str += cCharEncoderUtil::CharStringToHexValue( TSTRING( first, last ) );

    return str;
}


TSTRING cQuoteCharEncoder::EncodeRoundtrip( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    // should just be a quote
    ASSERT( IsSingleTCHAR( first, last ) );
    ASSERT( *first == char_test );

    TSTRING str;
    
    str += char_escape;
    str += char_identifier;

    return str;
}



TSTRING cBackslashCharEncoder::EncodeRoundtrip( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    // should just be a backslash
    ASSERT( IsSingleTCHAR( first, last ) );
    ASSERT( *first == char_test );

    TSTRING str;
    
    str += char_escape;
    str += char_identifier;

    return str;
}

//////////////////////////////////////////////////////////////////////////////
// NON-ROUNDTRIP ENCODINGS
//////////////////////////////////////////////////////////////////////////////


TSTRING cNonNarrowableCharEncoder::EncodePretty( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    return EncodeRoundtrip( first, last );
}


TSTRING cNonPrintableCharEncoder::EncodePretty( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    return EncodeRoundtrip( first, last );
}


TSTRING cQuoteCharEncoder::EncodePretty( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    // should just be a quote
    ASSERT( IsSingleTCHAR( first, last ) );
    ASSERT( *first == char_test );
    
    return TSTRING( 1, char_replace );
}


TSTRING cBackslashCharEncoder::EncodePretty( 
                                    TSTRING::const_iterator first, 
                                    TSTRING::const_iterator last ) const
{
    // should just be a backslash
    ASSERT( IsSingleTCHAR( first, last ) );
    ASSERT( *first == char_test );

    return TSTRING( 1, char_replace );
}

//////////////////////////////////////////////////////////////////////////////
// DECODINGS
//////////////////////////////////////////////////////////////////////////////


TSTRING cNonNarrowableCharEncoder::Decode(  TSTRING::const_iterator* pcur, 
                                      const TSTRING::const_iterator  end  ) const
{
    // check preconditions
    if( (*pcur) >= end || *(*pcur) != Identifier() )
        ThrowAndAssert( eBadDecoderInput() );

    return( cCharEncoderUtil::DecodeHexToChar( pcur, end ) );
}


TSTRING cNonPrintableCharEncoder::Decode(  TSTRING::const_iterator* pcur, 
                                     const TSTRING::const_iterator  end  ) const
{
    // check preconditions
    if( (*pcur) >= end || *(*pcur) != Identifier() )
        ThrowAndAssert( eBadDecoderInput() );

    return( cCharEncoderUtil::DecodeHexToChar( pcur, end ) );
}


TSTRING cQuoteCharEncoder::Decode(  TSTRING::const_iterator* pcur, 
                              const TSTRING::const_iterator  end  ) const
{
    if( (*pcur) >= end || *(*pcur) != Identifier() )
        ThrowAndAssert( eBadDecoderInput() );

    (*pcur)++; // advance past part decoded

    return TSTRING( 1, Identifier() );
}


TSTRING cBackslashCharEncoder::Decode(  TSTRING::const_iterator* pcur, 
                                  const TSTRING::const_iterator  end  ) const
{
    if( (*pcur) >= end || *(*pcur) != Identifier() )
        ThrowAndAssert( eBadDecoderInput() );

    (*pcur)++; // advance past part decoded

    return TSTRING( 1, Identifier() );
}

//////////////////////////////////////////////////////////////////////////////
// IDENTIFIERS
//////////////////////////////////////////////////////////////////////////////


TCHAR cNonNarrowableCharEncoder::Identifier() const
{
    return char_identifier;
}


TCHAR cNonPrintableCharEncoder::Identifier() const
{
    return char_identifier;
}


TCHAR cQuoteCharEncoder::Identifier() const
{
    return char_identifier;
}

TCHAR cBackslashCharEncoder::Identifier() const
{
    return char_identifier;
}

//////////////////////////////////////////////////////////////////////////////
// UTILITIES
//////////////////////////////////////////////////////////////////////////////

bool cCharEncoderUtil::IsWhiteSpace( TCHAR ch )
{
    return ( ch == '\r' ||
             ch == '\n' ||
             ch == '\t' ||
             ch == '\v' ||
             ch == ' ' );
}

bool cCharEncoderUtil::IsPrintable( TCHAR ch )
{
#if USE_CLIB_LOCALE && !defined(__APPLE__)

    return( ! isprint( ch ) );    // kludge for KAI

#else // USE_CLIB_LOCALE
    
#if IS_UNIX
        return( ! std::isprint<TCHAR>( ch, std::locale() ) );

    #endif

#endif // USE_CLIB_LOCALE
}

TSTRING cCharEncoderUtil::CharStringToHexValue( const TSTRING& str )
{
    TSTRING strOut;
    TSTRING::const_iterator at;

    for( at = str.begin(); at < str.end(); at++ )
    {
        strOut += char_to_hex( *at );
    }

    return strOut;
}


TSTRING cCharEncoderUtil::HexValueToCharString( const TSTRING& str )
{
    TSTRING strOut;    
    TSTRING::const_iterator     at;

    for( at = str.begin(); at < str.end(); at += TCHAR_AS_HEX__IN_TCHARS )
    {
        strOut += hex_to_char( at, at + TCHAR_AS_HEX__IN_TCHARS );
    }

    return strOut;
}

TCHAR cCharEncoderUtil::hex_to_char( TSTRING::const_iterator first, 
                                     TSTRING::const_iterator last )
{
    static const TCHAR max_char = std::numeric_limits<TCHAR>::max();
    static const TCHAR min_char = std::numeric_limits<TCHAR>::min();

    if( first + TCHAR_AS_HEX__IN_TCHARS != last )
        ThrowAndAssert( eBadHexConversion() );

    TISTRINGSTREAM ss( TSTRING( first, last ) );
    ss.imbue( std::locale::classic() );
    ss.fill ( _T('0') );
    ss.setf( std::ios_base::hex, std::ios_base::basefield );

    unsigned long ch;
    ss >> ch;
    
    if( ss.bad() || ss.fail() )
        ThrowAndAssert( eBadHexConversion( TSTRING( first, last ) ) );
    if( (TCHAR)ch > max_char || (TCHAR)ch < min_char )
        ThrowAndAssert( eBadHexConversion( TSTRING( first, last ) ) );

    return (TCHAR)ch;
}


TSTRING cCharEncoderUtil::char_to_hex( TCHAR ch )
{
    TOSTRINGSTREAM ss;

    ss.imbue( std::locale::classic() );
    ss.fill ( _T('0') );
    ss.width( TCHAR_AS_HEX__IN_TCHARS );
    ss.setf( std::ios_base::hex, std::ios_base::basefield );

    ss << tss::util::char_to_size( ch );

    if( ss.bad() || ss.fail() || 
        ss.str().length() != TCHAR_AS_HEX__IN_TCHARS )
        ThrowAndAssert( eBadHexConversion( TSTRING( 1, ch ) ) );
    return ss.str();
}

TSTRING cCharEncoderUtil::DecodeHexToChar(  TSTRING::const_iterator* pcur, 
                                      const TSTRING::const_iterator  end  )
{
    // get hex numbers -- 2 chars
    TSTRING str;
    size_t n = 0;
    for( (*pcur)++; 
         n < TCHAR_AS_HEX__IN_TCHARS && 
             (*pcur) != end; 
         n++, (*pcur)++ )
    {
        str += *(*pcur);
    }

    if( n != TCHAR_AS_HEX__IN_TCHARS )
        ThrowAndAssert( eBadDecoderInput() );

    // convert hex numbers
    return HexValueToCharString( str );
}

//////////////////////////////////////////////////////////////////////////////
// ENCODER MEMBERS
//////////////////////////////////////////////////////////////////////////////


cEncoder::cEncoder( int e, int f )
    : m_fFlags( f )
{
    // add encodings
    if( e & NON_NARROWABLE )
        m_encodings.push_back( new cNonNarrowableCharEncoder );    
    if( e & NON_PRINTABLE )
        m_encodings.push_back( new cNonPrintableCharEncoder( AllowWhiteSpace() ) );
    if( e & BACKSLASH )
        m_encodings.push_back( new cBackslashCharEncoder );
    if( e & DBL_QUOTE )
        m_encodings.push_back( new cQuoteCharEncoder );
    
    // assert that we weren't passed anything freaky
    ASSERT( 0 == ( e & ~( NON_NARROWABLE | 
                          NON_PRINTABLE | 
                          BACKSLASH | 
                          DBL_QUOTE ) ) );

    // add flags
    ASSERT( ! ( ( m_fFlags & ROUNDTRIP ) &&
                ( m_fFlags & NON_ROUNDTRIP ) ) );
 
#ifdef TSS_DO_SCHEMA_VALIDATION

    // check assumptions about encodings
    ValidateSchema();

#endif

}


cEncoder::~cEncoder() 
{
}

bool cEncoder::RoundTrip() const
{
    return( 0 != ( m_fFlags & ROUNDTRIP ) );
}

bool cEncoder::AllowWhiteSpace() const
{
    return( 0 != ( m_fFlags & ALLOW_WHITESPACE ) );
}

//////////////////////////////////////////////////////////////////////////////
// ENCODER BASIC FUNCTIONALITY
//////////////////////////////////////////////////////////////////////////////

void cEncoder::Encode( TSTRING& strIn ) const
{
    // TODO:BAM -- reserve space for strOut as an optimization?
    TSTRING strOut;         // encoded string we will build up

    TSTRING::const_iterator         cur = strIn.begin();  // pointer to working position in strIn
    const TSTRING::const_iterator   end = strIn.end();    // end of strIn
    TSTRING::const_iterator         first = end;          // identifies beginning of current character
    TSTRING::const_iterator         last  = end;          // identifies end of current character

    // while get next char (updates cur)
    while( cCharUtil::PopNextChar( cur, end, first, last ) )
    {
        bool fCharEncoded = false; // answers: did char need encoding?
        sack_type::const_iterator atE;

        // for all encoders
        for( atE = m_encodings.begin(); 
             atE != m_encodings.end();
             atE++ )
        {
            // does char need encoding?
            if( (*atE)->NeedsEncoding( first, last ) )
            {
                strOut += Encode( first, last, atE );
                fCharEncoded = true;
                break;  // each char should only fail at most one 
                        // encoding test, so it should be cool to quit
            }
        }

        if( ! fCharEncoded )
        {
            strOut.append( first, last ); // simply add current char to output since it needed no encoding
        }
    }

    // pass back encoded string
    strIn = strOut;
}

TSTRING cEncoder::Encode( TSTRING::const_iterator first,
                          TSTRING::const_iterator last,
                          sack_type::const_iterator encoding ) const
{
    // encode it 
    if( RoundTrip() )
        return (*encoding)->EncodeRoundtrip( first, last );
    else
        return (*encoding)->EncodePretty( first, last );
}

void cEncoder::Decode( TSTRING& strIn ) const
{
    // TODO:BAM -- reserve space for strOut as an optimization?
    TSTRING strOut;         // decoded string we will build up

    TSTRING::const_iterator         cur = strIn.begin();  // pointer to working position in strIn
    const TSTRING::const_iterator   end = strIn.end();    // end of strIn
    TSTRING::const_iterator         first = end;          // identifies beginning of current character
    TSTRING::const_iterator         last  = end;          // identifies end of current character
    

    // while get next char (updates cur)
    while( cCharUtil::PopNextChar( cur, end, first, last ) )
    {
        // is this char the escape character?
        if( IsSingleTCHAR( first, last ) && 
            *first == iCharEncoder::EscapeChar() )
        {
            // get to identifier
            if( ! cCharUtil::PopNextChar( cur, end, first, last ) )
                ThrowAndAssert( eBadDecoderInput() );
            
            // this algorithm assumes that all identifiers are single char
            // so anything following the escape char should be a 
            // single-char identifier
            if( ! IsSingleTCHAR( first, last ) ) 
                THROW_INTERNAL( "displayencoder.cpp" );

            // determine to which encoding the identifier belongs
            bool fFoundEncoding = false;
            sack_type::const_iterator atE;
            for( atE = m_encodings.begin(); 
                 atE != m_encodings.end(); 
                 atE++ )
            {
                // is this the right encoding?
                if( *first == (*atE)->Identifier() )
                {
                    // this is the correct encoding....
                    fFoundEncoding = true;

                    // ...so decode char
                    strOut += (*atE)->Decode( &first, end ); // should modify cur
                    
                    cur = first; // advance current char pointer

                    break;  // no need to run other tests after 
                            // this because all identifiers should be unique
                }
            }

            if( ! fFoundEncoding )
                ThrowAndAssert( eUnknownEscapeEncoding( TSTRING( 1, *first ) ) );
        }
        else
        {        
            strOut.append( first, last );
        }
    }

    strIn = strOut;
}

//////////////////////////////////////////////////////////////////////////////
// ENCODER SCHEMA VALIDATION
//////////////////////////////////////////////////////////////////////////////

void cEncoder::ValidateSchema() const
{
    ASSERT( OnlyOneCatagoryPerChar() );
    ASSERT( AllIdentifiersUnique() );
}


// only tests single TCHAR characters (but of those, tests all of them)
bool cEncoder::OnlyOneCatagoryPerChar() const
{
    // TODO:BAM - man, is there a better way to do this?
    TCHAR ach[2] = {0};
    TCHAR ch = std::numeric_limits<TCHAR>::min();
    if( ch != std::numeric_limits<TCHAR>::max() )
    {
        do
        {
            bool fFailedATest = false;

            ach[0] = ch;
            for( sack_type::const_iterator atE = m_encodings.begin(); atE != m_encodings.end(); atE++ )
            {
		TSTRING::const_iterator first(&ach[0]), last(&ach[1]);
                if( (*atE)->NeedsEncoding( first, last ) )
                {
                    if( fFailedATest )
                        return false; // each char can only fail one test
                    else
                        fFailedATest = true;
                }
            }
            ch++;
        }
        while( ch != std::numeric_limits<TCHAR>::max() );
    }
    return true;
}


bool cEncoder::AllIdentifiersUnique() const
{
    TSTRING chars;
    for( sack_type::const_iterator atE = m_encodings.begin(); atE != m_encodings.end(); atE++ )
    {
        TCHAR chID = (*atE)->Identifier();
        if( chars.find( chID ) == TSTRING::npos )
            chars += chID;
        else
            return false;
    }
    return true;
}


bool cEncoder::AllTestsRunOnEncodedString( const TSTRING& s ) const
{
    TSTRING::const_iterator         cur = s.begin();  // pointer to working position in s
    const TSTRING::const_iterator   end = s.end();    // end of s
    TSTRING::const_iterator         first = end;          // identifies beginning of current character
    TSTRING::const_iterator         last  = end;          // identifies end of current character
    

    // while get next char (updates cur)
    while( cCharUtil::PopNextChar( cur, end, first, last ) )
    {
        sack_type::const_iterator atE;
        for( atE = m_encodings.begin(); 
             atE != m_encodings.end(); 
             atE++ )
        {
            if( (*atE)->NeedsEncoding( first, last ) )
            {
                return false;
            }
        }
    }

    return true;
}


//////////////////////////////////////////////////////////////////////////////
// cDisplayEncoder MEMBERS
//////////////////////////////////////////////////////////////////////////////


cDisplayEncoder::cDisplayEncoder( Flags f )
    : cEncoder( 
                        NON_NARROWABLE  |
                        NON_PRINTABLE |
                        BACKSLASH | 
                        DBL_QUOTE,
                        f
                        ) 
{
}

void cDisplayEncoder::Encode( TSTRING& str ) const
{
    cEncoder::Encode( str );
}

bool cDisplayEncoder::Decode( TSTRING& str ) const
{
    cEncoder::Decode( str );
    return true; // TODO:BAM -- throw error!
}

