/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Simple client for translating between formats.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 * Copyright (c) 2008-2015, 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 "ogrsf_frmts.h"
#include "ogr_p.h"
#include "gdal_utils_priv.h"
#include "commonutils.h"
#include <vector>
#include <algorithm>

CPL_CVSID("$Id: ogr2ogr_bin.cpp 37362 2017-02-12 21:04:36Z rouault $");

static void Usage(int bShort = TRUE);
static void Usage(const char* pszAdditionalMsg, int bShort = TRUE);

/************************************************************************/
/*                 GDALVectorTranslateOptionsForBinaryNew()             */
/************************************************************************/

static GDALVectorTranslateOptionsForBinary *GDALVectorTranslateOptionsForBinaryNew(void)
{
    return (GDALVectorTranslateOptionsForBinary*) CPLCalloc(  1, sizeof(GDALVectorTranslateOptionsForBinary) );
}

/************************************************************************/
/*                  GDALVectorTranslateOptionsForBinaryFree()           */
/************************************************************************/

static void GDALVectorTranslateOptionsForBinaryFree( GDALVectorTranslateOptionsForBinary* psOptionsForBinary )
{
    if( psOptionsForBinary )
    {
        CPLFree(psOptionsForBinary->pszDataSource);
        CPLFree(psOptionsForBinary->pszDestDataSource);
        CSLDestroy(psOptionsForBinary->papszOpenOptions);
        CPLFree(psOptionsForBinary->pszFormat);
        CPLFree(psOptionsForBinary);
    }
}

/* -------------------------------------------------------------------- */
/*                  CheckDestDataSourceNameConsistency()                */
/* -------------------------------------------------------------------- */

static
void CheckDestDataSourceNameConsistency(const char* pszDestFilename,
                                        const char* pszDriverName)
{
    int i;
    char* pszDestExtension = CPLStrdup(CPLGetExtension(pszDestFilename));

    if( EQUAL(pszDriverName, "GMT") )
        pszDriverName = "OGR_GMT";
    CheckExtensionConsistency(pszDestFilename, pszDriverName);

    static const char* apszBeginName[][2] =  { { "PG:"      , "PostgreSQL" },
                                               { "MySQL:"   , "MySQL" },
                                               { "CouchDB:" , "CouchDB" },
                                               { "GFT:"     , "GFT" },
                                               { "MSSQL:"   , "MSSQLSpatial" },
                                               { "ODBC:"    , "ODBC" },
                                               { "OCI:"     , "OCI" },
                                               { "SDE:"     , "SDE" },
                                               { "WFS:"     , "WFS" },
                                               { NULL, NULL }
                                             };

    for(i=0; apszBeginName[i][0] != NULL; i++)
    {
        if (EQUALN(pszDestFilename, apszBeginName[i][0], strlen(apszBeginName[i][0])) &&
            !EQUAL(pszDriverName, apszBeginName[i][1]))
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                    "The target file has a name which is normally recognized by the %s driver,\n"
                    "but the requested output driver is %s. Is it really what you want ?\n",
                    apszBeginName[i][1],
                    pszDriverName);
            break;
        }
    }

    CPLFree(pszDestExtension);
}

/************************************************************************/
/*                                main()                                */
/************************************************************************/

