#pragma once

#ifndef USE_DESKTOP
#error "desktop_json.h is only for USE_DESKTOP builds"
#endif

#include <cstddef>
#include <type_traits>

class String;
class Print;
class Stream;

namespace desktop_json_detail {
  template <typename T> struct always_false : std::false_type {};
}

class JsonObject;
class JsonObjectConst;
class JsonArray;
class JsonVariant;
class JsonVariantConst;

class DeserializationError {
  bool failed_ = true;
public:
  DeserializationError(bool failed = true) : failed_(failed) {}
  explicit operator bool() const { return failed_; }
  const char* c_str() const { return failed_ ? "ArduinoJson disabled in desktop build" : "Ok"; }
};

class JsonVariantConst {
public:
  JsonVariantConst() = default;

  bool isNull() const { return true; }
  template <typename T> bool is() const { return false; }
  bool containsKey(const char*) const { return false; }
  bool containsKey(const String&) const { return false; }

  JsonVariantConst operator[](const char*) const { return JsonVariantConst(); }
  JsonVariantConst operator[](const String&) const { return JsonVariantConst(); }
  JsonVariantConst operator[](std::size_t) const { return JsonVariantConst(); }
  JsonVariantConst operator[](int) const { return JsonVariantConst(); }

  const char* operator|(const char* fallback) const { return fallback; }
  String operator|(const String& fallback) const;
  template <typename T, typename = typename std::enable_if<!std::is_array<T>::value && !std::is_pointer<T>::value>::type>
  T operator|(const T& fallback) const { return fallback; }

  template <typename T> T as() const;
  explicit operator bool() const { return false; }
};

class JsonVariant : public JsonVariantConst {
public:
  JsonVariant() = default;
  template <typename T> JsonVariant& operator=(const T&) { return *this; }
  JsonVariant operator[](const char*) const { return JsonVariant(); }
  JsonVariant operator[](const String&) const { return JsonVariant(); }
  JsonVariant operator[](std::size_t) const { return JsonVariant(); }
  JsonVariant operator[](int) const { return JsonVariant(); }
};

class JsonObjectConst {
public:
  JsonObjectConst() = default;
  JsonObjectConst(const JsonVariantConst&) {}

  bool isNull() const { return true; }
  bool containsKey(const char*) const { return false; }
  bool containsKey(const String&) const { return false; }

  JsonVariantConst operator[](const char*) const { return JsonVariantConst(); }
  JsonVariantConst operator[](const String&) const { return JsonVariantConst(); }

  template <typename T> bool is() const { return false; }
  template <typename T> T as() const;
  explicit operator bool() const { return false; }
};

class JsonObject : public JsonObjectConst {
public:
  JsonObject() = default;
  JsonObject(const JsonVariantConst&) {}

  JsonVariant operator[](const char*) { return JsonVariant(); }
  JsonVariant operator[](const String&) { return JsonVariant(); }
  JsonVariantConst operator[](const char* key) const { return JsonObjectConst::operator[](key); }
  JsonVariantConst operator[](const String& key) const { return JsonObjectConst::operator[](key); }

  JsonArray createNestedArray(const char*);
  JsonArray createNestedArray(const String&);
  JsonObject createNestedObject(const char*);
  JsonObject createNestedObject(const String&);
};

class JsonArray {
public:
  class iterator {
  public:
    iterator& operator++() { return *this; }
    bool operator!=(const iterator&) const { return false; }
    JsonVariant operator*() const { return JsonVariant(); }
  };

  JsonArray() = default;
  JsonArray(const JsonVariantConst&) {}

  bool isNull() const { return true; }
  std::size_t size() const { return 0; }
  JsonVariant operator[](std::size_t) const { return JsonVariant(); }
  JsonVariant operator[](int) const { return JsonVariant(); }
  JsonObject createNestedObject() { return JsonObject(); }
  JsonArray createNestedArray() { return JsonArray(); }
  iterator begin() const { return iterator(); }
  iterator end() const { return iterator(); }
  template <typename T> bool is() const { return false; }
  template <typename T> T as() const;
  explicit operator bool() const { return false; }
};

