/*
 * 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(XALANFORMATTERWRITER_HEADER_GUARD_1357924680)
#define XALANFORMATTERWRITER_HEADER_GUARD_1357924680



// Base include file.  Must be first.
#include <xalanc/XMLSupport/XMLSupportDefinitions.hpp>

#include <xercesc/sax/SAXException.hpp>

#include <xalanc/PlatformSupport/DOMStringHelper.hpp>
#include <xalanc/PlatformSupport/FormatterListener.hpp>
#include <xalanc/PlatformSupport/Writer.hpp>
#include <xalanc/PlatformSupport/XalanMessageLoader.hpp>
#include <xalanc/PlatformSupport/XalanOutputStream.hpp>



namespace XALAN_CPP_NAMESPACE {



using xercesc::MemoryManager;



class XalanFormatterWriter
{
public:

    typedef FormatterListener::size_type    size_type;


    template <class WriterType>
    class NewLineWriterFunctor
    {
    public:

        typedef WriterType  writer_type;

        NewLineWriterFunctor(WriterType& writer) :
          m_writer(writer),
          m_newlineString(0),
          m_newlineStringLength(0)
        {
            XalanOutputStream* stream = writer.getStream();

            if(stream != 0)
            {
                m_newlineString = stream->getNewlineString();
            }
            else
            {
                m_newlineString = XalanOutputStream::defaultNewlineString();
            }

            assert(m_newlineString != 0);

            m_newlineStringLength = length(m_newlineString);
        }

        void
        operator()() 
        {
            assert(m_newlineString != 0 && length(m_newlineString) == m_newlineStringLength);

            m_writer.write(m_newlineString, m_newlineStringLength);
        }

    private:

        WriterType&     m_writer;

        /**
        * The string of characters that represents the newline
        */
        const XalanDOMChar*         m_newlineString;

        /**
        * The length of the the string of characters that represents the newline
        */
        size_type   m_newlineStringLength;
    };

    template<class WriterType>
    class WhiteSpaceWriterFunctor
    {
        typedef typename WriterType::value_type value_type;

    public:
        typedef WriterType                  writer_type;

        WhiteSpaceWriterFunctor(WriterType& writer) :
          m_writer(writer)
          {
          }

        void
        operator()(size_type count) 
        {
            for ( size_type i = 0 ; i < count ; i++ ) 
            {
                m_writer.write(value_type(XalanUnicode::charSpace));
            }
        }

    private:

        WriterType& m_writer;
    };

    class CommonRepresentableCharFunctor
    {
    public:

        CommonRepresentableCharFunctor(const XalanOutputStream*     stream) :
            m_stream(stream)
        {
            assert(stream != 0);
        }

        bool
        operator()(XalanUnicodeChar     theChar) const
        {
            bool result = true;

            if (m_stream != 0)
            {
                result = m_stream->canTranscodeTo(theChar);
            }
            
            return result;
        }

    private:

        const XalanOutputStream* const  m_stream;
    };

