/*
 * 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.
 */
#if !defined(XALANUNICODESUBSETWRITER_HEADER_GUARD_1357924680)
#define XALANUNICODESUBSETWRITER_HEADER_GUARD_1357924680


#include <xalanc/XMLSupport/XalanFormatterWriter.hpp>



namespace XALAN_CPP_NAMESPACE {




template <class Predicate,
          class ConstantsType>
class XalanOtherEncodingWriter : public XalanFormatterWriter
{
public:

    typedef XalanOtherEncodingWriter<Predicate, ConstantsType>  ThisType;

    class WriteCharRef
    {
    public:

        WriteCharRef(ThisType&  writer) :
            m_writer(writer)
        {
        }

        void
        operator()(XalanUnicodeChar     value) const
        {
            m_writer.writeNumericCharacterReference(value);
        }

    private:

        ThisType&   m_writer;
    };

    class ThrowTranscodingException
    {
    public:

        ThrowTranscodingException(ThisType&     writer) :
            m_writer(writer)
        {
        }

        void
        operator()(XalanUnicodeChar     value) const
        {
            m_writer.throwUnrepresentableCharacterException(
                value,
                m_writer.getMemoryManager());
        }

    private:

        ThisType&   m_writer;
    };

    friend class WriteCharRef;
    friend class ThrowTranscodingException;

    typedef XalanDOMChar    value_type;

    XalanOtherEncodingWriter(
                Writer&         writer,
                MemoryManager&  theMemoryManager) :
        XalanFormatterWriter(
            writer,
            theMemoryManager),
        m_buffer(),
        m_bufferPosition(m_buffer),
        m_bufferRemaining(kBufferSize),
        m_predicate(writer.getStream()),
        m_constants(),
        m_charRefFunctor(*this),
        m_exceptionFunctor(*this)
    {
    }

    virtual
    ~XalanOtherEncodingWriter()
    {
    }

    /**
     * Output a line break.
     */
    void
    outputNewline()
    {
        assert(m_newlineString != 0);
        assert(length(m_newlineString) == m_newlineStringLength);

        write(
            m_newlineString,
            m_newlineStringLength);
    }

    /**
     * Writes CDATA chars , if not presentable, fixes it 
     * with addition CDATA sections
     */
    size_type
    writeCDATAChar(
                const XalanDOMChar  chars[],
                size_type           start,
                size_type           length,
                bool&               outsideCDATA)
    {
        assert(chars != 0 && length > 0 && start < length);

        const XalanDOMChar  theChar = chars[start];

        XalanUnicodeChar    value = theChar;

        size_type   result = start;

        if (isUTF16HighSurrogate(theChar) == true)
        {
            if (start + 1 >= length)
            {
                throwInvalidUTF16SurrogateException(
                    theChar, 
                    0,
                    getMemoryManager());
            }
            else 
            {
                value = decodeUTF16SurrogatePair(theChar, chars[start+1],  getMemoryManager());

                ++result;
            }
        }

        if(m_predicate(value))
        {
            if (outsideCDATA == false)
            {
                // We have a representable char in the normal state,
                // so just print it.
                write(value);
            }
            else
            {
                // The previous character was a not representable.
                // Open the CDATA section again, print the character,
                // then change the flag.
                write(
                    m_constants.s_cdataOpenString,
                    m_constants.s_cdataOpenStringLength);

                write(value);

                outsideCDATA = false;
            }
        }
        else
        {
            if(outsideCDATA == false)
            {
                // we have a non-representable char in the normal state - 
                // close the CDATA section and print the value
                write(
                    m_constants.s_cdataCloseString,
                    m_constants.s_cdataCloseStringLength);

                writeNumericCharacterReference(value);

                outsideCDATA = true;   
            }
            else
            {
                writeNumericCharacterReference(value);
            }
        }        

        return result;
    }

    /**
     * Writes name characters.  If a character is not representable,
     * an exception is thrown.
     */
    void
    writeNameChar(
            const XalanDOMChar*     data,
            size_type               theLength)
    {
        for( size_type i = 0; i < theLength; ++i)
        { 
            i = write(data, i , theLength, m_exceptionFunctor); 
        }
    }

    /**
     * Writes PI characters.  If a character is not representable,
     * an exception is thrown.
     */
    void
    writePIChars(
            const XalanDOMChar*     data,
            size_type               theLength)
    {
        for( size_type i = 0; i < theLength; )
        { 
            i = write(data, i , theLength, m_exceptionFunctor); 
        }
    }

    /**
     * Writes comment characters.  If a character is not representable,
     * or must be written as a character reference for compatibility with
     * XML 1.1, an exception is thrown.
     */
    void
    writeCommentChars(
            const XalanDOMChar*     data,
            size_type               theLength)
    {
        for( size_type i = 0; i < theLength; )
        { 
            i = write(data, i , theLength, m_exceptionFunctor); 
        }
    }

    void
    write(
            const XalanDOMChar*     theChars,
            size_type               theLength)
    {
        for(size_type i = 0; i < theLength; ++i)
        {
            write(theChars[i]);
        }
    }

    void
    write(const XalanDOMString&     theChars)
    {
        write(theChars.c_str(), theChars.length());
    }

