//===--- JSONSerialization.h - JSON serialization support -------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
/// \file
/// \brief Provides an interface for serializing to JSON.
/// \note This does not include support for deserializing JSON; since JSON is
/// a subset of YAML, use LLVM's YAML parsing support instead.
///
//===----------------------------------------------------------------------===//

#ifndef SWIFT_BASIC_JSONSERIALIZATION_H
#define SWIFT_BASIC_JSONSERIALIZATION_H

#include "swift/Basic/LLVM.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/raw_ostream.h"

namespace swift {
namespace json {

/// This class should be specialized by any type that needs to be converted
/// to/from a JSON object.  For example:
///
///     struct ObjectTraits<MyStruct> {
///       static void mapping(Output &out, MyStruct &s) {
///         out.mapRequired("name", s.name);
///         out.mapRequired("size", s.size);
///         out.mapOptional("age",  s.age);
///       }
///     };
template<class T>
struct ObjectTraits {
  // Must provide:
  // static void mapping(Output &out, T &fields);
  // Optionally may provide:
  // static StringRef validate(Output &out, T &fields);
};


/// This class should be specialized by any integral type that converts
/// to/from a JSON scalar where there is a one-to-one mapping between
/// in-memory values and a string in JSON.  For example:
///
///     struct ScalarEnumerationTraits<Colors> {
///         static void enumeration(Output &out, Colors &value) {
///           out.enumCase(value, "red",   cRed);
///           out.enumCase(value, "blue",  cBlue);
///           out.enumCase(value, "green", cGreen);
///         }
///       };
template<typename T>
struct ScalarEnumerationTraits {
  // Must provide:
  // static void enumeration(Output &out, T &value);
};


/// This class should be specialized by any integer type that is a union
/// of bit values and the JSON representation is an array of
/// strings.  For example:
///
///      struct ScalarBitSetTraits<MyFlags> {
///        static void bitset(Output &out, MyFlags &value) {
///          out.bitSetCase(value, "big",   flagBig);
///          out.bitSetCase(value, "flat",  flagFlat);
///          out.bitSetCase(value, "round", flagRound);
///        }
///      };
template<typename T>
struct ScalarBitSetTraits {
  // Must provide:
  // static void bitset(Output &out, T &value);
};


/// This class should be specialized by type that requires custom conversion
/// to/from a json scalar.  For example:
///
///    template<>
///    struct ScalarTraits<MyType> {
///      static void output(const MyType &val, llvm::raw_ostream &out) {
///        // stream out custom formatting
///        out << llvm::format("%x", val);
///      }
///      static bool mustQuote(StringRef) { return true; }
///    };
template<typename T>
struct ScalarTraits {
  // Must provide:
  //
  // Function to write the value as a string:
  //static void output(const T &value, void *ctxt, llvm::raw_ostream &out);
  //
  // Function to determine if the value should be quoted.
  //static bool mustQuote(StringRef);
};


/// This class should be specialized by any type that needs to be converted
/// to/from a JSON array.  For example:
///
///    template<>
///    struct ArrayTraits< std::vector<MyType> > {
///      static size_t size(Output &out, std::vector<MyType> &seq) {
///        return seq.size();
///      }
///      static MyType& element(Output &, std::vector<MyType> &seq, size_t index) {
///        if ( index >= seq.size() )
///          seq.resize(index+1);
///        return seq[index];
///      }
///    };
template<typename T>
struct ArrayTraits {
  // Must provide:
  // static size_t size(Output &out, T &seq);
  // static T::value_type& element(Output &out, T &seq, size_t index);
};

// Only used by compiler if both template types are the same
template <typename T, T>
struct SameType;

// Only used for better diagnostics of missing traits
template <typename T>
struct MissingTrait;

// Test if ScalarEnumerationTraits<T> is defined on type T.
template <class T>
struct has_ScalarEnumerationTraits
{
  typedef void (*Signature_enumeration)(class Output&, T&);

  template <typename U>
  static char test(SameType<Signature_enumeration, &U::enumeration>*);

  template <typename U>
  static double test(...);

public:
  static bool const value =
  (sizeof(test<ScalarEnumerationTraits<T> >(nullptr)) == 1);
};


// Test if ScalarBitSetTraits<T> is defined on type T.
template <class T>
struct has_ScalarBitSetTraits
{
  typedef void (*Signature_bitset)(class Output&, T&);

