//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once

#include "td/utils/common.h"

#include <functional>
#include <type_traits>
#include <utility>

namespace td {

namespace detail {

template <typename V>
struct transform_helper {
  template <class Func>
  auto transform(const V &v, const Func &f) {
    vector<decltype(f(*v.begin()))> result;
    result.reserve(v.size());
    for (auto &x : v) {
      result.push_back(f(x));
    }
    return result;
  }

  template <class Func>
  auto transform(V &&v, const Func &f) {
    vector<decltype(f(std::move(*v.begin())))> result;
    result.reserve(v.size());
    for (auto &x : v) {
      result.push_back(f(std::move(x)));
    }
    return result;
  }
};

}  // namespace detail

template <class V, class Func>
auto transform(V &&v, const Func &f) {
  return detail::transform_helper<std::decay_t<V>>().transform(std::forward<V>(v), f);
}

template <class V, class Func>
bool remove_if(V &v, const Func &f) {
  size_t i = 0;
  while (i != v.size() && !f(v[i])) {
    i++;
  }
  if (i == v.size()) {
    return false;
  }

  size_t j = i;
  while (++i != v.size()) {
    if (!f(v[i])) {
      v[j++] = std::move(v[i]);
    }
  }
  v.erase(v.begin() + j, v.end());
  return true;
}

template <class V, class T>
bool remove(V &v, const T &value) {
  size_t i = 0;
  while (i != v.size() && v[i] != value) {
    i++;
  }
  if (i == v.size()) {
    return false;
  }

  size_t j = i;
  while (++i != v.size()) {
    if (v[i] != value) {
      v[j++] = std::move(v[i]);
    }
  }
  v.erase(v.begin() + j, v.end());
  return true;
}

template <class V>
void unique(V &v) {
  if (v.empty()) {
    return;
  }

  // use ADL to find std::sort
  // caller will need to #include <algorithm>
  sort(v.begin(), v.end(), std::less<void>());

  size_t j = 1;
  for (size_t i = 1; i < v.size(); i++) {
    if (v[i] != v[j - 1]) {
      if (i != j) {
        v[j] = std::move(v[i]);
      }
      j++;
    }
  }
  v.resize(j);
}

template <class V, class T>
bool contains(const V &v, const T &value) {
  for (auto &x : v) {
    if (x == value) {
      return true;
    }
  }
  return false;
}

template <class T>
void reset_to_empty(T &value) {
  using std::swap;
  std::decay_t<T> tmp;
  swap(tmp, value);
}

template <class T>
void append(vector<T> &destination, const vector<T> &source) {
  destination.insert(destination.end(), source.begin(), source.end());
}

template <class T>
void append(vector<T> &destination, vector<T> &&source) {
  if (destination.empty()) {
    destination.swap(source);
    return;
  }
  destination.reserve(destination.size() + source.size());
  for (auto &elem : source) {
    destination.push_back(std::move(elem));
  }
  reset_to_empty(source);
}

template <class T>
void combine(vector<T> &destination, const vector<T> &source) {
  append(destination, source);
}

template <class T>
void combine(vector<T> &destination, vector<T> &&source) {
  if (destination.size() < source.size()) {
    destination.swap(source);
  }
  if (source.empty()) {
    return;
  }
  destination.reserve(destination.size() + source.size());
  for (auto &elem : source) {
    destination.push_back(std::move(elem));
  }
  reset_to_empty(source);
}

namespace detail {
template <typename T>
struct reversion_wrapper {
  T &iterable;
};

template <typename T>
auto begin(reversion_wrapper<T> w) {
  return w.iterable.rbegin();
}

template <typename T>
auto end(reversion_wrapper<T> w) {
  return w.iterable.rend();
}
}  // namespace detail

template <typename T>
detail::reversion_wrapper<T> reversed(T &iterable) {
  return {iterable};
}

}  // namespace td
