// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "google/protobuf/generated_enum_util.h"

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>

#include "absl/log/absl_check.h"
#include "absl/types/optional.h"
#include "absl/types/span.h"
#include "google/protobuf/generated_message_util.h"

// Must be included last.
#include "google/protobuf/port_def.inc"

namespace google {
namespace protobuf {
namespace internal {
namespace {

bool EnumCompareByName(const EnumEntry& a, const EnumEntry& b) {
  return absl::string_view(a.name) < absl::string_view(b.name);
}

// Gets the numeric value of the EnumEntry at the given index, but returns a
// special value for the index -1. This gives a way to use std::lower_bound on a
// sorted array of indices while searching for value that we associate with -1.
int GetValue(const EnumEntry* enums, int i, int target) {
  if (i == -1) {
    return target;
  } else {
    return enums[i].value;
  }
}

}  // namespace

bool LookUpEnumValue(const EnumEntry* enums, size_t size,
                     absl::string_view name, int* value) {
  EnumEntry target{name, 0};
  auto it = std::lower_bound(enums, enums + size, target, EnumCompareByName);
  if (it != enums + size && it->name == name) {
    *value = it->value;
    return true;
  }
  return false;
}

int LookUpEnumName(const EnumEntry* enums, const int* sorted_indices,
                   size_t size, int value) {
  auto comparator = [enums, value](int a, int b) {
    return GetValue(enums, a, value) < GetValue(enums, b, value);
  };
  auto it =
      std::lower_bound(sorted_indices, sorted_indices + size, -1, comparator);
  if (it != sorted_indices + size && enums[*it].value == value) {
    return it - sorted_indices;
  }
  return -1;
}

bool InitializeEnumStrings(
    const EnumEntry* enums, const int* sorted_indices, size_t size,
    internal::ExplicitlyConstructed<std::string>* enum_strings) {
  for (size_t i = 0; i < size; ++i) {
    enum_strings[i].Construct(enums[sorted_indices[i]].name);
    internal::OnShutdownDestroyString(enum_strings[i].get_mutable());
  }
  return true;
}

bool ValidateEnum(int value, const uint32_t* data) {
  return ValidateEnumInlined(value, data);
}

struct EytzingerLayoutSorter {
  absl::Span<const int32_t> input;
  absl::Span<uint32_t> output;
  size_t i;