  template <typename U>
  static char test(SameType<Signature_bitset, &U::bitset>*);

  template <typename U>
  static double test(...);

public:
  static bool const value = (sizeof(test<ScalarBitSetTraits<T> >(nullptr)) == 1);
};


// Test if ScalarTraits<T> is defined on type T.
template <class T>
struct has_ScalarTraits
{
  typedef void (*Signature_output)(const T&, llvm::raw_ostream&);
  typedef bool (*Signature_mustQuote)(StringRef);

  template <typename U>
  static char test(SameType<Signature_output, &U::output> *,
                   SameType<Signature_mustQuote, &U::mustQuote> *);

  template <typename U>
  static double test(...);

public:
  static bool const value =
  (sizeof(test<ScalarTraits<T>>(nullptr, nullptr)) == 1);
};


// Test if ObjectTraits<T> is defined on type T.
template <class T>
struct has_ObjectTraits
{
  typedef void (*Signature_mapping)(class Output&, T&);

  template <typename U>
  static char test(SameType<Signature_mapping, &U::mapping>*);

  template <typename U>
  static double test(...);

public:
  static bool const value = (sizeof(test<ObjectTraits<T> >(nullptr)) == 1);
};

// Test if ObjectTraits<T>::validate() is defined on type T.
template <class T>
struct has_ObjectValidateTraits
{
  typedef StringRef (*Signature_validate)(class Output&, T&);

  template <typename U>
  static char test(SameType<Signature_validate, &U::validate>*);

  template <typename U>
  static double test(...);

public:
  static bool const value = (sizeof(test<ObjectTraits<T> >(nullptr)) == 1);
};



// Test if ArrayTraits<T> is defined on type T.
template <class T>
struct has_ArrayMethodTraits
{
  typedef size_t (*Signature_size)(class Output&, T&);

  template <typename U>
  static char test(SameType<Signature_size, &U::size>*);
  
  template <typename U>
  static double test(...);
  
public:
  static bool const value =  (sizeof(test<ArrayTraits<T> >(nullptr)) == 1);
};

// Test if ArrayTraits<T> is defined on type T
template<typename T>
struct has_ArrayTraits : public std::integral_constant<bool,
    has_ArrayMethodTraits<T>::value > { };

inline bool isNumber(StringRef S) {
  static const char DecChars[] = "0123456789";
  if (S.find_first_not_of(DecChars) == StringRef::npos)
    return true;

  llvm::Regex FloatMatcher(
      "^(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)?$");
  if (FloatMatcher.match(S))
    return true;

  return false;
}

inline bool isNumeric(StringRef S) {
  if ((S.front() == '-' || S.front() == '+') && isNumber(S.drop_front()))
    return true;

  if (isNumber(S))
    return true;

  return false;
}

inline bool isNull(StringRef S) {
  return S.equals("null");
}

inline bool isBool(StringRef S) {
  return S.equals("true") || S.equals("false");
}

template<typename T>
struct missingTraits : public std::integral_constant<bool,
    !has_ScalarEnumerationTraits<T>::value
 && !has_ScalarBitSetTraits<T>::value
 && !has_ScalarTraits<T>::value
 && !has_ObjectTraits<T>::value
 && !has_ArrayTraits<T>::value>  {};

template<typename T>
struct validatedObjectTraits : public std::integral_constant<bool,
    has_ObjectTraits<T>::value
 && has_ObjectValidateTraits<T>::value> {};

template<typename T>
struct unvalidatedObjectTraits : public std::integral_constant<bool,
    has_ObjectTraits<T>::value
&& !has_ObjectValidateTraits<T>::value> {};

class Output {
  enum State {
    ArrayFirstValue,
    ArrayOtherValue,
    ObjectFirstKey,
    ObjectOtherKey
  };

  llvm::raw_ostream &Stream;
  SmallVector<State, 8> StateStack;
  bool PrettyPrint;
  bool NeedBitValueComma;
  bool EnumerationMatchFound;

public:
  Output(llvm::raw_ostream &os, bool PrettyPrint = true) : Stream(os),
      PrettyPrint(PrettyPrint), NeedBitValueComma(false),
      EnumerationMatchFound(false) {}
  virtual ~Output() = default;

