// Copyright (c) 2012-2017 VideoStitch SAS
// Copyright (c) 2018 stitchEm

#ifndef PTV_HPP_
#define PTV_HPP_

#include "config.hpp"

#include <iosfwd>
#include <utility>
#include <vector>

namespace VideoStitch {

/**
 * The PTV format is built on several layers:
 *  1 - The textual layer that uses Json to represent a VideoStitch project as a file.
 *  2 - Ptv::Value provides a very simple layer of abtraction that provides no semantics except typing.
 *      It's just there to easily manipulate objects of the textual layer.
 *      Ptv::Values can be parsed from Json text using Ptv::Parser and written to text using Ptv::Value::printJson().
 *  3 - Ptv::Objects provide a semantic layer on top of Ptv::Value.
 *      Ptv::Objects can be built from Ptv::Values using static factory methods for each object.
 *      Ptv::Objects can be serialized to Ptv::Values using Ptv::Object::serialize();
 *  4 - Applications may build their own semantic layer by writing a Ptv::Object.
 */
namespace Ptv {
/**
 * @brief A parsed PTV value.
 */
class VS_EXPORT Value {
 public:
  /**
   * Const Key/Value pair.
   */
  typedef std::pair<const std::string*, const Value*> ConstKV;
  /**
   * Key/Value pair.
   */
  typedef std::pair<const std::string*, Value*> KV;

  /**
   * \enum Type
   * \brief Defines the possible Variable types.
   */
  enum Type { NIL, BOOL, INT, DOUBLE, STRING, OBJECT, LIST };

  virtual ~Value() {}

  /**
   * Creates an empty OBJECT value.
   */
  static Value* emptyObject();

  /**
   * Creates an OBJECT from bool value.
   */
  static Value* boolObject(const bool& value);

  /**
   * Creates an OBJECT from int value.
   */
  static Value* intObject(const int64_t& value);

  /**
   * Creates an OBJECT from double value.
   */
  static Value* doubleObject(const double& value);

  /**
   * Creates an OBJECT from string value.
   */
  static Value* stringObject(const std::string& value);

  /**
   * Clones a value.
   */
  virtual Value* clone() const = 0;

  /**
   * Returns the type of the value.
   */
  virtual Type getType() const = 0;

  /**
   * Returns true this value is implicitly convertible to the given type.
   */
  bool isConvertibleTo(Type t) const;

  /**
   * Return the boolean value of this value, or false.
   */
  virtual bool asBool() const = 0;

  /**
   * Return the integer value of this value, or 0.
   */
  virtual int64_t asInt() const = 0;

  /**
   * Return the floating point value of this value, or 0.0.
   */
  virtual double asDouble() const = 0;

  /**
   * Return the string value of this value, or an empty string.
   */
  virtual const std::string& asString() const = 0;

  /**
   * Return the list value of this value, or an empty vector.
   */
  virtual const std::vector<Value*>& asList() const = 0;

  /**
   * Make this value into a NIL.
   * If the value is already a NIL.
   */
  virtual void asNil() = 0;

  /**
   * Make this value into a bool and return a mutable reference to its value.
   * If the value is already a bool, this is a noop in itself.
   */
  virtual bool& asBool() = 0;

  /**
   * Make this value into an int and return a mutable reference to its value.
   * If the value is already an int, this is a noop in itself.
   */
  virtual int64_t& asInt() = 0;

  /**
   * Make this value into a double and return a mutable reference to its value.
   * If the value is already a double, this is a noop in itself.
   */
  virtual double& asDouble() = 0;

  /**
   * Make this value into a string and return a mutable reference to its value.
   * If the value is already a string, this is a noop in itself.
   */
  virtual std::string& asString() = 0;

  /**
   * Make this value into a list and return itself.
   * If the value is already a list, this is a noop in itself.
   */
  virtual std::vector<Value*>& asList() = 0;

  /**
   * Make this value into an object and return itself.
   * If the value is already an object, this is a noop in itself.
   */
  virtual Value& asObject() = 0;

  /**
   * Returns the member with the given name.
   * @param name The name of the value to retrieve.
   * @return The value with this name, or NULL if this value does not exist.
   * @note Only OBJECT typed values support this.
   */
  virtual const Value* has(const std::string& name) const = 0;

