/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999, 2000 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xalan" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.ibm.com.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
// Class header file...
#include "XalanOutputStream.hpp"



#include <util/TransService.hpp>
#include <util/XMLException.hpp>



#include "XalanTranscodingServices.hpp"



XalanOutputStream::XalanOutputStream(
			BufferType::size_type			theBufferSize,
			TranscodeVectorType::size_type	theTranscoderBlockSize) :
	m_transcoderBlockSize(theTranscoderBlockSize),
	m_transcoder(0),
	m_bufferSize(theBufferSize),
	m_buffer(),
	m_encoding(),
	m_writeAsUTF16(false)
{
}



XalanOutputStream::~XalanOutputStream()
{
	XalanTranscodingServices::destroyTranscoder(m_transcoder);
}



void
XalanOutputStream::flush()
{
	flushBuffer();

	doFlush();
}



void
XalanOutputStream::write(char	theChar)
{
	write(&theChar, 1);
}



void
XalanOutputStream::write(XalanDOMChar	theChar)
{
	write(&theChar, 1);
}



void
XalanOutputStream::write(
			const XalanDOMChar*		theBuffer,
			unsigned long			theBufferLength)
{
	assert(theBuffer != 0);

	if (theBufferLength + m_buffer.size() > m_bufferSize)
	{
		flushBuffer();
	}

	if (theBufferLength > m_bufferSize)
	{
		doWrite(theBuffer, theBufferLength);
	}
	else
	{
		m_buffer.insert(m_buffer.end(),
						theBuffer,
						theBuffer + theBufferLength);
	}
}



void
XalanOutputStream::write(const XalanDOMChar*	theBuffer)
{
	if (theBuffer != 0)
	{
		write(theBuffer, length(theBuffer));
	}
}



void
XalanOutputStream::write(const char*	theBuffer)
{
	assert(theBuffer != 0);

	flushBuffer();

	writeData(theBuffer,
		      strlen(theBuffer));
}



void
XalanOutputStream::write(
			const char*		theBuffer,
			unsigned long	theBufferLength)
{
	assert(theBuffer != 0);

	flushBuffer();

	writeData(theBuffer,
			  theBufferLength);
}



void
XalanOutputStream::transcode(
			const XalanDOMChar*		theBuffer,
			TranscodeVectorType&	theDestination)
{
	// This is a special version that will short-cut when
	// transocding to the local code page.  On platforms
	// where XalanDOMChar == wchar_t, it saves copying
	// to a temporary buffer for the purposes of null-
	// terminating the string.
	if (m_transcoder == 0)
	{
		if (TranscodeToLocalCodePage(
				theBuffer,
				theDestination) == false)
		{
			throw TranscodingException();
		}
	}
	else
	{
		transcode(theBuffer, length(theBuffer), theDestination);
	}
}



