#pragma once

#include <yandex/maps/wiki/tasks/task_logger.h>
#include <yandex/maps/wiki/revision/common.h>

#include <maps/libs/common/include/exception.h>

#include <maps/libs/introspection/include/comparison.h>

#include <contrib/libs/gdal/ogr/ogrsf_frmts/ogrsf_frmts.h>

#include <boost/lexical_cast.hpp>

#include <optional>
#include <string>
#include <vector>
#include <set>
#include <map>

namespace maps {
namespace wiki {
namespace importer {

enum class Action
{
    Add,
    Edit,
    Delete
};

using TObjectId = revision::DBID;
using TFeatureId = int64_t;
using LevelKind = uint64_t;
using LevelId = int64_t;

using StringVec = std::vector<std::string>;
using StringSet = std::set<std::string>;
using StringMap = std::map<std::string, std::string>;
using CommitIds = std::list<revision::DBID>;
using ObjectIds = std::set<TObjectId>;

/**
OGR ID is unique through shapefile,
but not unique for objects in ObjectCache,
because several objects are generated from earch row of shapefile
*/
struct ID
{
    std::string layerName;
    TFeatureId featureId;

    auto introspect() const
    {
        return std::tie(layerName, featureId);
    }
};

using maps::introspection::operator==;
using maps::introspection::operator<;

inline std::ostream& operator<<(std::ostream& os, const ID& id)
{
    os << id.layerName << ":" << id.featureId;
    return os;
}

using DbIdToFeatureIdMap = std::map<std::string, ID>;

constexpr size_t COMMIT_BATCH_SIZE = 10000;
constexpr TObjectId EMPTY_DB_ID = 0;
const std::string IDS_DELIM = ",";

namespace cat {
const std::string AD = "ad";
const std::string ADDR = "addr";
const std::string RD = "rd";
const std::string AD_CNT = "ad_cnt";
const std::string BLD = "bld";
const std::string BLD_COMPLEX = "bld_complex";

const std::string POI_PREFIX = "poi_";
} // namespace cat

namespace role {
const std::string OFFICIAL = "official";
const std::string RENDER_LABEL = "render_label";
const std::string ADDRESS_LABEL = "address_label";
const std::string SHORT = "short";
const std::string SYNONYM = "synonym";
const std::string OLD = "old";
const std::string CENTER = "center";
const std::string ASSOCIATED_WITH = "associated_with";
const std::string CHILD = "child";
const std::string PART = "part";
const std::string FC_PART = "fc_part";
const std::string START = "start";
const std::string END = "end";
const std::string EXCLUSION = "exclusion";
const std::string ENTRANCE_ASSIGNED = "entrance_assigned";
} // namespace role

const std::array<std::string, 6> NAME_ROLE_IDS {{
    role::OFFICIAL,
    role::RENDER_LABEL,
    role::ADDRESS_LABEL,
    role::SHORT,
    role::SYNONYM,
    role::OLD
}};

namespace rel {
const std::string ROLE = "rel:role";
const std::string MASTER = "rel:master";
const std::string SLAVE = "rel:slave";
} // namespace rel

using LoggableAction = std::function<void()>;

void runLoggable(const LoggableAction& fun,
                 tasks::TaskPgLogger& logger,
                 const std::string& label);

std::string extractCategory(const StringMap& attributes);
std::string extractRole(const StringMap& attributes);
std::string extractLang(const StringMap& attributes);
std::optional<LevelKind> extractLevelKind(const StringMap& attributes);

std::string getWkb(const OGRGeometry* geometry);

double getRealLength(const OGRGeometry* geometry);

template<typename T>
T cast(const std::string& value)
{
    try {
        return boost::lexical_cast<T>(value);
    } catch (const boost::bad_lexical_cast&) {
        throw maps::LogicError() << "Can't cast string " << value << " to number";
    }
}

} // namespace importer
} // namespace wiki
} // namespace maps
