/******************************************************************************
 *
 * Project:  PDF driver
 * Purpose:  GDALDataset driver for PDF dataset.
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
 *
 ******************************************************************************
 * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "gdal_pdf.h"

#ifdef HAVE_POPPLER

#include "pdfio.h"

#include "cpl_vsi.h"

CPL_CVSID("$Id$")

/* Poppler 0.31.0 is the first one that needs to know the file size */
static vsi_l_offset VSIPDFFileStreamGetSize(VSILFILE* f)
{
    VSIFSeekL(f, 0, SEEK_END);
    vsi_l_offset nSize = VSIFTellL(f);
    VSIFSeekL(f, 0, SEEK_SET);
    return nSize;
}

/************************************************************************/
/*                         VSIPDFFileStream()                           */
/************************************************************************/

VSIPDFFileStream::VSIPDFFileStream(
    VSILFILE* fIn, const char* pszFilename,
    makeSubStream_object_type dictA
) :
#if POPPLER_MAJOR_VERSION >= 1 || POPPLER_MINOR_VERSION >= 58
    BaseStream(std::move(dictA), (Goffset)VSIPDFFileStreamGetSize(fIn)),
#else
    BaseStream(dictA, (setPos_offset_type)VSIPDFFileStreamGetSize(fIn)),
#endif
    poParent(nullptr),
    poFilename(new GooString(pszFilename)),
    f(fIn),
    nStart(0),
    bLimited(gFalse),
    nLength(0),
    nCurrentPos(VSI_L_OFFSET_MAX),
    bHasSavedPos(FALSE),
    nSavedPos(0),
    nPosInBuffer(-1),
    nBufferLength(-1)
{}

/************************************************************************/
/*                         VSIPDFFileStream()                           */
/************************************************************************/

VSIPDFFileStream::VSIPDFFileStream( VSIPDFFileStream* poParentIn,
                                    vsi_l_offset startA, GBool limitedA,
                                    vsi_l_offset lengthA,
                                    makeSubStream_object_type dictA) :
#if POPPLER_MAJOR_VERSION >= 1 || POPPLER_MINOR_VERSION >= 58
    BaseStream(std::move(dictA), (Goffset)lengthA),
#else
    BaseStream(dictA, (makeSubStream_offset_type)lengthA),
#endif
    poParent(poParentIn),
    poFilename(poParentIn->poFilename),
    f(poParentIn->f),
    nStart(startA),
    bLimited(limitedA),
    nLength(lengthA),
    nCurrentPos(VSI_L_OFFSET_MAX),
    bHasSavedPos(FALSE),
    nSavedPos(0),
    nPosInBuffer(-1),
    nBufferLength(-1)
{}

/************************************************************************/
/*                        ~VSIPDFFileStream()                           */
/************************************************************************/

VSIPDFFileStream::~VSIPDFFileStream()
{
    close();
    if (poParent == nullptr)
    {
        delete poFilename;
    }
}

/************************************************************************/
/*                                  copy()                              */
/************************************************************************/

#if POPPLER_MAJOR_VERSION >= 1 || POPPLER_MINOR_VERSION >= 58
BaseStream* VSIPDFFileStream::copy()
{
    return new VSIPDFFileStream(poParent, nStart, bLimited,
                                nLength, dict.copy());
}
#else
BaseStream* VSIPDFFileStream::copy()
{
    return new VSIPDFFileStream(poParent, nStart, bLimited,
                                nLength, &dict);
}
#endif

/************************************************************************/
/*                             makeSubStream()                          */
/************************************************************************/
Stream *VSIPDFFileStream::makeSubStream(makeSubStream_offset_type startA, GBool limitedA,
                                        makeSubStream_offset_type lengthA, makeSubStream_object_type dictA)
{
#if POPPLER_MAJOR_VERSION >= 1 || POPPLER_MINOR_VERSION >= 58
    return new VSIPDFFileStream(this,
                                startA, limitedA,
                                lengthA, std::move(dictA));
#else
    return new VSIPDFFileStream(this,
                                startA, limitedA,
                                lengthA, dictA);
#endif
}