  /**
   * Returns the member with the given name.
   * If no member with this name exist, a new member is created, with a default type of NIL.
   * @param name The name of the value to add.
   * @return The value with this name, or NULL if this value is not an OBJECT. Remains the property of the value. Call
   * remove() to get ownership.
   * @note Only OBJECT typed values support this.
   */
  virtual Value* get(const std::string& name) = 0;

  /**
   * Adds a member to an OBJECT value. Does nothing if the value is not an Object.
   * @param key Name of the member to add.
   * @param v The value to add. Ownership is transferred to the value.
   * @return The previous value with the given key, NULL if there was none, or @a v if not an object. Must be deleted.
   */
  virtual Value* push(const std::string& key, Value* v) = 0;

  /**
   * Remove and returns the member with the given name.
   * If no member with this name exist, return NULL.
   * @param name The name of the value to remove.
   * @note Only OBJECT typed values support this.
   */
  virtual Value* remove(const std::string& name) = 0;

  /**
   * Returns the number of values in an OBJECT value.
   * @return 0 if this value is not an OBJECT.
   */
  virtual int size() const = 0;

  /**
   * Returns the i-th value in an OBJECT value.
   * @param i index of the object to get.
   * @return The (key, value) pair. Ownership is retained.
   */
  virtual ConstKV get(int i) const = 0;

  /**
   * Returns the i-th value in an OBJECT value.
   * @param i index of the object to get.
   * @return The (key, value) pair. Ownership is retained.
   */
  virtual KV get(int i) = 0;

  /**
   * Print the variable as Json.
   * @param os Output stream. This must be a character stream
   * (opened with the default mode).
   * @param indent How much to indent. Usually left as default.
   */
  virtual void printJson(std::ostream& os, int indent = 0) const = 0;

  /**
   * @brief getJsonStr
   * @return Object serialized as a Json std::string.
   */
  virtual std::string getJsonStr() const = 0;

  /**
   * Print the variable as UBJson.
   * @param os Output stream. This must be a binary stream
   * (opened with the 'binary' mode).
   */
  virtual void printUBJson(std::ostream& os) const = 0;

  /**
   * Tests equality.
   *  - Types must be the same, no conversion,
   *  - Object members can be in any order,
   *  - List elements must be in the same order.
   * @param other Other value to test against.
   */
  bool operator==(const Value& other) const;

  /**
   * Tests inequality.
   * @param other Other value to test against.
   */
  bool operator!=(const Value& other) const;

  /**
   * Traverse a Value recursively and set the defaults from another Value.
   * We will only set values that are primitive types and lists (i.e. we will never copy a whole object that is in
   * defaults but not in *this). Lists of defaults are applied element-wise. If there are less elements in the default
   * than in *this, the last element is repeated. This makes it possible to apply the same default to all elements of a
   * list by specifying only one element. Empty lists are ignored (don't use them). *this before call defaults *this
   * after call { "a": { "b": 2 } }            { "a": { "b": 1 } }                 { "a": { "b": 2 } } { "a": {        }
   * }            { "a": { "b": 1 } }                 { "a": { "b": 1 } } {                 }            { "a": { "b": 1
   * } }                 {                 } {                 }            { "l": [{ "c": 3 }] }               { "l":
   * []         } { "l": [{},{}] }               { "l": [{ "c": 3 }] }               { "l": [ { "c": 3 }, { "c": 3 } ] }
   *  { "l": [{},{}] }               { "l": [{ "c": 3 }, { "d": 2 }] }   { "l": [ { "c": 3 }, { "d": 2 } ] }
   *  { "l": [{},{}] }               { "l": [] }                         { "l": [{},{}] }
   */
  void populateWithPrimitiveDefaults(const Value& defaults);

  /**
   * Traverse a Value recursively and set the defaults from another Value.
   * If object types are not aligned, we skip the recursion subtree and output a debug message.
   */
  // void populateWithDefaults(const Value& defaults);

  /**
   * Returns the name of a given type. Used for error messages and debug.
   * Don't rely on these values for anything else than display, and don't expect them to be immutable.
   * @param type The type of which to get the name.
   */
  static const char* getTypeName(Type type);
};
}  // namespace Ptv
}  // namespace VideoStitch

#endif