/*
 * GDevelop Core
 * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
 * reserved. This project is released under the MIT License.
 */

#ifndef GDCORE_SERIALIZERELEMENT_H
#define GDCORE_SERIALIZERELEMENT_H
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "GDCore/Serialization/SerializerValue.h"
#include "GDCore/String.h"

namespace gd {

/**
 * \brief A generic container that can represent a value (
 * containing a string, double, bool or int), an object ("associative array",
 * "dictionary") with children or an array (children indexed by numeric
 * properties).
 *
 * It is used for serialization (to JSON or XML), or as a generic
 * container for properties of objects (see for example gd::BehaviorContent).
 *
 * It also has specialized methods in GDevelop.js (see postjs.js) to be
 * converted to a JavaScript object.
 *
 * \note Children are stored with their order preserved, but this also
 * means that their access/removal is O(number of children). This class
 * is not appropriated for a use in game where fast access is required.
 *
 * \see gd::Serializer
 */
class GD_CORE_API SerializerElement {
 public:
  /**
   * \brief Create an empty element with no value, no children and no
   * attributes.
   */
  SerializerElement();

  /**
   * \brief Create an element with the specified value.
   */
  SerializerElement(const SerializerValue &value);

  /**
   * Copy constructor.
   */
  SerializerElement(const gd::SerializerElement &object) { Init(object); };

  /**
   * Assignment operator.
   */
  SerializerElement &operator=(const gd::SerializerElement &object) {
    if ((this) != &object) Init(object);
    return *this;
  }

  virtual ~SerializerElement();

  /** \name Value
   * Methods related to the value of the element, if any.
   */
  ///@{
  /**
   * \brief Set the value of the element.
   */
  void SetValue(const SerializerValue &value) {
    valueUndefined = false;
    elementValue = value;
  }

  /**
   * \brief Set the value of the element, as a boolean.
   */
  void SetValue(bool val) {
    valueUndefined = false;
    elementValue.SetBool(val);
  }

  /**
   * \brief Set the value of the element, as a boolean.
   */
  void SetBoolValue(bool val) { SetValue(val); }

  /**
   * \brief Set the value of the element, as a string.
   */
  void SetValue(const gd::String &val) {
    valueUndefined = false;
    elementValue.SetString(val);
  }

  /**
   * \brief Set the value of the element, as a string.
   */
  void SetStringValue(const gd::String &val) { SetValue(val); }

  /**
   * \brief Set the value of the element, as an integer.
   */
  void SetValue(int val) {
    valueUndefined = false;
    elementValue.SetInt(val);
  }

  /**
   * \brief Set the value of the element, as an integer.
   */
  void SetIntValue(int val) { SetValue(val); }

  /**
   * \brief Set the value of the element, as an unsigned integer.
   */
  void SetValue(unsigned int val) {
    valueUndefined = false;
    elementValue.SetInt((int)val);
  }

  /**
   * \brief Set the value of the element, as a double precision floating point
   * number.
   */
  void SetValue(double val) {
    valueUndefined = false;
    elementValue.SetDouble(val);
  }

  /**
   * \brief Set the value of the element, as a double precision floating point
   * number.
   */
  void SetDoubleValue(double val) { SetValue(val); }

  /**
   * \brief Set the value of the element, as a floating point number.
   */
  void SetValue(float val) { SetValue((double)val); }

  /**
   * \brief Set the value of the element, as a floating point number.
   */
  void SetFloatValue(float val) { SetValue(val); }

  /**
   * \brief Get the value of the element, as a generic gd::SerializerValue.
   *
   * \note If not value was set, an attribute named "value" is searched (for
   * backward compatiblity). If found, its value is returned.
   */
  const SerializerValue &GetValue() const;

  /**
   * \brief Get the value, its type being a boolean.
   */
  bool GetBoolValue() const { return GetValue().GetBool(); };

  /**
   * \brief Get the value, its type being a gd::String.
   */
  gd::String GetStringValue() const { return GetValue().GetString(); };

  /**
   * \brief Get the value, its type being an int.
   */
  int GetIntValue() const { return GetValue().GetInt(); };

