/******************************************************************************
 *
 * Project:  GDAL Core
 * Purpose:  Read metadata from Pleiades imagery.
 * Author:   Alexander Lisovenko
 * Author:   Dmitry Baryshnikov, polimax@mail.ru
 *
 ******************************************************************************
 * Copyright (c) 2014-2015 NextGIS <info@nextgis.ru>
 *
 * 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 "cpl_port.h"
#include "reader_pleiades.h"

#include <cstddef>
#include <cstring>
#include <ctime>

#include <string>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_minixml.h"
#include "cpl_string.h"

CPL_CVSID("$Id: reader_pleiades.cpp 36682 2016-12-04 20:34:45Z rouault $");

/**
 * GDALMDReaderPleiades()
 */
GDALMDReaderPleiades::GDALMDReaderPleiades(const char *pszPath,
                                        char **papszSiblingFiles) :
    GDALMDReaderBase(pszPath, papszSiblingFiles),
    m_osBaseFilename( pszPath ),
    m_osIMDSourceFilename( CPLString() ),
    m_osRPBSourceFilename( CPLString() )
{
    const char* pszBaseName = CPLGetBasename(pszPath);
    size_t nBaseNameLen = strlen(pszBaseName);
    if( nBaseNameLen < 4 || nBaseNameLen > 511 )
        return;

    const char* pszDirName = CPLGetDirname(pszPath);

    const char* pszIMDSourceFilename = CPLFormFilename( pszDirName,
                                CPLSPrintf("DIM_%s", pszBaseName + 4), "XML" );
    const char* pszRPBSourceFilename = CPLFormFilename( pszDirName,
                                CPLSPrintf("RPC_%s", pszBaseName + 4), "XML" );

    // find last underline
    char sBaseName[512];
    size_t nLastUnderline = 0;
    for(size_t i = 4; i < nBaseNameLen; i++)
    {
        sBaseName[i - 4] = pszBaseName[i];
        if(pszBaseName[i] == '_')
            nLastUnderline = i - 4U;
    }

    sBaseName[nLastUnderline] = 0;

    if (CPLCheckForFile((char*)pszIMDSourceFilename, papszSiblingFiles))
    {
        m_osIMDSourceFilename = pszIMDSourceFilename;
    }
    else
    {
        pszIMDSourceFilename = CPLFormFilename( pszDirName, CPLSPrintf("DIM_%s",
                                                            sBaseName), "XML" );
        if (CPLCheckForFile((char*)pszIMDSourceFilename, papszSiblingFiles))
        {
            m_osIMDSourceFilename = pszIMDSourceFilename;
        }
    }

    if (CPLCheckForFile((char*)pszRPBSourceFilename, papszSiblingFiles))
    {
        m_osRPBSourceFilename = pszRPBSourceFilename;
    }
    else
    {
        pszRPBSourceFilename = CPLFormFilename( pszDirName, CPLSPrintf("RPC_%s",
                                                            sBaseName), "XML" );
        if (CPLCheckForFile((char*)pszRPBSourceFilename, papszSiblingFiles))
        {
            m_osRPBSourceFilename = pszRPBSourceFilename;
        }
    }

    if( !m_osIMDSourceFilename.empty() )
        CPLDebug( "MDReaderPleiades", "IMD Filename: %s",
                  m_osIMDSourceFilename.c_str() );
    if( !m_osRPBSourceFilename.empty() )
        CPLDebug( "MDReaderPleiades", "RPB Filename: %s",
                  m_osRPBSourceFilename.c_str() );
}

GDALMDReaderPleiades::GDALMDReaderPleiades() : GDALMDReaderBase(NULL, NULL)
{
}

GDALMDReaderPleiades* GDALMDReaderPleiades::CreateReaderForRPC(const char* pszRPCSourceFilename)
{
    GDALMDReaderPleiades* poReader = new GDALMDReaderPleiades();
    poReader->m_osRPBSourceFilename = pszRPCSourceFilename;
    return poReader;
}