    /**
     * Writes writes a UTF-16 code unit that isn't 
     * part of the surrogate pair 
     */
    void
    write(XalanDOMChar    theChar)
    {
        assert(
            isUTF16HighSurrogate(theChar) == false &&
            isUTF16LowSurrogate(theChar) == false);

        if (m_bufferRemaining == 0)
        {
            flushBuffer();
        }

        if(m_predicate(theChar))
        {
            *m_bufferPosition = theChar;

            ++m_bufferPosition;
            --m_bufferRemaining;
        }
        else
        {
            writeNumericCharacterReference(theChar);
        }
    }

    size_type
    write(
            const XalanDOMChar  chars[],
            size_type           start,
            size_type           length)
    {

        return write(chars, start, length, m_charRefFunctor);
    }

    void
    writeSafe(
            const XalanDOMChar*     theChars,
            size_type               theLength)
    {
        for(size_type i = 0; i < theLength; ++i)
        {
            const XalanDOMChar  ch = theChars[i];

            if (isUTF16HighSurrogate(ch) == true)
            {
                if (i + 1 >= theLength)
                {
                    throwInvalidUTF16SurrogateException(ch, 0,  getMemoryManager());
                }
                else 
                {
                    XalanUnicodeChar    value = decodeUTF16SurrogatePair(ch, theChars[i+1],  getMemoryManager());

                    if (this->m_isPresentable(value))
                    {
                        write(value);
                    }
                    else
                    {
                        this->writeNumberedEntityReference(value);
                    }

                    ++i;
                }
            }
            else
            {
                write(static_cast<XalanUnicodeChar>(ch));
            }
        }
    }

    void
    write(const XalanDOMChar*     theChars)
    {
        write(theChars, XalanDOMString::length(theChars));
    }

    void
    flushWriter()
    {
        m_writer.flush();
    }    

    void
    flushBuffer()
    {
        m_writer.write(m_buffer, 0, m_bufferPosition - m_buffer);

        m_bufferPosition = m_buffer;
        m_bufferRemaining = kBufferSize;
    }

private:

    /**
     * Writes a representable code point
     *
     * @param chars        Array of the characters for transcoding
     *    
     * @param start        Place int the array the transcoding should start
     *    
     * @param length       The length of the array
     *    
     * @param failureHandler  The functor handles the non-representable characters
     *    
     * @return              Place int the array of the next character
     */

    template <class TranscodingFailureFunctor>
    size_type
    write(
            const XalanDOMChar          chars[],
            size_type                   start,
            size_type                   length,
            TranscodingFailureFunctor&  failureHandler)
    {
        assert(chars != 0 && length > 0);
        assert(start <= length);

        size_type result = start;

        const XalanDOMChar  ch = chars[start];

        XalanUnicodeChar    value = ch;

        if (isUTF16HighSurrogate(ch) == true)
        {
            if (start + 1 >= length)
            {
                throwInvalidUTF16SurrogateException(
                    ch, 
                    0,
                    getMemoryManager());
            }
            else 
            {
                value = decodeUTF16SurrogatePair(ch, chars[start+1],  getMemoryManager());

                ++result;
            }
        }

        if(m_predicate(value))
        {
            write(value);
        }
        else
        {
            failureHandler(value);
        }

        return result;
    }

    /**
     * Writes a representable code point
     *
     * @param theChar        UTF-32 code point . For passing it to the Xerces 
     *                       transcoder, we convert it back to UTF-16                         
     */
    void
    write(XalanUnicodeChar  theChar)
    {
        // encode back UTF-32 into UTF-16 

        if (theChar > 0xFFFF)
        {
            if (m_bufferRemaining < 2)
            {
                flushBuffer();
            }

            *m_bufferPosition = static_cast<XalanDOMChar>((theChar >> 10) + 0xD7C0);

            ++m_bufferPosition;

            *m_bufferPosition = static_cast<XalanDOMChar>((theChar &  0x03FF) + 0xDC00);

            ++m_bufferPosition;

            m_bufferRemaining = m_bufferRemaining - size_type(2);
        }   
        else
        {
            if (m_bufferRemaining == 0)
            {
                flushBuffer();
            }

            *m_bufferPosition = XalanDOMChar(theChar);

            ++m_bufferPosition;
            --m_bufferRemaining;
        }  
    }

    void
    writeNumericCharacterReference(XalanUnicodeChar     theChar)
    {
        const XalanDOMString&   theString =
            formatNumericCharacterReference(theChar);

        const XalanDOMString::size_type     theLength =
            theString.length();

        if (m_bufferRemaining < theLength)
        {
            flushBuffer();
        }

        using std::copy;

        assert(theString.size() <= m_bufferRemaining);

        m_bufferPosition =
            copy(
                theString.begin(),
                theString.end(),
                m_bufferPosition);

        m_bufferRemaining -= theLength;
    }

    enum
    {
        // The size of the buffer.  The minimum for this
        // is the length of the longest numeric character
        // reference that can be written.
        kBufferSize = 512u
    };


    // Data members...
    XalanDOMChar            m_buffer[kBufferSize];

    XalanDOMChar*           m_bufferPosition;

    size_type               m_bufferRemaining;

    const Predicate         m_predicate;

    const ConstantsType     m_constants;

    const WriteCharRef                  m_charRefFunctor;

    const ThrowTranscodingException     m_exceptionFunctor;
};



}



#endif  // XALANUNICODESUBSETWRITER_HEADER_GUARD_1357924680
