#ifndef SRC_NODE_URL_H_
#define SRC_NODE_URL_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "node.h"
#include "env-inl.h"

#include <string>

namespace node {
namespace url {

using v8::Local;
using v8::Value;


#define PARSESTATES(XX)                                                       \
  XX(kSchemeStart)                                                            \
  XX(kScheme)                                                                 \
  XX(kNoScheme)                                                               \
  XX(kSpecialRelativeOrAuthority)                                             \
  XX(kPathOrAuthority)                                                        \
  XX(kRelative)                                                               \
  XX(kRelativeSlash)                                                          \
  XX(kSpecialAuthoritySlashes)                                                \
  XX(kSpecialAuthorityIgnoreSlashes)                                          \
  XX(kAuthority)                                                              \
  XX(kHost)                                                                   \
  XX(kHostname)                                                               \
  XX(kPort)                                                                   \
  XX(kFile)                                                                   \
  XX(kFileSlash)                                                              \
  XX(kFileHost)                                                               \
  XX(kPathStart)                                                              \
  XX(kPath)                                                                   \
  XX(kCannotBeBase)                                                           \
  XX(kQuery)                                                                  \
  XX(kFragment)

#define FLAGS(XX)                                                             \
  XX(URL_FLAGS_NONE, 0)                                                       \
  XX(URL_FLAGS_FAILED, 0x01)                                                  \
  XX(URL_FLAGS_CANNOT_BE_BASE, 0x02)                                          \
  XX(URL_FLAGS_INVALID_PARSE_STATE, 0x04)                                     \
  XX(URL_FLAGS_TERMINATED, 0x08)                                              \
  XX(URL_FLAGS_SPECIAL, 0x10)                                                 \
  XX(URL_FLAGS_HAS_USERNAME, 0x20)                                            \
  XX(URL_FLAGS_HAS_PASSWORD, 0x40)                                            \
  XX(URL_FLAGS_HAS_HOST, 0x80)                                                \
  XX(URL_FLAGS_HAS_PATH, 0x100)                                               \
  XX(URL_FLAGS_HAS_QUERY, 0x200)                                              \
  XX(URL_FLAGS_HAS_FRAGMENT, 0x400)                                           \
  XX(URL_FLAGS_IS_DEFAULT_SCHEME_PORT, 0x800)                                 \

enum url_parse_state {
  kUnknownState = -1,
#define XX(name) name,
  PARSESTATES(XX)
#undef XX
};

enum url_flags {
#define XX(name, val) name = val,
  FLAGS(XX)
#undef XX
};

struct url_data {
  int32_t flags = URL_FLAGS_NONE;
  int port = -1;
  std::string scheme;
  std::string username;
  std::string password;
  std::string host;
  std::string query;
  std::string fragment;
  std::vector<std::string> path;
};

class URL {
 public:
  static void Parse(const char* input,
                    size_t len,
                    enum url_parse_state state_override,
                    struct url_data* url,
                    bool has_url,
                    const struct url_data* base,
                    bool has_base);

  URL(const char* input, const size_t len) {
    Parse(input, len, kUnknownState, &context_, false, nullptr, false);
  }

  URL(const char* input, const size_t len, const URL* base) {
    if (base != nullptr)
      Parse(input, len, kUnknownState,
            &context_, false,
            &(base->context_), true);
    else
      Parse(input, len, kUnknownState, &context_, false, nullptr, false);
  }

  URL(const char* input, const size_t len,
      const char* base, const size_t baselen) {
    if (base != nullptr && baselen > 0) {
      URL _base(base, baselen);
      Parse(input, len, kUnknownState,
            &context_, false,
            &(_base.context_), true);
    } else {
      Parse(input, len, kUnknownState, &context_, false, nullptr, false);
    }
  }

  explicit URL(std::string input) :
      URL(input.c_str(), input.length()) {}

  URL(std::string input, const URL* base) :
      URL(input.c_str(), input.length(), base) {}

  URL(std::string input, const URL& base) :
      URL(input.c_str(), input.length(), &base) {}

  URL(std::string input, std::string base) :
      URL(input.c_str(), input.length(), base.c_str(), base.length()) {}

  int32_t flags() {
    return context_.flags;
  }

  int port() {
    return context_.port;
  }

  const std::string& protocol() const {
    return context_.scheme;
  }

  const std::string& username() const {
    return context_.username;
  }

  const std::string& password() const {
    return context_.password;
  }

  const std::string& host() const {
    return context_.host;
  }

  const std::string& query() const {
    return context_.query;
  }

  const std::string& fragment() const {
    return context_.fragment;
  }

  std::string path() const {
    std::string ret;
    for (const std::string& element : context_.path) {
      ret += '/' + element;
    }
    return ret;
  }

  // Get the path of the file: URL in a format consumable by native file system
  // APIs. Returns an empty string if something went wrong.
  std::string ToFilePath() const;
  // Get the file URL from native file system path.
  static URL FromFilePath(const std::string& file_path);

  const Local<Value> ToObject(Environment* env) const;

  URL(const URL&) = default;
  URL& operator=(const URL&) = default;
  URL(URL&&) = default;
  URL& operator=(URL&&) = default;

  URL() : URL("") {}

 private:
  struct url_data context_;
};

}  // namespace url

}  // namespace node

#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif  // SRC_NODE_URL_H_
