/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Allow a user to dissolve geometries based on an attribute.
 * Author:   Howard Butler, hobu.inc@gmail.com
 *
 ******************************************************************************
 * Copyright (c) 2007, Howard Butler
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "ogrsf_frmts.h"
#include "ogr_p.h"
#include "cpl_conv.h"
#include "cpl_string.h"
#include "ogr_api.h"
#include "commonutils.h"
#include <map>
#include <list>

static void Usage();

static int DissolveLayer(OGRDataSource *poSrcDS, OGRLayer *poSrcLayer,
                         OGRDataSource *poDstDS, char **papszLSCO,
                         const char *pszNewLayerName, int bTransform,
                         OGRSpatialReference *poOutputSRS,
                         OGRSpatialReference *poSourceSRS,
                         char **papszSelFields, int bAppend, int eGType,
                         int bOverwrite);

static int bSkipFailures = FALSE;
static int nGroupTransactions = 200;
static int bPreserveFID = FALSE;
static int nFIDToFetch = OGRNullFID;

typedef std::multimap<CPLString, OGRGeometry *> StringGeometryMMap;
typedef std::map<CPLString, OGRGeometryCollection *> StringGeometryColMap;
typedef std::map<CPLString, OGRGeometry *> StringGeometryMap;
typedef std::list<OGRGeometry *> GeometriesList;

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

MAIN_START(nArgc, papszArgv)

