/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: biffinputstream.cxx,v $
 * $Revision: 1.4.20.2 $
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

#include "oox/xls/biffinputstream.hxx"
#include <algorithm>
#include <rtl/ustrbuf.hxx>

using ::rtl::OString;
using ::rtl::OStringToOUString;
using ::rtl::OUString;
using ::rtl::OUStringBuffer;

namespace oox {
namespace xls {

// ============================================================================

namespace prv {

BiffInputRecordBuffer::BiffInputRecordBuffer( BinaryInputStream& rInStrm ) :
    mrInStrm( rInStrm ),
    mpCurrentData( 0 ),
    mnHeaderPos( -1 ),
    mnBodyPos( 0 ),
    mnBufferBodyPos( 0 ),
    mnNextHeaderPos( 0 ),
    mnRecId( BIFF_ID_UNKNOWN ),
    mnRecSize( 0 ),
    mnRecPos( 0 ),
    mbValidHeader( false )
{
    OSL_ENSURE( mrInStrm.isSeekable(), "BiffInputRecordBuffer::BiffInputRecordBuffer - stream must be seekable" );
    mrInStrm.seekToStart();
    maOriginalData.reserve( SAL_MAX_UINT16 );
    maDecodedData.reserve( SAL_MAX_UINT16 );
    enableDecoder( false );     // updates mpCurrentData
}

void BiffInputRecordBuffer::restartAt( sal_Int64 nPos )
{
    mnHeaderPos = -1;
    mnBodyPos = mnBufferBodyPos = 0;
    mnNextHeaderPos = nPos;
    mnRecId = BIFF_ID_UNKNOWN;
    mnRecSize = mnRecPos = 0;
    mbValidHeader = false;
}

void BiffInputRecordBuffer::setDecoder( BiffDecoderRef xDecoder )
{
    mxDecoder = xDecoder;
    enableDecoder( true );
    updateDecoded();
}

void BiffInputRecordBuffer::enableDecoder( bool bEnable )
{
    mpCurrentData = (bEnable && mxDecoder.get() && mxDecoder->isValid()) ? &maDecodedData : &maOriginalData;
}

bool BiffInputRecordBuffer::startRecord( sal_Int64 nHeaderPos )
{
    mbValidHeader = (0 <= nHeaderPos) && (nHeaderPos + 4 <= mrInStrm.getLength());
    if( mbValidHeader )
    {
        mnHeaderPos = nHeaderPos;
        mrInStrm.seek( nHeaderPos );
        mrInStrm >> mnRecId >> mnRecSize;
        mnBodyPos = mrInStrm.tell();
        mnNextHeaderPos = mnBodyPos + mnRecSize;
        mbValidHeader = !mrInStrm.isEof() && (mnNextHeaderPos <= mrInStrm.getLength());
    }
    if( !mbValidHeader )
    {
        mnHeaderPos = mnBodyPos = -1;
        mnNextHeaderPos = 0;
        mnRecId = BIFF_ID_UNKNOWN;
        mnRecSize = 0;
    }
    mnRecPos = 0;
    return mbValidHeader;
}

bool BiffInputRecordBuffer::startNextRecord()
{
    return startRecord( mnNextHeaderPos );
}

sal_uInt16 BiffInputRecordBuffer::getNextRecId()
{
    sal_uInt16 nRecId = BIFF_ID_UNKNOWN;
    if( mbValidHeader && (mnNextHeaderPos + 4 <= mrInStrm.getLength()) )
    {
        mrInStrm.seek( mnNextHeaderPos );
        mrInStrm >> nRecId;
    }
    return nRecId;
}

void BiffInputRecordBuffer::read( void* opData, sal_uInt16 nBytes )
{
    updateBuffer();
    OSL_ENSURE( nBytes > 0, "BiffInputRecordBuffer::read - nothing to read" );
    OSL_ENSURE( nBytes <= getRecLeft(), "BiffInputRecordBuffer::read - buffer overflow" );
    memcpy( opData, &(*mpCurrentData)[ mnRecPos ], nBytes );
    mnRecPos = mnRecPos + nBytes;
}

void BiffInputRecordBuffer::skip( sal_uInt16 nBytes )
{
    OSL_ENSURE( nBytes > 0, "BiffInputRecordBuffer::skip - nothing to skip" );
    OSL_ENSURE( nBytes <= getRecLeft(), "BiffInputRecordBuffer::skip - buffer overflow" );
    mnRecPos = mnRecPos + nBytes;
}

void BiffInputRecordBuffer::updateBuffer()
{
    OSL_ENSURE( mbValidHeader, "BiffInputRecordBuffer::updateBuffer - invalid access" );
    if( mnBodyPos != mnBufferBodyPos )
    {
        mrInStrm.seek( mnBodyPos );
        maOriginalData.resize( mnRecSize );
        if( mnRecSize > 0 )
            mrInStrm.readMemory( &maOriginalData.front(), static_cast< sal_Int32 >( mnRecSize ) );
        mnBufferBodyPos = mnBodyPos;
        updateDecoded();
    }
}

void BiffInputRecordBuffer::updateDecoded()
{
    if( mxDecoder.get() && mxDecoder->isValid() )
    {
        maDecodedData.resize( mnRecSize );
        if( mnRecSize > 0 )
            mxDecoder->decode( &maDecodedData.front(), &maOriginalData.front(), mnBodyPos, mnRecSize );
    }
}

} // namespace prv

// ============================================================================

BiffInputStream::BiffInputStream( BinaryInputStream& rInStream, bool bContLookup ) :
    maRecBuffer( rInStream ),
    mnRecHandle( -1 ),
    mnRecId( BIFF_ID_UNKNOWN ),
    mnAltContId( BIFF_ID_UNKNOWN ),
    mnCurrRecSize( 0 ),
    mnComplRecSize( 0 ),
    mbHasComplRec( false ),
    mbCont( bContLookup ),
    mbNulChars( false )
{
    mbEof = true;   // EOF will be true if stream is not inside a record
}

// record control -------------------------------------------------------------

bool BiffInputStream::startNextRecord()
{
    bool bValidRec = false;
    /*  #i4266# ignore zero records (id==len==0) (e.g. the application
        "Crystal Report" writes zero records between other records) */
    bool bIsZeroRec = false;
    do
    {
        // record header is never encrypted
        maRecBuffer.enableDecoder( false );
        // read header of next raw record, returns false at end of stream
        bValidRec = maRecBuffer.startNextRecord();
        // ignore record, if identifier and size are zero
        bIsZeroRec = (maRecBuffer.getRecId() == 0) && (maRecBuffer.getRecSize() == 0);
    }
    while( bValidRec && ((mbCont && isContinueId( maRecBuffer.getRecId() )) || bIsZeroRec) );

    // setup other class members
    setupRecord();
    return isInRecord();
}

bool BiffInputStream::startRecordByHandle( sal_Int64 nRecHandle )
{
    rewindToRecord( nRecHandle );
    return startNextRecord();
}

void BiffInputStream::resetRecord( bool bContLookup, sal_uInt16 nAltContId )
{
    if( isInRecord() )
    {
        mbCont = bContLookup;
        mnAltContId = nAltContId;
        restartRecord( true );
        maRecBuffer.enableDecoder( true );
    }
}

void BiffInputStream::rewindRecord()
{
    rewindToRecord( mnRecHandle );
}

// decoder --------------------------------------------------------------------

void BiffInputStream::setDecoder( BiffDecoderRef xDecoder )
{
    maRecBuffer.setDecoder( xDecoder );
}

BiffDecoderRef BiffInputStream::getDecoder() const
{
    return maRecBuffer.getDecoder();
}

void BiffInputStream::enableDecoder( bool bEnable )
{
    maRecBuffer.enableDecoder( bEnable );
}

// stream/record state and info -----------------------------------------------

sal_uInt16 BiffInputStream::getNextRecId()
{
    sal_uInt16 nRecId = BIFF_ID_UNKNOWN;
    if( isInRecord() )
    {
        sal_Int64 nCurrPos = tell();            // save current position in record
        while( jumpToNextContinue() ) {}        // skip following CONTINUE records
        if( maRecBuffer.startNextRecord() )     // read header of next record
            nRecId = maRecBuffer.getRecId();
        seek( nCurrPos );                       // restore position, seek() resets old mbValid state
    }
    return nRecId;
}

// BinaryStreamBase interface (seeking) ---------------------------------------

bool BiffInputStream::isSeekable() const
{
    return true;
}

sal_Int64 BiffInputStream::tell() const
{
    return mbEof ? -1 : (mnCurrRecSize - maRecBuffer.getRecLeft());
}

sal_Int64 BiffInputStream::getLength() const
{
    if( !mbHasComplRec )
        const_cast< BiffInputStream* >( this )->calcRecordLength();
    return mnComplRecSize;
}

void BiffInputStream::seek( sal_Int64 nRecPos )
{
    if( isInRecord() )
    {
        if( mbEof || (nRecPos < tell()) )
            restartRecord( false );
        if( !mbEof && (nRecPos > tell()) )
            skip( static_cast< sal_Int32 >( nRecPos - tell() ) );
    }
}

sal_Int64 BiffInputStream::tellBase() const
{
    return maRecBuffer.getBaseStream().tell();
}

sal_Int64 BiffInputStream::getBaseLength() const
{
    return maRecBuffer.getBaseStream().getLength();
}

// BinaryInputStream interface (stream read access) ---------------------------

sal_Int32 BiffInputStream::readData( StreamDataSequence& orData, sal_Int32 nBytes )
{
    sal_Int32 nRet = 0;
    if( !mbEof )
    {
        orData.realloc( ::std::max< sal_Int32 >( nBytes, 0 ) );
        if( nBytes > 0 )
        {
            nRet = readMemory( orData.getArray(), nBytes );
            if( nRet < nBytes )
                orData.realloc( nRet );
        }
    }
    return nRet;
}

sal_Int32 BiffInputStream::readMemory( void* opMem, sal_Int32 nBytes )
{
    sal_Int32 nRet = 0;
    if( !mbEof && opMem && (nBytes > 0) )
    {
        sal_uInt8* pnBuffer = reinterpret_cast< sal_uInt8* >( opMem );
        sal_Int32 nBytesLeft = nBytes;

        while( !mbEof && (nBytesLeft > 0) )
        {
            sal_uInt16 nReadSize = getMaxRawReadSize( nBytesLeft );
            // check nReadSize, stream may already be located at end of a raw record
            if( nReadSize > 0 )
            {
                maRecBuffer.read( pnBuffer, nReadSize );
                nRet += nReadSize;
                pnBuffer += nReadSize;
                nBytesLeft -= nReadSize;
            }
            if( nBytesLeft > 0 )
                jumpToNextContinue();
            OSL_ENSURE( !mbEof, "BiffInputStream::readMemory - record overread" );
        }
    }
    return nRet;
}

void BiffInputStream::skip( sal_Int32 nBytes )
{
    sal_Int32 nBytesLeft = nBytes;
    while( !mbEof && (nBytesLeft > 0) )
    {
        sal_uInt16 nSkipSize = getMaxRawReadSize( nBytesLeft );
        // check nSkipSize, stream may already be located at end of a raw record
        if( nSkipSize > 0 )
        {
            maRecBuffer.skip( nSkipSize );
            nBytesLeft -= nSkipSize;
        }
        if( nBytesLeft > 0 )
            jumpToNextContinue();
        OSL_ENSURE( !mbEof, "BiffInputStream::skip - record overread" );
    }
}

// byte strings ---------------------------------------------------------------

OString BiffInputStream::readCharArray( sal_uInt16 nChars )
{
    if( nChars == 0 )
        return OString();

    ::std::vector< sal_Char > aBuffer( static_cast< size_t >( nChars ) );
    size_t nCharsRead = static_cast< size_t >( readMemory( &aBuffer.front(), nChars ) );
    if( !mbNulChars )
        ::std::replace( aBuffer.begin(), aBuffer.begin() + nCharsRead, '\0', '?' );
    return OString( &aBuffer.front(), nCharsRead );
}

OUString BiffInputStream::readCharArray( sal_uInt16 nChars, rtl_TextEncoding eTextEnc )
{
    return OStringToOUString( readCharArray( nChars ), eTextEnc );
}

OString BiffInputStream::readByteString( bool b16BitLen )
{
    sal_uInt16 nStrLen = b16BitLen ? readuInt16() : readuInt8();
    return readCharArray( nStrLen );
}

OUString BiffInputStream::readByteString( bool b16BitLen, rtl_TextEncoding eTextEnc )
{
    return OStringToOUString( readByteString( b16BitLen ), eTextEnc );
}

void BiffInputStream::skipByteString( bool b16BitLen )
{
    skip( b16BitLen ? readuInt16() : readuInt8() );
}

// Unicode strings ------------------------------------------------------------

OUString BiffInputStream::readNulUnicodeArray()
{
    OUStringBuffer aBuffer;
    for( sal_uInt16 nChar = readuInt16(); !mbEof && (nChar > 0); readValue( nChar ) )
        aBuffer.append( static_cast< sal_Unicode >( nChar ) );
    return aBuffer.makeStringAndClear();
}

OUString BiffInputStream::readUnicodeArray( sal_uInt16 nChars )
{
    OUStringBuffer aBuffer;
    appendUnicodeArray( aBuffer, nChars, true );
    return aBuffer.makeStringAndClear();
}

OUString BiffInputStream::readUniStringChars( sal_uInt16 nChars, bool b16Bit )
{
    OUStringBuffer aBuffer;
    aBuffer.ensureCapacity( nChars );

    /*  This function has to react on CONTINUE records to read the repeated
        flags field, so readUnicodeArray() cannot be used here. */
    sal_uInt16 nCharsLeft = nChars;
    while( !mbEof && (nCharsLeft > 0) )
    {
        sal_uInt16 nPortionCount = 0;
        if( b16Bit )
        {
            nPortionCount = ::std::min< sal_uInt16 >( nCharsLeft, maRecBuffer.getRecLeft() / 2 );
            OSL_ENSURE( (nPortionCount <= nCharsLeft) || ((maRecBuffer.getRecLeft() & 1) == 0),
                "BiffInputStream::readUniStringChars - missing a byte" );
        }
        else
        {
            nPortionCount = getMaxRawReadSize( nCharsLeft );
        }

        // read the character array
        appendUnicodeArray( aBuffer, nPortionCount, b16Bit );

        // prepare for next CONTINUE record
        nCharsLeft = nCharsLeft - nPortionCount;
        if( nCharsLeft > 0 )
            jumpToNextStringContinue( b16Bit );
    }

    return aBuffer.makeStringAndClear();
}

OUString BiffInputStream::readUniString( sal_uInt16 nChars )
{
    bool b16Bit;
    sal_Int32 nAddSize;
    readUniStringHeader( b16Bit, nAddSize );
    OUString aString = readUniStringChars( nChars, b16Bit );
    skip( nAddSize );
    return aString;
}

OUString BiffInputStream::readUniString()
{
    return readUniString( readuInt16() );
}

void BiffInputStream::skipUniStringChars( sal_uInt16 nChars, bool b16Bit )
{
    sal_uInt16 nCharsLeft = nChars;
    while( !mbEof && (nCharsLeft > 0) )
    {
        sal_uInt16 nPortionCount;
        if( b16Bit )
        {
            nPortionCount = ::std::min< sal_uInt16 >( nCharsLeft, maRecBuffer.getRecLeft() / 2 );
            OSL_ENSURE( (nPortionCount <= nCharsLeft) || ((maRecBuffer.getRecLeft() & 1) == 0),
                "BiffInputStream::skipUniStringChars - missing a byte" );
            skip( 2 * nPortionCount );
        }
        else
        {
            nPortionCount = getMaxRawReadSize( nCharsLeft );
            skip( nPortionCount );
        }

        // prepare for next CONTINUE record
        nCharsLeft = nCharsLeft - nPortionCount;
        if( nCharsLeft > 0 )
            jumpToNextStringContinue( b16Bit );
    }
}

void BiffInputStream::skipUniString( sal_uInt16 nChars )
{
    bool b16Bit;
    sal_Int32 nAddSize;
    readUniStringHeader( b16Bit, nAddSize );
    skipUniStringChars( nChars, b16Bit );
    skip( nAddSize );
}

void BiffInputStream::skipUniString()
{
    skipUniString( readuInt16() );
}

// private --------------------------------------------------------------------

void BiffInputStream::readAtom( void* opMem, sal_uInt8 nSize )
{
    // byte swapping is done in calling BinaryInputStream::readValue() template function
    if( ensureRawReadSize( nSize ) )
        maRecBuffer.read( opMem, nSize );
}

void BiffInputStream::setupRecord()
{
    // initialize class members
    mnRecHandle = maRecBuffer.getRecHeaderPos();
    mnRecId = maRecBuffer.getRecId();
    mnAltContId = BIFF_ID_UNKNOWN;
    mnCurrRecSize = mnComplRecSize = maRecBuffer.getRecSize();
    mbHasComplRec = !mbCont;
    mbNulChars = false;
    mbEof = !isInRecord();
    // enable decoder in new record
    enableDecoder( true );
}

void BiffInputStream::restartRecord( bool bInvalidateRecSize )
{
    if( isInRecord() )
    {
        maRecBuffer.startRecord( getRecHandle() );
        mnCurrRecSize = maRecBuffer.getRecSize();
        if( bInvalidateRecSize )
        {
            mnComplRecSize = mnCurrRecSize;
            mbHasComplRec = !mbCont;
        }
        mbEof = false;
    }
}

void BiffInputStream::rewindToRecord( sal_Int64 nRecHandle )
{
    if( nRecHandle >= 0 )
    {
        maRecBuffer.restartAt( nRecHandle );
        mnRecHandle = -1;
        mbEof = true;   // as long as the record is not started
    }
}

bool BiffInputStream::isContinueId( sal_uInt16 nRecId ) const
{
    return (nRecId == BIFF_ID_CONT) || (nRecId == mnAltContId);
}

bool BiffInputStream::jumpToNextContinue()
{
    mbEof = mbEof || !mbCont || !isContinueId( maRecBuffer.getNextRecId() ) || !maRecBuffer.startNextRecord();
    if( !mbEof )
        mnCurrRecSize += maRecBuffer.getRecSize();
    return !mbEof;
}

bool BiffInputStream::jumpToNextStringContinue( bool& rb16Bit )
{
    OSL_ENSURE( maRecBuffer.getRecLeft() == 0, "BiffInputStream::jumpToNextStringContinue - unexpected garbage" );

    if( mbCont && (getRemaining() > 0) )
    {
        jumpToNextContinue();
    }
    else if( mnRecId == BIFF_ID_CONT )
    {
        /*  CONTINUE handling is off, but we have started reading in a CONTINUE
            record -> start next CONTINUE for TXO import. We really start a new
            record here - no chance to return to string origin. */
        mbEof = mbEof || (maRecBuffer.getNextRecId() != BIFF_ID_CONT) || !maRecBuffer.startNextRecord();
        if( !mbEof )
            setupRecord();
    }

    // trying to read the flags invalidates stream, if no CONTINUE record has been found
    sal_uInt8 nFlags;
    readValue( nFlags );
    rb16Bit = getFlag( nFlags, BIFF_STRF_16BIT );
    return !mbEof;
}

void BiffInputStream::calcRecordLength()
{
    sal_Int64 nCurrPos = tell();            // save current position in record
    while( jumpToNextContinue() ) {}        // jumpToNextContinue() adds up mnCurrRecSize
    mnComplRecSize = mnCurrRecSize;
    mbHasComplRec = true;
    seek( nCurrPos );                       // restore position, seek() resets old mbValid state
}

bool BiffInputStream::ensureRawReadSize( sal_uInt16 nBytes )
{
    if( !mbEof && (nBytes > 0) )
    {
        while( !mbEof && (maRecBuffer.getRecLeft() == 0) ) jumpToNextContinue();
        mbEof = mbEof || (nBytes > maRecBuffer.getRecLeft());
        OSL_ENSURE( !mbEof, "BiffInputStream::ensureRawReadSize - record overread" );
    }
    return !mbEof;
}

sal_uInt16 BiffInputStream::getMaxRawReadSize( sal_Int32 nBytes ) const
{
    return getLimitedValue< sal_uInt16, sal_Int32 >( nBytes, 0, maRecBuffer.getRecLeft() );
}

void BiffInputStream::appendUnicodeArray( OUStringBuffer& orBuffer, sal_uInt16 nChars, bool b16Bit )
{
    orBuffer.ensureCapacity( orBuffer.getLength() + nChars );
    sal_uInt16 nChar;
    for( sal_uInt16 nCharIdx = 0; !mbEof && (nCharIdx < nChars); ++nCharIdx )
    {
        if( b16Bit ) readValue( nChar ); else nChar = readuInt8();
        orBuffer.append( static_cast< sal_Unicode >( (!mbNulChars && (nChar == 0)) ? '?' : nChar ) );
    }
}

void BiffInputStream::readUniStringHeader( bool& orb16Bit, sal_Int32& ornAddSize )
{
    sal_uInt8 nFlags = readuInt8();
    OSL_ENSURE( !getFlag( nFlags, BIFF_STRF_UNKNOWN ), "BiffInputStream::readUniStringHeader - unknown flags" );
    orb16Bit = getFlag( nFlags, BIFF_STRF_16BIT );
    sal_uInt16 nFontCount = getFlag( nFlags, BIFF_STRF_RICH ) ? readuInt16() : 0;
    sal_Int32 nPhoneticSize = getFlag( nFlags, BIFF_STRF_PHONETIC ) ? readInt32() : 0;
    ornAddSize = 4 * nFontCount + ::std::max< sal_Int32 >( 0, nPhoneticSize );
}

// ============================================================================

BiffInputStreamPos::BiffInputStreamPos( BiffInputStream& rStrm ) :
    mrStrm( rStrm ),
    mnRecHandle( rStrm.getRecHandle() ),
    mnRecPos( rStrm.tell() )
{
}

bool BiffInputStreamPos::restorePosition()
{
    bool bValidRec = mrStrm.startRecordByHandle( mnRecHandle );
    if( bValidRec )
        mrStrm.seek( mnRecPos );
    return bValidRec && !mrStrm.isEof();
}

// ============================================================================

BiffInputStreamPosGuard::BiffInputStreamPosGuard( BiffInputStream& rStrm ) :
    BiffInputStreamPos( rStrm )
{
}

BiffInputStreamPosGuard::~BiffInputStreamPosGuard()
{
    restorePosition();
}

// ============================================================================

} // namespace xls
} // namespace oox

