/******************************************************************************
 *
 * Project:  KML Translator
 * Purpose:  Implements OGRLIBKMLDriver
 * Author:   Brian Case, rush at winkey dot org
 *
 ******************************************************************************
 * Copyright (c) 2010, Brian Case
 *
 * 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 "libkml_headers.h"

#include "ogr_libkml.h"
#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_multiproc.h"


using kmldom::KmlFactory;

static CPLMutex *hMutex = nullptr;
static KmlFactory* m_poKmlFactory = nullptr;

/******************************************************************************
 OGRLIBKMLDriverUnload()
******************************************************************************/

static void OGRLIBKMLDriverUnload( GDALDriver * /* poDriver */ )
{
    if( hMutex != nullptr )
        CPLDestroyMutex(hMutex);
    hMutex = nullptr;
    m_poKmlFactory = nullptr;
}

/************************************************************************/
/*                    OGRLIBKMLDriverIdentify()                         */
/************************************************************************/

static int OGRLIBKMLDriverIdentify( GDALOpenInfo* poOpenInfo )

{
    if( !poOpenInfo->bStatOK )
        return FALSE;
    if( poOpenInfo->bIsDirectory )
        return -1;

    const char* pszExt = CPLGetExtension(poOpenInfo->pszFilename);
    if( EQUAL(pszExt, "kml") ||
        EQUAL(pszExt, "kmz") )
    {
        return TRUE;
    }

    if( poOpenInfo->pabyHeader &&
        ( strstr(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
                  "<kml") != nullptr ||
          strstr(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
                  "<kml:kml") != nullptr) )
    {
        return TRUE;
    }

    return FALSE;
}

/******************************************************************************
 Open()
******************************************************************************/

static GDALDataset *OGRLIBKMLDriverOpen( GDALOpenInfo* poOpenInfo )
{
    if( OGRLIBKMLDriverIdentify(poOpenInfo) == FALSE )
        return nullptr;

    {
        CPLMutexHolderD(&hMutex);
        if( m_poKmlFactory == nullptr )
            m_poKmlFactory = KmlFactory::GetFactory();
    }

    OGRLIBKMLDataSource *poDS = new OGRLIBKMLDataSource( m_poKmlFactory );

    if( !poDS->Open( poOpenInfo->pszFilename,
                     poOpenInfo->eAccess == GA_Update ) )
    {
        delete poDS;

        poDS = nullptr;
    }

    return poDS;
}

/************************************************************************/
/*                               Create()                               */
/************************************************************************/

static GDALDataset *OGRLIBKMLDriverCreate( const char * pszName,
                                           int /* nBands */,
                                           int /* nXSize */,
                                           int /* nYSize */,
                                           GDALDataType /* eDT */,
                                           char **papszOptions )
{
    CPLAssert( nullptr != pszName );
    CPLDebug( "LIBKML", "Attempt to create: %s", pszName );

    {
        CPLMutexHolderD(&hMutex);
        if( m_poKmlFactory == nullptr )
            m_poKmlFactory = KmlFactory::GetFactory();
    }

    OGRLIBKMLDataSource *poDS = new OGRLIBKMLDataSource( m_poKmlFactory );

    if( !poDS->Create( pszName, papszOptions ) ) {
        delete poDS;

        poDS = nullptr;
    }

    return poDS;
}

/******************************************************************************
 DeleteDataSource()

 Note: This method recursively deletes an entire dir if the datasource is a dir
       and all the files are kml or kmz.

******************************************************************************/

static CPLErr OGRLIBKMLDriverDelete( const char *pszName )
{
    /***** dir *****/
    VSIStatBufL sStatBuf;
    if( !VSIStatL( pszName, &sStatBuf ) && VSI_ISDIR( sStatBuf.st_mode ) )
    {
        char **papszDirList = VSIReadDir( pszName );
        for( int iFile = 0;
             papszDirList != nullptr && papszDirList[iFile] != nullptr;
             iFile++ )
        {
            if( CE_Failure == OGRLIBKMLDriverDelete( papszDirList[iFile] ) )
            {
                CSLDestroy( papszDirList );
                return CE_Failure;
            }
        }
        CSLDestroy( papszDirList );

        if( VSIRmdir( pszName ) < 0 )
        {
            return CE_Failure;
        }
    }

    /***** kml *****/
    else if( EQUAL( CPLGetExtension( pszName ), "kml" ) )
    {
        if( VSIUnlink( pszName ) < 0 )
            return CE_Failure;
    }

    /***** kmz *****/
    else if( EQUAL( CPLGetExtension( pszName ), "kmz" ) )
    {
        if( VSIUnlink( pszName ) < 0 )
            return CE_Failure;
    }

    /***** do not delete other types of files *****/
    else
    {
        return CE_Failure;
    }

    // TODO(schwehr): Isn't this redundant to the else case?
    return CE_None;
}

