/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include <oox/ole/axbinaryreader.hxx>

#include <oox/ole/axfontdata.hxx>
#include <oox/ole/olehelper.hxx>

#include <osl/diagnose.h>

namespace oox::ole {

namespace {

const sal_uInt32 AX_STRING_SIZEMASK         = 0x7FFFFFFF;
const sal_uInt32 AX_STRING_COMPRESSED       = 0x80000000;

} // namespace

AxAlignedInputStream::AxAlignedInputStream( BinaryInputStream& rInStrm ) :
    BinaryStreamBase( false ),
    mpInStrm( &rInStrm ),
    mnStrmPos( 0 ),
    mnStrmSize( rInStrm.getRemaining() )
{
    mbEof = mbEof || rInStrm.isEof();
}

sal_Int64 AxAlignedInputStream::size() const
{
    return mpInStrm ? mnStrmSize : -1;
}

sal_Int64 AxAlignedInputStream::tell() const
{
    return mpInStrm ? mnStrmPos : -1;
}

void AxAlignedInputStream::seek( sal_Int64 nPos )
{
    mbEof = mbEof || (nPos < mnStrmPos);
    if( !mbEof )
        skip( static_cast< sal_Int32 >( nPos - mnStrmPos ) );
}

void AxAlignedInputStream::close()
{
    mpInStrm = nullptr;
    mbEof = true;
}

sal_Int32 AxAlignedInputStream::readData( StreamDataSequence& orData, sal_Int32 nBytes, size_t nAtomSize )
{
    sal_Int32 nReadSize = 0;
    if( !mbEof )
    {
        nReadSize = mpInStrm->readData( orData, nBytes, nAtomSize );
        mnStrmPos += nReadSize;
        mbEof = mpInStrm->isEof();
    }
    return nReadSize;
}

sal_Int32 AxAlignedInputStream::readMemory( void* opMem, sal_Int32 nBytes, size_t nAtomSize )
{
    sal_Int32 nReadSize = 0;
    if( !mbEof )
    {
        nReadSize = mpInStrm->readMemory( opMem, nBytes, nAtomSize );
        mnStrmPos += nReadSize;
        mbEof = mpInStrm->isEof();
    }
    return nReadSize;
}

void AxAlignedInputStream::skip( sal_Int32 nBytes, size_t nAtomSize )
{
    if( !mbEof )
    {
        mpInStrm->skip( nBytes, nAtomSize );
        mnStrmPos += nBytes;
        mbEof = mpInStrm->isEof();
    }
}

void AxAlignedInputStream::align( size_t nSize )
{
    skip( static_cast< sal_Int32 >( (nSize - (mnStrmPos % nSize)) % nSize ) );
}

namespace {

bool lclReadString( AxAlignedInputStream& rInStrm, OUString& rValue, sal_uInt32 nSize, bool bArrayString )
{
    bool bCompressed = getFlag( nSize, AX_STRING_COMPRESSED );
    sal_uInt32 nBufSize = nSize & AX_STRING_SIZEMASK;
    // Unicode: simple strings store byte count, array strings store char count
    sal_Int32 nChars = static_cast< sal_Int32 >( nBufSize / ((bCompressed || bArrayString) ? 1 : 2) );
    bool bValidChars = nChars <= 65536;
    OSL_ENSURE( bValidChars, "lclReadString - string too long" );
    sal_Int64 nEndPos = rInStrm.tell() + nChars * (bCompressed ? 1 : 2);
    nChars = ::std::min< sal_Int32 >( nChars, 65536 );
    rValue = rInStrm.readCompressedUnicodeArray( nChars, bCompressed );
    rInStrm.seek( nEndPos );
    return bValidChars;
}

} // namespace

AxBinaryPropertyReader::ComplexProperty::~ComplexProperty()
{
}

bool AxBinaryPropertyReader::PairProperty::readProperty( AxAlignedInputStream& rInStrm )
{
    mrPairData.first = rInStrm.readInt32();
    mrPairData.second = rInStrm.readInt32();
    return true;
}

bool AxBinaryPropertyReader::StringProperty::readProperty( AxAlignedInputStream& rInStrm )
{
    return lclReadString( rInStrm, mrValue, mnSize, false );
}

bool AxBinaryPropertyReader::ArrayStringProperty::readProperty( AxAlignedInputStream& rInStrm )
{
    sal_Int64 nEndPos = rInStrm.tell() + mnSize;
    while( rInStrm.tell() < nEndPos )
    {
        OUString aString;
        if( !lclReadString( rInStrm, aString, rInStrm.readuInt32(), true ) )
            return false;
        mrArray.push_back( aString );
        // every array string is aligned on 4 byte boundaries
        rInStrm.align( 4 );
    }
    return true;
}

bool AxBinaryPropertyReader::GuidProperty::readProperty( AxAlignedInputStream& rInStrm )
{
    mrGuid = OleHelper::importGuid( rInStrm );
    return true;
}

bool AxBinaryPropertyReader::FontProperty::readProperty( AxAlignedInputStream& rInStrm )
{
    return mrFontData.importGuidAndFont( rInStrm );
}

bool AxBinaryPropertyReader::PictureProperty::readProperty( AxAlignedInputStream& rInStrm )
{
    return OleHelper::importStdPic( mrPicData, rInStrm );
}

AxBinaryPropertyReader::AxBinaryPropertyReader( BinaryInputStream& rInStrm, bool b64BitPropFlags ) :
    maInStrm( rInStrm ),
    mbValid( true )
{
    // version and size of property block
    maInStrm.skip( 2 );
    sal_uInt16 nBlockSize = maInStrm.readuInt16();
    mnPropsEnd = maInStrm.tell() + nBlockSize;
    // flagfield containing existing properties
    if( b64BitPropFlags )
        mnPropFlags = maInStrm.readInt64();
    else
        mnPropFlags = maInStrm.readuInt32();
    mnNextProp = 1;
}

void AxBinaryPropertyReader::readBoolProperty( bool& orbValue, bool bReverse )
{
    // there is no data, the boolean value is equivalent to the property flag itself
    orbValue = startNextProperty() != bReverse;
}

void AxBinaryPropertyReader::readPairProperty( AxPairData& orPairData )
{
    if( startNextProperty() )
        maLargeProps.push_back( ComplexPropVector::value_type( std::make_shared<PairProperty>( orPairData ) ) );
}

void AxBinaryPropertyReader::readStringProperty( OUString& orValue )
{
    if( startNextProperty() )
    {
        sal_uInt32 nSize = maInStrm.readAligned< sal_uInt32 >();
        maLargeProps.push_back( ComplexPropVector::value_type( std::make_shared<StringProperty>( orValue, nSize ) ) );
    }
}

void AxBinaryPropertyReader::readArrayStringProperty( std::vector<OUString>& orValue )
{
    if( startNextProperty() )
    {
        sal_uInt32 nSize = maInStrm.readAligned< sal_uInt32 >();
        maLargeProps.push_back( ComplexPropVector::value_type( std::make_shared<ArrayStringProperty>( orValue, nSize ) ) );
    }
}

void AxBinaryPropertyReader::readGuidProperty( OUString& orGuid )
{
    if( startNextProperty() )
        maLargeProps.push_back( ComplexPropVector::value_type( std::make_shared<GuidProperty>( orGuid ) ) );
}

void AxBinaryPropertyReader::readFontProperty( AxFontData& orFontData )
{
    if( startNextProperty() )
    {
        sal_Int16 nData = maInStrm.readAligned< sal_Int16 >();
        if( ensureValid( nData == -1 ) )
            maStreamProps.push_back( ComplexPropVector::value_type( std::make_shared<FontProperty>( orFontData ) ) );
    }
}

void AxBinaryPropertyReader::readPictureProperty( StreamDataSequence& orPicData )
{
    if( startNextProperty() )
    {
        sal_Int16 nData = maInStrm.readAligned< sal_Int16 >();
        if( ensureValid( nData == -1 ) )
            maStreamProps.push_back( ComplexPropVector::value_type( std::make_shared<PictureProperty>( orPicData ) ) );
    }
}

bool AxBinaryPropertyReader::finalizeImport()
{
    // read large properties
    maInStrm.align( 4 );
    if( ensureValid( mnPropFlags == 0 ) )
    {
        for (auto const& largeProp : maLargeProps)
        {
            if (!ensureValid())
                break;
            ensureValid( largeProp->readProperty( maInStrm ) );
            maInStrm.align( 4 );
        }
    }
    maInStrm.seek( mnPropsEnd );

    // read stream properties (no stream alignment between properties!)
    if( ensureValid() )
    {
        for (auto const& streamProp : maStreamProps)
        {
            if (!ensureValid())
                break;
            ensureValid( streamProp->readProperty( maInStrm ) );
        }
    }

    return mbValid;
}

bool AxBinaryPropertyReader::ensureValid( bool bCondition )
{
    mbValid = mbValid && bCondition && !maInStrm.isEof();
    return mbValid;
}

bool AxBinaryPropertyReader::startNextProperty()
{
    bool bHasProp = getFlag( mnPropFlags, mnNextProp );
    setFlag( mnPropFlags, mnNextProp, false );
    mnNextProp <<= 1;
    return ensureValid() && bHasProp;
}

} // namespace oox::ole

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
