//
//  Copyright (C) 2020 Gareth Jones, Glysade LLC
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.

#ifndef UTIL_H_
#define UTIL_H_

#include <cmath>
#include <limits>
#include <cstdio>
#include <ctime>
#include <string>
#include <iostream>
#include <iterator>
#include <fstream>
#include <sstream>
#include <vector>
#include <memory>
#include <map>
#include <algorithm>
#include <boost/optional.hpp>
#include "export.h"

/*
 * Utility methods
 */
namespace GarethUtil {

using namespace std;

/*
 * Return current time as string
 */
string currentTime();

/**
 *
 * @param str
 * @param prefix
 * @return true id str starts with prefix
 */
GA_EXPORT bool startsWith(string str, string prefix);

/**
 *
 * @return current user name
 */
GA_EXPORT string getUserName();

/**
 * Template function to convert a string to a type.  Can optionally process a
 * substring
 *
 * @param str
 * @param ok optional boolean reference to indicate success or failure
 * @param start optional start position
 * @param end optional end position
 * @return
 */
template <typename T>
T convertString(const string &str, bool *const ok = nullptr, size_t start = 0,
                size_t noChars = 0) {
  if (ok != nullptr) {
    *ok = false;
  }
  T rtn;
  if (str.empty()) {
    return rtn;
  }
  if (start >= str.length()) {
    return rtn;
  }
  if (noChars == 0) {
    noChars = str.length() - start;
  }
  string field = str.substr(start, noChars);
  stringstream ss(field);
  ss >> rtn;
  if (!ss.fail()) {
    if (ok != nullptr) {
      *ok = true;
    }
  }
  return rtn;
}

/**
 * Template function to write all elements of a vector to a string
 *
 * @param vec
 * @param seperator
 * @return
 */
template <typename T>
string collectionToString(const T &vec, const string &seperator = ", ") {
  stringstream ss;
  std::copy(vec.begin(), vec.end(),
            ostream_iterator<typename T::value_type>(ss, seperator.c_str()));
  // erase trailing separator
  string rtn = ss.str();
  if (!rtn.empty()) {
    rtn.erase(rtn.length() - seperator.length(), seperator.length());
  }
  return rtn;
}

/**
 * Removes any trailing linefeed (\r) from a string
 * @param line
 * @return
 */
GA_EXPORT string &removeTrailingLF(string &line);

/**
 * Determines if a key is present in a map
 *
 * @param key
 * @param map
 * @param value  optionally set the value for the key
 * @return
 */
template <typename K, typename V>
bool isKeyPresent(const K key, const map<K, V> &map, V *const value = nullptr) {
  auto iter = map.find(key);
  if (iter != map.end()) {
    if (value != nullptr) {
      *value = iter->second;
    }
    return true;
  }
  return false;
}

/**
 * Bs whitespace from beginning and end of string.  Original argument is
 * modified and returned.
 * @param str
 * @return
 */
GA_EXPORT string &trim(string &str);

/**
 * Converts string to upper case.  Original argument is modified and returned.
 * @param str
 * @return
 */
GA_EXPORT string &toUpperCase(string &str);

/**
 * Converts string to lower case.  Original argument is modified and returned.
 * @param str
 * @return
 */
GA_EXPORT string &toLowerCase(string &str);

/**
 * @param str1
 * @param str2
 * @return true if the two strings are equal
 */
GA_EXPORT bool equals(const string &str1, const string &str2);

/**
 * @param str1
 * @param str2
 * @return true if the two strings are equal (case insensitive)
 */
GA_EXPORT bool equalsIgnoreCase(const string &str1, const string &str2);

/**
 *
 * @param str
 * @param suffix
 * @return true if str ends with suffix
 */
GA_EXPORT bool endsWith(const string &str, const string &suffix);

/**
 *
 * @param vector
 * @param value
 * @return true if vector contains value
 */
template <typename T>
bool contains(std::vector<T> vector, T value) {
  return std::find(vector.begin(), vector.end(), value) != vector.end();
}

/**
 * Determines if two double numbers are equal within multiples of machine
 * precision
 *
 * @param d1
 * @param d2
 * @param ulp machine precision (units in the last place)
 * @return
 */
GA_EXPORT bool equals(const double d1, const double d2, const int ulp);

/**
 * Returns true if the two numbers are within epsilon of each other
 *
 * @param d1
 * @param d2
 * @param epsilon
 * @return
 */
GA_EXPORT bool equals(const double d1, const double d2, const double epsilon);

/**
 * Determines if two double numbers are within one unit of the last place.
 *
 * @param d1
 * @param d2
 * @return
 */
GA_EXPORT bool equals(const double d1, const double d2);

/**
 * Finds the first occurrence of an matching item in a list
 *
 * @param values
 * @param matcher
 * @return
 */
template <typename T>
boost::optional<T> findFirstInList(const std::vector<T> &values,
                                   const function<bool(T)> &matcher) {
  auto iter = std::find_if(values.cbegin(), values.cend(), matcher);
  if (iter != values.cend()) {
    return *iter;
  }
  return boost::none;
}

/**
 * Finds the first occurrence of an matching item in a list of unique pointers
 *
 * @param values
 * @param matcher
 * @return
 */
template <typename T>
boost::optional<T *> findFirstInUniquePtrList(
    const std::vector<unique_ptr<T>> &values,
    const function<bool(const unique_ptr<T> &)> &matcher) {
  auto iter = std::find_if(values.cbegin(), values.cend(), matcher);
  if (iter != values.cend()) {
    return iter->get();
  }
  return boost::none;
}

/**
 * Removes all items from a list that DON'T match the filter.  Alters and
 * returns the original list.
 *
 * @param values
 * @param filter
 * @return
 */
template <typename T>
std::vector<T> &filterList(std::vector<T> &values,
                           const function<bool(T &)> &filter) {
  auto iter = remove_if(values.begin(), values.end(), not1(filter));
  values.erase(iter, values.end());
  return values;
}

/**
 * Creates a new vector of items in the input vector that match the filter
 * @param values
 * @param filter
 * @return
 */
template <typename T>
std::vector<T> filterListToNewList(const std::vector<T> &values,
                                   const function<bool(const T &)> &filter) {
  std::vector<T> newValues;
  newValues.reserve(values.size());
  copy_if(values.begin(), values.end(), back_inserter(newValues), filter);
  return newValues;
}

/**
 * Creates a new vector of items in the input vector that match the filter.
 * This method is specialized for an input list of unique pointers.
 * A list of raw pointers is returned.  It is assumed that the input list will
 * outlast the filtered list.
 *
 * @param values
 * @param filter
 * @return
 */
template <typename T>
std::vector<T *> filterUniquePtrListToRawPtrList(
    const std::vector<unique_ptr<T>> &values,
    const function<bool(T &)> &filter) {
  std::vector<T *> newValues;
  newValues.reserve(values.size());
  for (const auto &value : values) {
    if (filter(*value)) {
      newValues.push_back(value.get());
    }
  }
  return newValues;
}

/**
 * Specialization of the above for filtering non-const unique_ptrs to const raw
 * pointer filtered list
 * @param values
 * @param filter
 * @return
 */
template <typename T>
std::vector<const T *> filterUniquePtrListToConstRawPtrList(
    const std::vector<unique_ptr<T>> &values,
    const function<bool(T &)> &filter) {
  std::vector<const T *> newValues;
  newValues.reserve(values.size());
  for (const auto &value : values) {
    if (filter(*value)) {
      newValues.push_back(value.get());
    }
  }
  return newValues;
}

/**
 * Creates a new list from an existing list by applying the mapping function.
 *
 * @param values
 * @param map
 * @return
 */
template <typename T, typename V>
std::vector<V> mapToNewList(const std::vector<T> &values,
                            const function<V(const T &)> &map) {
  std::vector<V> mappedValues;
  mappedValues.reserve(values.size());
  std::transform(values.cbegin(), values.cend(), back_inserter(mappedValues),
                 map);
  return mappedValues;
}

/**
 * Creates a new list from an existing list by applying the mapping function.
 *
 * @param values
 * @param map
 * @return
 */
template <typename T>
std::vector<T> mapToNewList(const std::vector<T> &values,
                            const function<T(const T &)> &map) {
  return mapToNewList<T, T>(values, map);
}

/**
 * Applies a function to every value in a list.
 *
 * @param values
 * @param func
 */
template <typename T>
void forEach(const std::vector<T> &values, const function<void(T &)> &func) {
  for (const auto &v : values) {
    func(v);
  }
}

/**
 * Reduces a vector to a single value of a different type using the accumulator
 * function.
 *
 * @param startingValue
 * @param accumulator function takes accumulating value as first argument and
 * current list value as second.
 * @return
 */
template <typename T, typename R>
R reduce(const std::vector<T> &values, const R &startingValue,
         const function<R(R &, const T &)> accumulator) {
  R currentValue = startingValue;
  for (auto iter = values.cbegin(); iter != values.cend(); ++iter) {
    currentValue = accumulator(currentValue, *iter);
  }
  return currentValue;
}

/**
 * Writes an object to a string using the stream << operator.
 *
 * @param obj
 * @return
 */
template <typename T>
string printToString(T obj) {
  stringstream ss;
  ss << obj;
  return ss.str();
}

/**
 * Retrieve an environment variable value, or none if it is not set.
 *
 * @param name
 * @return
 */
GA_EXPORT boost::optional<string> getEnv(const string &name);

}  // namespace GarethUtil

#endif /* UTIL_H_ */
