#ifndef UTILS_H
#define UTILS_H

#include <algorithm>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdio.h>
#include <string>
#include <vector>
#include <Rcpp.h>
#include <boost/optional.hpp>
#include <boost/date_time.hpp>
#include "thread.h"

// A callback for deleting objects on the main thread using later(). This is
// needed when the object is an Rcpp object or contains one, because deleting
// such objects invoke R's memory management functions.
template <typename T>
void deleter_main(void* obj) {
  ASSERT_MAIN_THREAD()
  // later() passes a void* to the callback, so we have to cast it.
  T* typed_obj = reinterpret_cast<T*>(obj);

  try {
    delete typed_obj;
  } catch (...) {}
}

// Does the same as deleter_main, but checks that it's running on the
// background thread (when thread debugging is enabled).
template <typename T>
void deleter_background(void* obj) {
  ASSERT_BACKGROUND_THREAD()
  T* typed_obj = reinterpret_cast<T*>(obj);

  try {
    delete typed_obj;
  } catch (...) {}
}

// It's not safe to call REprintf from the background thread but we need some
// way to output error messages. R CMD check does not it if the code uses the
// symbols stdout, stderr, and printf, so this function is a way to avoid
// those. It's to calling `fprintf(stderr, ...)`.
inline void err_printf(const char *fmt, ...) {
  const size_t max_size = 4096;
  char buf[max_size];

  va_list args;
  va_start(args, fmt);
  int n = vsnprintf(buf, max_size, fmt, args);
  va_end(args);

  if (n == -1)
    return;

  ssize_t res = write(STDERR_FILENO, buf, n);
  // This is here simply to avoid a warning about "ignoring return value" of
  // the write(), on some compilers. (Seen with gcc 4.4.7 on RHEL 6)
  res += 0;
}


// ============================================================================
// Logging 
// ============================================================================

enum LogLevel {
  LOG_OFF,
  LOG_ERROR,
  LOG_WARN,
  LOG_INFO,
  LOG_DEBUG
};

void debug_log(const std::string& msg, LogLevel level);

// ============================================================================


// Indexing into an empty vector causes assertion failures on some platforms
template <typename T>
T* safe_vec_addr(std::vector<T>& vec) {
  return vec.size() ? &vec[0] : NULL;
}

// Indexing into an empty vector causes assertion failures on some platforms
inline const char* safe_str_addr(const std::string& str) {
  return str.size() ? &str[0] : NULL;
}

inline std::string to_lower(const std::string& str) {
  std::string lowered = str;
  std::transform(lowered.begin(), lowered.end(), lowered.begin(), tolower);
  return lowered;
}

template <typename T>
std::string toString(T x) {
  std::stringstream ss;
  ss << x;
  return ss.str();
}

// This is used for converting an Rcpp named vector (T2) to a std::map.
template <typename T1, typename T2>
std::map<std::string, T1> toMap(T2 x) {
  ASSERT_MAIN_THREAD()

  std::map<std::string, T1> strmap;

  if (x.size() == 0) {
    return strmap;
  }

  Rcpp::CharacterVector names = x.names();
  if (names.isNULL()) {
    throw Rcpp::exception("Error converting R object to map<string, T>: vector does not have names.");
  }

  for (int i=0; i<x.size(); i++) {
    std::string name  = Rcpp::as<std::string>(names[i]);
    T1          value = Rcpp::as<T1>         (x[i]);
    if (name == "") {
      throw Rcpp::exception("Error converting R object to map<string, T>: element has empty name.");
    }

    strmap.insert(
      std::pair<std::string, T1>(name, value)
    );
  }

  return strmap;
}

// A wrapper for Rcpp::as. If the R value is NULL, this returns boost::none;
// otherwise it returns the usual value that Rcpp::as returns, wrapped in
// boost::optional<T2>.
template <typename T1, typename T2>
boost::optional<T1> optional_as(T2 value) {
  if (value.isNULL()) {
    return boost::none;
  }
  return boost::optional<T1>( Rcpp::as<T1>(value) );
}

// A wrapper for Rcpp::wrap. If the C++ value is boost::none, this returns the
// R value NULL; otherwise it returns the usual value that Rcpp::wrap returns, after
// unwrapping from the boost::optional<T>.
template <typename T>
Rcpp::RObject optional_wrap(boost::optional<T> value) {
  if (value == boost::none) {
    return R_NilValue;
  }
  return Rcpp::wrap(value.get());
}


// as() and wrap() for ResponseHeaders. Since the ResponseHeaders typedef is
// in constants.h and this file doesn't include constants.h, we'll define them
// using the actual vector type instead of the ResponseHeaders typedef.
// (constants.h doesn't include Rcpp.h so we can't define these functions
// there.)
namespace Rcpp {
  template <> inline std::vector<std::pair<std::string, std::string> > as(SEXP x) {
    ASSERT_MAIN_THREAD()
    Rcpp::CharacterVector headers(x);
    Rcpp::CharacterVector names = headers.names();

    if (names.isNULL()) {
      throw Rcpp::exception("All values must be named.");
    }

    std::vector<std::pair<std::string, std::string> > result;

    for (int i=0; i<headers.size(); i++) {
      std::string name = Rcpp::as<std::string>(names[i]);
      if (name == "") {
        throw Rcpp::exception("All values must be named.");
      }

      std::string value = Rcpp::as<std::string>(headers[i]);

      result.push_back(std::make_pair(name, value));
    }

    return result;
  }

