#include "tool.h"

#include <maps/libs/common/include/make_batches.h>
#include <maps/libs/json/include/builder.h>
#include <maps/libs/json/include/value.h>
#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/mrc/toloka_client/task_suite.h>

namespace maps {
namespace mrc {
namespace toloka {

namespace {

constexpr size_t MIN_LARGER_SIGN_SIDE_PX = 25;
constexpr size_t MIN_SMALLER_SIGN_SIDE_PX = 15;
constexpr size_t NUM_TASK_SUITES_PER_REQUEST = 20;

const std::string FIELD_BBOX = "bbox";
const std::string FIELD_DETECTION_TASK_RESULTS = "detection_task_results";
const std::string FIELD_IMAGE = "image";
const std::string FIELD_SOLUTIONS = "solutions";
const std::string FIELD_SOURCE = "source";

json::Value makeInputValues(const std::string& url, const json::Value& bbox)
{
    json::Builder builder;
    builder << [&](json::ObjectBuilder b) {
        b[FIELD_IMAGE] = url;
        b[FIELD_BBOX] = bbox;
    };
    return json::Value::fromString(builder.str());
}

using Tasks = std::vector<io::TaskSuiteItem>;
using TaskItr = Tasks::iterator;


bool isBboxSignificant(const json::Value& box)
{
    const size_t width = box[1][0].as<size_t>() - box[0][0].as<size_t>();
    const size_t height = box[1][1].as<size_t>() - box[0][1].as<size_t>();
    const size_t largerSide = std::max(width, height);
    const size_t smallerSide = std::min(width, height);

    return largerSide >= MIN_LARGER_SIGN_SIDE_PX &&
            smallerSide >= MIN_SMALLER_SIGN_SIDE_PX;
}

size_t findStartIndex(const json::Value& items,
                      const std::string& startImageUrl)
{
    if (startImageUrl.empty()) {
        return 0;
    }

    for (size_t i = 0; i < items.size(); ++i) {
        if (items[i][FIELD_SOURCE].as<std::string>() == startImageUrl) {
            return i;
        }
    }
    return items.size();
}

} // namespace


std::string ClassificationTasksGenerator::generate(
    const std::string& startImageUrl,
    size_t taskSuitesCount,
    size_t tasksPerSuite)
{
    auto items = input_[FIELD_DETECTION_TASK_RESULTS];
    size_t startIx = findStartIndex(items, startImageUrl);
    size_t stopIx = items.size();


    if (startIx == stopIx) {
        WARN() << "Start url not found";
        return startImageUrl;
    }

    const size_t tasksCount = taskSuitesCount * tasksPerSuite;
    Tasks tasks;
    tasks.reserve(tasksCount);

    for (size_t i = startIx; i < items.size(); ++i) {
        const auto& item = items[i];
        auto source = item[FIELD_SOURCE].as<std::string>();
        for (const auto& sol : item[FIELD_SOLUTIONS]) {
            const auto& box = sol[FIELD_BBOX];
            if (isBboxSignificant(box)) {
                tasks.emplace_back(makeInputValues(source, box));
            }
        }
        if (tasks.size() >= tasksCount) {
            stopIx = i + 1;
            break;
        }
    }

    for (auto taskSuitesBatch : maps::common::makeBatches(tasks,
            NUM_TASK_SUITES_PER_REQUEST * tasksPerSuite)) {

        std::vector<io::TaskSuiteCreationParams> tsParamsVec;
        for (auto batch : maps::common::makeBatches(taskSuitesBatch, tasksPerSuite)) {
            Tasks t(batch.begin(), batch.end());
            io::TaskSuiteCreationParams tsParams(poolId_, std::move(t));
            tsParams.setOverlap(overlap_);
            tsParamsVec.push_back(std::move(tsParams));
        }
        auto response = tolokaClient_.postTaskSuites(tsParamsVec);
        if (!response.errors().empty()) {
            json::Builder builder;
            for (const auto& item : response.errors()) {
                builder << [&](json::ObjectBuilder b) {
                    b[std::to_string(item.first)] = item.second;
                };
            }
            WARN() << "Failed to post some task suites. Errors: " << builder.str();

        }
    }

    return (stopIx < items.size()
            ? items[stopIx][FIELD_SOURCE].as<std::string>()
            : std::string());
}



} // toloka
} // mrc
} // maps