  unsigned beginArray();
  bool preflightElement(unsigned, void *&);
  void postflightElement(void*);
  void endArray();
  bool canElideEmptyArray();

  void beginObject();
  void endObject();
  bool preflightKey(const char*, bool, bool, bool &, void *&);
  void postflightKey(void*);

  void beginEnumScalar();
  bool matchEnumScalar(const char*, bool);
  void endEnumScalar();

  bool beginBitSetScalar(bool &);
  bool bitSetMatch(const char*, bool);
  void endBitSetScalar();

  void scalarString(StringRef &, bool);

  template <typename T>
  void enumCase(T &Val, const char* Str, const T ConstVal) {
    if ( matchEnumScalar(Str, Val == ConstVal) ) {
      Val = ConstVal;
    }
  }

  template <typename T>
  void bitSetCase(T &Val, const char* Str, const T ConstVal) {
    if ( bitSetMatch(Str, (Val & ConstVal) == ConstVal) ) {
      Val = Val | ConstVal;
    }
  }

  template <typename T>
  void maskedBitSetCase(T &Val, const char *Str, T ConstVal, T Mask) {
    if (bitSetMatch(Str, (Val & Mask) == ConstVal))
      Val = Val | ConstVal;
  }

  template <typename T>
  void maskedBitSetCase(T &Val, const char *Str, uint32_t ConstVal,
                        uint32_t Mask) {
    if (bitSetMatch(Str, (Val & Mask) == ConstVal))
      Val = Val | ConstVal;
  }

  template <typename T>
  void mapRequired(const char* Key, T& Val) {
    this->processKey(Key, Val, true);
  }

  template <typename T>
  typename std::enable_if<has_ArrayTraits<T>::value,void>::type
  mapOptional(const char* Key, T& Val) {
    // omit key/value instead of outputting empty array
    if ( this->canElideEmptyArray() && !(Val.begin() != Val.end()) )
      return;
    this->processKey(Key, Val, false);
  }

  template <typename T>
  void mapOptional(const char* Key, Optional<T> &Val) {
    processKeyWithDefault(Key, Val, Optional<T>(), /*Required=*/false);
  }

  template <typename T>
  typename std::enable_if<!has_ArrayTraits<T>::value,void>::type
  mapOptional(const char* Key, T& Val) {
    this->processKey(Key, Val, false);
  }

  template <typename T>
  void mapOptional(const char* Key, T& Val, const T& Default) {
    this->processKeyWithDefault(Key, Val, Default, false);
  }

private:
  template <typename T>
  void processKeyWithDefault(const char *Key, Optional<T> &Val,
                             const Optional<T> &DefaultValue, bool Required) {
    assert(DefaultValue.hasValue() == false &&
           "Optional<T> shouldn't have a value!");
    void *SaveInfo;
    bool UseDefault;
    const bool sameAsDefault = !Val.hasValue();
    if (!Val.hasValue())
      Val = T();
    if (this->preflightKey(Key, Required, sameAsDefault, UseDefault,
                           SaveInfo)) {
      jsonize(*this, Val.getValue(), Required);
      this->postflightKey(SaveInfo);
    } else {
      if (UseDefault)
        Val = DefaultValue;
    }
  }

  template <typename T>
  void processKeyWithDefault(const char *Key, T &Val, const T& DefaultValue,
                             bool Required) {
    void *SaveInfo;
    bool UseDefault;
    const bool sameAsDefault = Val == DefaultValue;
    if ( this->preflightKey(Key, Required, sameAsDefault, UseDefault,
                            SaveInfo) ) {
      jsonize(*this, Val, Required);
      this->postflightKey(SaveInfo);
    }
    else {
      if ( UseDefault )
        Val = DefaultValue;
    }
  }