/******************************************************************************
 RegisterOGRLIBKML()
******************************************************************************/

void RegisterOGRLIBKML()
{
    if( GDALGetDriverByName( "LIBKML" ) != nullptr )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "LIBKML" );
    poDriver->SetMetadataItem( GDAL_DCAP_VECTOR, "YES" );
    poDriver->SetMetadataItem( GDAL_DCAP_CREATE_LAYER, "YES" );
    poDriver->SetMetadataItem( GDAL_DCAP_DELETE_LAYER, "YES" );
    poDriver->SetMetadataItem( GDAL_DCAP_CREATE_FIELD, "YES" );
    poDriver->SetMetadataItem( GDAL_DCAP_Z_GEOMETRIES, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
                               "Keyhole Markup Language (LIBKML)" );
    poDriver->SetMetadataItem( GDAL_DMD_EXTENSIONS, "kml kmz" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/vector/libkml.html" );
    poDriver->SetMetadataItem( GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE" );

    poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
"<CreationOptionList>"
"  <Option name='DOCUMENT_ID' type='string' description='Id of the root &lt;Document&gt; node' default='root_doc'/>"
"  <Option name='AUTHOR_NAME' type='string' description='Name in &lt;atom:Author&gt; element'/>"
"  <Option name='AUTHOR_URI' type='string' description='URI in &lt;atom:Author&gt; element'/>"
"  <Option name='AUTHOR_EMAIL' type='string' description='Email in &lt;atom:Author&gt; element'/>"
"  <Option name='LINK' type='string' description='Href of &lt;atom:link&gt; element'/>"
"  <Option name='PHONENUMBER' type='string' description='Value of &lt;phoneNumber&gt; element'/>"
"  <Option name='NAME' type='string' description='Value of &lt;name&gt; element of top container'/>"
"  <Option name='VISIBILITY' type='integer' description='Value of &lt;visibility&gt; element of top container (0/1)'/>"
"  <Option name='OPEN' type='integer' description='Value of &lt;open&gt; element of top container (0/1)'/>"
"  <Option name='SNIPPET' type='string' description='Value of &lt;snippet&gt; element of top container'/>"
"  <Option name='DESCRIPTION' type='string' description='Value of &lt;description&gt; element of top container'/>"
"  <Option name='LISTSTYLE_TYPE' type='string-select' description='Value of &lt;listItemType&gt; element of top container'>"
"    <Value>check</Value>"
"    <Value>radioFolder</Value>"
"    <Value>checkOffOnly</Value>"
"    <Value>checkHideChildren</Value>"
"  </Option>"
"  <Option name='LISTSTYLE_ICON_HREF' type='string' description='URL of the icon to display for the main folder. Sets the href element of the &lt;ItemIcon&gt; element'/>"
"  <Option name='*_BALLOONSTYLE_BGCOLOR' type='string' description='Background color of a &lt;BallonStyle&gt; element if a style X is defined'/>"
"  <Option name='*_BALLOONSTYLE_TEXT' type='string' description='Text of a &lt;BallonStyle&gt; element if a style X is defined'/>"
"  <Option name='NLC_MINREFRESHPERIOD' type='float' description='&lt;minRefreshPeriod&gt; element of a &lt;NetworkLinkControl&gt; element'/>"
"  <Option name='NLC_MAXSESSIONLENGTH' type='float' description='&lt;maxSessionLength&gt; element of a &lt;NetworkLinkControl&gt; element'/>"
"  <Option name='NLC_COOKIE' type='string' description='&lt;cookie&gt; element of a &lt;NetworkLinkControl&gt; element'/>"
"  <Option name='NLC_MESSAGE' type='string' description='&lt;message&gt; element of a &lt;NetworkLinkControl&gt; element'/>"
"  <Option name='NLC_LINKNAME' type='string' description='&lt;linkName&gt; element of a &lt;NetworkLinkControl&gt; element'/>"
"  <Option name='NLC_LINKDESCRIPTION' type='string' description='&lt;linkDescription&gt; element of a &lt;NetworkLinkControl&gt; element'/>"
"  <Option name='NLC_LINKSNIPPET' type='string' description='&lt;linkSnippet&gt; element of a &lt;NetworkLinkControl&gt; element'/>"
"  <Option name='NLC_EXPIRES' type='string' description='Date to set in &lt;expires&gt; element of a &lt;NetworkLinkControl&gt; element'/>"
"  <Option name='UPDATE_TARGETHREF' type='string' description='If set, a NetworkLinkControl KML file with an &lt;Update&gt; element will be generated'/>"
"</CreationOptionList>");

    poDriver->SetMetadataItem( GDAL_DS_LAYER_CREATIONOPTIONLIST,
"<LayerCreationOptionList>"
"  <Option name='NAME' type='string' description='Value of &lt;name&gt; element of layer container'/>"
"  <Option name='VISIBILITY' type='integer' description='Value of &lt;visibility&gt; element of layer container (0/1)'/>"
"  <Option name='OPEN' type='integer' description='Value of &lt;open&gt; element of layer container (0/1)'/>"
"  <Option name='SNIPPET' type='string' description='Value of &lt;snippet&gt; element of layer container'/>"
"  <Option name='DESCRIPTION' type='string' description='Value of &lt;description&gt; element of layer container'/>"
"  <Option name='LOOKAT_LONGITUDE' type='float' description='&lt;longitude&gt; of a &lt;LookAt&gt; element at layer level' min='-180' max='180'/>"
"  <Option name='LOOKAT_LATITUDE' type='float' description='&lt;latitude&gt; of a &lt;LookAt&gt; element at layer level' min='-90' max='90'/>"
"  <Option name='LOOKAT_RANGE' type='float' description='&lt;range&gt; of a &lt;LookAt&gt; element at layer level' min='0'/>"
"  <Option name='LOOKAT_HEADING' type='float' description='&lt;heading&gt; of a &lt;LookAt&gt; element at layer level'/>"
"  <Option name='LOOKAT_TILT' type='float' description='&lt;tilt&gt; of a &lt;LookAt&gt; element at layer level'/>"
"  <Option name='LOOKAT_ALTITUDE' type='float' description='&lt;altitude&gt; of a &lt;LookAt&gt; element at layer level'/>"
"  <Option name='LOOKAT_ALTITUDEMODE' type='string-select' description='&lt;altitudeMode&gt; of a &lt;LookAt&gt; element at layer level'>"
"    <Value>clampToGround</Value>"
"    <Value>relativeToGround</Value>"
"    <Value>absolute</Value>"
"    <Value>clampToSeaFloor</Value>"
"    <Value>relativeToSeaFloor</Value>"
"  </Option>"
"  <Option name='CAMERA_LONGITUDE' type='float' description='&lt;longitude&gt; of a &lt;Camera&gt; element at layer level' min='-180' max='180'/>"
"  <Option name='CAMERA_LATITUDE' type='float' description='&lt;latitude&gt; of a &lt;Camera&gt; element at layer level' min='-90' max='90'/>"
"  <Option name='CAMERA_HEADING' type='float' description='&lt;heading&gt; of a &lt;Camera&gt; element at layer level'/>"
"  <Option name='CAMERA_TILT' type='float' description='&lt;tilt&gt; of a &lt;Camera&gt; element at layer level'/>"
"  <Option name='CAMERA_ROLL' type='float' description='&lt;roll&gt; of a &lt;Camera&gt; element at layer level'/>"
"  <Option name='CAMERA_ALTITUDE' type='float' description='&lt;altitude&gt; of a &lt;Camera&gt; element at layer level'/>"
"  <Option name='CAMERA_ALTITUDEMODE' type='string-select' description='&lt;altitudeMode&gt; of a &lt;Camera&gt; element at layer level'>"
"    <Value>clampToGround</Value>"
"    <Value>relativeToGround</Value>"
"    <Value>absolute</Value>"
"    <Value>clampToSeaFloor</Value>"
"    <Value>relativeToSeaFloor</Value>"
"  </Option>"
"  <Option name='ADD_REGION' type='boolean' description='Whether to generate a &lt;Region&gt; element to control when objects of the layer are visible or not' default='NO'/>"
"  <Option name='REGION_XMIN' type='float' description='West coordinate of the region' min='-180' max='180'/>"
"  <Option name='REGION_YMIN' type='float' description='South coordinate of the region' min='-90' max='90'/>"
"  <Option name='REGION_XMAX' type='float' description='East coordinate of the region' min='-180' max='180'/>"
"  <Option name='REGION_YMAX' type='float' description='North coordinate of the region' min='-90' max='90'/>"
"  <Option name='REGION_MIN_LOD_PIXELS' type='float' description='minimum size in pixels of the region so that it is displayed' default='256'/>"
"  <Option name='REGION_MAX_LOD_PIXELS' type='float' description='maximum size in pixels of the region so that it is displayed (-1=infinite)' default='-1'/>"
"  <Option name='REGION_MIN_FADE_EXTENT' type='float' description='distance over which the geometry fades, from fully opaque to fully transparent' default='0'/>"
"  <Option name='REGION_MAX_FADE_EXTENT' type='float' description='distance over which the geometry fades, from fully transparent to fully opaque' default='0'/>"
"  <Option name='SO_HREF' type='string' description='URL of the image to display in a &lt;ScreenOverlay&gt;'/>"
"  <Option name='SO_NAME' type='string' description='&lt;name&gt; of a &lt;ScreenOverlay&gt;'/>"
"  <Option name='SO_DESCRIPTION' type='string' description='&lt;description&gt; of a &lt;ScreenOverlay&gt;'/>"
"  <Option name='SO_OVERLAY_X' type='float' description='x attribute of the &lt;overlayXY&gt; of a &lt;ScreenOverlay&gt;'/>"
"  <Option name='SO_OVERLAY_Y' type='float' description='y attribute of the &lt;overlayXY&gt; of a &lt;ScreenOverlay&gt;'/>"
"  <Option name='SO_OVERLAY_XUNITS' type='string-select' description='xunits attribute of the &lt;overlayXY&gt; of a &lt;ScreenOverlay&gt;'>"
"    <Value>fraction</Value>"
"    <Value>pixels</Value>"
"    <Value>insetPixels</Value>"
"  </Option>"
"  <Option name='SO_OVERLAY_YUNITS' type='string-select' description='yunits attribute of the &lt;overlayXY&gt; of a &lt;ScreenOverlay&gt;'>"
"    <Value>fraction</Value>"
"    <Value>pixels</Value>"
"    <Value>insetPixels</Value>"
"  </Option>"
"  <Option name='SO_SCREEN_X' type='float' description='x attribute of the &lt;screenXY&gt; of a &lt;ScreenOverlay&gt;' default='0.05'/>"
"  <Option name='SO_SCREEN_Y' type='float' description='y attribute of the &lt;screenXY&gt; of a &lt;ScreenOverlay&gt;' default='0.05'/>"
"  <Option name='SO_SCREEN_XUNITS' type='string-select' description='xunits attribute of the &lt;screenXY&gt; of a &lt;ScreenOverlay&gt;' default='fraction'>"
"    <Value>fraction</Value>"
"    <Value>pixels</Value>"
"    <Value>insetPixels</Value>"
"  </Option>"
"  <Option name='SO_SCREEN_YUNITS' type='string-select' description='yunits attribute of the &lt;screenXY&gt; of a &lt;ScreenOverlay&gt;' default='fraction'>"
"    <Value>fraction</Value>"
"    <Value>pixels</Value>"
"    <Value>insetPixels</Value>"
"  </Option>"
"  <Option name='SO_SIZE_X' type='float' description='x attribute of the &lt;sizeXY&gt; of a &lt;ScreenOverlay&gt;'/>"
"  <Option name='SO_SIZE_Y' type='float' description='y attribute of the &lt;sizeXY&gt; of a &lt;ScreenOverlay&gt;'/>"
"  <Option name='SO_SIZE_XUNITS' type='string-select' description='xunits attribute of the &lt;sizeXY&gt; of a &lt;ScreenOverlay&gt;'>"
"    <Value>fraction</Value>"
"    <Value>pixels</Value>"
"    <Value>insetPixels</Value>"
"  </Option>"
"  <Option name='SO_SIZE_YUNITS' type='string-select' description='yunits attribute of the &lt;sizeXY&gt; of a &lt;ScreenOverlay&gt;'>"
"    <Value>fraction</Value>"
"    <Value>pixels</Value>"
"    <Value>insetPixels</Value>"
"  </Option>"
"  <Option name='FOLDER' type='boolean' description='Whether to generate a &lt;Folder&gt; element for layers, instead of a &lt;Document&gt;' default='NO'/>"
"  <Option name='LISTSTYLE_TYPE' type='string-select' description='Value of &lt;listItemType&gt; element of layer container'>"
"    <Value>check</Value>"
"    <Value>radioFolder</Value>"
"    <Value>checkOffOnly</Value>"
"    <Value>checkHideChildren</Value>"
"  </Option>"
"  <Option name='LISTSTYLE_ICON_HREF' type='string' description='URL of the icon to display for the layer folder. Sets the href element of the &lt;ItemIcon&gt; element'/>"
"</LayerCreationOptionList>");

    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATATYPES,
                               "Integer Real String" );
    poDriver->SetMetadataItem( GDAL_DCAP_FEATURE_STYLES, "YES" );
    poDriver->SetMetadataItem( GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES" );

    poDriver->pfnOpen = OGRLIBKMLDriverOpen;
    poDriver->pfnIdentify = OGRLIBKMLDriverIdentify;
    poDriver->pfnCreate = OGRLIBKMLDriverCreate;
    poDriver->pfnDelete = OGRLIBKMLDriverDelete;
    poDriver->pfnUnloadDriver = OGRLIBKMLDriverUnload;

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