  // This is recursive, but the maximum depth is log(N), so it should be safe.
  void Sort(size_t output_index = 0) {
    if (output_index < input.size()) {
      Sort(2 * output_index + 1);
      output[output_index] = input[i++];
      Sort(2 * output_index + 2);
    }
  }
};

std::vector<uint32_t> GenerateEnumData(absl::Span<const int32_t> values) {
  const auto sorted_and_unique = [&] {
    for (size_t i = 0; i + 1 < values.size(); ++i) {
      if (values[i] >= values[i + 1]) return false;
    }
    return true;
  };
  ABSL_DCHECK(sorted_and_unique());
  std::vector<int32_t> fallback_values_too_large, fallback_values_after_bitmap;
  std::vector<uint32_t> bitmap_values;
  constexpr size_t kBitmapBlockSize = 32;
  absl::optional<int16_t> start_sequence;
  uint32_t sequence_length = 0;
  for (int32_t v : values) {
    // If we don't yet have a sequence, start it.
    if (!start_sequence.has_value()) {
      // But only if we can fit it in the sequence.
      if (static_cast<int16_t>(v) != v) {
        fallback_values_too_large.push_back(v);
        continue;
      }

      start_sequence = v;
      sequence_length = 1;
      continue;
    }
    // If we can extend the sequence, do so.
    if (v == static_cast<int32_t>(*start_sequence) +
                 static_cast<int32_t>(sequence_length) &&
        sequence_length < 0xFFFF) {
      ++sequence_length;
      continue;
    }

    // We adjust the bitmap values to be relative to the end of the sequence.
    const auto adjust = [&](int32_t v) -> uint32_t {
      // Cast to int64_t first to avoid overflow. The result is guaranteed to be
      // positive and fit in uint32_t.
      int64_t a = static_cast<int64_t>(v) - *start_sequence - sequence_length;
      ABSL_DCHECK(a >= 0);
      ABSL_DCHECK_EQ(a, static_cast<uint32_t>(a));
      return a;
    };
    const uint32_t adjusted = adjust(v);

    const auto add_bit = [&](uint32_t bit) {
      bitmap_values[bit / kBitmapBlockSize] |= uint32_t{1}
                                               << (bit % kBitmapBlockSize);
    };

    // If we can fit it on the already allocated bitmap, do so.
    if (adjusted < kBitmapBlockSize * bitmap_values.size()) {
      // We can fit it in the existing bitmap.
      ABSL_DCHECK_EQ(fallback_values_after_bitmap.size(), 0);
      add_bit(adjusted);
      continue;
    }

    // We can't fit in the sequence and we can't fit in the current bitmap.
    // Evaluate if it is better to add to fallback, or to collapse all the
    // fallback values after the bitmap into the bitmap.
    const size_t cost_if_fallback =
        bitmap_values.size() + (1 + fallback_values_after_bitmap.size());
    const size_t rounded_bitmap_size =
        (adjusted + kBitmapBlockSize) / kBitmapBlockSize;
    const size_t cost_if_collapse = rounded_bitmap_size;

    if (cost_if_collapse <= cost_if_fallback &&
        kBitmapBlockSize * rounded_bitmap_size < 0x10000) {
      // Collapse the existing values, and add the new one.
      ABSL_DCHECK_GT(rounded_bitmap_size, bitmap_values.size());
      bitmap_values.resize(rounded_bitmap_size);
      for (int32_t to_collapse : fallback_values_after_bitmap) {
        add_bit(adjust(to_collapse));
      }
      fallback_values_after_bitmap.clear();
      add_bit(adjusted);
    } else {
      fallback_values_after_bitmap.push_back(v);
    }
  }

  std::vector<int32_t> fallback_values;
  if (fallback_values_after_bitmap.empty()) {
    fallback_values = std::move(fallback_values_too_large);
  } else if (fallback_values_too_large.empty()) {
    fallback_values = std::move(fallback_values_after_bitmap);
  } else {
    fallback_values.resize(fallback_values_too_large.size() +
                           fallback_values_after_bitmap.size());
    std::merge(fallback_values_too_large.begin(),
               fallback_values_too_large.end(),
               fallback_values_after_bitmap.begin(),
               fallback_values_after_bitmap.end(), &fallback_values[0]);
  }

  std::vector<uint32_t> output(
      2 /* seq start + seq len + bitmap len + ordered len */ +
      bitmap_values.size() + fallback_values.size());
  uint32_t* p = output.data();

  ABSL_DCHECK_EQ(sequence_length, static_cast<uint16_t>(sequence_length));
  *p++ = uint32_t{static_cast<uint16_t>(start_sequence.value_or(0))} |
         (uint32_t{sequence_length} << 16);
  ABSL_DCHECK_EQ(
      kBitmapBlockSize * bitmap_values.size(),
      static_cast<uint16_t>(kBitmapBlockSize * bitmap_values.size()));
  ABSL_DCHECK_EQ(fallback_values.size(),
                 static_cast<uint16_t>(fallback_values.size()));
  *p++ = static_cast<uint32_t>(kBitmapBlockSize * bitmap_values.size()) |
         static_cast<uint32_t>(fallback_values.size() << 16);
  p = std::copy(bitmap_values.begin(), bitmap_values.end(), p);

  EytzingerLayoutSorter{fallback_values,
                        absl::MakeSpan(p, fallback_values.size())}
      .Sort();

  return output;
}

}  // namespace internal
}  // namespace protobuf
}  // namespace google
