/******************************************************************************
 *
 * Project:  GDAL
 * Purpose:  Implementation of derived subdatasets
 * Author:   Julien Michel <julien dot michel at cnes dot fr>
 *
 ******************************************************************************
 * Copyright (c) 2016 Julien Michel <julien dot michel at cnes dot fr>
 *
 * 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 "../vrt/vrtdataset.h"
#include "gdal_pam.h"
#include "gdal_proxy.h"
#include "derivedlist.h"

CPL_CVSID("$Id: deriveddataset.cpp 7e07230bbff24eb333608de4dbd460b7312839d0 2017-12-11 19:08:47Z Even Rouault $")

class DerivedDataset : public VRTDataset
{
    public:
        DerivedDataset( int nXSize, int nYSize );
       ~DerivedDataset() {}

        static int Identify( GDALOpenInfo * );
        static GDALDataset *Open( GDALOpenInfo * );
};

DerivedDataset::DerivedDataset(int nXSize, int nYSize) :
    VRTDataset(nXSize, nYSize)
{
    poDriver = nullptr;
    SetWritable(FALSE);
}

int DerivedDataset::Identify(GDALOpenInfo * poOpenInfo)
{
    /* Try to open original dataset */
    CPLString filename(poOpenInfo->pszFilename);

    /* DERIVED_SUBDATASET should be first domain */
    const size_t dsds_pos = filename.find("DERIVED_SUBDATASET:");

    if (dsds_pos != 0)
    {
        /* Unable to Open in this case */
        return FALSE;
    }

    return TRUE;
}

GDALDataset * DerivedDataset::Open( GDALOpenInfo * poOpenInfo )
{
    /* Try to open original dataset */
    CPLString filename(poOpenInfo->pszFilename);

    /* DERIVED_SUBDATASET should be first domain */
    const size_t dsds_pos = filename.find("DERIVED_SUBDATASET:");
    const size_t nPrefixLen = strlen("DERIVED_SUBDATASET:");

    if (dsds_pos != 0)
    {
        /* Unable to Open in this case */
        return nullptr;
    }

    /* Next, we need to now which derived dataset to compute */
    const size_t alg_pos = filename.find(":",nPrefixLen+1);
    if (alg_pos == std::string::npos)
    {
        /* Unable to Open if we do not find the name of the derived dataset */
        return nullptr;
    }

    CPLString odDerivedName = filename.substr(nPrefixLen,alg_pos-nPrefixLen);

    CPLDebug("DerivedDataset::Open","Derived dataset requested: %s",odDerivedName.c_str());

    CPLString pixelFunctionName = "";
    bool datasetFound = false;

    unsigned int nbSupportedDerivedDS(0);
    GDALDataType type  = GDT_Float64;

    const DerivedDatasetDescription * poDDSDesc = GDALGetDerivedDatasetDescriptions(&nbSupportedDerivedDS);

    for(unsigned int derivedId = 0; derivedId<nbSupportedDerivedDS;++derivedId)
    {
        if(odDerivedName == poDDSDesc[derivedId].pszDatasetName)
        {
            datasetFound = true;
            pixelFunctionName = poDDSDesc[derivedId].pszPixelFunction;
            type = GDALGetDataTypeByName(poDDSDesc[derivedId].pszOutputPixelType);
        }
    }

    if(!datasetFound)
    {
        return nullptr;
    }

    CPLString odFilename = filename.substr(alg_pos+1,filename.size() - alg_pos);

    GDALDataset * poTmpDS = (GDALDataset*)GDALOpen(odFilename, GA_ReadOnly);

    if( poTmpDS == nullptr )
        return nullptr;

    int nbBands = poTmpDS->GetRasterCount();

    if(nbBands == 0)
    {
        GDALClose(poTmpDS);
        return nullptr;
    }

    int nRows = poTmpDS->GetRasterYSize();
    int nCols = poTmpDS->GetRasterXSize();

    DerivedDataset * poDS = new DerivedDataset(nCols,nRows);

    // Transfer metadata
    poDS->SetMetadata(poTmpDS->GetMetadata());

    // Transfer projection
    poDS->SetProjection(poTmpDS->GetProjectionRef());

    // Transfer geotransform
    double padfTransform[6];
    CPLErr transformOk = poTmpDS->GetGeoTransform(padfTransform);

    if(transformOk == CE_None)
    {
        poDS->SetGeoTransform(padfTransform);
    }

    // Transfer GCPs
    const char * gcpProjection = poTmpDS->GetGCPProjection();
    int nbGcps = poTmpDS->GetGCPCount();
    poDS->SetGCPs(nbGcps,poTmpDS->GetGCPs(),gcpProjection);

    // Map bands
    for(int nBand = 1; nBand <= nbBands; ++nBand)
    {
        VRTDerivedRasterBand *poBand =
            new VRTDerivedRasterBand(poDS,nBand,type,nCols,nRows);
        poDS->SetBand(nBand,poBand);

        poBand->SetPixelFunctionName(pixelFunctionName);
        poBand->SetSourceTransferType(poTmpDS->GetRasterBand(nBand)->GetRasterDataType());

        GDALProxyPoolDataset* proxyDS =
            new GDALProxyPoolDataset(
                odFilename,
                poDS->nRasterXSize,
                poDS->nRasterYSize,
                GA_ReadOnly,
                TRUE);
        for(int j=0;j<nbBands;++j)
        {
            int blockXSize, blockYSize;
            poTmpDS->GetRasterBand(nBand)->GetBlockSize(&blockXSize,&blockYSize);
            proxyDS->AddSrcBandDescription(poTmpDS->GetRasterBand(nBand)->GetRasterDataType(), blockXSize, blockYSize);
        }

        poBand->AddComplexSource(proxyDS->GetRasterBand(nBand),0,0,nCols,nRows,0,0,nCols,nRows);

        proxyDS->Dereference();
    }

    GDALClose(poTmpDS);

    // If dataset is a real file, initialize overview manager
    VSIStatBufL  sStat;
    if( VSIStatL( odFilename, &sStat ) == 0 )
    {
        CPLString path = CPLGetPath(odFilename);
        CPLString ovrFileName = "DERIVED_DATASET_"+odDerivedName+"_"+CPLGetFilename(odFilename);
        CPLString ovrFilePath = CPLFormFilename(path,ovrFileName,nullptr);

        poDS->oOvManager.Initialize( poDS, ovrFilePath );
    }

    return poDS;
}

void GDALRegister_Derived()
{
    if( GDALGetDriverByName( "DERIVED" ) != nullptr )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "DERIVED" );
#ifdef GDAL_DCAP_RASTER
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
#endif
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "Derived datasets using VRT pixel functions" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_derived.html" );
    poDriver->SetMetadataItem( GDAL_DMD_SUBDATASETS, "NO" );

    poDriver->pfnOpen = DerivedDataset::Open;
    poDriver->pfnIdentify = DerivedDataset::Identify;

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