/**
 * ~GDALMDReaderPleiades()
 */
GDALMDReaderPleiades::~GDALMDReaderPleiades()
{
}

/**
 * HasRequiredFiles()
 */
bool GDALMDReaderPleiades::HasRequiredFiles() const
{
    if (!m_osIMDSourceFilename.empty())
        return true;
    if (!m_osRPBSourceFilename.empty())
        return true;

    return false;
}

/**
 * GetMetadataFiles()
 */
char** GDALMDReaderPleiades::GetMetadataFiles() const
{
    char **papszFileList = NULL;
    if(!m_osIMDSourceFilename.empty())
        papszFileList= CSLAddString( papszFileList, m_osIMDSourceFilename );
    if(!m_osRPBSourceFilename.empty())
        papszFileList = CSLAddString( papszFileList, m_osRPBSourceFilename );

    return papszFileList;
}

/**
 * LoadMetadata()
 */
void GDALMDReaderPleiades::LoadMetadata()
{
    if(m_bIsMetadataLoad)
        return;

    if (!m_osIMDSourceFilename.empty())
    {
        CPLXMLNode* psNode = CPLParseXMLFile(m_osIMDSourceFilename);

        if(psNode != NULL)
        {
            CPLXMLNode* psisdNode = CPLSearchXMLNode(psNode, "=Dimap_Document");

            if(psisdNode != NULL)
            {
                m_papszIMDMD = ReadXMLToList(psisdNode->psChild, m_papszIMDMD);
            }
            CPLDestroyXMLNode(psNode);
        }
    }

    if(!m_osRPBSourceFilename.empty())
    {
        m_papszRPCMD = LoadRPCXmlFile( );
    }

    m_papszDEFAULTMD = CSLAddNameValue(m_papszDEFAULTMD, MD_NAME_MDTYPE, "DIMAP");

    m_bIsMetadataLoad = true;

    if(NULL == m_papszIMDMD)
    {
        return;
    }

    //extract imagery metadata
    int nCounter = -1;
    const char* pszSatId1 = CSLFetchNameValue(m_papszIMDMD,
                  "Dataset_Sources.Source_Identification.Strip_Source.MISSION");
    if(NULL == pszSatId1)
    {
        nCounter = 1;
        for(int i = 0; i < 5; i++)
        {
            pszSatId1 = CSLFetchNameValue(m_papszIMDMD,
            CPLSPrintf("Dataset_Sources.Source_Identification_%d.Strip_Source.MISSION",
                       nCounter));
            if(NULL != pszSatId1)
                break;
            nCounter++;
        }
    }

    const char* pszSatId2;
    if(nCounter == -1)
        pszSatId2 = CSLFetchNameValue(m_papszIMDMD,
            "Dataset_Sources.Source_Identification.Strip_Source.MISSION_INDEX");
    else
        pszSatId2 = CSLFetchNameValue(m_papszIMDMD, CPLSPrintf(
            "Dataset_Sources.Source_Identification_%d.Strip_Source.MISSION_INDEX",
            nCounter));

    if(NULL != pszSatId1 && NULL != pszSatId2)
    {
        m_papszIMAGERYMD = CSLAddNameValue(m_papszIMAGERYMD,
                           MD_NAME_SATELLITE, CPLSPrintf( "%s %s",
                           CPLStripQuotes(pszSatId1).c_str(),
                           CPLStripQuotes(pszSatId2).c_str()));
    }
    else if(NULL != pszSatId1 && NULL == pszSatId2)
    {
        m_papszIMAGERYMD = CSLAddNameValue(m_papszIMAGERYMD,
                                MD_NAME_SATELLITE, CPLStripQuotes(pszSatId1));
    }
    else if(NULL == pszSatId1 && NULL != pszSatId2)
    {
        m_papszIMAGERYMD = CSLAddNameValue(m_papszIMAGERYMD,
                                MD_NAME_SATELLITE, CPLStripQuotes(pszSatId2));
    }

    const char* pszDate;
    if(nCounter == -1)
        pszDate = CSLFetchNameValue(m_papszIMDMD,
             "Dataset_Sources.Source_Identification.Strip_Source.IMAGING_DATE");
    else
        pszDate = CSLFetchNameValue(m_papszIMDMD, CPLSPrintf(
             "Dataset_Sources.Source_Identification_%d.Strip_Source.IMAGING_DATE",
             nCounter));

    if(NULL != pszDate)
    {
        const char* pszTime;
        if(nCounter == -1)
            pszTime = CSLFetchNameValue(m_papszIMDMD,
             "Dataset_Sources.Source_Identification.Strip_Source.IMAGING_TIME");
        else
            pszTime = CSLFetchNameValue(m_papszIMDMD, CPLSPrintf(
             "Dataset_Sources.Source_Identification_%d.Strip_Source.IMAGING_TIME",
             nCounter));

        if(NULL == pszTime)
            pszTime = "00:00:00.0Z";

        char buffer[80];
        time_t timeMid = GetAcquisitionTimeFromString(CPLSPrintf( "%sT%s",
                                                     pszDate, pszTime));
        strftime (buffer, 80, MD_DATETIMEFORMAT, localtime(&timeMid));
        m_papszIMAGERYMD = CSLAddNameValue(m_papszIMAGERYMD,
                                           MD_NAME_ACQDATETIME, buffer);
    }

    m_papszIMAGERYMD = CSLAddNameValue(m_papszIMAGERYMD, MD_NAME_CLOUDCOVER,
                                       MD_CLOUDCOVER_NA);
}