int main( int nArgc, char ** papszArgv )
{
    GDALDatasetH hDS = NULL;
    GDALDatasetH hODS = NULL;
    int bCloseODS = TRUE;
    int bUsageError = FALSE;
    GDALDatasetH hDstDS;
    int nRetCode = 1;
    GDALVectorTranslateOptionsForBinary* psOptionsForBinary;
    GDALVectorTranslateOptions *psOptions;

    /* Check strict compilation and runtime library version as we use C++ API */
    if (! GDAL_CHECK_VERSION(papszArgv[0]))
        exit(1);

    EarlySetConfigOptions(nArgc, papszArgv);

/* -------------------------------------------------------------------- */
/*      Register format(s).                                             */
/* -------------------------------------------------------------------- */
    OGRRegisterAll();

/* -------------------------------------------------------------------- */
/*      Processing command line arguments.                              */
/* -------------------------------------------------------------------- */
    nArgc = OGRGeneralCmdLineProcessor( nArgc, &papszArgv, 0 );

    if( nArgc < 1 )
    {
        papszArgv = NULL;
        nRetCode = -nArgc;
        goto exit;
    }

    for( int iArg = 1; iArg < nArgc; iArg++ )
    {
        if( EQUAL(papszArgv[iArg], "--utility_version") )
        {
            printf("%s was compiled against GDAL %s and is running against GDAL %s\n",
                   papszArgv[0], GDAL_RELEASE_NAME, GDALVersionInfo("RELEASE_NAME"));
            nRetCode = 0;
            goto exit;
        }
        else if( EQUAL(papszArgv[iArg],"--help") )
        {
            Usage();
            goto exit;
        }
        else if ( EQUAL(papszArgv[iArg], "--long-usage") )
        {
            Usage(FALSE);
            goto exit;
        }
    }

    psOptionsForBinary = GDALVectorTranslateOptionsForBinaryNew();
    psOptions = GDALVectorTranslateOptionsNew(papszArgv + 1, psOptionsForBinary);

    if( psOptions == NULL )
    {
        Usage();
        GDALVectorTranslateOptionsForBinaryFree(psOptionsForBinary);
        goto exit;
    }

    if( psOptionsForBinary->pszDataSource == NULL ||
        psOptionsForBinary->pszDestDataSource == NULL )
    {
        if( psOptionsForBinary->pszDestDataSource == NULL )
            Usage("no target datasource provided");
        else
            Usage("no source datasource provided");
        GDALVectorTranslateOptionsFree(psOptions);
        GDALVectorTranslateOptionsForBinaryFree(psOptionsForBinary);
        goto exit;
    }

    if( strcmp(psOptionsForBinary->pszDestDataSource, "/vsistdout/") == 0 )
        psOptionsForBinary->bQuiet = TRUE;

    if (!psOptionsForBinary->bQuiet && !psOptionsForBinary->bFormatExplicitlySet &&
        psOptionsForBinary->eAccessMode == ACCESS_CREATION)
    {
        CheckDestDataSourceNameConsistency(psOptionsForBinary->pszDestDataSource,
                                           psOptionsForBinary->pszFormat);
    }
/* -------------------------------------------------------------------- */
/*      Open data source.                                               */
/* -------------------------------------------------------------------- */

    /* Avoid opening twice the same datasource if it is both the input and output */
    /* Known to cause problems with at least FGdb, SQlite and GPKG drivers. See #4270 */
    if (psOptionsForBinary->eAccessMode != ACCESS_CREATION &&
        strcmp(psOptionsForBinary->pszDestDataSource, psOptionsForBinary->pszDataSource) == 0)
    {
        hODS = GDALOpenEx( psOptionsForBinary->pszDataSource,
                GDAL_OF_UPDATE | GDAL_OF_VECTOR, NULL, psOptionsForBinary->papszOpenOptions, NULL );
        GDALDriverH hDriver = NULL;
        if( hODS != NULL )
            hDriver = GDALGetDatasetDriver(hODS);

        /* Restrict to those 3 drivers. For example it is known to break with */
        /* the PG driver due to the way it manages transactions... */
        if (hDriver && !(EQUAL(GDALGetDescription(hDriver), "FileGDB") ||
                         EQUAL(GDALGetDescription(hDriver), "SQLite") ||
                         EQUAL(GDALGetDescription(hDriver), "GPKG")))
        {
            hDS = GDALOpenEx( psOptionsForBinary->pszDataSource,
                        GDAL_OF_VECTOR, NULL, psOptionsForBinary->papszOpenOptions, NULL );
        }
        else
        {
            hDS = hODS;
            bCloseODS = FALSE;
        }
    }
    else
    {
        hDS = GDALOpenEx( psOptionsForBinary->pszDataSource,
                        GDAL_OF_VECTOR, NULL, psOptionsForBinary->papszOpenOptions, NULL );
    }

/* -------------------------------------------------------------------- */
/*      Report failure                                                  */
/* -------------------------------------------------------------------- */
    if( hDS == NULL )
    {
        GDALDriverManager *poDM = GetGDALDriverManager();

        fprintf( stderr, "FAILURE:\n"
                "Unable to open datasource `%s' with the following drivers.\n",
                psOptionsForBinary->pszDataSource );

        for( int iDriver = 0; iDriver < poDM->GetDriverCount(); iDriver++ )
        {
            GDALDriver* poIter = poDM->GetDriver(iDriver);
            char** papszDriverMD = poIter->GetMetadata();
            if( CPLTestBool( CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_VECTOR, "FALSE") ) )
            {
                fprintf( stderr,  "  -> `%s'\n", poIter->GetDescription() );
            }
        }

        GDALVectorTranslateOptionsFree(psOptions);
        GDALVectorTranslateOptionsForBinaryFree(psOptionsForBinary);
        goto exit;
    }

    if( hODS != NULL )
    {
        GDALDriverManager *poDM = GetGDALDriverManager();

        GDALDriver* poDriver = poDM->GetDriverByName(psOptionsForBinary->pszFormat);
        if( poDriver == NULL )
        {
            fprintf( stderr,  "Unable to find driver `%s'.\n", psOptionsForBinary->pszFormat );
            fprintf( stderr,  "The following drivers are available:\n" );

            for( int iDriver = 0; iDriver < poDM->GetDriverCount(); iDriver++ )
            {
                GDALDriver* poIter = poDM->GetDriver(iDriver);
                char** papszDriverMD = poIter->GetMetadata();
                if( CPLTestBool( CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_VECTOR, "FALSE") ) &&
                    (CPLTestBool( CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_CREATE, "FALSE") ) ||
                     CPLTestBool( CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_CREATECOPY, "FALSE") )) )
                {
                    fprintf( stderr,  "  -> `%s'\n", poIter->GetDescription() );
                }
            }
            GDALVectorTranslateOptionsFree(psOptions);
            GDALVectorTranslateOptionsForBinaryFree(psOptionsForBinary);
            goto exit;
        }
    }

    if( !(psOptionsForBinary->bQuiet) )
    {
        GDALVectorTranslateOptionsSetProgress(psOptions, GDALTermProgress, NULL);
    }

    hDstDS = GDALVectorTranslate(psOptionsForBinary->pszDestDataSource, hODS,
                                              1, &hDS, psOptions, &bUsageError);
    if( bUsageError )
        Usage();
    else
        nRetCode = (hDstDS) ? 0 : 1;

    GDALVectorTranslateOptionsFree(psOptions);
    GDALVectorTranslateOptionsForBinaryFree(psOptionsForBinary);

    if(hDS)
        GDALClose(hDS);
    if(bCloseODS)
        GDALClose(hDstDS);

