/******************************************************************************
 * Project:  Geography Network utility
 * Purpose:  To manage GNM networks
 * Authors:  Mikhail Gusev, gusevmihs at gmail dot com
 *           Dmitry Baryshnikov, polimax@mail.ru
 *
 ******************************************************************************
 * Copyright (c) 2014, Mikhail Gusev
 * Copyright (c) 2014-2015, NextGIS <info@nextgis.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "commonutils.h"
#include "cpl_string.h"
#include "gdal_version.h"
#include "gdal.h"
#include "gnm.h"
#include "gnm_priv.h"

// #include "ogr_p.h"
// #include "gnm.h"
// #include "gnm_api.h"

enum operation
{
    op_unknown = 0, /** no operation */
    op_info,        /** print information about network */
    op_create,      /** create a new network */
    op_import,      /** add a OGR layer to the network */
    op_connect,     /** connect features from layers added to the network */
    op_disconnect,  /** disconnect features from layers added to the network */
    op_rule,        /** add connect rule */
    op_autoconnect, /** try to connect features base on their tolerance */
    op_delete,      /** delete network */
    op_change_st    /** change vertex or edge blocking state */
};

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

static void Usage(bool bIsError, const char *pszAdditionalMsg = nullptr,
                  bool bShort = true, bool bHelpDoc = false) CPL_NO_RETURN;

static void Usage(bool bIsError, const char *pszAdditionalMsg, bool bShort,
                  bool bHelpDoc)
{
    fprintf(
        bIsError ? stderr : stdout,
        "Usage: gnmmanage [--help][--help-general][-q][-quiet][--long-usage]\n"
        "                 [info]\n"
        "                 [create [-f <format_name>] [-t_srs <srs_name>] "
        "[-dsco "
        "<NAME>=<VALUE>]... ]\n"
        "                 [import <src_dataset_name>] [-l <layer_name>]\n"
        "                 [connect <gfid_src> <gfid_tgt> <gfid_con> [-c "
        "<cost>] "
        "[-ic <inv_cost>] [-dir <dir>]]\n"
        "                 [disconnect <gfid_src> <gfid_tgt> <gfid_con>]\n"
        "                 [rule <rule_str>]\n"
        "                 [autoconnect <tolerance>]\n"
        "                 [delete]\n"
        "                 [change [-bl <gfid>][-unbl <gfid>][-unblall]]\n"
        "                 <gnm_name> [<layer> [<layer>]...]\n");

    if (bHelpDoc)
    {
        exit(0);
    }

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

    fprintf(bIsError ? stderr : stdout,
            "\n   info: different information about network: system and class "
            "layers, network metadata, network spatial reference\n"
            "   create: create network\n"
            "      -f format_name: output file format name, possible values "
            "are:\n");

    int nGNMDriverCounter = 1;
    for (int iDr = 0; iDr < GDALGetDriverCount(); iDr++)
    {
        GDALDriverH hDriver = GDALGetDriver(iDr);

        const char *pszRFlag = "", *pszWFlag, *pszVirtualIO, *pszSubdatasets;
        char **papszMD = GDALGetMetadata(hDriver, nullptr);

        if (CPLFetchBool(papszMD, GDAL_DCAP_RASTER, false))
            continue;
        if (CPLFetchBool(papszMD, GDAL_DCAP_VECTOR, false))
            continue;

        if (CPLFetchBool(papszMD, GDAL_DCAP_OPEN, false))
            pszRFlag = "r";

        if (CPLFetchBool(papszMD, GDAL_DCAP_CREATE, false))
            pszWFlag = "w+";
        else if (CPLFetchBool(papszMD, GDAL_DCAP_CREATECOPY, false))
            pszWFlag = "w";
        else
            pszWFlag = "o";

        if (CPLFetchBool(papszMD, GDAL_DCAP_VIRTUALIO, false))
            pszVirtualIO = "v";
        else
            pszVirtualIO = "";

        if (CPLFetchBool(papszMD, GDAL_DMD_SUBDATASETS, false))
            pszSubdatasets = "s";
        else
            pszSubdatasets = "";

        fprintf(bIsError ? stderr : stdout, "          %d. %s (%s%s%s%s): %s\n",
                nGNMDriverCounter++, GDALGetDriverShortName(hDriver), pszRFlag,
                pszWFlag, pszVirtualIO, pszSubdatasets,
                GDALGetDriverLongName(hDriver));
    }

    fprintf(
        bIsError ? stderr : stdout,
        "      -t_srs srs_name: spatial reference input\n"
        "      -dsco NAME=VALUE: network creation option set as pair=value\n"
        "   import src_dataset_name: import external layer where "
        "src_dataset_name is a dataset name to copy from\n"
        "      -l layer_name: layer name in dataset. If unset, 0 layer is "
        "copied\n"
        "   connect gfid_src gfid_tgt gfid_con: make a topological connection, "
        "where the gfid_src and gfid_tgt are vertices and gfid_con is edge "
        "(gfid_con can be -1, so the virtual connection will be created)\n"
        "      -c cost -ic inv_cost -dir dir: manually assign the following "
        "values: the cost (weight), inverse cost and direction of the edge "
        "(optional)\n"
        "   disconnect gfid_src gfid_tgt gfid_con: removes the connection from "
        "the graph\n"
        "   rule rule_str: creates a rule in the network by the given rule_str "
        "string\n"
        "   autoconnect tolerance: create topology automatically with the "
        "given double tolerance\n"
        "   delete: delete network\n"
        "   change: modify blocking state of vertices and edges and save them "
        "in the network"
        "   -bl gfid: block feature before the main operation. Blocking "
        "features are saved in the special layer\n"
        "   -unbl gfid: unblock feature before the main operation\n"
        "   -unblall: unblock all blocked features before the main operation\n"
        "   gnm_name: the network to work with (path and name)\n");

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

    exit(bIsError ? 1 : 0);
}

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