/************************************************************************/
/*                                 getPos()                             */
/************************************************************************/

getPos_ret_type VSIPDFFileStream::getPos()
{
    return (getPos_ret_type) nCurrentPos;
}

/************************************************************************/
/*                                getStart()                            */
/************************************************************************/

getStart_ret_type VSIPDFFileStream::getStart()
{
    return (getStart_ret_type) nStart;
}

/************************************************************************/
/*                             getKind()                                */
/************************************************************************/

StreamKind VSIPDFFileStream::getKind()
#if POPPLER_MAJOR_VERSION >= 1 || POPPLER_MINOR_VERSION >= 83
                                        const
#endif
{
    return strFile;
}

/************************************************************************/
/*                           getFileName()                               */
/************************************************************************/

GooString *VSIPDFFileStream::getFileName()
{
    return poFilename;
}

/************************************************************************/
/*                             FillBuffer()                             */
/************************************************************************/

int VSIPDFFileStream::FillBuffer()
{
    if (nBufferLength == 0)
        return FALSE;
    if (nBufferLength != -1 && nBufferLength < BUFFER_SIZE)
        return FALSE;

    nPosInBuffer = 0;
    int nToRead;
    if (!bLimited)
        nToRead = BUFFER_SIZE;
    else if (nCurrentPos + BUFFER_SIZE > nStart + nLength)
        nToRead = (int)(nStart + nLength - nCurrentPos);
    else
        nToRead = BUFFER_SIZE;
    if( nToRead < 0 )
        return FALSE;
    nBufferLength = (int) VSIFReadL(abyBuffer, 1, nToRead, f);
    if (nBufferLength == 0)
        return FALSE;

    // Since we now report a non-zero length (as BaseStream::length member),
    // PDFDoc::getPage() can go to the linearized mode if the file is linearized,
    // and thus create a pageCache. If so, in PDFDoc::~PDFDoc(),
    // if pageCache is not null, it would try to access the stream (str) through
    // getPageCount(), but we have just freed and nullify str before in PDFFreeDoc().
    // So make as if the file is not linearized to avoid those issues...
    // All this is due to our attempt of avoiding cross-heap issues with allocation
    // and liberation of VSIPDFFileStream as PDFDoc::str member.
    if( nCurrentPos == 0 || nCurrentPos == VSI_L_OFFSET_MAX )
    {
        for(int i=0;i<nBufferLength-(int)strlen("/Linearized ");i++)
        {
            if( memcmp(abyBuffer + i, "/Linearized ",
                       strlen("/Linearized ")) == 0 )
            {
                bFoundLinearizedHint = true;
                memcpy(abyBuffer + i, "/XXXXXXXXXX ", strlen("/Linearized "));
                break;
            }
        }
    }

    return TRUE;
}

/************************************************************************/
/*                                getChar()                             */
/************************************************************************/

/* The unoptimized version performs a bit less since we must go through */
/* the whole virtual I/O chain for each character reading. We save a few */
/* percent with this extra internal caching */

int VSIPDFFileStream::getChar()
{
#ifdef unoptimized_version
    GByte chRead;
    if (bLimited && nCurrentPos >= nStart + nLength)
        return EOF;
    if (VSIFReadL(&chRead, 1, 1, f) == 0)
        return EOF;
#else
    if (nPosInBuffer == nBufferLength)
    {
        if (!FillBuffer() || nPosInBuffer >= nBufferLength)
            return EOF;
    }

    GByte chRead = abyBuffer[nPosInBuffer];
    nPosInBuffer ++;
#endif
    nCurrentPos ++;
    return chRead;
}

/************************************************************************/
/*                       getUnfilteredChar()                            */
/************************************************************************/

