#include "proto_utils.h"
#include <util/generic/string.h>
#include <mapreduce/yt/interface/client_method_options.h>

namespace NRTYT {

int CompareValues(
    ::google::protobuf::Message* a,
    ::google::protobuf::Message* b,
    const ::google::protobuf::FieldDescriptor* descriptor,
    const ::google::protobuf::FieldDescriptor* descriptor2) {
    auto reflection = a->GetReflection();
    auto reflection2 = b->GetReflection();
    if (descriptor2 && descriptor->cpp_type() != descriptor2->cpp_type()) {
        ythrow yexception() << "Can't compare fields of different types! " << descriptor->full_name() << ' ' << descriptor2->full_name();
    }
    if (descriptor2 == nullptr) {
        descriptor2 = descriptor;
    }

    switch (descriptor->cpp_type()) {
    case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
        return reflection->GetString(*a, descriptor).compare(reflection2->GetString(*b, descriptor2));
    case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
        {
            i64 first = reflection->GetInt64(*a, descriptor);
            i64 second = reflection2->GetInt64(*b, descriptor2);
            return (first > second) - (second > first);
        }
    case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
        {
            i32 first = reflection->GetInt32(*a, descriptor);
            i32 second = reflection2->GetInt32(*b, descriptor2);
            return (first > second) - (second > first);
        }
    case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
        {
            ui64 first = reflection->GetUInt64(*a, descriptor);
            ui64 second = reflection2->GetUInt64(*b, descriptor2);
            return (first > second) - (second > first);
        }
    case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
        {
            ui32 first = reflection->GetUInt32(*a, descriptor);
            ui32 second = reflection2->GetUInt32(*b, descriptor2);
            return (first > second) - (second > first);
        }
    case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
        {
            bool first = reflection->GetBool(*a, descriptor);
            bool second = reflection2->GetBool(*b, descriptor2);
            return (first > second) - (second > first);
        }
    case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
        {
            double first = reflection->GetDouble(*a, descriptor);
            double second = reflection2->GetDouble(*b, descriptor2);
            return (first > second) - (second > first);
        }
    case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
        {
            float first = reflection->GetFloat(*a, descriptor);
            float second = reflection2->GetFloat(*b, descriptor2);
            return (first > second) - (second > first);
        }
    case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
    case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
        GOOGLE_LOG(FATAL) << "Can't get here.";
        break;
    }
    return 0;
}

void SortProtoVector(TVector<THolder<::google::protobuf::Message>>& data, const TVector<const ::google::protobuf::FieldDescriptor*>& keyDescriptors) {
    ::Sort(data.begin(), data.end(),
            [&keyDescriptors] 
            (
            const THolder<::google::protobuf::Message>& a,
            const THolder<::google::protobuf::Message>& b) {
        for (const auto& keyDescr : keyDescriptors) {
            int result = NRTYT::CompareValues(a.Get(), b.Get(), keyDescr);
            if (result < 0) {
                return true;
            } else if (result > 0) {
                return false;
            }
        }
        return false;
    });
}

TString SerializeFileDescriptor(const ::google::protobuf::FileDescriptor* descriptor) {
    ::google::protobuf::FileDescriptorProto serialized;
    descriptor->CopyTo(&serialized);

    TString descriptorString;
    Y_PROTOBUF_SUPPRESS_NODISCARD serialized.SerializeToString(&descriptorString);
    return descriptorString;
}

TVector<const ::google::protobuf::FieldDescriptor*> GetFieldDescriptorsByName(
                    const ::google::protobuf::Descriptor* descriptor,
                    const TVector<TString>& columnNames,
                    bool ignoreUnknownColumns) {
    TVector<const ::google::protobuf::FieldDescriptor*> keyDescriptors;
    for (const auto& columnName : columnNames) {
        auto fieldDescriptor = descriptor->FindFieldByName(columnName);
        if (fieldDescriptor) {
            keyDescriptors.push_back(fieldDescriptor);
        } else if (!ignoreUnknownColumns) {
            ythrow yexception() << "Unknown column name: " << columnName << " in protobuf " << descriptor->full_name();
        }
    }
    return keyDescriptors;
}

} // namespace NRTYT
