/******************************************************************************
 * $Id: gdalopeninfo.cpp 33758 2016-03-21 09:06:22Z rouault $
 *
 * Project:  GDAL Core
 * Purpose:  Implementation of GDALOpenInfo class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 **********************************************************************
 * Copyright (c) 2002, Frank Warmerdam
 * Copyright (c) 2008-2012, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * 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_priv.h"  // Must be included first for mingw VSIStatBufL.
#include "cpl_conv.h"
#include "cpl_vsi.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <vector>

CPL_CVSID("$Id: gdalopeninfo.cpp 33758 2016-03-21 09:06:22Z rouault $");

using std::vector;

/************************************************************************/
/* ==================================================================== */
/*                             GDALOpenInfo                             */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                            GDALOpenInfo()                            */
/************************************************************************/

GDALOpenInfo::GDALOpenInfo( const char * pszFilenameIn, int nOpenFlagsIn,
                            char **papszSiblingsIn ) :
    bHasGotSiblingFiles(false),
    papszSiblingFiles(NULL),
    nHeaderBytesTried(0),
    pszFilename(CPLStrdup(pszFilenameIn)),
    papszOpenOptions(NULL),
    eAccess(nOpenFlagsIn & GDAL_OF_UPDATE ? GA_Update : GA_ReadOnly),
    nOpenFlags(nOpenFlagsIn),
    bStatOK(FALSE),
    bIsDirectory(FALSE),
    fpL(NULL),
    nHeaderBytes(0),
    pabyHeader(NULL)
{

/* -------------------------------------------------------------------- */
/*      Ensure that C: is treated as C:\ so we can stat it on           */
/*      Windows.  Similar to what is done in CPLStat().                 */
/* -------------------------------------------------------------------- */
#ifdef WIN32
    if( strlen(pszFilenameIn) == 2 && pszFilenameIn[1] == ':' )
    {
        char    szAltPath[10];

        strcpy( szAltPath, pszFilenameIn );
        strcat( szAltPath, "\\" );
        CPLFree( pszFilename );
        pszFilename = CPLStrdup( szAltPath );
    }
#endif  // WIN32

/* -------------------------------------------------------------------- */
/*      Collect information about the file.                             */
/* -------------------------------------------------------------------- */

#ifdef HAVE_READLINK
    bool bHasRetried = false;

retry:  // TODO(schwehr): Stop using goto.

#endif  // HAVE_READLINK

#ifdef __FreeBSD__
    /* FreeBSD 8 oddity: fopen(a_directory, "rb") returns non NULL */
    bool bPotentialDirectory = (eAccess == GA_ReadOnly);
#else
    bool bPotentialDirectory = false;
#endif  // __FreeBDS__

    /* Check if the filename might be a directory of a special virtual file system */
    if( STARTS_WITH(pszFilename, "/vsizip/") ||
        STARTS_WITH(pszFilename, "/vsitar/") )
    {
        const char* pszExt = CPLGetExtension(pszFilename);
        if( EQUAL(pszExt, "zip") || EQUAL(pszExt, "tar") || EQUAL(pszExt, "gz")
#ifdef DEBUG
            // For AFL, so that .cur_input is detected as the archive filename.
            || EQUAL( CPLGetFilename(pszFilename), ".cur_input" )
#endif  // DEBUG
          )
        {
            bPotentialDirectory = true;
        }
    }
    else if( STARTS_WITH(pszFilename, "/vsicurl/") )
    {
        bPotentialDirectory = true;
    }

    if( bPotentialDirectory )
    {
        int nStatFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
        if(nOpenFlagsIn & GDAL_OF_VERBOSE_ERROR)
            nStatFlags |= VSI_STAT_SET_ERROR_FLAG;

        // For those special files, opening them with VSIFOpenL() might result
        // in content, even if they should be considered as directories, so
        // use stat.
        VSIStatBufL sStat;

        if(VSIStatExL( pszFilename, &sStat, nStatFlags) == 0) {
            bStatOK = TRUE;
            if( VSI_ISDIR( sStat.st_mode ) )
                bIsDirectory = TRUE;
        }
    }

    if( !bIsDirectory ) {
        fpL = VSIFOpenExL( pszFilename, (eAccess == GA_Update) ? "r+b" : "rb", (nOpenFlagsIn & GDAL_OF_VERBOSE_ERROR) > 0);
    }
    if( fpL != NULL )
    {
        bStatOK = TRUE;
        const int nBufSize = 1025;
        pabyHeader = static_cast<GByte *>( CPLCalloc(nBufSize, 1) );
        nHeaderBytesTried = nBufSize - 1;
        nHeaderBytes = static_cast<int>(
            VSIFReadL( pabyHeader, 1, nHeaderBytesTried, fpL ) );
        VSIRewindL( fpL );

        /* If we cannot read anything, check if it is not a directory instead */
        VSIStatBufL sStat;
        if( nHeaderBytes == 0 &&
            VSIStatExL( pszFilename, &sStat,
                        VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG ) == 0 &&
            VSI_ISDIR( sStat.st_mode ) )
        {
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
            fpL = NULL;
            CPLFree(pabyHeader);
            pabyHeader = NULL;
            bIsDirectory = TRUE;
        }
    }
    else if( !bStatOK )
    {
        VSIStatBufL sStat;
        if( !bPotentialDirectory && VSIStatExL( pszFilename, &sStat,
                        VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG ) == 0 )
        {
            bStatOK = TRUE;
            if( VSI_ISDIR( sStat.st_mode ) )
                bIsDirectory = TRUE;
        }
#ifdef HAVE_READLINK
        else if ( !bHasRetried && !STARTS_WITH(pszFilename, "/vsi") )
        {
            // If someone creates a file with "ln -sf
            // /vsicurl/http://download.osgeo.org/gdal/data/gtiff/utm.tif
            // my_remote_utm.tif" we will be able to open it by passing
            // my_remote_utm.tif.  This helps a lot for GDAL based readers that
            // only provide file explorers to open datasets.
            const int nBufSize = 2048;
            vector<char> oFilename(nBufSize);
            char *szPointerFilename = &oFilename[0];
            int nBytes = static_cast<int>(
                readlink( pszFilename, szPointerFilename, nBufSize ) );
            if (nBytes != -1)
            {
                szPointerFilename[MIN(nBytes, nBufSize - 1)] = 0;
                CPLFree(pszFilename);
                pszFilename = CPLStrdup(szPointerFilename);
                papszSiblingsIn = NULL;
                bHasRetried = true;
                goto retry;
            }
        }
#endif  // HAVE_READLINK
    }

/* -------------------------------------------------------------------- */
/*      Capture sibling list either from passed in values, or by        */
/*      scanning for them only if requested through GetSiblingFiles().  */
/* -------------------------------------------------------------------- */
    if( papszSiblingsIn != NULL )
    {
        papszSiblingFiles = CSLDuplicate( papszSiblingsIn );
        bHasGotSiblingFiles = true;
    }
    else if( bStatOK && !bIsDirectory )
    {
        const char* pszOptionVal =
            CPLGetConfigOption( "GDAL_DISABLE_READDIR_ON_OPEN", "NO" );
        if (EQUAL(pszOptionVal, "EMPTY_DIR"))
        {
            papszSiblingFiles =
                CSLAddString( NULL, CPLGetFilename(pszFilename) );
            bHasGotSiblingFiles = true;
        }
        else if( CPLTestBool(pszOptionVal) )
        {
            /* skip reading the directory */
            papszSiblingFiles = NULL;
            bHasGotSiblingFiles = true;
        }
        else
        {
            /* will be lazy loaded */
            papszSiblingFiles = NULL;
            bHasGotSiblingFiles = false;
        }
    }
    else
    {
        papszSiblingFiles = NULL;
        bHasGotSiblingFiles = true;
    }
}