  /**
   * \brief Get the value, its type being a double
   */
  double GetDoubleValue() const { return GetValue().GetDouble(); };

  /**
   * \brief Return true if no value was set for the element.
   */
  bool IsValueUndefined() const { return valueUndefined; }
  ///@}

  /** \name Attributes
   * Methods related to the attributes of the element.
   *
   * \deprecated Prefer using AddChild/GetChild methods.
   *
   * Attributes are stored differently than children elements, but
   * are serialized to the same in JSON. Hence, the attribute getters
   * will also search in children elements.
   */
  ///@{
  /**
   * \brief Set the boolean value of an attribute of the element
   * \param name The name of the attribute.
   * \param value The value of the attribute.
   */
  SerializerElement &SetAttribute(const gd::String &name, bool value);

  /**
   * \brief Set the boolean value of an attribute of the element
   * \param name The name of the attribute.
   * \param value The value of the attribute.
   */
  SerializerElement &SetBoolAttribute(const gd::String &name, bool value) {
    return SetAttribute(name, value);
  }

  /**
   * \brief Set the string value of an attribute of the element
   * \param name The name of the attribute.
   * \param value The value of the attribute.
   */
  SerializerElement &SetAttribute(const gd::String &name,
                                  const gd::String &value);

  /**
   * \brief Set the string value of an attribute of the element
   * \param name The name of the attribute.
   * \param value The value of the attribute.
   */
  SerializerElement &SetStringAttribute(const gd::String &name,
                                        const gd::String &value) {
    return SetAttribute(name, value);
  }

  /**
   * \brief Set the string value of an attribute of the element
   * \param name The name of the attribute.
   * \param value The value of the attribute.
   */
  SerializerElement &SetAttribute(const gd::String &name, const char *value) {
    if (value) SetAttribute(name, gd::String(value));
    return *this;
  };

  /**
   * \brief Set the integer value of an attribute of the element
   * \param name The name of the attribute.
   * \param value The value of the attribute.
   */
  SerializerElement &SetAttribute(const gd::String &name, int value);

  /**
   * \brief Set the integer value of an attribute of the element
   * \param name The name of the attribute.
   * \param value The value of the attribute.
   */
  SerializerElement &SetIntAttribute(const gd::String &name, int value) {
    return SetAttribute(name, value);
  }

  /**
   * \brief Set the double precision floating point number value of an attribute
   * of the element \param name The name of the attribute. \param value The
   * value of the attribute.
   */
  SerializerElement &SetAttribute(const gd::String &name, double value);

  /**
   * \brief Set the double precision floating point number value of an attribute
   * of the element \param name The name of the attribute. \param value The
   * value of the attribute.
   */
  SerializerElement &SetDoubleAttribute(const gd::String &name, double value) {
    return SetAttribute(name, value);
  }

  /**
   * Get the value of an attribute being a boolean.
   * \param name The name of the attribute
   * \param defaultValue The value returned if the attribute is not found.
   * \param deprecatedName An alternative name for the attribute that will be
   * used if the first one doesn't exist.
   */
  bool GetBoolAttribute(const gd::String &name,
                        bool defaultValue = false,
                        gd::String deprecatedName = "") const;

  /**
   * Get the value of an attribute being a string.
   * \param name The name of the attribute
   * \param defaultValue The value returned if the attribute is not found.
   * \param deprecatedName An alternative name for the attribute that will be
   * used if the first one doesn't exist.
   */
  gd::String GetStringAttribute(const gd::String &name,
                                gd::String defaultValue = "",
                                gd::String deprecatedName = "") const;

  /**
   * Get the value of an attribute being an int.
   * \param name The name of the attribute
   * \param defaultValue The value returned if the attribute is not found.
   * \param deprecatedName An alternative name for the attribute that will be
   * used if the first one doesn't exist.
   */
  int GetIntAttribute(const gd::String &name,
                      int defaultValue = 0,
                      gd::String deprecatedName = "") const;

  /**
   * Get the value of an attribute being a double.
   * \param name The name of the attribute
   * \param defaultValue The value returned if the attribute is not found.
   * \param deprecatedName An alternative name for the attribute that will be
   * used if the first one doesn't exist.
   */
  double GetDoubleAttribute(const gd::String &name,
                            double defaultValue = 0.0,
                            gd::String deprecatedName = "") const;

