/***************************************************************************
 *   Copyright (C) 2006 by Dominik Seichter                                *
 *   domseichter@web.de                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Library 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.             *
 *                                                                         *
 *   In addition, as a special exception, the copyright holders give       *
 *   permission to link the code of portions of this program with the      *
 *   OpenSSL library under certain conditions as described in each         *
 *   individual source file, and distribute linked combinations            *
 *   including the two.                                                    *
 *   You must obey the GNU General Public License in all respects          *
 *   for all of the code used other than OpenSSL.  If you modify           *
 *   file(s) with this exception, you may extend this exception to your    *
 *   version of the file(s), but you are not obligated to do so.  If you   *
 *   do not wish to do so, delete this exception statement from your       *
 *   version.  If you delete this exception statement from all source      *
 *   files in the program, then also delete it here.                       *
 ***************************************************************************/

#include "PdfOutputDevice.h"
#include "PdfRefCountedBuffer.h"
#include "PdfDefinesPrivate.h"

#include <fstream>
#include <sstream>


namespace PoDoFo {


PdfOutputDevice::PdfOutputDevice()
{
    this->Init();
}

PdfOutputDevice::PdfOutputDevice( const char* pszFilename, bool bTruncate )
{
    this->Init();

    if( !pszFilename ) 
    {
        PODOFO_RAISE_ERROR( ePdfError_InvalidHandle );
    }

    std::ios_base::openmode openmode = std::fstream::binary | std::ios_base::in | std::ios_base::out;
    if( bTruncate )
        openmode |= std::ios_base::trunc;

    std::fstream *pStream = new std::fstream( pszFilename, openmode );
    if( pStream->fail() )
    {
        delete pStream;
        PODOFO_RAISE_ERROR_INFO( ePdfError_FileNotFound, pszFilename );
    }

    m_pStream = pStream;
    m_pReadStream = pStream;
    PdfLocaleImbue( *m_pStream );

    if( !bTruncate )
    {
        m_pStream->seekp( 0, std::ios_base::end );

        m_ulPosition = m_pStream->tellp();
        m_ulLength = m_ulPosition;
    }
}

#ifdef _WIN32
PdfOutputDevice::PdfOutputDevice( const wchar_t* pszFilename, bool bTruncate )
{
    this->Init();

    if( !pszFilename ) 
    {
        PODOFO_RAISE_ERROR( ePdfError_InvalidHandle );
    }

    m_hFile = _wfopen( pszFilename, bTruncate ? L"w+b" : L"r+b" );
    if( !m_hFile )
    {
        PdfError e( ePdfError_FileNotFound, __FILE__, __LINE__ );
        e.SetErrorInformation( pszFilename );
        throw e;
    }

    if( !bTruncate )
    {
        if( fseeko( m_hFile, 0, SEEK_END ) == -1 )
        {
            PODOFO_RAISE_ERROR_INFO( ePdfError_ValueOutOfRange, "Failed to seek to the end of the file" );
        }

        m_ulPosition = ftello( m_hFile );
        m_ulLength = m_ulPosition;
    }
}
#endif // _WIN32

PdfOutputDevice::PdfOutputDevice( char* pBuffer, size_t lLen )
{
    this->Init();

    if( !pBuffer )
    {
        PODOFO_RAISE_ERROR( ePdfError_InvalidHandle );
    }

    m_lBufferLen = lLen;
    m_pBuffer    = pBuffer;
}

PdfOutputDevice::PdfOutputDevice( const std::ostream* pOutStream )
{
    this->Init();

    m_pStream = const_cast< std::ostream* >( pOutStream );
    m_pStreamOwned = false;

#if USE_CXX_LOCALE
    m_pStreamSavedLocale = m_pStream->getloc();
    PdfLocaleImbue(*m_pStream);
#endif

}

PdfOutputDevice::PdfOutputDevice( PdfRefCountedBuffer* pOutBuffer )
{
    this->Init();
    m_pRefCountedBuffer = pOutBuffer;
}

PdfOutputDevice::PdfOutputDevice( std::iostream* pStream )
{
    this->Init();

    m_pStream = pStream;
    m_pReadStream = pStream;
    m_pStreamOwned = false;

#if USE_CXX_LOCALE
    m_pStreamSavedLocale = m_pStream->getloc();
    PdfLocaleImbue(*m_pStream);
#endif	
}

PdfOutputDevice::~PdfOutputDevice()
{
    if( m_pStreamOwned ) 
        // remember, deleting a null pointer is safe
        delete m_pStream; // will call close

#if USE_CXX_LOCALE
    if( !m_pStreamOwned )	
        m_pStream->imbue(m_pStreamSavedLocale);
#endif
	
    if( m_hFile )
        fclose( m_hFile );
}

void PdfOutputDevice::Init()
{
    m_ulLength          = 0;

    m_hFile             = NULL;
    m_pBuffer           = NULL;
    m_pStream           = NULL;
	m_pReadStream       = NULL;
    m_pRefCountedBuffer = NULL;
    m_lBufferLen        = 0;
    m_ulPosition        = 0;
    m_pStreamOwned      = true;
}

void PdfOutputDevice::Print( const char* pszFormat, ... )
{
    va_list args;
    long lBytes;

	va_start( args, pszFormat );
	lBytes = PrintVLen(pszFormat, args);
	va_end( args );

	va_start( args, pszFormat );
	PrintV(pszFormat, lBytes, args);
	va_end( args );
}

long PdfOutputDevice::PrintVLen( const char* pszFormat, va_list args )
{
    long    lBytes;

    if( !pszFormat )
    {
        PODOFO_RAISE_ERROR( ePdfError_InvalidHandle );
    }

    if( m_hFile )
    {
        if( (lBytes = vfprintf( m_hFile, pszFormat, args )) < 0 )
        {
            perror( NULL );
            PODOFO_RAISE_ERROR( ePdfError_UnexpectedEOF );
        }
    }
    else
    {
        // OC 17.08.2010: Use new function _vscprintf to get the number of characters:
        // visual c++  8.0 == 1400 (Visual Studio 2005)
        // i am not shure if 1300 is ok here, but who cares this cruel compiler version
#if (defined _MSC_VER && _MSC_VER >= 1400 )
        lBytes = _vscprintf( pszFormat, args );
#elif (defined _MSC_VER || defined __hpux)  // vsnprintf without buffer does not work with MS-VC or HPUX
        int len = 1024;
        do
        {
            char * temp = new char[len+1]; // OC 17.08.2010 BugFix: +1 avoids corrupted heap
            lBytes = vsnprintf( temp, len+1, pszFormat, args );
            delete[] temp;
            len *= 2;
        } while (lBytes < 0 );
#else
        lBytes = vsnprintf( NULL, 0, pszFormat, args );
#endif
    }

    return lBytes;
}

void PdfOutputDevice::PrintV( const char* pszFormat, long lBytes, va_list args )
{
    if( !pszFormat )
    {
        PODOFO_RAISE_ERROR( ePdfError_InvalidHandle );
    }

    if( m_pBuffer )
    {
        if( m_ulPosition + lBytes <= m_lBufferLen )
        {
            vsnprintf( m_pBuffer + m_ulPosition, m_lBufferLen - m_ulPosition, pszFormat, args );
        }
        else
        {
            PODOFO_RAISE_ERROR( ePdfError_OutOfMemory );
        }
    }
    else if( m_pStream || m_pRefCountedBuffer )
    {
        ++lBytes;
        m_printBuffer.Resize( lBytes );
        char* data = m_printBuffer.GetBuffer();
        if( !data )
        {
            PODOFO_RAISE_ERROR( ePdfError_OutOfMemory );
        }

        vsnprintf( data, lBytes, pszFormat, args );
        if( lBytes )
            --lBytes;

        if( m_pStream ) 
        {
            std::string str;
            str.assign( data, lBytes );
            *m_pStream << str;
        }
        else // if( m_pRefCountedBuffer ) 
        {
            if( m_ulPosition + lBytes > static_cast<unsigned long>(m_pRefCountedBuffer->GetSize()) )
            {
                m_pRefCountedBuffer->Resize( m_ulPosition + lBytes );
            }

            memcpy( m_pRefCountedBuffer->GetBuffer() + m_ulPosition, data, lBytes );
        }

    }

    m_ulPosition += static_cast<size_t>(lBytes);
    if(m_ulPosition>m_ulLength) 
    {
        m_ulLength = m_ulPosition;
    }
}

size_t PdfOutputDevice::Read( char* pBuffer, size_t lLen )
{
	size_t numRead = 0;
    if( m_hFile )
    {
		numRead = fread( pBuffer, sizeof(char), lLen, m_hFile );
		if(ferror(m_hFile)!=0)
        {
            PODOFO_RAISE_ERROR( ePdfError_InvalidDeviceOperation );
        }		
    }
    else if( m_pBuffer )
    {		
        if( m_ulPosition <= m_ulLength )
        {
			numRead = PODOFO_MIN(lLen, m_ulLength-m_ulPosition);
            memcpy( pBuffer, m_pBuffer + m_ulPosition, numRead);
        }
    }
    else if( m_pReadStream )
    {
		size_t iPos = m_pReadStream->tellg();
		m_pReadStream->read( pBuffer, lLen );
		if(m_pReadStream->fail()&&!m_pReadStream->eof()) {
			PODOFO_RAISE_ERROR( ePdfError_InvalidDeviceOperation );
		}
		numRead = m_pReadStream->tellg();
		numRead -= iPos;
    }
    else if( m_pRefCountedBuffer ) 
    {
        if( m_ulPosition <= m_ulLength )
		{
			numRead = PODOFO_MIN(lLen, m_ulLength-m_ulPosition);
            memcpy( pBuffer, m_pRefCountedBuffer->GetBuffer() + m_ulPosition, numRead );
		}
    }

    m_ulPosition += static_cast<size_t>(numRead);
	return numRead;
}

void PdfOutputDevice::Write( const char* pBuffer, size_t lLen )
{
    if( m_hFile )
    {
        if( fwrite( pBuffer, sizeof(char), lLen, m_hFile ) != static_cast<size_t>(lLen) )
        {
            PODOFO_RAISE_ERROR( ePdfError_UnexpectedEOF );
        }
    }
    else if( m_pBuffer )
    {
        if( m_ulPosition + lLen <= m_lBufferLen )
        {
            memcpy( m_pBuffer + m_ulPosition, pBuffer, lLen );
        }
        else
        {
            PODOFO_RAISE_ERROR_INFO( ePdfError_OutOfMemory, "Allocated buffer to small for PdfOutputDevice. Cannot write!"  );
        }
    }
    else if( m_pStream )
    {
        m_pStream->write( pBuffer, lLen );
    }
    else if( m_pRefCountedBuffer ) 
    {
        if( m_ulPosition + lLen > m_pRefCountedBuffer->GetSize() )
            m_pRefCountedBuffer->Resize( m_ulPosition + lLen );

        memcpy( m_pRefCountedBuffer->GetBuffer() + m_ulPosition, pBuffer, lLen );
    }

    m_ulPosition += static_cast<size_t>(lLen);
	if(m_ulPosition>m_ulLength) m_ulLength = m_ulPosition;
}

void PdfOutputDevice::Seek( size_t offset )
{
    if( m_hFile )
    {
        if( fseeko( m_hFile, offset, SEEK_SET ) == -1 )
        {
            PODOFO_RAISE_ERROR( ePdfError_ValueOutOfRange );
        }
    }
    else if( m_pBuffer )
    {
        if( offset >= m_lBufferLen )
        {
            PODOFO_RAISE_ERROR( ePdfError_ValueOutOfRange );
        }
    }
    else if( m_pStream )
    {
        m_pStream->seekp( offset, std::ios_base::beg );
    }
    else if( m_pRefCountedBuffer ) 
    {
        m_ulPosition = offset;
    }

    m_ulPosition = offset;
    // Seek should not change the length of the device
    // m_ulLength = offset;
}

void PdfOutputDevice::Flush()
{
    if( m_hFile )
    {
        if( fflush( m_hFile ) )
        {
            PODOFO_RAISE_ERROR( ePdfError_InvalidHandle );
        }
    }
    else if( m_pStream )
    {
        m_pStream->flush();
    }
}

};
