/*!
  \brief Load geometry (polygon BUD/PAR layers)

  \return number of invalid features
*/
int VFKDataBlockSQLite::LoadGeometryPolygon()
{
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    VFKDataBlockSQLite *poDataBlockLines1 = NULL;
    VFKDataBlockSQLite *poDataBlockLines2 = NULL;
    bool bIsPar = false;
    if (EQUAL (m_pszName, "PAR")) {
        poDataBlockLines1 = (VFKDataBlockSQLite *) m_poReader->GetDataBlock("HP");
        poDataBlockLines2 = poDataBlockLines1;
        bIsPar = true;
    }
    else {
        poDataBlockLines1 = (VFKDataBlockSQLite *) m_poReader->GetDataBlock("OB");
        poDataBlockLines2 = (VFKDataBlockSQLite *) m_poReader->GetDataBlock("SBP");
        bIsPar = false;
    }
    if( NULL == poDataBlockLines1 )
    {
        CPLError(CE_Warning, CPLE_FileIO,
                 "Data block %s not found. Unable to build geometry for %s.",
                 bIsPar ? "HP" : "OB", m_pszName);
        return -1;
    }
    if( NULL == poDataBlockLines2 )
    {
        CPLError(CE_Warning, CPLE_FileIO,
                 "Data block %s not found. Unable to build geometry for %s.",
                 "SBP", m_pszName);
        return -1;
    }

    poDataBlockLines1->LoadGeometry();
    poDataBlockLines2->LoadGeometry();

    if( LoadGeometryFromDB() )  // Try to load geometry from DB.
        return 0;

    const char *vrColumn[2] = { NULL, NULL };
    GUIntBig vrValue[2] = { 0, 0 };
    if (bIsPar) {
        vrColumn[0] = "PAR_ID_1";
        vrColumn[1] = "PAR_ID_2";
    }
    else {
        vrColumn[0] = "OB_ID";
        vrColumn[1] = "PORADOVE_CISLO_BODU";
        vrValue[1]  = 1;
    }

    CPLString osSQL;
    osSQL.Printf("SELECT ID,%s,rowid FROM %s", FID_COLUMN, m_pszName);
    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("BEGIN");

    VFKFeatureSQLiteList poLineList;
    /* first is to be considered as exterior */
    PointListArray poRingList;
    std::vector<OGRLinearRing *> poLinearRingList;
    OGRPolygon ogrPolygon;
    int nInvalidNoLines = 0;
    int nInvalidNoRings = 0;
    int nGeometries = 0;

    while(poReader->ExecuteSQL(hStmt) == OGRERR_NONE) {
        /* read values */
        const GUIntBig id = sqlite3_column_int64(hStmt, 0);
        const long iFID = static_cast<long>(sqlite3_column_int64(hStmt, 1));
        const int rowId = sqlite3_column_int(hStmt, 2);

        VFKFeatureSQLite *poFeature =
            (VFKFeatureSQLite *) GetFeatureByIndex(rowId - 1);
        CPLAssert(NULL != poFeature && poFeature->GetFID() == iFID);

        if( bIsPar )
        {
            vrValue[0] = vrValue[1] = id;
            poLineList = poDataBlockLines1->GetFeatures(vrColumn, vrValue, 2);
        }
        else
        {
            // std::vector<VFKFeatureSQLite *> poLineListOb;

            osSQL.Printf("SELECT ID FROM %s WHERE BUD_ID = " CPL_FRMT_GUIB,
                         poDataBlockLines1->GetName(), id);
            if (poReader->IsSpatial()) {
                CPLString osColumn;

                osColumn.Printf(" AND %s IS NULL", GEOM_COLUMN);
                osSQL += osColumn;
            }
            sqlite3_stmt *hStmtOb = poReader->PrepareStatement(osSQL.c_str());

            while(poReader->ExecuteSQL(hStmtOb) == OGRERR_NONE) {
                const GUIntBig idOb = sqlite3_column_int64(hStmtOb, 0);
                vrValue[0] = idOb;
                VFKFeatureSQLite *poLineSbp =
                    poDataBlockLines2->GetFeature(vrColumn, vrValue, 2);
                if (poLineSbp)
                    poLineList.push_back(poLineSbp);
            }
        }
        size_t nLines = poLineList.size();
        if (nLines < 1) {
            CPLDebug("OGR-VFK",
                     "%s: unable to collect rings for polygon fid = %ld (no lines)",
                     m_pszName, iFID);
            nInvalidNoLines++;
            continue;
        }

        /* clear */
        ogrPolygon.empty();
        poRingList.clear();

        /* collect rings from lines */
        bool bFound = false;
        int nCount = 0;
        const int nCountMax = static_cast<int>(nLines) * 2;
        while( poLineList.size() > 0 && nCount < nCountMax )
        {
            bool bNewRing = !bFound;
            bFound = false;
            int i = 1;
            for (VFKFeatureSQLiteList::iterator iHp = poLineList.begin(), eHp = poLineList.end();
                 iHp != eHp; ++iHp, ++i) {
                const OGRLineString *pLine = (OGRLineString *) (*iHp)->GetGeometry();
                if (pLine && AppendLineToRing(&poRingList, pLine, bNewRing)) {
                    bFound = true;
                    poLineList.erase(iHp);
                    break;
                }
            }
            nCount++;
        }
        CPLDebug("OGR-VFK", "%s: fid = %ld nlines = %d -> nrings = %d", m_pszName,
                 iFID, (int)nLines, (int)poRingList.size());

        if (poLineList.size() > 0) {
            CPLDebug("OGR-VFK",
                     "%s: unable to collect rings for polygon fid = %ld",
                     m_pszName, iFID);
            nInvalidNoRings++;
            continue;
        }

        /* build rings */
        poLinearRingList.clear();
        OGRLinearRing *poOgrRing = NULL;
        int i = 1;
        for( PointListArray::const_iterator iRing = poRingList.begin(),
                 eRing = poRingList.end();
             iRing != eRing;
             ++iRing)
        {
            PointList *poList = *iRing;

            poLinearRingList.push_back(new OGRLinearRing());
            poOgrRing = poLinearRingList.back();
            CPLAssert(NULL != poOgrRing);

            for( PointList::iterator iPoint = poList->begin(),
                     ePoint = poList->end();
                 iPoint != ePoint;
                 ++iPoint)
            {
                OGRPoint *poPoint = &(*iPoint);
                poOgrRing->addPoint(poPoint);
            }
            i++;
        }

        /* find exterior ring */
        if( poLinearRingList.size() > 1 )
        {
            std::vector<OGRLinearRing *>::iterator exteriorRing;

            exteriorRing = poLinearRingList.begin();
            double dMaxArea = -1.0;
            for( std::vector<OGRLinearRing *>::iterator iRing =
                     poLinearRingList.begin(),
                     eRing = poLinearRingList.end();
                 iRing != eRing;
                 ++iRing )
            {
                poOgrRing = *iRing;
                if (!IsRingClosed(poOgrRing))
                    continue; /* skip unclosed rings */

                const double dArea = poOgrRing->get_Area();
                if (dArea > dMaxArea) {
                    dMaxArea = dArea;
                    exteriorRing = iRing;
                }
            }
            if (exteriorRing != poLinearRingList.begin()) {
                std::swap(*poLinearRingList.begin(), *exteriorRing);
            }
        }

        /* build polygon from rings */
        int nBridges = 0;
        for( std::vector<OGRLinearRing *>::iterator iRing =
                 poLinearRingList.begin(),
                 eRing = poLinearRingList.end();
             iRing != eRing;
             ++iRing )
        {
            poOgrRing = *iRing;

            /* check if ring is closed */
            if (IsRingClosed(poOgrRing)) {
                ogrPolygon.addRing(poOgrRing);
            }
            else {
                if (poOgrRing->getNumPoints() == 2) {
                    CPLDebug("OGR-VFK", "%s: Polygon (fid = %ld) bridge removed",
                             m_pszName, iFID);
                    nBridges++;
                }
                else {
                    CPLDebug("OGR-VFK",
                             "%s: Polygon (fid = %ld) unclosed ring skipped",
                             m_pszName, iFID);
                }
            }
            delete poOgrRing;
            *iRing = NULL;
        }

        /* set polygon */
        ogrPolygon.setCoordinateDimension(2); /* force 2D */
        if (ogrPolygon.getNumInteriorRings() + nBridges != (int) poLinearRingList.size() - 1 ||
            !poFeature->SetGeometry(&ogrPolygon)) {
            nInvalidNoRings++;
            continue;
        }

        /* store also geometry in DB */
        if (poReader->IsSpatial() &&
            SaveGeometryToDB(&ogrPolygon, rowId) != OGRERR_FAILURE)
            nGeometries++;
    }

    /* free ring list */
    for (PointListArray::iterator iRing = poRingList.begin(), eRing = poRingList.end();
         iRing != eRing; ++iRing) {
        delete (*iRing);
        *iRing = NULL;
    }

    CPLDebug("OGR-VFK", "%s: nolines = %d norings = %d",
             m_pszName, nInvalidNoLines, nInvalidNoRings);

    /* update number of geometries in VFK_DB_TABLE table */
    UpdateVfkBlocks(nGeometries);

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("COMMIT");

    return nInvalidNoLines + nInvalidNoRings;
}
/*!
  \brief Load geometry from DB

  \return true if geometry successfully loaded otherwise false
*/
bool VFKDataBlockSQLite::LoadGeometryFromDB()
{
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    if (!poReader->IsSpatial())   /* check if DB is spatial */
        return false;

    CPLString osSQL;
    osSQL.Printf("SELECT num_geometries FROM %s WHERE table_name = '%s'",
                 VFK_DB_TABLE, m_pszName);
    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());
    if (poReader->ExecuteSQL(hStmt) != OGRERR_NONE)
        return false;
    const int nGeometries = sqlite3_column_int(hStmt, 0);
    sqlite3_finalize(hStmt);

    if( nGeometries < 1 )
        return false;

    const bool bSkipInvalid =
        EQUAL(m_pszName, "OB") ||
        EQUAL(m_pszName, "OP") ||
        EQUAL(m_pszName, "OBBP");

    /* load geometry from DB */
    osSQL.Printf("SELECT %s,rowid,%s FROM %s ",
                 GEOM_COLUMN, FID_COLUMN, m_pszName);
    if (EQUAL(m_pszName, "SBP"))
        osSQL += "WHERE PORADOVE_CISLO_BODU = 1 ";
    osSQL += "ORDER BY ";
    osSQL += FID_COLUMN;
    hStmt = poReader->PrepareStatement(osSQL.c_str());

    int rowId = 0;
    int nInvalid = 0;
    int nGeometriesCount = 0;

    while(poReader->ExecuteSQL(hStmt) == OGRERR_NONE) {
        rowId++; // =sqlite3_column_int(hStmt, 1);
#ifdef DEBUG
        const GIntBig iFID = sqlite3_column_int64(hStmt, 2);
#endif

        VFKFeatureSQLite *poFeature = (VFKFeatureSQLite *) GetFeatureByIndex(rowId - 1);
        CPLAssert(NULL != poFeature && poFeature->GetFID() == iFID);

        // read geometry from DB
        const int nBytes = sqlite3_column_bytes(hStmt, 0);
        OGRGeometry *poGeometry = NULL;
        if (nBytes > 0 &&
            OGRGeometryFactory::createFromWkb((GByte*) sqlite3_column_blob(hStmt, 0),
                                              NULL, &poGeometry, nBytes) == OGRERR_NONE) {
            nGeometriesCount++;
            if (!poFeature->SetGeometry(poGeometry)) {
                nInvalid++;
            }
            delete poGeometry;
        }
        else {
            nInvalid++;
        }
    }

    CPLDebug("OGR-VFK", "%s: %d geometries loaded from DB",
             m_pszName, nGeometriesCount);

    if (nGeometriesCount != nGeometries) {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "%s: %d geometries loaded (should be %d)",
                 m_pszName, nGeometriesCount, nGeometries);
    }

    if (nInvalid > 0 && !bSkipInvalid) {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "%s: %d features with invalid or empty geometry",
                 m_pszName, nInvalid);
    }

    return true;
}
/*!
  \brief Load geometry (linestring HP/DPM layer)

  \return number of invalid features
*/
int VFKDataBlockSQLite::LoadGeometryLineStringHP()
{
    int nInvalid = 0;
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    VFKDataBlockSQLite *poDataBlockLines =
        (VFKDataBlockSQLite *) m_poReader->GetDataBlock("SBP");
    if (NULL == poDataBlockLines) {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Data block %s not found.", m_pszName);
        return nInvalid;
    }

    poDataBlockLines->LoadGeometry();

    if (LoadGeometryFromDB()) /* try to load geometry from DB */
        return 0;

    CPLString osColumn;
    osColumn.Printf("%s_ID", m_pszName);
    const char *vrColumn[2] = {
        osColumn.c_str(),
        "PORADOVE_CISLO_BODU"
    };

    GUIntBig vrValue[2] = { 0, 1 }; // Reduce to first segment.

    CPLString osSQL;
    osSQL.Printf("SELECT ID,%s,rowid FROM %s", FID_COLUMN, m_pszName);
    /* TODO: handle points in DPM */
    if (EQUAL(m_pszName, "DPM"))
        osSQL += " WHERE SOURADNICE_X IS NULL";
    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("BEGIN");

    int nGeometries = 0;

    while( poReader->ExecuteSQL(hStmt) == OGRERR_NONE )
    {
        /* read values */
        vrValue[0] = sqlite3_column_int64(hStmt, 0);
        const long iFID = static_cast<long>(sqlite3_column_int64(hStmt, 1));
        const int rowId = sqlite3_column_int(hStmt, 2);

        VFKFeatureSQLite *poFeature =
            (VFKFeatureSQLite *) GetFeatureByIndex(rowId - 1);
        CPLAssert(NULL != poFeature && poFeature->GetFID() == iFID);

        VFKFeatureSQLite *poLine =
            poDataBlockLines->GetFeature(vrColumn, vrValue, 2, TRUE);

        OGRGeometry *poOgrGeometry = NULL;
        if( !poLine )
        {
            poOgrGeometry = NULL;
        }
        else
        {
            poOgrGeometry = poLine->GetGeometry();
        }
        if (!poOgrGeometry || !poFeature->SetGeometry(poOgrGeometry)) {
            CPLDebug("OGR-VFK", "VFKDataBlockSQLite::LoadGeometryLineStringHP(): name=%s fid=%ld "
                     "id=" CPL_FRMT_GUIB " -> %s geometry", m_pszName, iFID, vrValue[0],
                     poOgrGeometry ? "invalid" : "empty");
            nInvalid++;
            continue;
        }

        /* store also geometry in DB */
        if (poReader->IsSpatial() &&
            SaveGeometryToDB(poOgrGeometry, rowId) != OGRERR_FAILURE &&
            poOgrGeometry)
            nGeometries++;
    }

    /* update number of geometries in VFK_DB_TABLE table */
    UpdateVfkBlocks(nGeometries);

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("COMMIT");

    return nInvalid;
}