// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include "arrow/result.h"
#include "arrow/status.h"
#include "arrow/util/key_value_metadata.h"
#include "arrow/util/logging.h"
#include "arrow/util/sort.h"

using std::size_t;

namespace arrow {

static std::vector<std::string> UnorderedMapKeys(
    const std::unordered_map<std::string, std::string>& map) {
  std::vector<std::string> keys;
  keys.reserve(map.size());
  for (const auto& pair : map) {
    keys.push_back(pair.first);
  }
  return keys;
}

static std::vector<std::string> UnorderedMapValues(
    const std::unordered_map<std::string, std::string>& map) {
  std::vector<std::string> values;
  values.reserve(map.size());
  for (const auto& pair : map) {
    values.push_back(pair.second);
  }
  return values;
}

KeyValueMetadata::KeyValueMetadata() : keys_(), values_() {}

KeyValueMetadata::KeyValueMetadata(
    const std::unordered_map<std::string, std::string>& map)
    : keys_(UnorderedMapKeys(map)), values_(UnorderedMapValues(map)) {
  ARROW_CHECK_EQ(keys_.size(), values_.size());
}

KeyValueMetadata::KeyValueMetadata(std::vector<std::string> keys,
                                   std::vector<std::string> values)
    : keys_(std::move(keys)), values_(std::move(values)) {
  ARROW_CHECK_EQ(keys.size(), values.size());
}

void KeyValueMetadata::ToUnorderedMap(
    std::unordered_map<std::string, std::string>* out) const {
  DCHECK_NE(out, nullptr);
  const int64_t n = size();
  out->reserve(n);
  for (int64_t i = 0; i < n; ++i) {
    out->insert(std::make_pair(key(i), value(i)));
  }
}

void KeyValueMetadata::Append(const std::string& key, const std::string& value) {
  keys_.push_back(key);
  values_.push_back(value);
}

Result<std::string> KeyValueMetadata::Get(const std::string& key) const {
  auto index = FindKey(key);
  if (index < 0) {
    return Status::KeyError(key);
  } else {
    return value(index);
  }
}

Status KeyValueMetadata::Delete(int64_t index) {
  keys_.erase(keys_.begin() + index);
  values_.erase(values_.begin() + index);
  return Status::OK();
}

Status KeyValueMetadata::DeleteMany(std::vector<int64_t> indices) {
  std::sort(indices.begin(), indices.end());
  const int64_t size = static_cast<int64_t>(keys_.size());
  indices.push_back(size);

  int64_t shift = 0;
  for (int64_t i = 0; i < static_cast<int64_t>(indices.size() - 1); ++i) {
    ++shift;
    const auto start = indices[i] + 1;
    const auto stop = indices[i + 1];
    DCHECK_GE(start, 0);
    DCHECK_LE(start, size);
    DCHECK_GE(stop, 0);
    DCHECK_LE(stop, size);
    for (int64_t index = start; index < stop; ++index) {
      keys_[index - shift] = std::move(keys_[index]);
      values_[index - shift] = std::move(values_[index]);
    }
  }
  keys_.resize(size - shift);
  values_.resize(size - shift);
  return Status::OK();
}

Status KeyValueMetadata::Delete(const std::string& key) {
  auto index = FindKey(key);
  if (index < 0) {
    return Status::KeyError(key);
  } else {
    return Delete(index);
  }
}

Status KeyValueMetadata::Set(const std::string& key, const std::string& value) {
  auto index = FindKey(key);
  if (index < 0) {
    Append(key, value);
  } else {
    keys_[index] = key;
    values_[index] = value;
  }
  return Status::OK();
}

bool KeyValueMetadata::Contains(const std::string& key) const {
  return FindKey(key) >= 0;
}

void KeyValueMetadata::reserve(int64_t n) {
  DCHECK_GE(n, 0);
  const auto m = static_cast<size_t>(n);
  keys_.reserve(m);
  values_.reserve(m);
}

int64_t KeyValueMetadata::size() const {
  DCHECK_EQ(keys_.size(), values_.size());
  return static_cast<int64_t>(keys_.size());
}

const std::string& KeyValueMetadata::key(int64_t i) const {
  DCHECK_GE(i, 0);
  DCHECK_LT(static_cast<size_t>(i), keys_.size());
  return keys_[i];
}

const std::string& KeyValueMetadata::value(int64_t i) const {
  DCHECK_GE(i, 0);
  DCHECK_LT(static_cast<size_t>(i), values_.size());
  return values_[i];
}

std::vector<std::pair<std::string, std::string>> KeyValueMetadata::sorted_pairs() const {
  std::vector<std::pair<std::string, std::string>> pairs;
  pairs.reserve(size());

  auto indices = internal::ArgSort(keys_);
  for (const auto i : indices) {
    pairs.emplace_back(keys_[i], values_[i]);
  }
  return pairs;
}

int KeyValueMetadata::FindKey(const std::string& key) const {
  for (size_t i = 0; i < keys_.size(); ++i) {
    if (keys_[i] == key) {
      return static_cast<int>(i);
    }
  }
  return -1;
}

std::shared_ptr<KeyValueMetadata> KeyValueMetadata::Copy() const {
  return std::make_shared<KeyValueMetadata>(keys_, values_);
}

std::shared_ptr<KeyValueMetadata> KeyValueMetadata::Merge(
    const KeyValueMetadata& other) const {
  std::unordered_set<std::string> observed_keys;
  std::vector<std::string> result_keys;
  std::vector<std::string> result_values;

  result_keys.reserve(keys_.size());
  result_values.reserve(keys_.size());

  for (int64_t i = 0; i < other.size(); ++i) {
    const auto& key = other.key(i);
    auto it = observed_keys.find(key);
    if (it == observed_keys.end()) {
      result_keys.push_back(key);
      result_values.push_back(other.value(i));
      observed_keys.insert(key);
    }
  }
  for (size_t i = 0; i < keys_.size(); ++i) {
    auto it = observed_keys.find(keys_[i]);
    if (it == observed_keys.end()) {
      result_keys.push_back(keys_[i]);
      result_values.push_back(values_[i]);
      observed_keys.insert(keys_[i]);
    }
  }

  return std::make_shared<KeyValueMetadata>(std::move(result_keys),
                                            std::move(result_values));
}

bool KeyValueMetadata::Equals(const KeyValueMetadata& other) const {
  if (size() != other.size()) {
    return false;
  }

  auto indices = internal::ArgSort(keys_);
  auto other_indices = internal::ArgSort(other.keys_);

  for (int64_t i = 0; i < size(); ++i) {
    auto j = indices[i];
    auto k = other_indices[i];
    if (keys_[j] != other.keys_[k] || values_[j] != other.values_[k]) {
      return false;
    }
  }
  return true;
}

std::string KeyValueMetadata::ToString() const {
  std::stringstream buffer;

  buffer << "\n-- metadata --";
  for (int64_t i = 0; i < size(); ++i) {
    buffer << "\n" << keys_[i] << ": " << values_[i];
  }

  return buffer.str();
}

std::shared_ptr<KeyValueMetadata> key_value_metadata(
    const std::unordered_map<std::string, std::string>& pairs) {
  return std::make_shared<KeyValueMetadata>(pairs);
}

std::shared_ptr<KeyValueMetadata> key_value_metadata(std::vector<std::string> keys,
                                                     std::vector<std::string> values) {
  return std::make_shared<KeyValueMetadata>(std::move(keys), std::move(values));
}

}  // namespace arrow
