/******************************************************************************
 *
 * Project:  GDAL Core
 * Purpose:  Implementation of GDALMultiDomainMetadata class.  This class
 *           manages metadata items for a variable list of domains.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2009-2011, 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 "cpl_port.h"
#include "gdal_priv.h"

#include <cstring>

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


//! @cond Doxygen_Suppress
/************************************************************************/
/*                      GDALMultiDomainMetadata()                       */
/************************************************************************/

GDALMultiDomainMetadata::GDALMultiDomainMetadata() :
    papszDomainList(nullptr),
    papoMetadataLists(nullptr)
{}

/************************************************************************/
/*                      ~GDALMultiDomainMetadata()                      */
/************************************************************************/

GDALMultiDomainMetadata::~GDALMultiDomainMetadata()

{
    Clear();
}

/************************************************************************/
/*                               Clear()                                */
/************************************************************************/

void GDALMultiDomainMetadata::Clear()

{
    const int nDomainCount = CSLCount( papszDomainList );
    CSLDestroy( papszDomainList );
    papszDomainList = nullptr;

    for( int i = 0; i < nDomainCount; i++ )
    {
        delete papoMetadataLists[i];
    }
    CPLFree( papoMetadataLists );
    papoMetadataLists = nullptr;
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

char **GDALMultiDomainMetadata::GetMetadata( const char *pszDomain )

{
    if( pszDomain == nullptr )
        pszDomain = "";

    const int iDomain = CSLFindString( papszDomainList, pszDomain );

    if( iDomain == -1 )
        return nullptr;

    return papoMetadataLists[iDomain]->List();
}

/************************************************************************/
/*                            SetMetadata()                             */
/************************************************************************/

CPLErr GDALMultiDomainMetadata::SetMetadata( char **papszMetadata,
                                             const char *pszDomain )

{
    if( pszDomain == nullptr )
        pszDomain = "";

    int iDomain = CSLFindString( papszDomainList, pszDomain );

    if( iDomain == -1 )
    {
        papszDomainList = CSLAddString( papszDomainList, pszDomain );
        const int nDomainCount = CSLCount( papszDomainList );

        papoMetadataLists = static_cast<CPLStringList **>(
            CPLRealloc( papoMetadataLists, sizeof(void*)*(nDomainCount+1) ));
        papoMetadataLists[nDomainCount] = nullptr;
        papoMetadataLists[nDomainCount-1] = new CPLStringList();
        iDomain = nDomainCount-1;
    }

    papoMetadataLists[iDomain]->Assign( CSLDuplicate( papszMetadata ) );

    // we want to mark name/value pair domains as being sorted for fast
    // access.
    if( !STARTS_WITH_CI(pszDomain, "xml:") &&
        !STARTS_WITH_CI(pszDomain, "json:") && 
        !EQUAL(pszDomain, "SUBDATASETS") )
    {
        papoMetadataLists[iDomain]->Sort();
    }

    return CE_None;
}

/************************************************************************/
/*                          GetMetadataItem()                           */
/************************************************************************/

const char *GDALMultiDomainMetadata::GetMetadataItem( const char *pszName,
                                                      const char *pszDomain )

{
    if( pszDomain == nullptr )
        pszDomain = "";

    const int iDomain = CSLFindString( papszDomainList, pszDomain );

    if( iDomain == -1 )
        return nullptr;

    return papoMetadataLists[iDomain]->FetchNameValue( pszName );
}

/************************************************************************/
/*                          SetMetadataItem()                           */
/************************************************************************/

CPLErr GDALMultiDomainMetadata::SetMetadataItem( const char *pszName,
                                                 const char *pszValue,
                                                 const char *pszDomain )

{
    if( pszDomain == nullptr )
        pszDomain = "";

/* -------------------------------------------------------------------- */
/*      Create the domain if it does not already exist.                 */
/* -------------------------------------------------------------------- */
    int iDomain = CSLFindString( papszDomainList, pszDomain );

    if( iDomain == -1 )
    {
        SetMetadata( nullptr, pszDomain );
        iDomain = CSLFindString( papszDomainList, pszDomain );
    }

/* -------------------------------------------------------------------- */
/*      Set the value in the domain list.                               */
/* -------------------------------------------------------------------- */
    papoMetadataLists[iDomain]->SetNameValue( pszName, pszValue );

    return CE_None;
}

/************************************************************************/
/*                              XMLInit()                               */
/*                                                                      */
/*      This method should be invoked on the parent of the              */
/*      <Metadata> elements.                                            */
/************************************************************************/

int GDALMultiDomainMetadata::XMLInit( CPLXMLNode *psTree, int /* bMerge */ )
{
    CPLXMLNode *psMetadata = nullptr;

/* ==================================================================== */
/*      Process all <Metadata> elements, each for one domain.           */
/* ==================================================================== */
    for( psMetadata = psTree->psChild;
         psMetadata != nullptr;
         psMetadata = psMetadata->psNext )
    {
        if( psMetadata->eType != CXT_Element
            || !EQUAL(psMetadata->pszValue,"Metadata") )
            continue;

        const char *pszDomain = CPLGetXMLValue( psMetadata, "domain", "" );
        const char *pszFormat = CPLGetXMLValue( psMetadata, "format", "" );

        // Make sure we have a CPLStringList for this domain,
        // without wiping out an existing one.
        if( GetMetadata( pszDomain ) == nullptr )
            SetMetadata( nullptr, pszDomain );

        const int iDomain = CSLFindString( papszDomainList, pszDomain );
        CPLAssert( iDomain != -1 );

        CPLStringList *poMDList = papoMetadataLists[iDomain];

/* -------------------------------------------------------------------- */
/*      XML format subdocuments.                                        */
/* -------------------------------------------------------------------- */
        if( EQUAL(pszFormat,"xml")  )
        {
            // Find first non-attribute child of current element.
            CPLXMLNode *psSubDoc = psMetadata->psChild;
            while( psSubDoc != nullptr && psSubDoc->eType == CXT_Attribute )
                psSubDoc = psSubDoc->psNext;

            char *pszDoc = CPLSerializeXMLTree( psSubDoc );

            poMDList->Clear();
            poMDList->AddStringDirectly( pszDoc );
        }

/* -------------------------------------------------------------------- */
/*      JSon format subdocuments.                                       */
/* -------------------------------------------------------------------- */
        else if( EQUAL(pszFormat,"json") )
        {
            // Find first text child of current element.
            CPLXMLNode *psSubDoc = psMetadata->psChild;
            while( psSubDoc != nullptr && psSubDoc->eType != CXT_Text )
                psSubDoc = psSubDoc->psNext;
            if( psSubDoc )
            {
                poMDList->Clear();
                poMDList->AddString( psSubDoc->pszValue );
            }
        }

/* -------------------------------------------------------------------- */
/*      Name value format.                                              */
/*      <MDI key="...">value_Text</MDI>                                 */
/* -------------------------------------------------------------------- */
        else
        {
            for( CPLXMLNode *psMDI = psMetadata->psChild;
                 psMDI != nullptr;
                 psMDI = psMDI->psNext )
            {
                if( !EQUAL(psMDI->pszValue,"MDI")
                    || psMDI->eType != CXT_Element
                    || psMDI->psChild == nullptr
                    || psMDI->psChild->psNext == nullptr
                    || psMDI->psChild->eType != CXT_Attribute
                    || psMDI->psChild->psChild == nullptr )
                    continue;

                char* pszName = psMDI->psChild->psChild->pszValue;
                char* pszValue = psMDI->psChild->psNext->pszValue;
                if( pszName != nullptr && pszValue != nullptr )
                    poMDList->SetNameValue( pszName, pszValue );
            }
        }
    }

    return CSLCount(papszDomainList) != 0;
}

/************************************************************************/
/*                             Serialize()                              */
/************************************************************************/

CPLXMLNode *GDALMultiDomainMetadata::Serialize()

{
    CPLXMLNode *psFirst = nullptr;

    for( int iDomain = 0;
         papszDomainList != nullptr && papszDomainList[iDomain] != nullptr;
         iDomain++ )
    {
        char **papszMD = papoMetadataLists[iDomain]->List();
        // Do not serialize empty domains.
        if( papszMD == nullptr || papszMD[0] == nullptr )
            continue;

        CPLXMLNode *psMD = CPLCreateXMLNode( nullptr, CXT_Element, "Metadata" );

        if( strlen( papszDomainList[iDomain] ) > 0 )
            CPLCreateXMLNode(
                CPLCreateXMLNode( psMD, CXT_Attribute, "domain" ),
                CXT_Text, papszDomainList[iDomain] );

        bool bFormatXMLOrJSon = false;

        if( STARTS_WITH_CI(papszDomainList[iDomain], "xml:")
            && CSLCount(papszMD) == 1 )
        {
            CPLXMLNode *psValueAsXML = CPLParseXMLString( papszMD[0] );
            if( psValueAsXML != nullptr )
            {
                bFormatXMLOrJSon = true;

                CPLCreateXMLNode(
                    CPLCreateXMLNode( psMD, CXT_Attribute, "format" ),
                    CXT_Text, "xml" );

                CPLAddXMLChild( psMD, psValueAsXML );
            }
        }

        if( STARTS_WITH_CI(papszDomainList[iDomain], "json:")
            && CSLCount(papszMD) == 1 )
        {
            bFormatXMLOrJSon = true;

            CPLCreateXMLNode(
                CPLCreateXMLNode( psMD, CXT_Attribute, "format" ),
                CXT_Text, "json" );
            CPLCreateXMLNode( psMD, CXT_Text, *papszMD );
        }

        if( !bFormatXMLOrJSon )
        {
            CPLXMLNode* psLastChild = nullptr;
            // To go after domain attribute.
            if( psMD->psChild != nullptr )
            {
                psLastChild = psMD->psChild;
                while( psLastChild->psNext != nullptr )
                    psLastChild = psLastChild->psNext;
            }
            for( int i = 0; papszMD[i] != nullptr; i++ )
            {
                char *pszKey = nullptr;

                const char *pszRawValue =
                    CPLParseNameValue( papszMD[i], &pszKey );

                CPLXMLNode *psMDI =
                    CPLCreateXMLNode( nullptr, CXT_Element, "MDI" );
                if( psLastChild == nullptr )
                    psMD->psChild = psMDI;
                else
                    psLastChild->psNext = psMDI;
                psLastChild = psMDI;

                CPLSetXMLValue( psMDI, "#key", pszKey );
                CPLCreateXMLNode( psMDI, CXT_Text, pszRawValue );

                CPLFree( pszKey );
            }
        }

        if( psFirst == nullptr )
            psFirst = psMD;
        else
            CPLAddXMLSibling( psFirst, psMD );
    }

    return psFirst;
}
//! @endcond