void
XalanOutputStream::transcode(
			const XalanDOMChar*		theBuffer,
			unsigned long			theBufferLength,
			TranscodeVectorType&	theDestination)
{
	if (m_transcoder == 0)
	{
		if (TranscodeToLocalCodePage(
				theBuffer,
				theBufferLength,
				theDestination) == false)
		{
			throw TranscodingException();
		}
	}
	else
	{
		bool					fDone = false;

		// Keep track of the total bytes we've added to the
		// destination vector, and the total bytes we've
		// eaten from theBuffer.
		unsigned int			theTotalBytesFilled = 0;
		unsigned int			theTotalBytesEaten = 0;

		// Keep track of the current position in the input buffer,
		// and amount remaining in the buffer, since we may not be
		// able to transcode it all at once.
		const XalanDOMChar*		theBufferPosition = theBuffer;
		unsigned int			theRemainingBufferLength = theBufferLength;

		// Keep track of the destination size, and the target size, which is
		// the size of the destination that has not yet been filled with
		// transcoded characters.  Double the buffer size, in case we're
		// transcoding to a 16-bit encoding.
		// $$$ ToDo: We need to know the size of an encoding, so we can
		// do the right thing with the destination size.
		unsigned int			theDestinationSize = theBufferLength * 2;
		unsigned int			theTargetSize = theDestinationSize;

		do
		{
			// Resize the buffer...
			theDestination.resize(theDestinationSize + 1);

			unsigned int						theSourceBytesEaten = 0;
			unsigned int						theTargetBytesEaten = 0;

			XalanTranscodingServices::eCode		theResult =
				m_transcoder->transcode(
						theBufferPosition,
						theRemainingBufferLength,
#if defined(XALAN_OLD_STYLE_CASTS)
						(XMLByte*)&theDestination[0] + theTotalBytesFilled,
#else
						reinterpret_cast<XMLByte*>(&theDestination[0]) + theTotalBytesFilled,
#endif
						theTargetSize,
						theSourceBytesEaten,
						theTargetBytesEaten);

			if(theResult != XalanTranscodingServices::OK)
			{
				throw TranscodingException();
			}

			theTotalBytesFilled += theTargetBytesEaten;
			theTotalBytesEaten += theSourceBytesEaten;

			if (theTotalBytesEaten == theBufferLength)
			{
				fDone = true;
			}
			else
			{
				assert(theTotalBytesEaten < theBufferLength);

				// Update everything...
				theBufferPosition += theSourceBytesEaten;
				theRemainingBufferLength -= theSourceBytesEaten;

				// The new target size will always be the
				// current destination size, since we
				// grow by a factor of 2.  This will
				// need to change if the factor is
				// every changed.
				theTargetSize = theDestinationSize;

				// Grow the destination by a factor of
				// two 2.  See the previous comment if
				// you want to change this.
				theDestinationSize = theDestinationSize * 2;
			}
		} while(fDone == false);

		// Resize things, if there are any extra bytes...
		if (theDestination.size() != theTotalBytesFilled)
		{
			theDestination.resize(theTotalBytesFilled);
		}
	}
}



const XalanDOMString&
XalanOutputStream::getOutputEncoding() const
{
	return m_encoding;
}



void
XalanOutputStream::setOutputEncoding(const XalanDOMString&	theEncoding)
{
	// Flush, just in case.  This should problably be an error...
	flushBuffer();

	XalanTranscodingServices::destroyTranscoder(m_transcoder);

	XalanTranscodingServices::eCode		theCode = XalanTranscodingServices::OK;

	// This turns on an optimization that we can only do if
	// XalanDOMChar == sizeof(ushort).  See doWrite().
#if !defined(XALAN_XALANDOMCHAR_USHORT_MISMATCH)
	if (XalanTranscodingServices::encodingIsUTF16(theEncoding) == true)
	{
		m_writeAsUTF16 = true;
	}
	else
#endif
	{
		m_transcoder = XalanTranscodingServices::makeNewTranscoder(
					theEncoding,
					theCode,
					m_transcoderBlockSize);

		if (theCode == XalanTranscodingServices::UnsupportedEncoding)
		{
			throw UnsupportedEncodingException(theEncoding);
		}
		else if (theCode != XalanTranscodingServices::OK)
		{
			throw TranscoderInternalFailureException(theEncoding);
		}

		assert(m_transcoder != 0);
	}

	m_encoding = theEncoding;

	typedef XalanTranscodingServices::XalanXMLByteVectorType	XalanXMLByteVectorType;

	const XalanXMLByteVectorType&	theProlog =
		XalanTranscodingServices::getStreamProlog(theEncoding);

	const XalanXMLByteVectorType::size_type		theSize = theProlog.size();

	if (theSize > 0)
	{
#if defined(XALAN_OLD_STYLE_CASTS)
		write((const char*)theProlog[0], theProlog.size());
#else
		write(reinterpret_cast<const char*>(&theProlog[0]), theProlog.size());
#endif
	}
}



void
XalanOutputStream::flushBuffer()
{
	if (m_buffer.size() > 0)
	{
		m_buffer.push_back(0);

		doWrite(m_buffer.begin());

		m_buffer.clear();
	}
}



