#include <yandex/maps/wiki/validator/validator.h>

#include "check_meta.h"
#include "task_context.h"
#include "common/exception.h"

#include <boost/filesystem/path.hpp>
#include <map>

namespace fs = boost::filesystem;

namespace maps {
namespace wiki {
namespace validator {

namespace {

const size_t DEFAULT_CHECK_THREADS_COUNT = 8;

typedef std::set<TCategoryId> TCategories;

void buildAoi(AreaOfInterest& aoi)
{
    try {
        aoi.build();
    } catch (const std::exception& e) {
        throw InitializationError() << e.what();
    }
}

} // namespace

ModuleInfo::ModuleInfo(
        std::string name,
        std::vector<TCheckId> checkIds,
        std::map<TCheckId, TCategories> checkToCategories)
    : name_(std::move(name))
    , checkIds_(std::move(checkIds))
    , checkToCategories_(std::move(checkToCategories))
{ }

const TCategories& ModuleInfo::categoriesByCheckId(
    const TCheckId& checkId) const
{
    auto it = checkToCategories_.find(checkId);
    REQUIRE(it != checkToCategories_.end(),
            "Check '" << checkId << "' not loaded");
    return it->second;
}

//==================== Validator ========================

Validator::Validator(const ValidatorConfig& validatorConfig)
    : validatorConfig_(validatorConfig)
    , checkCardinality_(CheckCardinality::No)
    , checkThreadsCount_(DEFAULT_CHECK_THREADS_COUNT)
{ }

void Validator::initModules()
{
    for (const auto& pair : globalCheckRegistry().modulesInfo()) {
        const auto& moduleName = pair.first;
        const auto& checksInfo = pair.second;

        std::vector<TCheckId> moduleCheckIds;
        std::map<TCheckId, TCategories> checkToCategories;

        for (const auto& checkIdPartsPair : checksInfo) {
            DEBUG() << "From module: " << moduleName
                    << " loading check: " << checkIdPartsPair.first;
            CheckMeta check;
            check.id = checkIdPartsPair.first;
            for (const auto& partIdInfoPair : checkIdPartsPair.second) {
                check.parts.push_back(CheckPartMeta{
                    partIdInfoPair.first,
                    partIdInfoPair.second.runner,
                    partIdInfoPair.second.categoryDependencies});
            }

            for (const auto& part : check.parts) {
                checkToCategories[check.id].insert(
                    part.dependencies.begin(),
                    part.dependencies.end());
            }

            moduleCheckIds.push_back(check.id);
            checksById_.emplace(check.id, std::move(check));
        }

        moduleInfos_.emplace_back(
            moduleName,
            std::move(moduleCheckIds),
            std::move(checkToCategories));
    }
}

const CheckMeta& Validator::check(const TCheckId& id) const
{
    auto check = checksById_.find(id);
    REQUIRE(check != checksById_.end(), "Check '" << id << "' not loaded");
    return check->second;
}

ResultPtr Validator::run(
        const std::vector<TCheckId>& checks,
        pgpool3::Pool& pgPool,
        DBID branchId,
        DBID commitId)
{
    return runImpl(checks, pgPool, branchId, commitId, AreaOfInterest());
}

ResultPtr Validator::run(
        const std::vector<TCheckId>& checks,
        pgpool3::Pool& pgPool,
        DBID branchId,
        DBID commitId,
        geolib3::Polygon2 aoiGeom,
        std::string aoiCoverageDir,
        double aoiBuffer)
{
    AreaOfInterest aoi;
    aoi.setBuffer(aoiBuffer);
    aoi.setCoverageDir(std::move(aoiCoverageDir));
    aoi.addPolygon(std::move(aoiGeom));
    buildAoi(aoi);

    return runImpl(checks, pgPool, branchId, commitId, std::move(aoi));
}

ResultPtr Validator::run(
        const std::vector<TCheckId>& checks,
        pgpool3::Pool& pgPool,
        DBID branchId,
        DBID commitId,
        const std::vector<DBID>& aoiObjectIds,
        std::string aoiCoverageDir,
        double aoiBuffer)
{
    AreaOfInterest aoi;
    aoi.setBuffer(aoiBuffer);
    aoi.setCoverageDir(std::move(aoiCoverageDir));
    aoi.addByIds(pgPool, branchId, commitId, aoiObjectIds);
    buildAoi(aoi);

    return runImpl(checks, pgPool, branchId, commitId, std::move(aoi));
}

ResultPtr Validator::runImpl(
        const std::vector<TCheckId>& checkIds,
        pgpool3::Pool& pgPool,
        DBID branchId,
        DBID commitId,
        AreaOfInterest aoi)
{
    if (checkIds.empty()) {
        return ResultPtr();
    }

    std::vector<CheckMeta> checks;
    for (const TCheckId& id : checkIds) {
        checks.push_back(check(id));
    }

    TaskContextPtr taskContext(
            new TaskContext(
                    validatorConfig_,
                    checkCardinality_,
                    pgPool,
                    branchId,
                    commitId,
                    std::move(aoi),
                    {}, // object ids
                    checks,
                    checkThreadsCount_,
                    canceledChecker_));

    return std::make_shared<Result>(std::move(taskContext));
}

ResultPtr Validator::run(
        const std::vector<TCheckId>& checkIds,
        pgpool3::Pool& pgPool,
        DBID branchId,
        DBID commitId,
        const ObjectIdSet& objectIds)
{
    if (checkIds.empty()) {
        return ResultPtr();
    }

    std::vector<CheckMeta> checks;
    for (const TCheckId& id : checkIds) {
        checks.push_back(check(id));
    }

    auto taskContext = std::make_unique<TaskContext>(
        validatorConfig_,
        checkCardinality_,
        pgPool,
        branchId,
        commitId,
        AreaOfInterest(),
        objectIds,
        checks,
        checkThreadsCount_,
        canceledChecker_);

    return std::make_shared<Result>(std::move(taskContext));
}

} // namespace validator
} // namespace wiki
} // namespace maps