  /**
   * \brief Return true if the specified attribute exists.
   * \param name The name of the attribute to find.
   */
  bool HasAttribute(const gd::String &name) const;

  /**
   * \brief Return all the attributes of the element.
   */
  const std::map<gd::String, SerializerValue> &GetAllAttributes() const {
    return attributes;
  };
  ///@}

  /** \name Children
   * Methods related to the children elements
   */
  ///@{
  /**
   * \brief Consider that the element is an array of elements, without specific
   * name for the children element.
   *
   * When serialized to a format accepting arrays (like JSON), the element will
   * be serialized to an array.
   */
  void ConsiderAsArray() const { isArray = true; };

  /**
   * \brief Check if the element is considered as an array containing its
   * children.
   *
   * As possible, this should be serialized to an array (possible JSON, but not
   * in XML).
   */
  bool ConsideredAsArray() const { return isArray; };

  /**
   * \brief Consider that the element is an array for elements with the given
   * name.
   *
   * In this case, no child with a different name should be added. When
   * serialized to a format accepting arrays (like JSON), the element will be
   * serialized to an array.
   *
   * \param name The name of the children.
   */
  void ConsiderAsArrayOf(const gd::String &name,
                         const gd::String &deprecatedName = "") const {
    ConsiderAsArray();
    arrayOf = name;
    deprecatedArrayOf = deprecatedName;
  };

  /**
   * \brief Return the name of the children the element is considered an array
   * of.
   *
   * Return an empty string if the element is not considered as an array.
   */
  const gd::String &ConsideredAsArrayOf() const { return arrayOf; };

  /**
   * \brief Add a child at the end of the children list with the given name and
   * return a reference to it.
   *
   * \param name The name of the new child.
   */
  SerializerElement &AddChild(gd::String name);

  /**
   * \brief Get a child of the element using its name.
   *
   * \param name The name of the child.
   * \param name The index of the child, in case of an array.
   */
  SerializerElement &GetChild(gd::String name,
                              std::size_t index = 0,
                              gd::String deprecatedName = "") const;

  /**
   * \brief Get a child of the element using its index (when the element is
   * considered as an array).
   *
   * \param name The index of the child
   */
  SerializerElement &GetChild(std::size_t index) const;

  /**
   * \brief Get the number of children having a specific name.
   *
   * If no children name is specified, return the number of children being part
   * of the array. (ConsiderAsArrayOf must have been called before). Note that
   * unnamed children are considered to be valid element of the array.
   *
   * \param name The name of the children.
   *
   * \see SerializerElement::ConsiderAsArrayOf
   */
  std::size_t GetChildrenCount(gd::String name = "",
                               gd::String deprecatedName = "") const;

  /**
   * \brief Return true if the specified child exists.
   * \note Complexity is O(number of children).
   * \param name The name of the child to find.
   */
  bool HasChild(const gd::String &name, gd::String deprecatedName = "") const;

  /**
   * \brief Remove the child with the specified name
   * \note Complexity is O(number of children).
   * \param name The name of the child to remove.
   */
  void RemoveChild(const gd::String &name);

  /**
   * \brief Return all the children of the element.
   */
  const std::vector<std::pair<gd::String, std::shared_ptr<SerializerElement> > >
      &GetAllChildren() const {
    return children;
  };
  ///@}

  static SerializerElement nullElement;

 private:
  /**
   * Initialize element using another element. Used by copy-ctor and assign-op.
   * Don't forget to update me if members were changed!
   */
  void Init(const gd::SerializerElement& other);

  bool valueUndefined;  ///< If true, the element does not have a value.
  SerializerValue elementValue;

  std::map<gd::String, SerializerValue> attributes;
  std::vector<std::pair<gd::String, std::shared_ptr<SerializerElement> > >
      children;
  mutable bool isArray;        ///< true if element is considered as an array
  mutable gd::String arrayOf;  ///< The name of the children (was useful for XML
                               ///< parsed elements).
  mutable gd::String deprecatedArrayOf;  ///< Alternate name for children
};

}  // namespace gd

#endif