/************************************************************************/
/*                           ~GDALOpenInfo()                            */
/************************************************************************/

GDALOpenInfo::~GDALOpenInfo()

{
    VSIFree( pabyHeader );
    CPLFree( pszFilename );

    if( fpL != NULL )
        CPL_IGNORE_RET_VAL(VSIFCloseL( fpL ));
    CSLDestroy( papszSiblingFiles );
}

/************************************************************************/
/*                         GetSiblingFiles()                            */
/************************************************************************/

char** GDALOpenInfo::GetSiblingFiles()
{
    if( bHasGotSiblingFiles )
        return papszSiblingFiles;
    bHasGotSiblingFiles = true;

    CPLString osDir = CPLGetDirname( pszFilename );
    const int nMaxFiles =
        atoi(CPLGetConfigOption("GDAL_READDIR_LIMIT_ON_OPEN", "1000"));
    papszSiblingFiles = VSIReadDirEx( osDir, nMaxFiles );
    if( nMaxFiles > 0 && CSLCount(papszSiblingFiles) > nMaxFiles )
    {
        CPLDebug("GDAL", "GDAL_READDIR_LIMIT_ON_OPEN reached on %s",
                 osDir.c_str());
        CSLDestroy(papszSiblingFiles);
        papszSiblingFiles = NULL;
    }

    /* Small optimization to avoid unnecessary stat'ing from PAux or ENVI */
    /* drivers. The MBTiles driver needs no companion file. */
    if( papszSiblingFiles == NULL &&
        STARTS_WITH(pszFilename, "/vsicurl/") &&
        EQUAL(CPLGetExtension( pszFilename ),"mbtiles") )
    {
        papszSiblingFiles = CSLAddString( NULL, CPLGetFilename(pszFilename) );
    }

    return papszSiblingFiles;
}

/************************************************************************/
/*                         StealSiblingFiles()                          */
/*                                                                      */
/*      Same as GetSiblingFiles() except that the list is stealed       */
/*      (ie ownership transferred to the caller) and the associated     */
/*      member variable is set to NULL.                                 */
/************************************************************************/

char** GDALOpenInfo::StealSiblingFiles()
{
    char** papszRet = GetSiblingFiles();
    papszSiblingFiles = NULL;
    return papszRet;
}

/************************************************************************/
/*                        AreSiblingFilesLoaded()                       */
/************************************************************************/

bool GDALOpenInfo::AreSiblingFilesLoaded() const
{
    return bHasGotSiblingFiles;
}

/************************************************************************/
/*                           TryToIngest()                              */
/************************************************************************/

int GDALOpenInfo::TryToIngest(int nBytes)
{
    if( fpL == NULL )
        return FALSE;
    if( nHeaderBytes < nHeaderBytesTried )
        return TRUE;
    pabyHeader = static_cast<GByte *>( CPLRealloc(pabyHeader, nBytes + 1) );
    memset(pabyHeader, 0, nBytes + 1);
    VSIRewindL(fpL);
    nHeaderBytesTried = nBytes;
    nHeaderBytes = static_cast<int>( VSIFReadL(pabyHeader, 1, nBytes, fpL) );
    VSIRewindL(fpL);

    return TRUE;
}