  template <typename T>
  void processKey(const char *Key, T &Val, bool Required) {
    void *SaveInfo;
    bool UseDefault;
    if ( this->preflightKey(Key, Required, false, UseDefault, SaveInfo) ) {
      jsonize(*this, Val, Required);
      this->postflightKey(SaveInfo);
    }
  }

private:
  void indent();
};

template<>
struct ScalarTraits<bool> {
  static void output(const bool &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<StringRef> {
  static void output(const StringRef &, llvm::raw_ostream &);
  static bool mustQuote(StringRef S) { return true; }
};

template<>
struct ScalarTraits<std::string> {
  static void output(const std::string &, llvm::raw_ostream &);
  static bool mustQuote(StringRef S) { return true; }
};

template<>
struct ScalarTraits<uint8_t> {
  static void output(const uint8_t &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<uint16_t> {
  static void output(const uint16_t &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<uint32_t> {
  static void output(const uint32_t &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<uint64_t> {
  static void output(const uint64_t &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<int8_t> {
  static void output(const int8_t &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<int16_t> {
  static void output(const int16_t &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<int32_t> {
  static void output(const int32_t &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<int64_t> {
  static void output(const int64_t &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<float> {
  static void output(const float &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<>
struct ScalarTraits<double> {
  static void output(const double &, llvm::raw_ostream &);
  static bool mustQuote(StringRef) { return false; }
};

template<typename T>
typename std::enable_if<has_ScalarEnumerationTraits<T>::value,void>::type
jsonize(Output &out, T &Val, bool) {
  out.beginEnumScalar();
  ScalarEnumerationTraits<T>::enumeration(out, Val);
  out.endEnumScalar();
}

template<typename T>
typename std::enable_if<has_ScalarBitSetTraits<T>::value,void>::type
jsonize(Output &out, T &Val, bool) {
  bool DoClear;
  if ( out.beginBitSetScalar(DoClear) ) {
    if ( DoClear )
      Val = static_cast<T>(0);
    ScalarBitSetTraits<T>::bitset(out, Val);
    out.endBitSetScalar();
  }
}


template<typename T>
typename std::enable_if<has_ScalarTraits<T>::value,void>::type
jsonize(Output &out, T &Val, bool) {
  {
    std::string Storage;
    llvm::raw_string_ostream Buffer(Storage);
    ScalarTraits<T>::output(Val, Buffer);
    StringRef Str = Buffer.str();
    out.scalarString(Str, ScalarTraits<T>::mustQuote(Str));
  }
}


template<typename T>
typename std::enable_if<validatedObjectTraits<T>::value, void>::type
jsonize(Output &out, T &Val, bool) {
  out.beginObject();
  {
    StringRef Err = ObjectTraits<T>::validate(out, Val);
    if (!Err.empty()) {
      llvm::errs() << Err << "\n";
      assert(Err.empty() && "invalid struct trying to be written as json");
    }
  }
  ObjectTraits<T>::mapping(out, Val);
  out.endObject();
}

template<typename T>
typename std::enable_if<unvalidatedObjectTraits<T>::value, void>::type
jsonize(Output &out, T &Val, bool) {
  out.beginObject();
  ObjectTraits<T>::mapping(out, Val);
  out.endObject();
}

template<typename T>
typename std::enable_if<missingTraits<T>::value, void>::type
jsonize(Output &out, T &Val, bool) {
  char missing_json_trait_for_type[sizeof(MissingTrait<T>)];
}

template<typename T>
typename std::enable_if<has_ArrayTraits<T>::value,void>::type
jsonize(Output &out, T &Seq, bool) {
  {
    out.beginArray();
    unsigned count = ArrayTraits<T>::size(out, Seq);
    for(unsigned i=0; i < count; ++i) {
      void *SaveInfo;
      if ( out.preflightElement(i, SaveInfo) ) {
        jsonize(out, ArrayTraits<T>::element(out, Seq, i), true);
        out.postflightElement(SaveInfo);
      }
    }
    out.endArray();
  }
}

// Define non-member operator<< so that Output can stream out a map.
template <typename T>
inline
typename
std::enable_if<swift::json::has_ObjectTraits<T>::value, Output &>::type
operator<<(Output &yout, T &map) {
  jsonize(yout, map, true);
  return yout;
}

// Define non-member operator<< so that Output can stream out a array.
template <typename T>
inline
typename
std::enable_if<swift::json::has_ArrayTraits<T>::value, Output &>::type
operator<<(Output &yout, T &seq) {
  jsonize(yout, seq, true);
  return yout;
}

} // end namespace json
} // end namespace swift

#endif