exit:
    CSLDestroy( papszArgv );
    OGRCleanupAll();

    return nRetCode;
}

/************************************************************************/
/*                               Usage()                                */
/************************************************************************/

static void Usage(int bShort)
{
    Usage(NULL, bShort);
}

static bool StringCISortFunction(const CPLString& a, const CPLString& b)
{
    return STRCASECMP(a.c_str(), b.c_str()) < 0;
}

static void Usage(const char* pszAdditionalMsg, int bShort)

{
    OGRSFDriverRegistrar        *poR = OGRSFDriverRegistrar::GetRegistrar();

    printf( "Usage: ogr2ogr [--help-general] [-skipfailures] [-append] [-update]\n"
            "               [-select field_list] [-where restricted_where|@filename]\n"
            "               [-progress] [-sql <sql statement>|@filename] [-dialect dialect]\n"
            "               [-preserve_fid] [-fid FID] [-limit nb_features]\n"
            "               [-spat xmin ymin xmax ymax] [-spat_srs srs_def] [-geomfield field]\n"
            "               [-a_srs srs_def] [-t_srs srs_def] [-s_srs srs_def]\n"
            "               [-f format_name] [-overwrite] [[-dsco NAME=VALUE] ...]\n"
            "               dst_datasource_name src_datasource_name\n"
            "               [-lco NAME=VALUE] [-nln name] \n"
            "               [-nlt type|PROMOTE_TO_MULTI|CONVERT_TO_LINEAR|CONVERT_TO_CURVE]\n"
            "               [-dim XY|XYZ|XYM|XYZM|layer_dim] [layer [layer ...]]\n"
            "\n"
            "Advanced options :\n"
            "               [-gt n] [-ds_transaction]\n"
            "               [[-oo NAME=VALUE] ...] [[-doo NAME=VALUE] ...]\n"
            "               [-clipsrc [xmin ymin xmax ymax]|WKT|datasource|spat_extent]\n"
            "               [-clipsrcsql sql_statement] [-clipsrclayer layer]\n"
            "               [-clipsrcwhere expression]\n"
            "               [-clipdst [xmin ymin xmax ymax]|WKT|datasource]\n"
            "               [-clipdstsql sql_statement] [-clipdstlayer layer]\n"
            "               [-clipdstwhere expression]\n"
            "               [-wrapdateline][-datelineoffset val]\n"
            "               [[-simplify tolerance] | [-segmentize max_dist]]\n"
            "               [-addfields] [-unsetFid]\n"
            "               [-relaxedFieldNameMatch] [-forceNullable] [-unsetDefault]\n"
            "               [-fieldTypeToString All|(type1[,type2]*)] [-unsetFieldWidth]\n"
            "               [-mapFieldType srctype|All=dsttype[,srctype2=dsttype2]*]\n"
            "               [-fieldmap identity | index1[,index2]*]\n"
            "               [-splitlistfields] [-maxsubfields val]\n"
            "               [-explodecollections] [-zfield field_name]\n"
            "               [-gcp pixel line easting northing [elevation]]* [-order n | -tps]\n"
            "               [-nomd] [-mo \"META-TAG=VALUE\"]* [-noNativeData]\n");

    if (bShort)
    {
        printf( "\nNote: ogr2ogr --long-usage for full help.\n");
        if( pszAdditionalMsg )
            fprintf(stderr, "\nFAILURE: %s\n", pszAdditionalMsg);
        exit( 1 );
    }

    printf("\n -f format_name: output file format name, possible values are:\n");

    std::vector<CPLString> aoSetDrivers;
    for( int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++ )
    {
        GDALDriver *poDriver = poR->GetDriver(iDriver);

        if( CPLTestBool( CSLFetchNameValueDef(poDriver->GetMetadata(), GDAL_DCAP_CREATE, "FALSE") ) )
            aoSetDrivers.push_back( poDriver->GetDescription() );
    }
    std::sort (aoSetDrivers.begin(), aoSetDrivers.end(), StringCISortFunction);
    for( size_t i = 0; i < aoSetDrivers.size(); i++ )
    {
        printf( "     -f \"%s\"\n", aoSetDrivers[i].c_str() );
    }

    printf( " -append: Append to existing layer instead of creating new if it exists\n"
            " -overwrite: delete the output layer and recreate it empty\n"
            " -update: Open existing output datasource in update mode\n"
            " -progress: Display progress on terminal. Only works if input layers have the \n"
            "                                          \"fast feature count\" capability\n"
            " -select field_list: Comma-delimited list of fields from input layer to\n"
            "                     copy to the new layer (defaults to all)\n"
            " -where restricted_where: Attribute query (like SQL WHERE)\n"
            " -wrapdateline: split geometries crossing the dateline meridian\n"
            "                (long. = +/- 180deg)\n"
            " -datelineoffset: offset from dateline in degrees\n"
            "                (default long. = +/- 10deg,\n"
            "                geometries within 170deg to -170deg will be split)\n"
            " -sql statement: Execute given SQL statement and save result.\n"
            " -dialect value: select a dialect, usually OGRSQL to avoid native sql.\n"
            " -skipfailures: skip features or layers that fail to convert\n"
            " -gt n: group n features per transaction (default 20000). n can be set to unlimited\n"
            " -spat xmin ymin xmax ymax: spatial query extents\n"
            " -simplify tolerance: distance tolerance for simplification.\n"
            " -segmentize max_dist: maximum distance between 2 nodes.\n"
            "                       Used to create intermediate points\n"
            " -dsco NAME=VALUE: Dataset creation option (format specific)\n"
            " -lco  NAME=VALUE: Layer creation option (format specific)\n"
            " -oo   NAME=VALUE: Input dataset open option (format specific)\n"
            " -doo  NAME=VALUE: Destination dataset open option (format specific)\n"
            " -nln name: Assign an alternate name to the new layer\n"
            " -nlt type: Force a geometry type for new layer.  One of NONE, GEOMETRY,\n"
            "      POINT, LINESTRING, POLYGON, GEOMETRYCOLLECTION, MULTIPOINT,\n"
            "      MULTIPOLYGON, or MULTILINESTRING, or PROMOTE_TO_MULTI or CONVERT_TO_LINEAR.  Add \"25D\" for 3D layers.\n"
            "      Default is type of source layer.\n"
            " -dim dimension: Force the coordinate dimension to the specified value.\n"
            " -fieldTypeToString type1,...: Converts fields of specified types to\n"
            "      fields of type string in the new layer. Valid types are : Integer,\n"
            "      Integer64, Real, String, Date, Time, DateTime, Binary, IntegerList, Integer64List, RealList,\n"
            "      StringList. Special value All will convert all fields to strings.\n"
            " -fieldmap index1,index2,...: Specifies the list of field indexes to be\n"
            "      copied from the source to the destination. The (n)th value specified\n"
            "      in the list is the index of the field in the target layer definition\n"
            "      in which the n(th) field of the source layer must be copied. Index count\n"
            "      starts at zero. There must be exactly as many values in the list as\n"
            "      the count of the fields in the source layer. We can use the 'identity'\n"
            "      setting to specify that the fields should be transferred by using the\n"
            "      same order. This setting should be used along with the append setting.");

    printf(" -a_srs srs_def: Assign an output SRS\n"
           " -t_srs srs_def: Reproject/transform to this SRS on output\n"
           " -s_srs srs_def: Override source SRS\n"
           "\n"
           " Srs_def can be a full WKT definition (hard to escape properly),\n"
           " or a well known definition (i.e. EPSG:4326) or a file with a WKT\n"
           " definition.\n" );

    if( pszAdditionalMsg )
        fprintf(stderr, "\nFAILURE: %s\n", pszAdditionalMsg);
}