int VSIPDFFileStream::getUnfilteredChar ()
{
    return getChar();
}

/************************************************************************/
/*                               lookChar()                             */
/************************************************************************/

int VSIPDFFileStream::lookChar()
{
#ifdef unoptimized_version
    int nPosBefore = nCurrentPos;
    int chRead = getChar();
    if (chRead == EOF)
        return EOF;
    VSIFSeekL(f, nCurrentPos = nPosBefore, SEEK_SET);
    return chRead;
#else
    int chRead = getChar();
    if (chRead == EOF)
        return EOF;
    nPosInBuffer --;
    nCurrentPos --;
    return chRead;
#endif
}

/************************************************************************/
/*                                reset()                               */
/************************************************************************/

void VSIPDFFileStream::reset()
{
    nSavedPos = VSIFTellL(f);
    bHasSavedPos = TRUE;
    VSIFSeekL(f, nCurrentPos = nStart, SEEK_SET);
    nPosInBuffer = -1;
    nBufferLength = -1;
}

/************************************************************************/
/*                         unfilteredReset()                            */
/************************************************************************/

void VSIPDFFileStream::unfilteredReset ()
{
    reset();
}

/************************************************************************/
/*                                close()                               */
/************************************************************************/

void VSIPDFFileStream::close()
{
    if (bHasSavedPos)
        VSIFSeekL(f, nCurrentPos = nSavedPos, SEEK_SET);
    bHasSavedPos = FALSE;
    nSavedPos = 0;
}

/************************************************************************/
/*                               setPos()                               */
/************************************************************************/

void VSIPDFFileStream::setPos(setPos_offset_type pos, int dir)
{
    if (dir >= 0)
    {
        VSIFSeekL(f, nCurrentPos = pos, SEEK_SET);
    }
    else
    {
        if (bLimited == gFalse)
        {
            VSIFSeekL(f, 0, SEEK_END);
        }
        else
        {
            VSIFSeekL(f, nStart + nLength, SEEK_SET);
        }
        vsi_l_offset size = VSIFTellL(f);
        vsi_l_offset newpos = (vsi_l_offset) pos;
        if (newpos > size)
            newpos = size;
        VSIFSeekL(f, nCurrentPos = size - newpos, SEEK_SET);
    }
    nPosInBuffer = -1;
    nBufferLength = -1;
}

/************************************************************************/
/*                            moveStart()                               */
/************************************************************************/

void VSIPDFFileStream::moveStart(moveStart_delta_type delta)
{
    nStart += delta;
    VSIFSeekL(f, nCurrentPos = nStart, SEEK_SET);
    nPosInBuffer = -1;
    nBufferLength = -1;
}

/************************************************************************/
/*                          hasGetChars()                               */
/************************************************************************/

GBool VSIPDFFileStream::hasGetChars()
{
    return true;
}

/************************************************************************/
/*                            getChars()                                */
/************************************************************************/

int VSIPDFFileStream::getChars(int nChars, Guchar *buffer)
{
    int nRead = 0;
    while (nRead < nChars)
    {
        int nToRead = nChars - nRead;
        if (nPosInBuffer == nBufferLength)
        {
            if (!bLimited && nToRead > BUFFER_SIZE)
            {
                int nJustRead = (int) VSIFReadL(buffer + nRead, 1, nToRead, f);
                nPosInBuffer = -1;
                nBufferLength = -1;
                nCurrentPos += nJustRead;
                nRead += nJustRead;
                break;
            }
            else if (!FillBuffer() || nPosInBuffer >= nBufferLength)
                break;
        }
        if( nToRead > nBufferLength - nPosInBuffer )
            nToRead = nBufferLength - nPosInBuffer;

        memcpy( buffer + nRead, abyBuffer + nPosInBuffer, nToRead );
        nPosInBuffer += nToRead;
        nCurrentPos += nToRead;
        nRead += nToRead;
    }
    return nRead;
}

#endif