  template <> inline SEXP wrap(const std::vector<std::pair<std::string, std::string> > &x) {
    ASSERT_MAIN_THREAD()

    std::vector<std::string> values(x.size());
    std::vector<std::string> names(x.size());

    for (unsigned int i=0; i<x.size(); i++) {
      names[i]  = x[i].first;
      values[i] = x[i].second;
    }

    Rcpp::CharacterVector result = Rcpp::wrap(values);
    result.attr("names") = Rcpp::wrap(names);

    return result;
  }
}


// Return a date string in the format required for the HTTP Date header. For
// example: "Wed, 21 Oct 2015 07:28:00 GMT"
inline std::string http_date_string(const time_t& t) {
  struct tm timeptr;
  #ifdef _WIN32
  gmtime_s(&timeptr, &t);
  #else
  gmtime_r(&t, &timeptr);
  #endif

  std::string day_name;
  switch(timeptr.tm_wday) {
    case 0:  day_name = "Sun"; break;
    case 1:  day_name = "Mon"; break;
    case 2:  day_name = "Tue"; break;
    case 3:  day_name = "Wed"; break;
    case 4:  day_name = "Thu"; break;
    case 5:  day_name = "Fri"; break;
    case 6:  day_name = "Sat"; break;
    default: return "";
  }

  std::string month_name;
  switch(timeptr.tm_mon) {
    case 0:  month_name = "Jan"; break;
    case 1:  month_name = "Feb"; break;
    case 2:  month_name = "Mar"; break;
    case 3:  month_name = "Apr"; break;
    case 4:  month_name = "May"; break;
    case 5:  month_name = "Jun"; break;
    case 6:  month_name = "Jul"; break;
    case 7:  month_name = "Aug"; break;
    case 8:  month_name = "Sep"; break;
    case 9:  month_name = "Oct"; break;
    case 10: month_name = "Nov"; break;
    case 11: month_name = "Dec"; break;
    default: return "";
  }

  const int maxlen = 50;
  char res[maxlen];
  snprintf(res, maxlen, "%s, %02d %s %04d %02d:%02d:%02d GMT",
    day_name.c_str(),
    timeptr.tm_mday,
    month_name.c_str(),
    timeptr.tm_year + 1900,
    timeptr.tm_hour,
    timeptr.tm_min,
    timeptr.tm_sec
  );

  return std::string(res);
}


// Given a date string of format "Wed, 21 Oct 2015 07:28:00 GMT", return a
// time_t representing that time. If the date is malformed, then return 0.
inline time_t parse_http_date_string(const std::string& date) {
  // This is because the static std::locale may not be thread-safe. If in the
  // future we need to call this from multiple threads, we can remove this and
  // make the std::locale non-static.
  ASSERT_BACKGROUND_THREAD()

  if (date.length() != 29) {
    return 0;
  }

  // Strip off leading "Wed, ". We'll just ignore it.
  std::string date_str = date.substr(5);

  // Make sure it ends with " GMT", then strip it off.
  if (date_str.substr(date_str.size() - 4, 4) != " GMT") {
    return 0;
  }
  date_str = date_str.substr(0, date_str.size() - 4);

  // Format is now "21 Oct 2015 07:28:00". Next, replace month with number, so
  // that there are no locale issues parsing date.
  std::string month_names[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
  std::string month_name = date_str.substr(3, 3);
  int month_num = 0;
  for (int i=0; i<12; i++) {
    if (month_name == month_names[i]) {
      month_num = i + 1;
      break;
    }
  }

  if (month_num == 0) {
    return 0;
  }

  date_str.replace(3, 3, toString(month_num));

  // Format is now something like "21 10 2015 07:28:00"
  // Convert to a boost::posix_time::ptime object
  const static std::locale time_format = std::locale(
    std::locale::classic(),
    // Apparently it's not necessary to delete the time_input_facet.
    new boost::posix_time::time_input_facet("%d %m %Y %H:%M:%S")
  );

  boost::posix_time::ptime pt, ptbase;
  std::istringstream is(date_str);
  is.imbue(time_format);
  is >> pt;
  if (pt == ptbase) {
    // Error parsing time
    return 0;
  }

  return to_time_t(pt);
}

// Compares two strings in constant time. Returns true if they are the same;
// false otherwise.
inline bool constant_time_compare(const std::string& a, const std::string& b) {
  if (a.length() != b.length())
    return false;

  volatile const char* ac = a.c_str();
  volatile const char* bc = b.c_str();
  volatile char result = 0;
  int len = a.length();

  for (int i=0; i<len; i++) {
    result |= ac[i] ^ bc[i];
  }

  return (result == 0);
}

#endif