#define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg)                            \
    do                                                                         \
    {                                                                          \
        if (iArg + nExtraArg >= nArgc)                                         \
            Usage(true, CPLSPrintf("%s option requires %d argument(s)",        \
                                   papszArgv[iArg], nExtraArg));               \
    } while (false)

MAIN_START(nArgc, papszArgv)

{
    int bQuiet = FALSE;
    const char *pszFormat = nullptr;
    const char *pszSRS = nullptr;
    GNMGFID nSrcFID = -1;
    GNMGFID nTgtFID = -1;
    GNMGFID nConFID = -1;
    double dfDirCost = 1.0;
    double dfInvCost = 1.0;
    GNMDirection eDir = GNM_EDGE_DIR_BOTH;
    const char *pszRuleStr = "";
    const char *pszDataSource = nullptr;
    char **papszDSCO = nullptr;
    const char *pszInputDataset = nullptr;
    const char *pszInputLayer = nullptr;
    double dfTolerance = 0.0001;
    operation stOper = op_unknown;
    char **papszLayers = nullptr;
    GNMNetwork *poDS = nullptr;
    std::vector<GNMGFID> anFIDsToBlock;
    std::vector<GNMGFID> anFIDsToUnblock;
    bool bUnblockAll = false;
    int nRet = 0;

    // 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).                                             */
    /* -------------------------------------------------------------------- */
    GDALAllRegister();

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

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

    for (int iArg = 1; iArg < nArgc; iArg++)
    {
        if (EQUAL(papszArgv[1], "--utility_version"))
        {
            printf("%s was compiled against GDAL %s and is running against "
                   "GDAL %s\n",
                   papszArgv[0], GDAL_RELEASE_NAME,
                   GDALVersionInfo("RELEASE_NAME"));
            CSLDestroy(papszArgv);
            return 0;
        }

        else if (EQUAL(papszArgv[iArg], "--help"))
        {
            Usage(false);
        }

        else if (EQUAL(papszArgv[iArg], "--help-doc"))
        {
            Usage(false, nullptr, true, true);
        }

        else if (EQUAL(papszArgv[iArg], "--long-usage"))
        {
            Usage(false, nullptr, false);
        }

        else if (EQUAL(papszArgv[iArg], "-q") ||
                 EQUAL(papszArgv[iArg], "-quiet"))
        {
            bQuiet = TRUE;
        }

        else if (EQUAL(papszArgv[iArg], "info"))
        {
            stOper = op_info;
        }

        else if (EQUAL(papszArgv[iArg], "-f") || EQUAL(papszArgv[iArg], "-of"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            pszFormat = papszArgv[++iArg];
        }

        else if (EQUAL(papszArgv[iArg], "-dsco"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            papszDSCO = CSLAddString(papszDSCO, papszArgv[++iArg]);
        }

        else if (EQUAL(papszArgv[iArg], "create"))
        {
            stOper = op_create;
        }

        else if (EQUAL(papszArgv[iArg], "-t_srs"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            pszSRS = papszArgv[++iArg];
        }

        else if (EQUAL(papszArgv[iArg], "import"))
        {
            stOper = op_import;
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            pszInputDataset = papszArgv[++iArg];
        }

        else if (EQUAL(papszArgv[iArg], "-l"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            pszInputLayer = papszArgv[++iArg];
        }

        else if (EQUAL(papszArgv[iArg], "connect"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(3);
            stOper = op_connect;
            // coverity[tainted_data]
            nSrcFID = atoi(papszArgv[++iArg]);
            // coverity[tainted_data]
            nTgtFID = atoi(papszArgv[++iArg]);
            // coverity[tainted_data]
            nConFID = atoi(papszArgv[++iArg]);
        }

        else if (EQUAL(papszArgv[iArg], "-c"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            dfDirCost = CPLAtofM(papszArgv[++iArg]);
        }
        else if (EQUAL(papszArgv[iArg], "-ic"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            dfInvCost = CPLAtofM(papszArgv[++iArg]);
        }
        else if (EQUAL(papszArgv[iArg], "-dir"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            eDir = atoi(papszArgv[++iArg]);
        }

        else if (EQUAL(papszArgv[iArg], "disconnect"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(3);
            stOper = op_disconnect;
            // coverity[tainted_data]
            nSrcFID = atoi(papszArgv[++iArg]);
            // coverity[tainted_data]
            nTgtFID = atoi(papszArgv[++iArg]);
            // coverity[tainted_data]
            nConFID = atoi(papszArgv[++iArg]);
        }

        else if (EQUAL(papszArgv[iArg], "autoconnect"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            stOper = op_autoconnect;
            // coverity[tainted_data]
            dfTolerance = CPLAtofM(papszArgv[++iArg]);
        }

        else if (EQUAL(papszArgv[iArg], "rule"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            stOper = op_rule;
            // coverity[tainted_data]
            pszRuleStr = papszArgv[++iArg];
        }

        else if (EQUAL(papszArgv[iArg], "delete"))
        {
            stOper = op_delete;
        }

        else if (EQUAL(papszArgv[iArg], "change"))
        {
            stOper = op_change_st;
        }

        else if (EQUAL(papszArgv[iArg], "-bl"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            anFIDsToBlock.push_back(atoi(papszArgv[++iArg]));
        }

        else if (EQUAL(papszArgv[iArg], "-unbl"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            // coverity[tainted_data]
            anFIDsToUnblock.push_back(atoi(papszArgv[++iArg]));
        }

        else if (EQUAL(papszArgv[iArg], "-unblall"))
        {
            bUnblockAll = true;
        }

        else if (papszArgv[iArg][0] == '-')
        {
            Usage(true,
                  CPLSPrintf("Unknown option name '%s'", papszArgv[iArg]));
        }

        else if (pszDataSource == nullptr)
            pszDataSource = papszArgv[iArg];
        else
            papszLayers = CSLAddString(papszLayers, papszArgv[iArg]);
    }

    // do the work
    // ////////////////////////////////////////////////////////////////

    if (stOper == op_info)
    {
        if (pszDataSource == nullptr)
            Usage(true, "No network dataset provided");

        // TODO for output:
        //  stats about graph and blocked features

        // open

        poDS = cpl::down_cast<GNMNetwork *>(static_cast<GDALDataset *>(
            GDALOpenEx(pszDataSource, GDAL_OF_READONLY | GDAL_OF_GNM, nullptr,
                       nullptr, nullptr)));

        GDALDriver *poDriver = nullptr;
        if (poDS != nullptr)
            poDriver = poDS->GetDriver();

        if (poDS == nullptr)
        {
            fprintf(stderr, "FAILURE:\nUnable to open datasource `%s'.\n",
                    pszDataSource);
            exit(1);
        }

        if (poDriver == nullptr)
        {
            CPLAssert(false);
            exit(1);
        }

        printf("INFO: Open of `%s'\n      using driver `%s' successful.\n",
               pszDataSource, poDriver->GetDescription());

        // Report projection.
        int nMajor = poDS->GetVersion() / 100;
        printf("Network version: %d.%d.\n", nMajor,
               poDS->GetVersion() - nMajor * 100);
        const char *pszName = poDS->GetName();
        if (nullptr != pszName)
            printf("Network name: %s.\n", pszName);
        const char *pszDescript = poDS->GetDescription();
        if (nullptr != pszDescript)
            printf("Network description: %s.\n", pszDescript);

        char *pszProjection = const_cast<char *>(poDS->GetProjectionRef());
        OGRSpatialReferenceH hSRS = OSRNewSpatialReference(nullptr);
        if (OSRImportFromWkt(hSRS, &pszProjection) == CE_None)
        {
            char *pszPrettyWkt = nullptr;
            OSRExportToPrettyWkt(hSRS, &pszPrettyWkt, FALSE);

            printf("Coordinate System is:\n%s\n", pszPrettyWkt);
            CPLFree(pszPrettyWkt);
        }
        else
        {
            printf("Coordinate System is '%s'\n", pszProjection);
        }
        OSRDestroySpatialReference(hSRS);

        // report layers
        if (poDS->GetLayerCount() > 0)
        {
            printf("\nNetwork\'s layers: \n");
            for (int iLayer = 0; iLayer < poDS->GetLayerCount(); iLayer++)
            {
                OGRLayer *poLayer = poDS->GetLayer(iLayer);

                if (poLayer != nullptr)
                {
                    printf("  %d: %s", iLayer + 1, poLayer->GetName());

                    int nGeomFieldCount =
                        poLayer->GetLayerDefn()->GetGeomFieldCount();
                    if (nGeomFieldCount > 1)
                    {
                        printf(" (");
                        for (int iGeom = 0; iGeom < nGeomFieldCount; iGeom++)
                        {
                            if (iGeom > 0)
                                printf(", ");
                            OGRGeomFieldDefn *poGFldDefn =
                                poLayer->GetLayerDefn()->GetGeomFieldDefn(
                                    iGeom);
                            printf("%s", OGRGeometryTypeToName(
                                             poGFldDefn->GetType()));
                        }
                        printf(")");
                    }
                    else if (poLayer->GetGeomType() != wkbUnknown)
                        printf(" (%s)",
                               OGRGeometryTypeToName(poLayer->GetGeomType()));

                    printf("\n");
                }
            }
        }

        // report rules
        GNMGenericNetwork *poGenericNetwork =
            dynamic_cast<GNMGenericNetwork *>(poDS);

        if (nullptr != poGenericNetwork)
        {
            CPLStringList oList(poGenericNetwork->GetRules());
            if (oList.Count() > 0)
            {
                printf("\nNetwork\'s rules: \n");
                for (int iRule = 0; iRule < oList.Count(); ++iRule)
                {
                    printf("  %d: %s\n", iRule + 1, oList[iRule]);
                }
            }
        }
    }
    else if (stOper == op_create)
    {
        std::string osPath;
        std::string osNetworkName =
            CSLFetchNameValueDef(papszDSCO, GNM_MD_NAME, "");

        if (pszDataSource == nullptr)
            Usage(true, "No network dataset provided");

        // the DSCO have priority on input keys
        if (osNetworkName.empty())
        {
            osPath = CPLGetPathSafe(pszDataSource);
            osNetworkName = CPLGetBasenameSafe(pszDataSource);
            papszDSCO =
                CSLAddNameValue(papszDSCO, GNM_MD_NAME, osNetworkName.c_str());
        }
        else
        {
            osPath = pszDataSource;
        }

        if (osNetworkName.empty())
            Usage(true, "No dataset name provided");

        const char *pszFinalSRS = CSLFetchNameValue(papszDSCO, GNM_MD_SRS);
        if (nullptr == pszFinalSRS)
        {
            pszFinalSRS = pszSRS;
            papszDSCO = CSLAddNameValue(papszDSCO, GNM_MD_SRS, pszSRS);
        }

        if (nullptr == pszFinalSRS)
            Usage(true, "No spatial reference provided");
        if (pszFormat == nullptr)
            Usage(true, "No output format provided");

        GDALDriver *poDriver =
            GetGDALDriverManager()->GetDriverByName(pszFormat);
        if (poDriver == nullptr)
        {
            Usage(true, CPLSPrintf("%s driver not available", pszFormat));
        }
        else
        {
            char **papszMD = poDriver->GetMetadata();

            if (!CPLFetchBool(papszMD, GDAL_DCAP_GNM, false))
                Usage(true, "not a GNM driver");

            poDS = cpl::down_cast<GNMNetwork *>(poDriver->Create(
                osPath.c_str(), 0, 0, 0, GDT_Unknown, papszDSCO));

            if (nullptr == poDS)
            {
                fprintf(
                    stderr,
                    "\nFAILURE: Failed to create network in a new dataset at "
                    "%s and with driver %s\n",
                    CPLFormFilenameSafe(osPath.c_str(), osNetworkName.c_str(),
                                        nullptr)
                        .c_str(),
                    pszFormat);
                nRet = 1;
            }
            else
            {
                if (bQuiet == FALSE)
                    printf("\nNetwork created successfully in a "
                           "new dataset at %s\n",
                           CPLFormFilenameSafe(osPath.c_str(),
                                               osNetworkName.c_str(), nullptr)
                               .c_str());
            }
        }
    }
    else if (stOper == op_import)
    {
        if (pszDataSource == nullptr)
            Usage(true, "No network dataset provided");

        if (pszInputDataset == nullptr)
            Usage(true, "No input dataset name provided");

        // open
        poDS = cpl::down_cast<GNMNetwork *>(static_cast<GDALDataset *>(
            GDALOpenEx(pszDataSource, GDAL_OF_READONLY | GDAL_OF_GNM, nullptr,
                       nullptr, nullptr)));

        if (nullptr == poDS)
        {
            printf("\nFailed to open network at %s\n", pszDataSource);
            goto exit;
        }

        GDALDataset *poSrcDS = static_cast<GDALDataset *>(
            GDALOpenEx(pszInputDataset, GDAL_OF_VECTOR | GDAL_OF_READONLY,
                       nullptr, nullptr, nullptr));
        if (nullptr == poSrcDS)
        {
            fprintf(stderr, "\nFAILURE: Can not open dataset at %s\n",
                    pszInputDataset);

            nRet = 1;
            goto exit;
        }

        OGRLayer *poSrcLayer;
        if (pszInputLayer != nullptr)
            poSrcLayer = poSrcDS->GetLayerByName(pszInputLayer);
        else
            poSrcLayer = poSrcDS->GetLayer(0);

        if (nullptr == poSrcLayer)
        {
            if (pszInputLayer != nullptr)
                fprintf(stderr, "\nFAILURE: Can not open layer %s in %s\n",
                        pszInputLayer, pszInputDataset);
            else
                fprintf(stderr, "\nFAILURE: Can not open layer in %s\n",
                        pszInputDataset);

            GDALClose(poSrcDS);

            nRet = 1;
            goto exit;
        }

        OGRLayer *poLayer = poDS->CopyLayer(poSrcLayer, poSrcLayer->GetName());
        if (nullptr == poLayer)
        {
            if (pszInputLayer != nullptr)
                fprintf(stderr, "\nFAILURE: Can not copy layer %s from %s\n",
                        pszInputLayer, pszInputDataset);
            else
                fprintf(stderr, "\nFAILURE: Can not copy layer from %s\n",
                        pszInputDataset);
            GDALClose(poSrcDS);

            nRet = 1;
            goto exit;
        }

        if (bQuiet == FALSE)
        {
            if (pszInputLayer != nullptr)
                printf("\nLayer %s successfully copied from %s and added to "
                       "the network at %s\n",
                       pszInputLayer, pszInputDataset, pszDataSource);
            else
                printf("\nLayer successfully copied from %s and added to the "
                       "network at %s\n",
                       pszInputDataset, pszDataSource);
        }

        GDALClose(poSrcDS);
    }
    else if (stOper == op_connect)
    {
        if (pszDataSource == nullptr)
            Usage(true, "No network dataset provided");

        // open
        poDS = cpl::down_cast<GNMNetwork *>(static_cast<GDALDataset *>(
            GDALOpenEx(pszDataSource, GDAL_OF_UPDATE | GDAL_OF_GNM, nullptr,
                       nullptr, nullptr)));

        if (nullptr == poDS)
        {
            fprintf(stderr, "\nFailed to open network at %s\n", pszDataSource);
            nRet = 1;
            goto exit;
        }

        GNMGenericNetwork *poGenericNetwork =
            dynamic_cast<GNMGenericNetwork *>(poDS);

        if (nullptr == poGenericNetwork)
        {
            fprintf(stderr,
                    "\nUnsupported datasource type for this operation\n");
            nRet = 1;
            goto exit;
        }

        if (poGenericNetwork->ConnectFeatures(nSrcFID, nTgtFID, nConFID,
                                              dfDirCost, dfInvCost,
                                              eDir) != CE_None)
        {
            fprintf(stderr, "Failed to connect features\n");
            nRet = 1;
            goto exit;
        }

        if (bQuiet == FALSE)
        {
            printf("Features connected successfully\n");
        }
    }
    else if (stOper == op_disconnect)
    {
        if (pszDataSource == nullptr)
            Usage(true, "No network dataset provided");

        // open
        poDS = cpl::down_cast<GNMNetwork *>(static_cast<GDALDataset *>(
            GDALOpenEx(pszDataSource, GDAL_OF_UPDATE | GDAL_OF_GNM, nullptr,
                       nullptr, nullptr)));

        if (nullptr == poDS)
        {
            fprintf(stderr, "\nFailed to open network at %s\n", pszDataSource);
            nRet = 1;
            goto exit;
        }

        GNMGenericNetwork *poGenericNetwork =
            dynamic_cast<GNMGenericNetwork *>(poDS);

        if (nullptr == poGenericNetwork)
        {
            fprintf(stderr,
                    "\nUnsupported datasource type for this operation\n");
            nRet = 1;
            goto exit;
        }

        if (poGenericNetwork->DisconnectFeatures(nSrcFID, nTgtFID, nConFID) !=
            CE_None)
        {
            fprintf(stderr, "Failed to disconnect features\n");
            nRet = 1;
            goto exit;
        }

        if (bQuiet == FALSE)
        {
            printf("Features disconnected successfully\n");
        }
    }
    else if (stOper == op_rule)
    {
        if (pszDataSource == nullptr)
            Usage(true, "No network dataset provided");

        // open
        poDS = cpl::down_cast<GNMNetwork *>(static_cast<GDALDataset *>(
            GDALOpenEx(pszDataSource, GDAL_OF_UPDATE | GDAL_OF_GNM, nullptr,
                       nullptr, nullptr)));

        if (nullptr == poDS)
        {
            fprintf(stderr, "\nFailed to open network at %s\n", pszDataSource);
            nRet = 1;
            goto exit;
        }

        GNMGenericNetwork *poGenericNetwork =
            dynamic_cast<GNMGenericNetwork *>(poDS);

        if (nullptr == poGenericNetwork)
        {
            fprintf(stderr,
                    "\nUnsupported datasource type for this operation\n");
            nRet = 1;
            goto exit;
        }

        if (poGenericNetwork->CreateRule(pszRuleStr) != CE_None)
        {
            fprintf(stderr, "Failed to create rule %s\n", pszRuleStr);
            nRet = 1;
            goto exit;
        }

        if (bQuiet == FALSE)
        {
            printf("Create rule '%s' successfully\n", pszRuleStr);
        }
    }
    else if (stOper == op_autoconnect)
    {
        if (pszDataSource == nullptr)
            Usage(true, "No network dataset provided");

        // open
        poDS = cpl::down_cast<GNMNetwork *>(static_cast<GDALDataset *>(
            GDALOpenEx(pszDataSource, GDAL_OF_UPDATE | GDAL_OF_GNM, nullptr,
                       nullptr, nullptr)));

        if (nullptr == poDS)
        {
            fprintf(stderr, "\nFailed to open network at %s\n", pszDataSource);
            nRet = 1;
            goto exit;
        }

        GNMGenericNetwork *poGenericNetwork =
            dynamic_cast<GNMGenericNetwork *>(poDS);

        if (nullptr == poGenericNetwork)
        {
            fprintf(stderr,
                    "\nUnsupported datasource type for this operation\n");
            nRet = 1;
            goto exit;
        }

        if (CSLCount(papszLayers) == 0 && poDS->GetLayerCount() > 1)
        {
            if (bQuiet == FALSE)
            {
                printf("No layers provided. Use all layers of network:\n");
            }

            for (int i = 0; i < poDS->GetLayerCount(); ++i)
            {
                OGRLayer *poLayer = poDS->GetLayer(i);
                if (bQuiet == FALSE)
                {
                    printf("%d. %s\n", i + 1, poLayer->GetName());
                }
                papszLayers = CSLAddString(papszLayers, poLayer->GetName());
            }
        }

        if (poGenericNetwork->ConnectPointsByLines(papszLayers, dfTolerance,
                                                   dfDirCost, dfInvCost,
                                                   eDir) != CE_None)
        {
            fprintf(stderr, "Failed to autoconnect features\n");
            nRet = 1;
            goto exit;
        }

        if (bQuiet == FALSE)
        {
            printf("Features connected successfully\n");
        }
    }
    else if (stOper == op_delete)
    {
        if (pszDataSource == nullptr)
            Usage(true, "No network dataset provided");

        // open
        poDS = cpl::down_cast<GNMNetwork *>(static_cast<GDALDataset *>(
            GDALOpenEx(pszDataSource, GDAL_OF_UPDATE | GDAL_OF_GNM, nullptr,
                       nullptr, nullptr)));

        if (nullptr == poDS)
        {
            fprintf(stderr, "\nFailed to open network at %s\n", pszDataSource);
            nRet = 1;
            goto exit;
        }

        if (poDS->Delete() != CE_None)
        {
            fprintf(stderr, "Delete failed.\n");
            nRet = 1;
            goto exit;
        }

        if (bQuiet == FALSE)
        {
            printf("Delete successfully\n");
        }

        /** if hDriver == NULL this code delete everything in folder,
         *  not only GNM files

         GDALDriverH hDriver = NULL;
         if( pszFormat != NULL )
         {
             hDriver = GDALGetDriverByName( pszFormat );
             if( hDriver == NULL )
             {
                 fprintf( stderr, "Unable to find driver named '%s'.\n",
                          pszFormat );
                 exit( 1 );
             }
         }
         GDALDeleteDataset( hDriver, pszDataSource );
         */
    }
    else if (stOper == op_change_st)
    {
        if (pszDataSource == nullptr)
            Usage(true, "No dataset in input");

        // open
        poDS = cpl::down_cast<GNMNetwork *>(static_cast<GDALDataset *>(
            GDALOpenEx(pszDataSource, GDAL_OF_UPDATE | GDAL_OF_GNM, nullptr,
                       nullptr, nullptr)));

        if (nullptr == poDS)
        {
            fprintf(stderr, "\nFailed to open network at %s\n", pszDataSource);
            nRet = 1;
            goto exit;
        }

        GNMGenericNetwork *poGenericNetwork =
            dynamic_cast<GNMGenericNetwork *>(poDS);

        if (nullptr == poGenericNetwork)
        {
            fprintf(stderr,
                    "\nUnsupported datasource type for this operation\n");
            nRet = 1;
            goto exit;
        }

        if (bUnblockAll)
        {
            if (poGenericNetwork->ChangeAllBlockState(false) != CE_None)
            {
                fprintf(stderr, "\nChange all block state failed\n");
                nRet = 1;
                goto exit;
            }
        }
        else
        {
            size_t i;
            for (i = 0; i < anFIDsToBlock.size(); ++i)
            {
                if (poGenericNetwork->ChangeBlockState(anFIDsToBlock[i],
                                                       true) != CE_None)
                {
                    fprintf(stderr,
                            "\nChange block state of id " GNMGFIDFormat
                            " failed\n",
                            anFIDsToBlock[i]);
                    nRet = 1;
                    goto exit;
                }
            }

            for (i = 0; i < anFIDsToUnblock.size(); ++i)
            {
                if (poGenericNetwork->ChangeBlockState(anFIDsToUnblock[i],
                                                       false) != CE_None)
                {
                    fprintf(stderr,
                            "\nChange block state of id " GNMGFIDFormat
                            " failed\n",
                            anFIDsToBlock[i]);
                    nRet = 1;
                    goto exit;
                }
            }
        }

        if (bQuiet == FALSE)
        {
            printf("Change block state successfully\n");
        }
    }
    else
    {
        fprintf(
            stderr,
            "Need an operation. See help what you can do with gnmmanage:\n");
        Usage(true);
    }

exit:
    CSLDestroy(papszArgv);
    CSLDestroy(papszDSCO);
    CSLDestroy(papszLayers);

    if (poDS != nullptr)
    {
        if (GDALClose(poDS) != CE_None)
            nRet = 1;
    }

    GDALDestroyDriverManager();

    return nRet;
}

MAIN_END
