#include "calc.h"
#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/category_traits.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/attr_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/areal_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/junction.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/linear_element.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/point_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/complex_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/relation_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects_cache.h>
#include "registry.h"

#include <maps/wikimap/mapspro/libs/dbutils/include/parser.h>
#include <maps/libs/locale/include/find_helpers.h>
#include <maps/libs/locale/include/codes.h>
#include <maps/libs/locale/include/convert.h>
#include <map>
#include <array>

namespace maps {
namespace wiki {
namespace srv_attrs
{
/**
* Documented
* http://wiki.yandex-team.ru/JandeksKarty/development/fordevelopers/wikimap/mapspro/serviceattrs
*/

namespace {

const std::string ERROR_TYPE = "error:type";
const std::string ERROR_STATUS = "error:status";
const std::string ERROR_JIRA = "error:jira";

const std::string OUTSOURCE_REGION_NAME = "outsource_region:name";
const std::string OUTSOURCE_REGION_TASK_TYPE = "outsource_region:task_type";

const std::string MERGE_NAME = "merge_region:name";
const std::string MERGE_SKIP = "merge_region:skip";
const std::string MERGE_STATUS = "merge_region:status";


const locale::Locale USER_LOCALE(locale::Lang::Ru, locale::Region::Ru);

} // namespace

CalcSrvAttrs::CalcSrvAttrs(ObjectsCache& cache)
        : cache_(cache)
{}

std::string
CalcSrvAttrs::calcErrorHotSpotLabel(const std::string& attrsHstore)
{
    const auto attributes = dbutils::parsePGHstore(attrsHstore);
    std::string result = getValueLabel(attributes, ERROR_TYPE) + ", " +
           getValueLabel(attributes, ERROR_STATUS);
    auto it = attributes.find(ERROR_JIRA);
    if (it != attributes.end()) {
        result += "\n" + it->second;
    }
    return result;
}

namespace {

std::string
calcNameTypeHotSpotLabel(
    const std::string& attrsHstore,
    const std::string& nameAttr,
    const std::string& typeAttr)
{
    ASSERT(!nameAttr.empty() || !typeAttr.empty());
    const auto attributes = dbutils::parsePGHstore(attrsHstore);
    std::vector<std::string> nameAndType;
    if (!nameAttr.empty()) {
        auto it = attributes.find(nameAttr);
        if (it != attributes.end() && !it->second.empty()) {
            nameAndType.push_back(it->second);
        }
    }
    if (!typeAttr.empty()) {
        const auto typeLabel = getValueLabel(attributes, typeAttr);
        if (!typeLabel.empty()) {
            nameAndType.push_back(typeLabel);
        }
    }
    return common::join(nameAndType, ", ");
}

using LabelCallback = std::function<std::string(
    const std::string& attrsHstore,
    const std::string& nameAttr,
    const std::string& typeAttr)>;

struct LabelCalcParams
{
    std::string nameAttr;
    std::string typeAttr;
    LabelCallback callback = calcNameTypeHotSpotLabel;
};

const std::map<std::string, LabelCalcParams> CATEGORY_TO_NAME_TYPE
    {
        {CATEGORY_AOI, {ATTR_AOI_NAME, ATTR_AOI_TYPE}},
        {CATEGORY_MRC_REGION, {ATTR_MRC_REGION_NAME, ATTR_MRC_REGION_TYPE}},
        {CATEGORY_MRC_POINT, {ATTR_MRC_POINT_NAME, ""}},
        {CATEGORY_OUTSOURCE_REGION, {OUTSOURCE_REGION_NAME, OUTSOURCE_REGION_TASK_TYPE}},
        {CATEGORY_ROAD_MARKING_POLYGONAL, {"", ATTR_ROAD_MARKING_POLYGONAL_TYPE}},
        {CATEGORY_ROAD_MARKING_LINEAR, {"", ATTR_ROAD_MARKING_LINEAR_TYPE}},
        {CATEGORY_ROAD_SURFACE, {"", ATTR_ROAD_SURFACE_TYPE}},
        {CATEGORY_ROAD_MARKING_POINT_ROAD_SIGN, {"", ATTR_ROAD_MARKING_POINT_ROAD_SIGN_TYPE}},
        {CATEGORY_ROAD_MARKING_POINT_SYMBOL, {"", ATTR_ROAD_MARKING_POINT_SYMBOL_TYPE}},
        {CATEGORY_ROAD_MARKING_POINT_LANE_DIRECTION, {"", ATTR_ROAD_MARKING_POINT_LANE_DIRECTION_TYPE}},
    };
} // namespace

bool
CalcSrvAttrs::hasRealtimeHotSpotLabel(const std::string& categoryId)
{
    return CATEGORY_TO_NAME_TYPE.count(categoryId);
}

std::string
CalcSrvAttrs::calcRealtimeHotSpotLabel(
    const std::string& attrsHstore,
    const std::string& categoryId)
{
    auto it = CATEGORY_TO_NAME_TYPE.find(categoryId);
    ASSERT(it != CATEGORY_TO_NAME_TYPE.end());
    const auto& params = it->second;
    return params.callback(
        attrsHstore,
        params.nameAttr,
        params.typeAttr);
}

std::string
CalcSrvAttrs::calcMergeRegionHotSpotLabel(const std::string& attrsHstore)
{
    const auto attributes = dbutils::parsePGHstore(attrsHstore);
    std::string mergeStatusLabel = getValueLabel(attributes, MERGE_STATUS);

    auto skipIt = attributes.find(MERGE_SKIP);
    auto it = attributes.find(MERGE_NAME);
    return (it != attributes.end() ? it->second + ", " : "")
        + (skipIt != attributes.end() ? " skip" : mergeStatusLabel);
}

void CalcSrvAttrs::process(const ObjectPtr& obj)
{
    const auto& srvAttrs = ServiceAttributesRegistry::get().categoryAttrs(obj->categoryId());
    for (const auto& srvAttr : srvAttrs) {
        if (srvAttr.resultType() == ServiceAttribute::ResultType::String) {
            obj->setServiceAttrValue(srvAttr.id(), srvAttr.calc(obj, cache()));
        } else {
            obj->removeServiceAttributes(srvAttr.id());
            const auto suffixesAndValues = srvAttr.calcWithSuffix(obj, cache());
            for (const auto& suffixValue : suffixesAndValues) {
                obj->setServiceAttrValue(srvAttr.id() + suffixValue.suffix, suffixValue.value);
            }
        }
    }
}

SuggestTexts
CalcSrvAttrs::suggestTexts(TOid oid) const
{
    ObjectPtr obj = cache().getExisting(oid);
    return ServiceAttributesRegistry::get().calculateSuggest(obj->categoryId(), obj.get(), cache());
}


namespace {
struct ObjectName
{
    std::string nameType;
    locale::Locale locale;
    bool isLocal;
    std::string name;
};

std::vector<ObjectName> readNames(
        const std::string& nameTypeAttribute,
        const std::string& nameValueAttribute,
        const std::string& nameISLocalAttribute,
        const std::string& nameLangAttribute,
        const TableValues& nameTableAttr,
        const std::string& lang)
{
    std::vector<ObjectName> names;
    names.reserve(nameTableAttr.numRows());
    for (size_t row = 0; row < nameTableAttr.numRows(); ++row) {
        const auto& nameLang = nameTableAttr.value(row, nameLangAttribute);
        if ((!lang.empty() && lang != nameLang) || nameLang == NO_LANG) {
            continue;
        }
        names.push_back({
            nameTableAttr.value(row, nameTypeAttribute),
            locale::to<locale::Locale>(nameLang),
            !nameTableAttr.value(row, nameISLocalAttribute).empty(),
            nameTableAttr.value(row, nameValueAttribute)
        });
    }
    return names;
}

std::map<locale::Locale, std::string>
findNamesByType(const std::vector<ObjectName>& names, const std::string& nameType, bool local)
{
    std::map<locale::Locale, std::string> foundNames;
    for (const auto& name : names) {
        if (local == name.isLocal && nameType == name.nameType) {
            foundNames.emplace(name.locale, name.name);
        }
    }
    return foundNames;
}
} // namespace

std::string
objectNameByType(TOid oid, const std::string& nmType, ObjectsCache& cache, const std::string& lang /* = s_emptyString */)
{
    ObjectPtr obj = cache.getExisting(oid);
    if (!isNamedCategory(obj->categoryId())) {
        return {};
    }
    auto nmAttrId = nameAttrId(obj->categoryId());
    ASSERT(!nmAttrId.empty());
    const auto& nmAttr = obj->tableAttributes().find(nmAttrId);
    const auto& nmTypeAttr = findAttrColumnNameBySuffix(nmAttrId, NM_TYPE_SUFFIX);
    const auto& nmValueAttr = findAttrColumnNameBySuffix(nmAttrId, NM_NAME_SUFFIX);
    const auto& nmIsLocalAttr = findAttrColumnNameBySuffix(nmAttrId, NM_IS_LOCAL_SUFFIX);
    const auto& nmLangAttr = findAttrColumnNameBySuffix(nmAttrId, NM_LANG_SUFFIX);

    auto allNames = readNames(
                nmTypeAttr,
                nmValueAttr,
                nmIsLocalAttr,
                nmLangAttr,
                nmAttr,
                lang);

    auto bestLocaleMatch = [](const auto& namesByLocale) {
        auto best = locale::findBest(namesByLocale, USER_LOCALE);
        ASSERT(best != namesByLocale.end());
        return best->second;
    };

    for (const auto needLocal : {true, false}) {
        auto namesOfType = findNamesByType(allNames, nmType, needLocal);
        if (!namesOfType.empty()) {
            return bestLocaleMatch(namesOfType);
        }
        for (const auto& altNmType : NAME_TYPES_PRIORITY) {
            auto namesOfType = findNamesByType(allNames, altNmType, needLocal);
            if (!namesOfType.empty()) {
                return bestLocaleMatch(namesOfType);
            }
        }
    }
    return s_emptyString;
}
}//srv_attrs
}// namespace wiki
}// namespace maps