inline JsonArray JsonObject::createNestedArray(const char*) { return JsonArray(); }
inline JsonArray JsonObject::createNestedArray(const String&) { return JsonArray(); }
inline JsonObject JsonObject::createNestedObject(const char*) { return JsonObject(); }
inline JsonObject JsonObject::createNestedObject(const String&) { return JsonObject(); }

template <typename T>
inline T JsonVariantConst::as() const {
  if constexpr (std::is_same<T, const char*>::value) return "";
  else if constexpr (std::is_same<T, JsonObject>::value) return JsonObject();
  else if constexpr (std::is_same<T, JsonObjectConst>::value) return JsonObjectConst();
  else if constexpr (std::is_same<T, JsonArray>::value) return JsonArray();
  else if constexpr (std::is_same<T, JsonVariant>::value) return JsonVariant();
  else if constexpr (std::is_same<T, JsonVariantConst>::value) return JsonVariantConst();
  else return T();
}

template <typename T>
inline T JsonObjectConst::as() const {
  if constexpr (std::is_same<T, JsonObject>::value) return JsonObject();
  else if constexpr (std::is_same<T, JsonObjectConst>::value) return JsonObjectConst();
  else if constexpr (std::is_same<T, JsonArray>::value) return JsonArray();
  else if constexpr (std::is_same<T, const char*>::value) return "";
  else return T();
}

template <typename T>
inline T JsonArray::as() const {
  if constexpr (std::is_same<T, JsonArray>::value) return JsonArray();
  else if constexpr (std::is_same<T, JsonObject>::value) return JsonObject();
  else if constexpr (std::is_same<T, const char*>::value) return "";
  else return T();
}

class DynamicJsonDocument {
public:
  explicit DynamicJsonDocument(std::size_t) {}
  bool containsKey(const char*) const { return false; }
  bool containsKey(const String&) const { return false; }
  JsonVariant operator[](const char*) { return JsonVariant(); }
  JsonVariant operator[](const String&) { return JsonVariant(); }
  JsonVariantConst operator[](const char*) const { return JsonVariantConst(); }
  JsonVariantConst operator[](const String&) const { return JsonVariantConst(); }
  JsonArray createNestedArray(const char*) { return JsonArray(); }
  JsonArray createNestedArray(const String&) { return JsonArray(); }
  JsonObject createNestedObject(const char*) { return JsonObject(); }
  JsonObject createNestedObject(const String&) { return JsonObject(); }
  template <typename T> bool is() const { return false; }
  template <typename T> T as() const {
    if constexpr (std::is_same<T, JsonObject>::value) return JsonObject();
    else if constexpr (std::is_same<T, JsonObjectConst>::value) return JsonObjectConst();
    else if constexpr (std::is_same<T, JsonArray>::value) return JsonArray();
    else return T();
  }
};

template <std::size_t N>
class StaticJsonDocument : public DynamicJsonDocument {
public:
  StaticJsonDocument() : DynamicJsonDocument(N) {}
};

namespace DeserializationOption {
  class Filter {
  public:
    template <typename T> explicit Filter(const T&) {}
  };
}

inline DeserializationError deserializeJson(DynamicJsonDocument&, const String&) { return DeserializationError(true); }
inline DeserializationError deserializeJson(DynamicJsonDocument&, const char*) { return DeserializationError(true); }
inline DeserializationError deserializeJson(DynamicJsonDocument&, Stream&) { return DeserializationError(true); }
inline DeserializationError deserializeJson(DynamicJsonDocument&, Print&) { return DeserializationError(true); }
template <typename Input>
inline DeserializationError deserializeJson(DynamicJsonDocument&, Input&) { return DeserializationError(true); }
template <typename Input, typename Option>
inline DeserializationError deserializeJson(DynamicJsonDocument&, Input&, Option) { return DeserializationError(true); }

template <typename Doc, typename Out>
inline std::size_t serializeJsonPretty(const Doc&, Out& out) {
  return out.write(reinterpret_cast<const uint8_t*>("{}\n"), 3);
}
