#include "limits.h"
#include "limits_impl.h"

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

#include <limits>
#include <map>


namespace maps::wiki::assessment::sampler {

namespace impl {

// Attention! Keep maps below in sync with array from assessment_sampler.sh
const std::map<Mode, double> MODE_TO_UNITS_RATIO = {
    {{Entity::Domain::Edits,      StaffType::Staff,     HypothesesMode::No},  0.10},
    {{Entity::Domain::Edits,      StaffType::Piecework, HypothesesMode::No},  0.03},
    {{Entity::Domain::Edits,      StaffType::Outsource, HypothesesMode::No},  0.03},
    {{Entity::Domain::Moderation, StaffType::Staff,     HypothesesMode::No},  0.10},
    {{Entity::Domain::Moderation, StaffType::Piecework, HypothesesMode::No},  0.03},
    {{Entity::Domain::Feedback,   StaffType::Staff,     HypothesesMode::No},  0.10},
    {{Entity::Domain::Feedback,   StaffType::Piecework, HypothesesMode::No},  0.15},
    {{Entity::Domain::Feedback,   StaffType::Staff,     HypothesesMode::Yes}, 0.10},
    {{Entity::Domain::Feedback,   StaffType::Piecework, HypothesesMode::Yes}, 0.10},
    {{Entity::Domain::Feedback,   StaffType::Outsource, HypothesesMode::Yes}, 0.10},
    {{Entity::Domain::Tracker,    StaffType::Staff,     HypothesesMode::No},  1.00},
};

const auto NO_LIMIT = std::numeric_limits<size_t>::max();
const std::map<Mode, size_t> MODE_TO_MAX_UNITS = {
    {{Entity::Domain::Edits,      StaffType::Staff,     HypothesesMode::No},  NO_LIMIT},
    {{Entity::Domain::Edits,      StaffType::Piecework, HypothesesMode::No},      1500},
    {{Entity::Domain::Edits,      StaffType::Outsource, HypothesesMode::No},  NO_LIMIT},
    {{Entity::Domain::Moderation, StaffType::Staff,     HypothesesMode::No},  NO_LIMIT},
    {{Entity::Domain::Moderation, StaffType::Piecework, HypothesesMode::No},      2000},
    {{Entity::Domain::Feedback,   StaffType::Staff,     HypothesesMode::No},  NO_LIMIT},
    {{Entity::Domain::Feedback,   StaffType::Piecework, HypothesesMode::No},  NO_LIMIT},
    {{Entity::Domain::Feedback,   StaffType::Staff,     HypothesesMode::Yes}, NO_LIMIT},
    {{Entity::Domain::Feedback,   StaffType::Piecework, HypothesesMode::Yes},     1000},
    {{Entity::Domain::Feedback,   StaffType::Outsource, HypothesesMode::Yes},     1000},
    {{Entity::Domain::Tracker,    StaffType::Staff,     HypothesesMode::No},       100},
};

const size_t NO_CROSS_CHECK = 1;
const size_t BASIC_TASKS_PER_UNIT = 2;
const std::map<Mode, size_t> MODE_TO_TASKS_PER_UNIT = {
    {{Entity::Domain::Edits,      StaffType::Staff,     HypothesesMode::No},  NO_CROSS_CHECK},
    {{Entity::Domain::Edits,      StaffType::Piecework, HypothesesMode::No},  BASIC_TASKS_PER_UNIT},
    {{Entity::Domain::Edits,      StaffType::Outsource, HypothesesMode::No},  BASIC_TASKS_PER_UNIT},
    {{Entity::Domain::Moderation, StaffType::Staff,     HypothesesMode::No},  NO_CROSS_CHECK},
    {{Entity::Domain::Moderation, StaffType::Piecework, HypothesesMode::No},  BASIC_TASKS_PER_UNIT},
    {{Entity::Domain::Feedback,   StaffType::Staff,     HypothesesMode::No},  NO_CROSS_CHECK},
    {{Entity::Domain::Feedback,   StaffType::Piecework, HypothesesMode::No},  BASIC_TASKS_PER_UNIT},
    {{Entity::Domain::Feedback,   StaffType::Staff,     HypothesesMode::Yes}, NO_CROSS_CHECK},
    {{Entity::Domain::Feedback,   StaffType::Piecework, HypothesesMode::Yes}, BASIC_TASKS_PER_UNIT},
    {{Entity::Domain::Feedback,   StaffType::Outsource, HypothesesMode::Yes}, BASIC_TASKS_PER_UNIT},
    {{Entity::Domain::Tracker,    StaffType::Staff,     HypothesesMode::No},  NO_CROSS_CHECK},
};

} // namespace impl


double unitsRatio(Mode mode)
{
    const auto result = impl::MODE_TO_UNITS_RATIO.find(mode);
    ASSERT(result != impl::MODE_TO_UNITS_RATIO.cend());
    return result->second;
}


size_t tasksPerUnit(Mode mode)
{
    const auto result = impl::MODE_TO_TASKS_PER_UNIT.find(mode);
    ASSERT(result != impl::MODE_TO_TASKS_PER_UNIT.cend());
    return result->second;
}


size_t maxUnits(Mode mode, size_t totalUnitsNumber)
{
    const auto modeToMaxUnitsIt = impl::MODE_TO_MAX_UNITS.find(mode);
    ASSERT(modeToMaxUnitsIt != impl::MODE_TO_MAX_UNITS.cend());

    return std::min(
        modeToMaxUnitsIt->second,
        size_t(std::abs(std::lround(totalUnitsNumber * unitsRatio(mode))))
    );
}


GroupNameToUnits
cropGroups(GroupNameToUnits&& groupNameToUnits, double ratio, size_t maxUnits)
{
    size_t size{0};
    for (auto& [_, units]: groupNameToUnits) {
        units.resize(std::max(units.size() * ratio, 1.0));
        size += units.size();
    }

    while (size >= maxUnits) {
        bool unitPoped = false;

        for (auto& [_, units]: groupNameToUnits) {
            if (units.size() <= 1) {
                continue;
            }

            units.pop_back();
            unitPoped = true;
            size -= 1;
            if (size <= maxUnits) {
                return groupNameToUnits;
            }
        }

        if (!unitPoped) {       // all groups already of size 1
            break;
        }
    }

    return groupNameToUnits;
}

} // namespace maps::wiki::assessment::sampler