public:

    XalanFormatterWriter(
                Writer&         theWriter, 
                MemoryManager&  theMemoryManager) :
        m_writer(theWriter),
        m_memoryManager(theMemoryManager),
        m_stringBuffer(5, 0, theMemoryManager)
    {
        const XalanOutputStream* const  theStream =
            theWriter.getStream();

        if (theStream == 0)
        {
            m_newlineString = XalanOutputStream::defaultNewlineString();
        }
        else
        {
            m_newlineString = theStream->getNewlineString();
        }

        assert(m_newlineString != 0);

        m_newlineStringLength = length(m_newlineString);

        assert(m_newlineString != 0);
    }

    MemoryManager&
    getMemoryManager()
    {
        return m_memoryManager;
    }

    virtual
    ~XalanFormatterWriter()
    {
    }
 
    Writer*
    getWriter() const
    {
        return &m_writer;
    }

    XalanOutputStream*
    getStream()
    {
        return m_writer.getStream();
    }

    const XalanOutputStream*
    getStream() const
    {
        return m_writer.getStream();
    }

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


    static bool
    isUTF16HighSurrogate(XalanDOMChar   theChar)
    {
        return 0xD800u <= theChar && theChar <= 0xDBFFu ? true : false;
    }

    static bool
    isUTF16LowSurrogate(XalanDOMChar    theChar)
    {
        return 0xDC00u <= theChar && theChar <= 0xDFFFu ? true : false;
    }

    static XalanUnicodeChar
    decodeUTF16SurrogatePair(
                XalanDOMChar    theHighSurrogate,
                XalanDOMChar    theLowSurrogate,
                MemoryManager&  theManager)
    {
        assert(isUTF16HighSurrogate(theHighSurrogate) == true);

        if (isUTF16LowSurrogate(theLowSurrogate) == false)
        {
            throwInvalidUTF16SurrogateException(theHighSurrogate, theLowSurrogate, theManager);
        }

        return ((theHighSurrogate - 0xD800u) << 10) + theLowSurrogate - 0xDC00u + 0x00010000u;
    }

    static void
    throwInvalidCharacterException(
                XalanUnicodeChar    ch,
                MemoryManager&      theManager)
    {
        XalanDOMString  theMessage(theManager);
        XalanDOMString  theBuffer(theManager);  

        XalanMessageLoader::getMessage(
                    theMessage,
                    XalanMessages::InvalidScalar_1Param, 
                    NumberToHexDOMString(ch, theBuffer));

        using xercesc::SAXException;

        throw SAXException(theMessage.c_str(), &theManager);
    }

    void
    throwUnrepresentableCharacterException(
                XalanUnicodeChar    ch,
                MemoryManager&      theManager)
    {
        XalanDOMString  theBuffer(theManager);  

        const XalanOutputStream* const  theStream =
            m_writer.getStream();

        throw XalanTranscodingServices::UnrepresentableCharacterException(
                    ch,
                    theStream->getOutputEncoding(),
                    theBuffer);
    }

    static void
    throwInvalidUTF16SurrogateException(
                XalanDOMChar        ch,
                XalanDOMChar        next,
                MemoryManager&  theManager)
    {

        XalanDOMString  chStr(theManager); 

        XalanDOMString  nextStr(theManager); 

        NumberToHexDOMString(ch, chStr);

        NumberToHexDOMString(next, nextStr);

        XalanDOMString  theMessage(theManager);

        XalanMessageLoader::getMessage(
                    theMessage,
                    XalanMessages::InvalidSurrogatePair_2Param,
                    theMessage,
                    chStr,
                    nextStr);

        using xercesc::SAXException;

        throw SAXException(theMessage.c_str(),&theManager);
    }

protected:

    /** 
     * The writer.
     */
    Writer&                     m_writer;

    /**
     * The MemoryManager instance to use for any dynamically-
     * allocated memory.
     */
    MemoryManager&              m_memoryManager;

    XalanDOMString          m_stringBuffer;

    /**
     * The string of characters that represents the newline
     */
    const XalanDOMChar*         m_newlineString;

    /**
     * The length of the the string of characters that represents the newline
     */
    size_type   m_newlineStringLength;

    /**
     * Format a code point as a numeric character reference.
     *
     * @param theChar A Unicode code point.
     */
    const XalanDOMString&
    formatNumericCharacterReference(XalanUnicodeChar    theChar)
    {
        m_stringBuffer.clear();

        m_stringBuffer.push_back(XalanDOMChar(XalanUnicode::charAmpersand));
        m_stringBuffer.push_back(XalanDOMChar(XalanUnicode::charNumberSign));

        NumberToDOMString(theChar, m_stringBuffer);

        m_stringBuffer.push_back(XalanDOMChar(XalanUnicode::charSemicolon));

        return m_stringBuffer;
    }

private:

    // These are not implemented.
    XalanFormatterWriter();

    XalanFormatterWriter&
    operator=(const XalanFormatterWriter&);
};



}



#endif  // XALANFORMATTERWRITER_HEADER_GUARD_1357924680