void
XalanOutputStream::doWrite(const XalanDOMChar*	theBuffer)
{
	assert(theBuffer != 0);

	try
	{
		if (m_writeAsUTF16 == true)
		{
			assert(sizeof(XalanDOMChar) == sizeof(char) * 2);

			// This is a hack to write UTF-16 through as if it
			// were just chars.  Saves lots of time "transcoding."
#if defined(XALAN_OLD_STYLE_CASTS)
			writeData((const char*)theBuffer, length(theBuffer) * 2);
#else
			writeData(reinterpret_cast<const char*>(theBuffer), length(theBuffer) * 2);
#endif
		}
		else
		{
			TranscodeVectorType		theTranscodedData;

			transcode(theBuffer, theTranscodedData);

			assert(&theTranscodedData[0] != 0);

			writeData(&theTranscodedData[0],
					  theTranscodedData.size());
		}
	}
	catch(const XalanOutputStreamException&)
	{
		// Have to catch this error and flush any output remaining...
		m_buffer.clear();

		throw;
	}
}



void
XalanOutputStream::doWrite(
			const XalanDOMChar*		theBuffer,
			unsigned long			theBufferLength)
{
	// $$$ ToDo: Revisit this!!!
	BufferType	theLocalBuffer(theBuffer, theBuffer + theBufferLength);

	theLocalBuffer.push_back(0);

	doWrite(&theLocalBuffer[0]);
}



void
XalanOutputStream::setBufferSize(BufferType::size_type		theBufferSize)
{
	flushBuffer();

	m_bufferSize = theBufferSize;

	if (m_buffer.size() < m_bufferSize)
	{
		// Enlarge the buffer...
		m_buffer.reserve(theBufferSize);
	}
	else if (m_buffer.size() > m_bufferSize)
	{
		// Shrink the buffer.

		// Create a temp buffer and make it
		// the correct size.
		BufferType	temp;
		
		temp.reserve(theBufferSize);
		
		// Swap temp with m_buffer so that
		// m_buffer is now the correct size.
		temp.swap(m_buffer);
	}
}



XalanOutputStream::XalanOutputStreamException::XalanOutputStreamException(
			const XalanDOMString&	theMessage,
			const XalanDOMString&	theType) :
	XSLException(theMessage, theType)
{
}



XalanOutputStream::XalanOutputStreamException::~XalanOutputStreamException()
{
}



XalanOutputStream::UnknownEncodingException::UnknownEncodingException() :
	XalanOutputStreamException(
			XALAN_STATIC_UCODE_STRING("Unknown error occurred while transcoding!"),
			XALAN_STATIC_UCODE_STRING("UnknownEncodingException"))
{
}



XalanOutputStream::UnknownEncodingException::~UnknownEncodingException()
{
}



XalanOutputStream::UnsupportedEncodingException::UnsupportedEncodingException(const XalanDOMString&	theEncoding) :
	XalanOutputStreamException(
			XALAN_STATIC_UCODE_STRING("Unsupported encoding: ") + theEncoding,
			XALAN_STATIC_UCODE_STRING("UnsupportedEncodingException")),
	m_encoding(theEncoding)
{
}



XalanOutputStream::UnsupportedEncodingException::~UnsupportedEncodingException()
{
}



XalanOutputStream::TranscoderInternalFailureException::TranscoderInternalFailureException(const XalanDOMString&	theEncoding) :
	XalanOutputStreamException(
			XALAN_STATIC_UCODE_STRING("Unknown error occurred while transcoding to ") +
					theEncoding +
					XALAN_STATIC_UCODE_STRING("!"),
			XALAN_STATIC_UCODE_STRING("TranscoderInternalFailureException")),
	m_encoding(theEncoding)
{
}



XalanOutputStream::TranscoderInternalFailureException::~TranscoderInternalFailureException()
{
}



XalanOutputStream::TranscodingException::TranscodingException() :
	XalanOutputStreamException(
			XALAN_STATIC_UCODE_STRING("An error occurred while transcoding!"),
			XALAN_STATIC_UCODE_STRING("TranscodingException"))
{
}



XalanOutputStream::TranscodingException::~TranscodingException()
{
}