{
    const char *pszFormat = "ESRI Shapefile";
    const char *pszDataSource = NULL;
    const char *pszDestDataSource = NULL;
    char **papszLayers = NULL;
    char **papszDSCO = NULL, **papszLCO = NULL;
    int bTransform = FALSE;
    int bAppend = FALSE, bUpdate = FALSE, bOverwrite = FALSE;
    const char *pszOutputSRSDef = NULL;
    const char *pszSourceSRSDef = NULL;
    OGRSpatialReference *poOutputSRS = NULL;
    OGRSpatialReference *poSourceSRS = NULL;
    const char *pszNewLayerName = NULL;
    const char *pszWHERE = NULL;
    OGRGeometry *poSpatialFilter = NULL;
    const char *pszSelect;
    char **papszSelFields = NULL;
    const char *pszSQLStatement = NULL;
    int eGType = -2;

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

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

    if (nArgc < 1)
        exit(-nArgc);

    for (int iArg = 1; iArg < nArgc; iArg++)
    {
        if (EQUAL(papszArgv[iArg], "--help"))
        {
            Usage();
        }
        if ((EQUAL(papszArgv[iArg], "-f") || EQUAL(papszArgv[iArg], "-of")) &&
            iArg < nArgc - 1)
        {
            pszFormat = papszArgv[++iArg];
        }
        else if (EQUAL(papszArgv[iArg], "-dsco") && iArg < nArgc - 1)
        {
            papszDSCO = CSLAddString(papszDSCO, papszArgv[++iArg]);
        }
        else if (EQUAL(papszArgv[iArg], "-lco") && iArg < nArgc - 1)
        {
            papszLCO = CSLAddString(papszLCO, papszArgv[++iArg]);
        }
        else if (EQUAL(papszArgv[iArg], "-preserve_fid"))
        {
            bPreserveFID = TRUE;
        }
        else if (STARTS_WITH_CI(papszArgv[iArg], "-skip"))
        {
            bSkipFailures = TRUE;
        }
        else if (EQUAL(papszArgv[iArg], "-append"))
        {
            bAppend = TRUE;
        }
        else if (EQUAL(papszArgv[iArg], "-overwrite"))
        {
            bOverwrite = TRUE;
        }
        else if (EQUAL(papszArgv[iArg], "-update"))
        {
            bUpdate = TRUE;
        }
        else if (EQUAL(papszArgv[iArg], "-fid") && papszArgv[iArg + 1] != NULL)
        {
            nFIDToFetch = atoi(papszArgv[++iArg]);
        }
        else if (EQUAL(papszArgv[iArg], "-sql") && papszArgv[iArg + 1] != NULL)
        {
            pszSQLStatement = papszArgv[++iArg];
        }
        else if (EQUAL(papszArgv[iArg], "-nln") && iArg < nArgc - 1)
        {
            pszNewLayerName = papszArgv[++iArg];
        }
        else if (EQUAL(papszArgv[iArg], "-nlt") && iArg < nArgc - 1)
        {
            int bIs3D = FALSE;
            CPLString osGeomName = papszArgv[iArg + 1];
            if (strlen(papszArgv[iArg + 1]) > 3 &&
                STARTS_WITH_CI(papszArgv[iArg + 1] +
                                   strlen(papszArgv[iArg + 1]) - 3,
                               "25D"))
            {
                bIs3D = TRUE;
                osGeomName.resize(osGeomName.size() - 3);
            }
            else if (strlen(papszArgv[iArg + 1]) > 1 &&
                     STARTS_WITH_CI(papszArgv[iArg + 1] +
                                        strlen(papszArgv[iArg + 1]) - 1,
                                    "Z"))
            {
                bIs3D = TRUE;
                osGeomName.pop_back();
            }
            if (EQUAL(osGeomName, "NONE"))
                eGType = wkbNone;
            else if (EQUAL(osGeomName, "GEOMETRY"))
                eGType = wkbUnknown;
            else
            {
                eGType = OGRFromOGCGeomType(osGeomName);
                if (eGType == wkbUnknown)
                {
                    fprintf(stderr, "-nlt %s: type not recognised.\n",
                            papszArgv[iArg + 1]);
                    exit(1);
                }
            }
            if (eGType != wkbNone && bIs3D)
                eGType = wkbSetZ((OGRwkbGeometryType)eGType);
            iArg++;
        }
        else if (EQUAL(papszArgv[iArg], "-tg") && iArg < nArgc - 1)
        {
            nGroupTransactions = atoi(papszArgv[++iArg]);
        }
        else if (EQUAL(papszArgv[iArg], "-s_srs") && iArg < nArgc - 1)
        {
            pszSourceSRSDef = papszArgv[++iArg];
        }
        else if (EQUAL(papszArgv[iArg], "-a_srs") && iArg < nArgc - 1)
        {
            pszOutputSRSDef = papszArgv[++iArg];
        }
        else if (EQUAL(papszArgv[iArg], "-t_srs") && iArg < nArgc - 1)
        {
            pszOutputSRSDef = papszArgv[++iArg];
            bTransform = TRUE;
        }
        else if (EQUAL(papszArgv[iArg], "-spat") &&
                 papszArgv[iArg + 1] != NULL && papszArgv[iArg + 2] != NULL &&
                 papszArgv[iArg + 3] != NULL && papszArgv[iArg + 4] != NULL)
        {
            OGRLinearRing oRing;

            oRing.addPoint(CPLAtof(papszArgv[iArg + 1]),
                           CPLAtof(papszArgv[iArg + 2]));
            oRing.addPoint(CPLAtof(papszArgv[iArg + 1]),
                           CPLAtof(papszArgv[iArg + 4]));
            oRing.addPoint(CPLAtof(papszArgv[iArg + 3]),
                           CPLAtof(papszArgv[iArg + 4]));
            oRing.addPoint(CPLAtof(papszArgv[iArg + 3]),
                           CPLAtof(papszArgv[iArg + 2]));
            oRing.addPoint(CPLAtof(papszArgv[iArg + 1]),
                           CPLAtof(papszArgv[iArg + 2]));

            poSpatialFilter = new OGRPolygon();
            poSpatialFilter->toPolygon()->addRing(&oRing);
            iArg += 4;
        }
        else if (EQUAL(papszArgv[iArg], "-where") &&
                 papszArgv[iArg + 1] != NULL)
        {
            pszWHERE = papszArgv[++iArg];
        }
        else if (EQUAL(papszArgv[iArg], "-select") &&
                 papszArgv[iArg + 1] != NULL)
        {
            pszSelect = papszArgv[++iArg];
            papszSelFields =
                CSLTokenizeStringComplex(pszSelect, " ,", FALSE, FALSE);
        }
        else if (papszArgv[iArg][0] == '-')
        {
            Usage();
        }
        else if (pszDestDataSource == NULL)
            pszDestDataSource = papszArgv[iArg];
        else if (pszDataSource == NULL)
            pszDataSource = papszArgv[iArg];
        else
            papszLayers = CSLAddString(papszLayers, papszArgv[iArg]);
    }

    if (pszDataSource == NULL)
        Usage();

    /* -------------------------------------------------------------------- */
    /*      Open data source.                                               */
    /* -------------------------------------------------------------------- */
    OGRDataSource *poDS;

    poDS = OGRSFDriverRegistrar::Open(pszDataSource, FALSE);

    /* -------------------------------------------------------------------- */
    /*      Report failure                                                  */
    /* -------------------------------------------------------------------- */
    if (poDS == NULL)
    {
        OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar();

        printf("FAILURE:\n"
               "Unable to open datasource `%s' with the following drivers.\n",
               pszDataSource);

        for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++)
        {
            printf("  -> %s\n", poR->GetDriver(iDriver)->GetName());
        }

        exit(1);
    }

    /* -------------------------------------------------------------------- */
    /*      Try opening the output datasource as an existing, writable      */
    /* -------------------------------------------------------------------- */
    OGRDataSource *poODS;

    if (bUpdate)
    {
        poODS = OGRSFDriverRegistrar::Open(pszDestDataSource, TRUE);
        if (poODS == NULL)
        {
            printf("FAILURE:\n"
                   "Unable to open existing output datasource `%s'.\n",
                   pszDestDataSource);
            exit(1);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Find the output driver.                                         */
    /* -------------------------------------------------------------------- */
    else
    {
        OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar();
        OGRSFDriver *poDriver = NULL;
        int iDriver;

        for (iDriver = 0; iDriver < poR->GetDriverCount() && poDriver == NULL;
             iDriver++)
        {
            if (EQUAL(poR->GetDriver(iDriver)->GetName(), pszFormat))
            {
                poDriver = poR->GetDriver(iDriver);
            }
        }

        if (poDriver == NULL)
        {
            printf("Unable to find driver `%s'.\n", pszFormat);
            printf("The following drivers are available:\n");

            for (iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++)
            {
                printf("  -> `%s'\n", poR->GetDriver(iDriver)->GetName());
            }
            exit(1);
        }

        if (!poDriver->TestCapability(ODrCCreateDataSource))
        {
            printf("%s driver does not support data source creation.\n",
                   pszFormat);
            exit(1);
        }

        /* --------------------------------------------------------------------
         */
        /*      Create the output data source. */
        /* --------------------------------------------------------------------
         */
        poODS = poDriver->CreateDataSource(pszDestDataSource, papszDSCO);
        if (poODS == NULL)
        {
            printf("%s driver failed to create %s\n", pszFormat,
                   pszDestDataSource);
            exit(1);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Parse the output SRS definition if possible.                    */
    /* -------------------------------------------------------------------- */
    if (pszOutputSRSDef != NULL)
    {
        poOutputSRS = new OGRSpatialReference();
        poOutputSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
        if (poOutputSRS->SetFromUserInput(pszOutputSRSDef) != OGRERR_NONE)
        {
            printf("Failed to process SRS definition: %s\n", pszOutputSRSDef);
            exit(1);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Parse the source SRS definition if possible.                    */
    /* -------------------------------------------------------------------- */
    if (pszSourceSRSDef != NULL)
    {
        poSourceSRS = new OGRSpatialReference();
        poSourceSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
        if (poSourceSRS->SetFromUserInput(pszSourceSRSDef) != OGRERR_NONE)
        {
            printf("Failed to process SRS definition: %s\n", pszSourceSRSDef);
            exit(1);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Special case for -sql clause.  No source layers required.       */
    /* -------------------------------------------------------------------- */
    if (pszSQLStatement != NULL)
    {
        OGRLayer *poResultSet;

        if (pszWHERE != NULL)
            printf("-where clause ignored in combination with -sql.\n");
        if (CSLCount(papszLayers) > 0)
            printf("layer names ignored in combination with -sql.\n");

        poResultSet = poDS->ExecuteSQL(pszSQLStatement, poSpatialFilter, NULL);

        if (poResultSet != NULL)
        {
            if (!DissolveLayer(poDS, poResultSet, poODS, papszLCO,
                               pszNewLayerName, bTransform, poOutputSRS,
                               poSourceSRS, papszSelFields, bAppend, eGType,
                               bOverwrite))
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Terminating translation prematurely after failed\n"
                         "translation from sql statement.");

                exit(1);
            }
            poDS->ReleaseResultSet(poResultSet);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Process each data source layer.                                 */
    /* -------------------------------------------------------------------- */
    for (int iLayer = 0;
         pszSQLStatement == NULL && iLayer < poDS->GetLayerCount(); iLayer++)
    {
        OGRLayer *poLayer = poDS->GetLayer(iLayer);

        if (poLayer == NULL)
        {
            printf("FAILURE: Couldn't fetch advertised layer %d!\n", iLayer);
            exit(1);
        }

        if (CSLCount(papszLayers) == 0 ||
            CSLFindString(papszLayers, poLayer->GetLayerDefn()->GetName()) !=
                -1)
        {
            if (pszWHERE != NULL)
                poLayer->SetAttributeFilter(pszWHERE);

            if (poSpatialFilter != NULL)
                poLayer->SetSpatialFilter(poSpatialFilter);

            if (!DissolveLayer(poDS, poLayer, poODS, papszLCO, pszNewLayerName,
                               bTransform, poOutputSRS, poSourceSRS,
                               papszSelFields, bAppend, eGType, bOverwrite) &&
                !bSkipFailures)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Terminating translation prematurely after failed\n"
                         "translation of layer %s\n",
                         poLayer->GetLayerDefn()->GetName());

                exit(1);
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Close down.                                                     */
    /* -------------------------------------------------------------------- */
    OGRSpatialReference::DestroySpatialReference(poOutputSRS);
    OGRSpatialReference::DestroySpatialReference(poSourceSRS);
    OGRDataSource::DestroyDataSource(poODS);
    OGRDataSource::DestroyDataSource(poDS);

    CSLDestroy(papszSelFields);
    CSLDestroy(papszArgv);
    CSLDestroy(papszLayers);
    CSLDestroy(papszDSCO);
    CSLDestroy(papszLCO);

    OGRCleanupAll();

#ifdef DBMALLOC
    malloc_dump(1);
#endif

    return 0;
}

MAIN_END

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

static void Usage()

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

    printf("Usage: ogr2ogr [--help] [--help-general]\n"
           "               [-skipfailures] [-append] [-update]\n"
           "               [-select field_list] [-where restricted_where] \n"
           "               [-sql <sql statement>] \n"
           "               [-spat xmin ymin xmax ymax] [-preserve_fid] [-fid "
           "FID]\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] [-nlt type] [layer "
           "[layer ...]]\n"
           "\n"
           " -f format_name: output file format name, possible values are:\n");

    for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++)
    {
        OGRSFDriver *poDriver = poR->GetDriver(iDriver);

        if (poDriver->TestCapability(ODrCCreateDataSource))
            printf("     -f \"%s\"\n", poDriver->GetName());
    }

    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"
        " -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"
        " -sql statement: Execute given SQL statement and save result.\n"
        " -skipfailures: skip features or layers that fail to convert\n"
        " -spat xmin ymin xmax ymax: spatial query extents\n"
        " -dsco NAME=VALUE: Dataset creation option (format specific)\n"
        " -lco  NAME=VALUE: Layer creation 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, "
        "MULTILINE,\n"
        "      MULTIPOLYGON, or MULTILINESTRING.  Add \"25D\" for 3D layers.\n"
        "      Default is type of source layer.\n");

    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");

    exit(1);
}

StringGeometryMap *CollectGeometries(OGRLayer *poSrcLayer,
                                     const char **papszFields)
{

    /* -------------------------------------------------------------------- */
    /*      CollectGeometries returns a dictionary where the keys are the   */
    /*      values in the fields that the user has selected and the values  */
    /*      are a GeometryCollection of all of the geometries for records   */
    /*      with that value.                                                */
    /* -------------------------------------------------------------------- */

    StringGeometryMMap poGeometriesMap;

    poSrcLayer->ResetReading();

    int iField;
    /* -------------------------------------------------------------------- */
    /*      Get all of the features and put them in a multi map.  This may   */
    /*      include values for which the selected fields is NULL.           */
    /* -------------------------------------------------------------------- */

    for (auto &poFeature : poSrcLayer)
    {
        CPLString poKey("");

        for (iField = 0; papszFields[iField] != NULL; iField++)
        {
            int nField = poFeature->GetFieldIndex(papszFields[iField]);
            poKey = poKey + poFeature->GetFieldAsString(nField);
        }

        if (poFeature->GetGeometryRef()->IsValid())
        {
            poGeometriesMap.insert(
                std::make_pair(CPLString(poKey), poFeature->GetGeometryRef()));
        }
        else
        {
            CPLDebug("CollectGeometries",
                     "Geometry was invalid not adding!!!!");
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Loop through our features map and get a unique list of field    */
    /*      values.  This could be done using something other than a map    */
    /*      of course, but it was convenient.                               */
    /* -------------------------------------------------------------------- */

    typedef std::map<CPLString, int> StringIntMap;
    StringIntMap::const_iterator ipos;
    StringIntMap poFieldsmap;

    StringGeometryMMap::const_iterator pos;

    for (pos = poGeometriesMap.begin(); pos != poGeometriesMap.end(); ++pos)
    {

        /* we currently throw out any null field values at this time */
        //  if (!(pos->first.empty())) {
        poFieldsmap[CPLString(pos->first.c_str())] = 1;
        //    }
    }

    /* -------------------------------------------------------------------- */
    /*      Make a new map of GeometryCollection for each value in the     */
    /*      poFieldsmap.  This is a 1:1 relationship, and all of the        */
    /*      geometries for a given field are all put into the same          */
    /*      GeometryCollection.  After we build the poCollections, we will  */
    /*      use GEOS' buffer(0) trick to have GEOS perform the segmentation */
    /* -------------------------------------------------------------------- */

    StringGeometryColMap poCollections;

    CPLDebug("CollectGeometries", "Field map size: %d", poFieldsmap.size());

    for (ipos = poFieldsmap.begin(); ipos != poFieldsmap.end(); ++ipos)
    {

        CPLString fid = ipos->first;
        CPLDebug("CollectGeometries", "First %s Second %d", ipos->first.c_str(),
                 ipos->second);

        OGRGeometryCollection *geom = new OGRGeometryCollection;

        for (pos = poGeometriesMap.lower_bound(fid);
             pos != poGeometriesMap.upper_bound(fid); ++pos)
        {
            geom->addGeometry(pos->second);
        }
        poCollections.insert(std::make_pair(fid, geom));
    }

    CPLDebug("CollectGeometries", "Geo map size: %d", poCollections.size());

    /* -------------------------------------------------------------------- */
    /*      Loop through our poCollections map and buffer(0) each           */
    /*      GeometryCollection.  GEOS will collapse the geometries down     */
    /* -------------------------------------------------------------------- */

    StringGeometryMap *buffers = new StringGeometryMap;

    StringGeometryColMap::const_iterator collections_i;

    for (collections_i = poCollections.begin();
         collections_i != poCollections.end(); ++collections_i)
    {
        CPLDebug("CollectGeometries", "poCollections Geometry size %d",
                 collections_i->second->getNumGeometries());
        OGRGeometry *buffer = collections_i->second->Buffer(0);
        buffers->insert(std::make_pair(collections_i->first, buffer));
    }

    for (collections_i = poCollections.begin();
         collections_i != poCollections.end(); ++collections_i)
    {
        delete collections_i->second;
    }

    return buffers;
}

GeometriesList *FlattenGeometries(GeometriesList *input)
{

    GeometriesList::const_iterator geometry_i;
    GeometriesList *output = new GeometriesList;

    CPLDebug("CollectGeometries",
             "Input geometries in FlattenGeometries size: %d", input->size());
    for (geometry_i = input->begin(); geometry_i != input->end(); ++geometry_i)
    {

        OGRGeometry *buffer = (*geometry_i);
        // int nGeometries = buffer->getNumGeometries();
        OGRwkbGeometryType iGType = buffer->getGeometryType();

        if (iGType == wkbPolygon)
        {
            output->push_back(buffer);
            CPLDebug("CollectGeometries",
                     "Collapsing wkbPolygon geometries......");
        }
        if (iGType == wkbMultiPolygon)
        {
            OGRMultiPolygon *geom = buffer->toMultiPolygon();
            for (int i = 0; i < geom->getNumGeometries(); i++)
            {
                OGRPolygon *g = geom->getGeometryRef(i)->toPolygon();
                output->push_back((OGRGeometry *)g);
            }

            CPLDebug("CollectGeometries",
                     "Collapsing wkbMultiPolygon geometries......");
        }
        if (iGType == wkbGeometryCollection)
        {
            OGRGeometryCollection *geom = buffer->toGeometryCollection();
            GeometriesList *collection = new GeometriesList;
            GeometriesList::const_iterator g_i;
            for (int i = 0; i < geom->getNumGeometries(); i++)
            {
                OGRGeometry *g = geom->getGeometryRef(i);
                collection->push_back(g);
            }
            GeometriesList *collapsed = FlattenGeometries(collection);
            for (g_i = collapsed->begin(); g_i != collapsed->end(); g_i++)
            {
                output->push_back((OGRGeometry *)(*g_i));
                CPLDebug("CollectGeometries",
                         "Collapsing wkbGeometryCollection geometries.");
            }
        }
        // CPLDebug(   "CollectGeometries",
        //             "Buffered Geometry size %d",
        //             nGeometries);
    }

    return output;
}

/************************************************************************/
/*                           DissolveLayer()                            */
/************************************************************************/

static int DissolveLayer(OGRDataSource *poSrcDS, OGRLayer *poSrcLayer,
                         OGRDataSource *poDstDS, char **papszLCO,
                         const char *pszNewLayerName, int bTransform,
                         OGRSpatialReference *poOutputSRS,
                         OGRSpatialReference *poSourceSRS,
                         char **papszSelFields, int bAppend, int eGType,
                         int bOverwrite)

{

    OGRLayer *poDstLayer;
    OGRFeatureDefn *poFDefn;
    OGRErr eErr;
    int bForceToPolygon = FALSE;
    int bForceToMultiPolygon = FALSE;

    if (pszNewLayerName == NULL)
        pszNewLayerName = poSrcLayer->GetLayerDefn()->GetName();

    if (wkbFlatten(eGType) == wkbPolygon)
        bForceToPolygon = TRUE;
    else if (wkbFlatten(eGType) == wkbMultiPolygon)
        bForceToMultiPolygon = TRUE;

    /* -------------------------------------------------------------------- */
    /*      Setup coordinate transformation if we need it.                  */
    /* -------------------------------------------------------------------- */
    OGRCoordinateTransformation *poCT = NULL;

    if (bTransform)
    {
        if (poSourceSRS == NULL)
            poSourceSRS = poSrcLayer->GetSpatialRef();

        if (poSourceSRS == NULL)
        {
            printf("Can't transform coordinates, source layer has no\n"
                   "coordinate system.  Use -s_srs to set one.\n");
            exit(1);
        }

        CPLAssert(NULL != poSourceSRS);
        CPLAssert(NULL != poOutputSRS);

        poCT = OGRCreateCoordinateTransformation(poSourceSRS, poOutputSRS);
        if (poCT == NULL)
        {
            char *pszWKT = NULL;

            printf("Failed to create coordinate transformation between the\n"
                   "following coordinate systems.  This may be because they\n"
                   "are not transformable, or because projection services\n"
                   "(PROJ.4 DLL/.so) could not be loaded.\n");

            poSourceSRS->exportToPrettyWkt(&pszWKT, FALSE);
            printf("Source:\n%s\n", pszWKT);

            poOutputSRS->exportToPrettyWkt(&pszWKT, FALSE);
            printf("Target:\n%s\n", pszWKT);
            exit(1);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Get other info.                                                 */
    /* -------------------------------------------------------------------- */
    poFDefn = poSrcLayer->GetLayerDefn();

    if (poOutputSRS == NULL)
        poOutputSRS = poSrcLayer->GetSpatialRef();

    /* -------------------------------------------------------------------- */
    /*      Find the layer.                                                 */
    /* -------------------------------------------------------------------- */
    int iLayer = -1;
    poDstLayer = NULL;

    for (iLayer = 0; iLayer < poDstDS->GetLayerCount(); iLayer++)
    {
        OGRLayer *poLayer = poDstDS->GetLayer(iLayer);

        if (poLayer != NULL &&
            EQUAL(poLayer->GetLayerDefn()->GetName(), pszNewLayerName))
        {
            poDstLayer = poLayer;
            break;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      If the user requested overwrite, and we have the layer in       */
    /*      question we need to delete it now so it will get recreated      */
    /*      (overwritten).                                                  */
    /* -------------------------------------------------------------------- */
    if (poDstLayer != NULL && bOverwrite)
    {
        if (poDstDS->DeleteLayer(iLayer) != OGRERR_NONE)
        {
            fprintf(stderr, "DeleteLayer() failed when overwrite requested.\n");
            return FALSE;
        }
        poDstLayer = NULL;
    }

    /* -------------------------------------------------------------------- */
    /*      If the layer does not exist, then create it.                    */
    /* -------------------------------------------------------------------- */
    if (poDstLayer == NULL)
    {
        if (eGType == -2)
            eGType = poFDefn->GetGeomType();

        if (!poDstDS->TestCapability(ODsCCreateLayer))
        {
            fprintf(
                stderr,
                "Layer %s not found, and CreateLayer not supported by driver.",
                pszNewLayerName);
            return FALSE;
        }

        CPLErrorReset();

        poDstLayer = poDstDS->CreateLayer(pszNewLayerName, poOutputSRS,
                                          (OGRwkbGeometryType)eGType, papszLCO);

        if (poDstLayer == NULL)
            return FALSE;

        bAppend = FALSE;
    }

    /* -------------------------------------------------------------------- */
    /*      Otherwise we will append to it, if append was requested.        */
    /* -------------------------------------------------------------------- */
    else if (!bAppend)
    {
        printf("FAILED: Layer %s already exists, and -append not specified.\n"
               "        Consider using -append, or -overwrite.\n",
               pszNewLayerName);
        return FALSE;
    }

    /* -------------------------------------------------------------------- */
    /*      Add fields.  Default to copy all field.                         */
    /*      If only a subset of all fields requested, then output only      */
    /*      the selected fields, and in the order that they were            */
    /*      selected.                                                       */
    /* -------------------------------------------------------------------- */
    int iField;

    if (papszSelFields && !bAppend)
    {
        for (iField = 0; papszSelFields[iField] != NULL; iField++)
        {
            int iSrcField = poFDefn->GetFieldIndex(papszSelFields[iField]);
            if (iSrcField >= 0)
                poDstLayer->CreateField(poFDefn->GetFieldDefn(iSrcField));
            else
            {
                printf("Field '%s' not found in source layer.\n",
                       papszSelFields[iField]);
                if (!bSkipFailures)
                    return FALSE;
            }
        }
    }
    else if (!bAppend)
    {
        for (iField = 0; iField < poFDefn->GetFieldCount(); iField++)
            poDstLayer->CreateField(poFDefn->GetFieldDefn(iField));
    }

    /* -------------------------------------------------------------------- */
    /*      Transfer features.                                              */
    /* -------------------------------------------------------------------- */
    OGRFeature *poFeature;
    int nFeaturesInTransaction = 0;

    poSrcLayer->ResetReading();

    if (nGroupTransactions)
        poDstLayer->StartTransaction();

    StringGeometryMap *buffers =
        CollectGeometries(poSrcLayer, (const char **)papszSelFields);

    StringGeometryMap::const_iterator buffers_i;
    GeometriesList *input = new GeometriesList;

    CPLDebug("CollectGeometries", "Buffers size: %d", buffers->size());
    for (buffers_i = buffers->begin(); buffers_i != buffers->end(); ++buffers_i)
    {
        input->push_back(buffers_i->second);
    }
    GeometriesList *geometries = FlattenGeometries(input);

    GeometriesList::const_iterator g_i;
    for (g_i = geometries->begin(); g_i != geometries->end(); g_i++)
    {
        OGRFeature *feature = new OGRFeature(poFDefn);
        feature->SetGeometry((*g_i));
        feature->SetField("TAXDIST", "fid");
        poDstLayer->CreateFeature(feature);
    }

    if (nGroupTransactions)
        poDstLayer->CommitTransaction();

    //  getGeometryType
    //     if( pszNewLayerName == NULL )
    //         pszNewLayerName = poSrcLayer->GetLayerDefn()->GetName();
    //
    //     if( wkbFlatten(eGType) == wkbPolygon )
    //         bForceToPolygon = TRUE;
    //     else if( wkbFlatten(eGType) == wkbMultiPolygon )
    //         bForceToMultiPolygon = TRUE;
    //
    // /* --------------------------------------------------------------------
    // */
    // /*      Setup coordinate transformation if we need it. */
    // /* --------------------------------------------------------------------
    // */
    //     OGRCoordinateTransformation *poCT = NULL;
    //
    //     if( bTransform )
    //     {
    //         if( poSourceSRS == NULL )
    //             poSourceSRS = poSrcLayer->GetSpatialRef();
    //
    //         if( poSourceSRS == NULL )
    //         {
    //             printf( "Can't transform coordinates, source layer has no\n"
    //                     "coordinate system.  Use -s_srs to set one.\n" );
    //             exit( 1 );
    //         }
    //
    //         CPLAssert( NULL != poSourceSRS );
    //         CPLAssert( NULL != poOutputSRS );
    //
    //         poCT = OGRCreateCoordinateTransformation( poSourceSRS,
    //         poOutputSRS ); if( poCT == NULL )
    //         {
    //             char        *pszWKT = NULL;
    //
    //             printf("Failed to create coordinate transformation between
    //             the\n"
    //                    "following coordinate systems.  This may be because
    //                    they\n" "are not transformable, or because projection
    //                    services\n"
    //                    "(PROJ.4 DLL/.so) could not be loaded.\n" );
    //
    //             poSourceSRS->exportToPrettyWkt( &pszWKT, FALSE );
    //             printf( "Source:\n%s\n", pszWKT );
    //
    //             poOutputSRS->exportToPrettyWkt( &pszWKT, FALSE );
    //             printf( "Target:\n%s\n", pszWKT );
    //             exit( 1 );
    //         }
    //     }
    //
    // /* --------------------------------------------------------------------
    // */
    // /*      Get other info. */
    // /* --------------------------------------------------------------------
    // */
    //     poFDefn = poSrcLayer->GetLayerDefn();
    //
    //     if( poOutputSRS == NULL )
    //         poOutputSRS = poSrcLayer->GetSpatialRef();
    //
    // /* --------------------------------------------------------------------
    // */
    // /*      Find the layer. */
    // /* --------------------------------------------------------------------
    // */
    //     int iLayer = -1;
    //     poDstLayer = NULL;
    //
    //     for( iLayer = 0; iLayer < poDstDS->GetLayerCount(); iLayer++ )
    //     {
    //         OGRLayer        *poLayer = poDstDS->GetLayer(iLayer);
    //
    //         if( poLayer != NULL
    //             && EQUAL(poLayer->GetLayerDefn()->GetName(),pszNewLayerName)
    //             )
    //         {
    //             poDstLayer = poLayer;
    //             break;
    //         }
    //     }
    //
    // /* --------------------------------------------------------------------
    // */
    // /*      If the user requested overwrite, and we have the layer in */
    // /*      question we need to delete it now so it will get recreated */
    // /*      (overwritten). */
    // /* --------------------------------------------------------------------
    // */
    //     if( poDstLayer != NULL && bOverwrite )
    //     {
    //         if( poDstDS->DeleteLayer( iLayer ) != OGRERR_NONE )
    //         {
    //             fprintf( stderr,
    //                      "DeleteLayer() failed when overwrite requested.\n"
    //                      );
    //             return FALSE;
    //         }
    //         poDstLayer = NULL;
    //     }
    //
    // /* --------------------------------------------------------------------
    // */
    // /*      If the layer does not exist, then create it. */
    // /* --------------------------------------------------------------------
    // */
    //     if( poDstLayer == NULL )
    //     {
    //         if( eGType == -2 )
    //             eGType = poFDefn->GetGeomType();
    //
    //         if( !poDstDS->TestCapability( ODsCCreateLayer ) )
    //         {
    //             fprintf( stderr,
    //               "Layer %s not found, and CreateLayer not supported by
    //               driver.",
    //                      pszNewLayerName );
    //             return FALSE;
    //         }
    //
    //         CPLErrorReset();
    //
    //         poDstLayer = poDstDS->CreateLayer( pszNewLayerName, poOutputSRS,
    //                                            (OGRwkbGeometryType) eGType,
    //                                            papszLCO );
    //
    //         if( poDstLayer == NULL )
    //             return FALSE;
    //
    //         bAppend = FALSE;
    //     }
    //
    // /* --------------------------------------------------------------------
    // */
    // /*      Otherwise we will append to it, if append was requested. */
    // /* --------------------------------------------------------------------
    // */
    //     else if( !bAppend )
    //     {
    //         printf( "FAILED: Layer %s already exists, and -append not
    //         specified.\n"
    //                 "        Consider using -append, or -overwrite.\n",
    //                 pszNewLayerName );
    //         return FALSE;
    //     }
    //
    // /* --------------------------------------------------------------------
    // */
    // /*      Add fields.  Default to copy all field. */
    // /*      If only a subset of all fields requested, then output only */
    // /*      the selected fields, and in the order that they were */
    // /*      selected. */
    // /* --------------------------------------------------------------------
    // */
    //     int         iField;
    //
    //     if (papszSelFields && !bAppend )
    //     {
    //         for( iField=0; papszSelFields[iField] != NULL; iField++)
    //         {
    //             int iSrcField =
    //             poFDefn->GetFieldIndex(papszSelFields[iField]); if (iSrcField
    //             >= 0)
    //                 poDstLayer->CreateField( poFDefn->GetFieldDefn(iSrcField)
    //                 );
    //             else
    //             {
    //                 printf( "Field '%s' not found in source layer.\n",
    //                         papszSelFields[iField] );
    //                 if( !bSkipFailures )
    //                     return FALSE;
    //             }
    //         }
    //     }
    //     else if( !bAppend )
    //     {
    //         for( iField = 0; iField < poFDefn->GetFieldCount(); iField++ )
    //             poDstLayer->CreateField( poFDefn->GetFieldDefn(iField) );
    //     }
    //
    // /* --------------------------------------------------------------------
    // */
    // /*      Transfer features. */
    // /* --------------------------------------------------------------------
    // */
    //     OGRFeature  *poFeature;
    //     int         nFeaturesInTransaction = 0;
    //
    //     poSrcLayer->ResetReading();
    //
    //     if( nGroupTransactions )
    //         poDstLayer->StartTransaction();
    //
    //     while( true )
    //     {
    //         OGRFeature      *poDstFeature = NULL;
    //
    //         if( nFIDToFetch != OGRNullFID )
    //         {
    //             // Only fetch feature on first pass.
    //             if( nFeaturesInTransaction == 0 )
    //                 poFeature = poSrcLayer->GetFeature(nFIDToFetch);
    //             else
    //                 poFeature = NULL;
    //         }
    //         else
    //             poFeature = poSrcLayer->GetNextFeature();
    //
    //         if( poFeature == NULL )
    //             break;
    //
    //         if( ++nFeaturesInTransaction == nGroupTransactions )
    //         {
    //             poDstLayer->CommitTransaction();
    //             poDstLayer->StartTransaction();
    //             nFeaturesInTransaction = 0;
    //         }
    //
    //         CPLErrorReset();
    //            poFDefn = poSrcLayer->GetLayerDefn();
    //
    //         if( poDstFeature->SetFrom( poFeature, TRUE ) != OGRERR_NONE )
    //         {
    //             if( nGroupTransactions )
    //                 poDstLayer->CommitTransaction();
    //
    //             CPLError( CE_Failure, CPLE_AppDefined,
    //                       "Unable to translate feature %d from layer %s.\n",
    //                       poFeature->GetFID(), poFDefn->GetName() );
    //
    //             OGRFeature::DestroyFeature( poFeature );
    //             OGRFeature::DestroyFeature( poDstFeature );
    //             return FALSE;
    //         }
    //
    //         if( bPreserveFID )
    //             poDstFeature->SetFID( poFeature->GetFID() );
    //
    //         if( poCT && poDstFeature->GetGeometryRef() != NULL )
    //         {
    //             eErr = poDstFeature->GetGeometryRef()->transform( poCT );
    //             if( eErr != OGRERR_NONE )
    //             {
    //                 if( nGroupTransactions )
    //                     poDstLayer->CommitTransaction();
    //
    //                 printf( "Failed to transform feature %d.\n",
    //                         static_cast<int>(poFeature->GetFID()) );
    //                 if( !bSkipFailures )
    //                 {
    //                     OGRFeature::DestroyFeature( poFeature );
    //                     OGRFeature::DestroyFeature( poDstFeature );
    //                     return FALSE;
    //                 }
    //             }
    //         }
    //
    //         if( poDstFeature->GetGeometryRef() != NULL && bForceToPolygon )
    //         {
    //             poDstFeature->SetGeometryDirectly(
    //                 OGRGeometryFactory::forceToPolygon(
    //                     poDstFeature->StealGeometry() ) );
    //         }
    //
    //         if( poDstFeature->GetGeometryRef() != NULL &&
    //         bForceToMultiPolygon )
    //         {
    //             poDstFeature->SetGeometryDirectly(
    //                 OGRGeometryFactory::forceToMultiPolygon(
    //                     poDstFeature->StealGeometry() ) );
    //         }
    //
    //         OGRFeature::DestroyFeature( poFeature );
    //
    //         CPLErrorReset();
    //         if( poDstLayer->CreateFeature( poDstFeature ) != OGRERR_NONE
    //             && !bSkipFailures )
    //         {
    //             if( nGroupTransactions )
    //                 poDstLayer->RollbackTransaction();
    //
    //             OGRFeature::DestroyFeature( poDstFeature );
    //             return FALSE;
    //         }
    //
    //         OGRFeature::DestroyFeature( poDstFeature );
    //     }
    //
    //     if( nGroupTransactions )
    //         poDstLayer->CommitTransaction();
    //
    // /* --------------------------------------------------------------------
    // */
    // /*      Cleaning */
    // /* --------------------------------------------------------------------
    // */
    //     delete poCT;

    return TRUE;
}
