#include "attributes.h"

#include <kernel/keyinv/invkeypos/keyconv.h>

#include <util/charset/utf8.h>
#include <util/charset/wide.h>

namespace NRTYServer {
    namespace {
        constexpr ui64 SearchAttrsMask =
            TFullDocAttrs::AttrSearchDate |
            TFullDocAttrs::AttrSearchInteger |
            TFullDocAttrs::AttrSearchLiteral |
            TFullDocAttrs::AttrSearchUrl;

        TString ConvertValueString(
            TStringBuf rawValue, TFullDocAttrs::EAttrType type, bool detectUtf8) {
            const bool search = type & SearchAttrsMask;
            if (search && detectUtf8 && UTF8Detect(rawValue) == UTF8) {
                TFormToKeyConvertor converter;
                const TUtf16String wideValue = UTF8ToWide(rawValue);
                return converter.ConvertAttrValue(wideValue.data(), wideValue.size());
            }
            return TString{rawValue};
        }

        TAttr::TUpdateAction ExtractAction(const TString& data) {
            return data.StartsWith("add:") ? TAttr::uaADD : TAttr::uaREPLACE;
        }

        TString SerializeValue(TAttr::TUpdateAction action, const TString& value) {
            switch (action) {
                case TAttr::uaREPLACE:
                    return value;
                case TAttr::uaADD:
                    return "add:" + value;
                default:
                    Y_FAIL("invalid usage");
            }
        }
    }

    TAttr& TAttrs::Insert(const TString& name, const TString& data, TFullDocAttrs::EAttrType type, ui32 index, bool detectUtf8) {
        TAttr& attr = operator[](name);
        TAttr::TUpdateAction action = ExtractAction(data);
        if (attr.Type == TFullDocAttrs::AttrAuxPars) {
            attr.Type = type;
            attr.Index = index;
            attr.Action = action;
        } else {
            Y_ENSURE(attr.Type == type, "attribute " << name << " has different types");
            Y_ENSURE(attr.Index == index, "attribute " << name << " has different indexes");
            Y_ENSURE(attr.Action == action, "attribute " << name << " has different actions");
        }

        attr.Values.emplace_back(ExtractValue(data, type, detectUtf8));
        Y_ENSURE(
            attr.Action == TAttr::uaREPLACE || attr.Values.size() == 1,
            "attribute " << name << " has more than one operand for action");
        return attr;
    }

    void TAttrs::Clear(const TString& name) {
        operator[](name).Values.clear();
    }

    TString TAttrs::ExtractValue(TStringBuf data, TFullDocAttrs::EAttrType type, bool detectUtf8) {
        data.SkipPrefix("add:");
        return ConvertValueString(data, type, detectUtf8);
    }

    void TAttrs::Serialize(google::protobuf::RepeatedPtrField<TAttribute>& field, TAttrTypeGeter* attrTypeGetter) const {
        for (const auto& [name, attr] : *this) {
            auto type = attrTypeGetter(attr);
            for (const auto& value : attr.Values) {
                TAttribute& attrProto = *field.Add();
                attrProto.set_name(name);
                attrProto.set_value(SerializeValue(attr.Action, value));
                attrProto.set_type(type);
            }
        }
    }

    void TAttrs::Serialize(google::protobuf::RepeatedPtrField<TMessage::TDocument::TProperty>& field) const {
        for (const auto& [name, attr] : *this) {
            for (const auto& value : attr.Values) {
                TMessage::TDocument::TProperty& prop = *field.Add();
                prop.set_name(name);
                prop.set_value(SerializeValue(attr.Action, value));
            }
        }
    }

    void TAttrs::ApplyPatch(const TAttrs& patch) {
        for (const auto& [name, patchValue] : patch) {
            Y_ENSURE(patchValue.Values.size() == 1, "invalid attr values " << name);
            iterator attr = find(name);
            const TString& newVal = patchValue.Values[0];
            if (newVal == "__delete__") {
                if (attr != end()) {
                    erase(attr);
                }
            } else if (attr == end() || attr->second.Values.empty())
                Insert(name, SerializeValue(patchValue.Action, newVal), patchValue.Type, patchValue.Index);
            else {
                switch (patchValue.Action) {
                    case TAttr::uaREPLACE:
                        attr->second = patchValue;
                        break;
                    case TAttr::uaADD:
                        for (auto& val : attr->second.Values) {
                            switch (patchValue.Type) {
                                case TFullDocAttrs::AttrSearchLiteral:
                                case TFullDocAttrs::AttrSearchUrl:
                                case TFullDocAttrs::AttrArcText:
                                case TFullDocAttrs::AttrArcFull:
                                    val = TAttr::GetUpdateValue<TString>(val, newVal, patchValue.Action);
                                    break;
                                case TFullDocAttrs::AttrSearchDate:
                                case TFullDocAttrs::AttrSearchInteger:
                                    val = ToString(TAttr::GetUpdateValue<ui64>(FromString<ui64>(val), newVal, patchValue.Action));
                                    break;
                                case TFullDocAttrs::AttrGrInt:
                                    val = ToString(TAttr::GetUpdateValue<TCateg>(FromString<TCateg>(val), newVal, patchValue.Action));
                                    break;
                                case TFullDocAttrs::AttrErf:
                                    val = ToString(TAttr::GetUpdateValue<float>(FromString<float>(val), newVal, patchValue.Action));
                                    break;
                                default:
                                    FAIL_LOG("Invalid patchValue.Type:  %i", (int)patchValue.Type);
                            }
                        }
                        break;
                    default:
                        FAIL_LOG("Invalid patchValue.Action:  %i", (int)patchValue.Action);
                }
            }
        }
    }
}
