// Copyright (c) Glyn Matthews 2011-2016.
// Copyright 2012 Dean Michael Berris <dberris@google.com>
// Copyright 2012 Google, Inc.
// 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)

#ifndef NETWORK_URI_ENCODE_INC
#define NETWORK_URI_ENCODE_INC

#include <network/uri/uri_errors.hpp>
#include <iterator>
#include <cstring>
#include <cassert>
#include <algorithm>

namespace network {
namespace detail {
template <typename CharT>
inline CharT hex_to_letter(CharT in) {
  if ((in >= 0) && (in < 10)) {
    return in + '0';
  }

  if ((in >= 10) && (in < 16)) {
    return in - 10 + 'A';
  }

  return in;
}

template <class charT, class OutputIterator>
void percent_encode(charT in, OutputIterator &out) {
  out++ = '%';
  out++ = hex_to_letter((in >> 4) & 0x0f);
  out++ = hex_to_letter(in & 0x0f);
}

template <class charT>
bool is_unreserved(charT in) {
  // clang-format off
  return ((in >= 'a') && (in <= 'z')) ||
         ((in >= 'A') && (in <= 'Z')) ||
         ((in >= '0') && (in <= '9')) ||
         (in == '-') ||
         (in == '.') ||
         (in == '_') ||
         (in == '~');
  // clang-format on
}

template <class charT, class OutputIterator>
void encode_char(charT in, OutputIterator &out, const char *ignore = "") {
  if (is_unreserved(in)) {
    out++ = in;
  } else {
    auto first = ignore, last = ignore + std::strlen(ignore);
    if (std::find(first, last, in) != last) {
      out++ = in;
    } else {
      percent_encode(in, out);
    }
  }
}

template <typename InputIterator, typename OutputIterator>
OutputIterator encode_user_info(InputIterator first, InputIterator last,
                                OutputIterator out) {
  auto it = first;
  while (it != last) {
    detail::encode_char(*it, out, ":");
    ++it;
  }
  return out;
}

template <typename InputIterator, typename OutputIterator>
OutputIterator encode_host(InputIterator first, InputIterator last,
                           OutputIterator out) {
  auto it = first;
  while (it != last) {
    detail::encode_char(*it, out, "[:]");
    ++it;
  }
  return out;
}

template <typename InputIterator, typename OutputIterator>
OutputIterator encode_port(InputIterator first, InputIterator last,
                           OutputIterator out) {
  auto it = first;
  while (it != last) {
    detail::encode_char(*it, out);
    ++it;
  }
  return out;
}

template <typename InputIterator, typename OutputIterator>
OutputIterator encode_path(InputIterator first, InputIterator last,
                           OutputIterator out) {
  auto it = first;
  while (it != last) {
    detail::encode_char(*it, out, "/.@%;=");
    ++it;
  }
  return out;
}

template <typename InputIterator, typename OutputIterator>
OutputIterator encode_query_component(InputIterator first, InputIterator last,
                                      OutputIterator out) {
  auto it = first;
  while (it != last) {
    detail::encode_char(*it, out, "/?");
    ++it;
  }
  return out;
}

template <typename InputIterator, typename OutputIterator>
OutputIterator encode_fragment(InputIterator first, InputIterator last,
                               OutputIterator out) {
  auto it = first;
  while (it != last) {
    detail::encode_char(*it, out, "/.@&l;=%");
    ++it;
  }
  return out;
}

template <class String>
String encode_user_info(const String &user_info) {
  String encoded;
  encode_user_info(std::begin(user_info), std::end(user_info),
                   std::back_inserter(encoded));
  return encoded;
}

template <class String>
String encode_host(const String &host) {
  String encoded;
  encode_host(std::begin(host), std::end(host), std::back_inserter(encoded));
  return encoded;
}

template <class String>
String encode_port(const String &port) {
  String encoded;
  encode_port(std::begin(port), std::end(port), std::back_inserter(encoded));
  return encoded;
}

template <class String>
String encode_path(const String &path) {
  String encoded;
  encode_path(std::begin(path), std::end(path), std::back_inserter(encoded));
  return encoded;
}

template <class String>
String encode_fragment(const String &fragment) {
  String encoded;
  encode_fragment(std::begin(fragment), std::end(fragment),
                  std::back_inserter(encoded));
  return encoded;
}
}  // namespace detail
}  // namespace network

#endif  // NETWORK_URI_ENCODE_INC
