#include "view_utils.h"
#include "geom.h"

#include <yandex/maps/wiki/common/string_utils.h>

#include <geos/geom/MultiLineString.h>
#include <geos/geom/CoordinateArraySequence.h>
#include <geos/geom/Polygon.h>
#include <geos/geom/Point.h>
#include <geos/geom/GeometryFactory.h>

namespace maps {
namespace wiki {

TOIds
findContainingAdByView(
        const std::vector<Geom>& geoms,
        const geos::geom::Envelope& testEnvelope,
        const StringSet& adLevelKinds,
        const std::unordered_set<std::string>& isocodes,
        bool skipIgnoredDispClass,
        Transaction& viewTxn)
{
    if (geoms.empty() || adLevelKinds.empty()) {
        return {};
    }

    Geom aggregateGeom;
    if (geoms.size() == 1) {
        aggregateGeom = geoms[0];
    } else {
        std::vector<std::unique_ptr<geos::geom::Geometry>> geosGeoms;
        for (const auto& geom : geoms) {
            switch (geom->getGeometryTypeId()) {
                case geos::geom::GEOS_LINESTRING: {
                    geosGeoms.push_back(geom->clone());
                    break;
                }
                case geos::geom::GEOS_POINT: {
                    // convert point into little LineString
                    auto point = dynamic_cast<const geos::geom::Point*>(
                            geom.geosGeometryPtr());
                    ASSERT(point);
                    auto coords = make_unique<geos::geom::CoordinateArraySequence>();
                    coords->add({point->getX(), point->getY() - CALCULATION_TOLERANCE});
                    coords->add({point->getX(), point->getY() + CALCULATION_TOLERANCE});
                    geosGeoms.push_back(
                        geom->getFactory()->createLineString(std::move(coords)));
                    break;
                }
                default:
                    throw maps::LogicError()
                        << "unsupported geom-part geometry type: "
                        << geom->getGeometryType();
            }
        }

        aggregateGeom = Geom(geos::geom::GeometryFactory::getDefaultInstance()
                    ->createMultiLineString(std::move(geosGeoms)));
    }

    std::ostringstream query;
    query << "SELECT object_id, ST_SetSrid(ST_Envelope(ST_Extent(the_geom)), 3395) FROM contour_objects_geom WHERE"
        " ST_Intersects(the_geom, ST_GeomFromWKB('" << viewTxn.esc_raw(aggregateGeom.wkb()) << "', 3395))"
        " AND " <<
        common::join(adLevelKinds,
            [&](const std::string& levelKind) {
                return "(domain_attrs @> hstore('ad:level_kind', '" + levelKind + "'))";
            },
            " OR ");
    if (!isocodes.empty()) {
        query
            << " AND " <<
            common::join(isocodes,
            [&](const std::string& isocode) {
                return "(domain_attrs @> hstore('ad:isocode', '" + isocode + "'))";
            },
            " OR ");
    }
    if (skipIgnoredDispClass) {
        query << " AND (NOT domain_attrs @> hstore('ad:disp_class', '10'))";
    }
    query <<
        " AND domain_attrs ? 'cat:ad'"
        " GROUP BY object_id"
        " LIMIT 2";
    auto rows = viewTxn.exec(query.str());
    TOIds foundAdIds;
    for (const auto& row : rows) {
        const auto adId = row[0].as<TOid>();
        if (foundAdIds.count(adId)) {
            continue;
        }
        if (testEnvelope.isNull()) {
            foundAdIds.insert(adId);
        } else {
            auto adEnvelope = *(Geom(row[1])->getEnvelopeInternal());
            if (adEnvelope.contains(testEnvelope)) {
                foundAdIds.insert(adId);
            }
        }
    }
    return foundAdIds;
}

} // namespace wiki
} // namespace maps
