#pragma once

#include <util/generic/algorithm.h>
#include <util/generic/array_ref.h>
#include <util/generic/strbuf.h>
#include <array>
#include <functional>

namespace NEnumSerializationRuntime {
    enum class ESortOrder: int {
        Unordered = 0,         // беспорядок
        Ascending = 1,         // можно искать бинарным поиском, но есть эквивалентные ключи. Гланый ключ находится через lower_bound
        StrictlyAscending = 2, //   + все ключи уникальны
        DirectMapping = 3,     // последовательность целых чисел без пропусков, индекс элемента можно вычислить из его значения не делая поиск
    };

    template <typename TEnumRepresentationType>
    struct TEnumStringPair {
        TEnumRepresentationType Key;
        TStringBuf Name;
    };

    template <typename TEnumRepresentationType>
    constexpr ESortOrder GetKeyFieldSortOrder(const TArrayRef<const TEnumStringPair<TEnumRepresentationType>> initializer) {
        if (initializer.empty()) {
            return ESortOrder::DirectMapping;
        }
        bool direct = true;
        bool strict = true;
        bool sorted = true;
        const auto* data = initializer.data();
        const size_t size = initializer.size();
        TEnumRepresentationType expected = data[0].Key;
        for (size_t i = 1; i < size; ++i) {
            const auto& prev = data[i - 1].Key;
            const auto& next = data[i - 0].Key;
            if (++expected != next) {
                direct = false;
            }
            if (prev >= next) {
                strict = false;
            }
            if (prev > next) {
                sorted = false;
                break;
            }
        }
        return direct   ? ESortOrder::DirectMapping
               : strict ? ESortOrder::StrictlyAscending
               : sorted ? ESortOrder::Ascending
                        : ESortOrder::Unordered;
    }

    template <typename TEnumRepresentationType>
    constexpr ESortOrder GetNameFieldSortOrder(const TArrayRef<const TEnumStringPair<TEnumRepresentationType>> initializer) {
        if (initializer.empty()) {
            return ESortOrder::DirectMapping;
        }
        bool strict = true;
        bool sorted = true;
        const auto* data = initializer.data();
        const size_t size = initializer.size();
        for (size_t i = 1; i < size; ++i) {
            const std::string_view& prev = data[i - 1].Name;
            const std::string_view& next = data[i - 0].Name;
            const int cmp = prev.compare(next);
            if (cmp >= 0) {
                strict = false;
            }
            if (cmp > 0) {
                sorted = false;
                break;
            }
        }
        return strict   ? ESortOrder::StrictlyAscending
               : sorted ? ESortOrder::Ascending
                        : ESortOrder::Unordered;
    }

#if defined(__cpp_lib_array_constexpr) && defined(__cpp_lib_constexpr_algorithms) && defined(__cpp_lib_constexpr_functional)

    // Функция должна состоять из единственного вызова
    // std::stable_sort(v.begin(), v.end(), [](const T& a, const T& b) { return a.Key < b.Key; });
    // и возврата отсортированного массива.
    // Но в C++20 stable_sort ещё не имеет спецификатора constexpr и не может использоваться тут.
    // Поэтому в текущей реализации вместо этого делается обычная нестабильная сортировка пар {ключ элемента, положение элемента}.
    template <class T, size_t N>
    constexpr std::array<T, N> TryStableSortKeys(std::array<T, N> v) {
        // Компилятор обычно ограничивает число шагов, которые можно сделать при вычислении constexpr-функции (см. опции `-fconstexpr-steps=N` или `/constexpr:stepsN`).
        // Число же шагов, необходимых для сортировки, зависит не только от длины массива,
        // но и от используемого алгоритма, от числа вложенных функций в его реализации, и от наличия assert'ов в ней.
        // Что также означает, что число шагов может меняться в несколько раз при смене реализации STL или при сборке с NDEBUG и без.
        //
        // Исчерпание бюджета на действия приведёт к ошибки компиляции без возможности восстановления.
        // То есть без возможности обнаружить эту ситуацию и переключится на другой алгоритм.
        //
        // Поэтому максимальный размер сортируемого массива заранее ограничивается безопасной константой.
        // А все массивы большего размера досортировываются уже во время исполнения программы.
        constexpr size_t MAX_COMPILE_TIME_SORT_ARRAY_SIZE = 2'000;
        if (v.size() > MAX_COMPILE_TIME_SORT_ARRAY_SIZE) {
            return v;
        }

        // Многие перечисления уже отсортированы. Но текущая реализация constexpr std::sort в libcxx не проверяет этот случай и всегда работает за время Θ(NlogN)
        if (IsSortedBy(v.begin(), v.end(), std::mem_fn(&T::Key))) {
            return v;
        }

        std::array<const T*, N> ptrs;
        Iota(ptrs.begin(), ptrs.end(), &v[0]);
        auto cmpKeyPointersFn = [](const T* a, const T* b) {
            if (a->Key != b->Key) {
                return a->Key < b->Key;
            }
            return a < b; // ensure stable sort order
        };
        Sort(ptrs.begin(), ptrs.end(), cmpKeyPointersFn);

        std::array<T, N> r;
        for (size_t i = 0; i < N; ++i) {
            r[i] = *ptrs[i];
        }
        return r;
    }

#else

    template <class T, size_t N>
    constexpr std::array<T, N> TryStableSortKeys(std::array<T, N> v) {
        return v; // skip sort in case of old language version
    }

#endif
}