/**
 * LoadRPCXmlFile()
 */

static const char * const apszRPBMap[] = {
    RPC_LINE_OFF,   "RFM_Validity.LINE_OFF", // do not change order !
    RPC_SAMP_OFF,   "RFM_Validity.SAMP_OFF", // do not change order !
    RPC_LAT_OFF,    "RFM_Validity.LAT_OFF",
    RPC_LONG_OFF,   "RFM_Validity.LONG_OFF",
    RPC_HEIGHT_OFF, "RFM_Validity.HEIGHT_OFF",
    RPC_LINE_SCALE, "RFM_Validity.LINE_SCALE",
    RPC_SAMP_SCALE, "RFM_Validity.SAMP_SCALE",
    RPC_LAT_SCALE,  "RFM_Validity.LAT_SCALE",
    RPC_LONG_SCALE, "RFM_Validity.LONG_SCALE",
    RPC_HEIGHT_SCALE,   "RFM_Validity.HEIGHT_SCALE",
    NULL,             NULL };

static const char * const apszRPCTXT20ValItems[] =
{
    RPC_LINE_NUM_COEFF,
    RPC_LINE_DEN_COEFF,
    RPC_SAMP_NUM_COEFF,
    RPC_SAMP_DEN_COEFF,
    NULL
};

char** GDALMDReaderPleiades::LoadRPCXmlFile()
{
    CPLXMLNode* pNode = CPLParseXMLFile(m_osRPBSourceFilename);

    if(NULL == pNode)
        return NULL;

    // search Global_RFM
    char** papszRawRPCList = NULL;
    CPLXMLNode* pGRFMNode = CPLSearchXMLNode(pNode, "=Global_RFM");

    if(pGRFMNode != NULL)
    {
        papszRawRPCList = ReadXMLToList(pGRFMNode->psChild, papszRawRPCList);
    }

    if( NULL == papszRawRPCList )
    {
        CPLDestroyXMLNode(pNode);
        return NULL;
    }

    // If we are not the top-left tile, then we must shift LINE_OFF and SAMP_OFF
    int nLineOffShift = 0;
    int nPixelOffShift = 0;
    for(int i=1; TRUE; i++ )
    {
        CPLString osKey;
        osKey.Printf("Raster_Data.Data_Access.Data_Files.Data_File_%d.DATA_FILE_PATH.href", i);
        const char* pszHref = CSLFetchNameValue(m_papszIMDMD, osKey);
        if( pszHref == NULL )
            break;
        if( strcmp( CPLGetFilename(pszHref), CPLGetFilename(m_osBaseFilename) ) == 0 )
        {
            osKey.Printf("Raster_Data.Data_Access.Data_Files.Data_File_%d.tile_C", i);
            const char* pszC = CSLFetchNameValue(m_papszIMDMD, osKey);
            osKey.Printf("Raster_Data.Data_Access.Data_Files.Data_File_%d.tile_R", i);
            const char* pszR = CSLFetchNameValue(m_papszIMDMD, osKey);
            const char* pszTileWidth = CSLFetchNameValue(m_papszIMDMD,
                "Raster_Data.Raster_Dimensions.Tile_Set.Regular_Tiling.NTILES_SIZE.ncols");
            const char* pszTileHeight = CSLFetchNameValue(m_papszIMDMD,
                "Raster_Data.Raster_Dimensions.Tile_Set.Regular_Tiling.NTILES_SIZE.nrows");
            const char* pszOVERLAP_COL = CSLFetchNameValueDef(m_papszIMDMD,
                "Raster_Data.Raster_Dimensions.Tile_Set.Regular_Tiling.OVERLAP_COL", "0");
            const char* pszOVERLAP_ROW = CSLFetchNameValueDef(m_papszIMDMD,
                "Raster_Data.Raster_Dimensions.Tile_Set.Regular_Tiling.OVERLAP_ROW", "0");

            if( pszC && pszR && pszTileWidth && pszTileHeight &&
                atoi(pszOVERLAP_COL) == 0 && atoi(pszOVERLAP_ROW) == 0 )
            {
                nLineOffShift = - (atoi(pszR) - 1) * atoi(pszTileHeight);
                nPixelOffShift = - (atoi(pszC) - 1) * atoi(pszTileWidth);
            }
            break;
        }
    }

    // format list
    char** papszRPB = NULL;
    for( int i = 0; apszRPBMap[i] != NULL; i += 2 )
    {
        // Pleiades RPCs use "center of upper left pixel is 1,1" convention, convert to
        // Digital globe convention of "center of upper left pixel is 0,0".
        if (i == 0 || i == 2)
        {
            CPLString osField;
            const char *pszOffset = CSLFetchNameValue(papszRawRPCList,
                                                    apszRPBMap[i + 1]);
            double dfVal = CPLAtofM( pszOffset ) -1.0 ;
            if( i == 0 )
                dfVal += nLineOffShift;
            else
                dfVal += nPixelOffShift;
            osField.Printf( "%.15g", dfVal );
            papszRPB = CSLAddNameValue( papszRPB, apszRPBMap[i], osField );
        }
        else
        {
            papszRPB = CSLAddNameValue(papszRPB, apszRPBMap[i],
                                    CSLFetchNameValue(papszRawRPCList,
                                                        apszRPBMap[i + 1]));
        }
    }

    // merge coefficients
    for( int i = 0; apszRPCTXT20ValItems[i] != NULL; i++ )
    {
        CPLString value;
        for( int j = 1; j < 21; j++ )
        {
            // We want to use the Inverse_Model
            // Quoting PleiadesUserGuideV2-1012.pdf:
            // """When using the inverse model (ground --> image), the user
            // supplies geographic coordinates (lon, lat) and an altitude (alt)"""
            const char* pszValue = CSLFetchNameValue(papszRawRPCList,
                 CPLSPrintf("Inverse_Model.%s_%d", apszRPCTXT20ValItems[i], j));
            if(NULL != pszValue)
                value = value + " " + CPLString(pszValue);
        }
        papszRPB = CSLAddNameValue(papszRPB, apszRPCTXT20ValItems[i], value);
    }

    CSLDestroy(papszRawRPCList);
    CPLDestroyXMLNode(pNode);
    return papszRPB;
}
