/**
 * @file llsd.h
 * @brief LLSD flexible data system.
 *
 * $LicenseInfo:firstyear=2005&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifndef LL_LLSD_NEW_H
#define LL_LLSD_NEW_H

#include <map>
#include <string>
#include <vector>

#include "stdtypes.h"

#include "lldate.h"
#include "lluri.h"
#include "lluuid.h"

/**
    LLSD provides a flexible data system similar to the data facilities of
    dynamic languages like Perl and Python.  It is created to support exchange
    of structured data between loosely coupled systems.  (Here, "loosely coupled"
    means not compiled together into the same module.)

    Data in such exchanges must be highly tolerant of changes on either side
    such as:
            - recompilation
            - implementation in a different langauge
            - addition of extra parameters
            - execution of older versions (with fewer parameters)

    To this aim, the C++ API of LLSD strives to be very easy to use, and to
    default to "the right thing" wherever possible.  It is extremely tolerant
    of errors and unexpected situations.

    The fundamental class is LLSD.  LLSD is a value holding object.  It holds
    one value that is either undefined, one of the scalar types, or a map or an
    array.  LLSD objects have value semantics (copying them copies the value,
    though it can be considered efficient, due to sharing), and mutable.

    Undefined is the singular value given to LLSD objects that are not
    initialized with any data.  It is also used as the return value for
    operations that return an LLSD.

    The scalar data types are:
        - Boolean   - true or false
        - Integer   - a 32 bit signed integer
        - Real      - a 64 IEEE 754 floating point value
        - UUID      - a 128 unique value
        - String    - a sequence of zero or more Unicode chracters
        - Date      - an absolute point in time, UTC,
                        with resolution to the second
        - URI       - a String that is a URI
        - Binary    - a sequence of zero or more octets (unsigned bytes)

    A map is a dictionary mapping String keys to LLSD values.  The keys are
    unique within a map, and have only one value (though that value could be
    an LLSD array).

    An array is a sequence of zero or more LLSD values.

    Thread Safety

    In general, these LLSD classes offer *less* safety than STL container
    classes.  Implementations prior to this one were unsafe even when
    completely unrelated LLSD trees were in two threads due to reference
    sharing of special 'undefined' values that participated in the reference
    counting mechanism.

    The dereference-before-refcount and aggressive tree sharing also make
    it impractical to share an LLSD across threads.  A strategy of passing
    ownership or a copy to another thread is still difficult due to a lack
    of a cloning interface but it can be done with some care.

    One way of transferring ownership is as follows:

        void method(const LLSD input) {
        ...
        LLSD * xfer_tree = new LLSD();
        {
            // Top-level values
            (* xfer_tree)['label'] = "Some text";
            (* xfer_tree)['mode'] = APP_MODE_CONSTANT;

            // There will be a second-level
            LLSD subtree(LLSD::emptyMap());
            (* xfer_tree)['subtree'] = subtree;

            // Do *not* copy from LLSD objects via LLSD
            // intermediaries.  Only use plain-old-data
            // types as intermediaries to prevent reference
            // sharing.
            subtree['value1'] = input['value1'].asInteger();
            subtree['value2'] = input['value2'].asString();

            // Close scope and drop 'subtree's reference.
            // Only xfer_tree has a reference to the second
            // level data.
        }
        ...
        // Transfer the LLSD pointer to another thread.  Ownership
        // transfers, this thread no longer has a reference to any
        // part of the xfer_tree and there's nothing to free or
        // release here.  Receiving thread does need to delete the
        // pointer when it is done with the LLSD.  Transfer
        // mechanism must perform correct data ordering operations
        // as dictated by architecture.
        other_thread.sendMessageAndPointer("Take This", xfer_tree);
        xfer_tree = NULL;


    Avoid this pattern which provides half of a race condition:

        void method(const LLSD input) {
        ...
        LLSD xfer_tree(LLSD::emptyMap());
        xfer_tree['label'] = "Some text";
        xfer_tree['mode'] = APP_MODE_CONSTANT;
        ...
        other_thread.sendMessageAndPointer("Take This", xfer_tree);


    @nosubgrouping
*/

// Normally undefined, used for diagnostics
//#define LLSD_DEBUG_INFO   1

class LL_COMMON_API LLSD
{
public:
        LLSD();     ///< initially Undefined
        ~LLSD();    ///< this class may NOT be subclassed

    /** @name Copyable and Assignable */
    //@{
        LLSD(const LLSD&);
        void assign(const LLSD& other);
        LLSD& operator=(const LLSD& other)  { assign(other); return *this; }

    //@}

    /** @name Movable */
    //@{
        LLSD(LLSD&& other) noexcept;
        void  assign(LLSD&& other);
        LLSD& operator=(LLSD&& other) noexcept;
    //@}

    void clear();   ///< resets to Undefined


    /** @name Scalar Types
        The scalar types, and how they map onto C++
    */
    //@{
        typedef bool            Boolean;
        typedef S32             Integer;
        typedef F64             Real;
        typedef std::string     String;
        typedef LLUUID          UUID;
        typedef LLDate          Date;
        typedef LLURI           URI;
        typedef std::vector<U8> Binary;
    //@}

    /** @name Scalar Constructors */
    //@{
        LLSD(Boolean);
        LLSD(Integer);
        LLSD(Real);
        LLSD(const String&);
        LLSD(const UUID&);
        LLSD(const Date&);
        LLSD(const URI&);
        LLSD(const Binary&);
        LLSD(String&&);
        LLSD(UUID&&);
        LLSD(Date&&);
        LLSD(URI&&);
        LLSD(Binary&&);
    //@}

    /** @name Convenience Constructors */
    //@{
        // support construction from size_t et al.
        template <typename VALUE,
                  typename std::enable_if<std::is_integral<VALUE>::value &&
                                          ! std::is_same<VALUE, Boolean>::value,
                                          bool>::type = true>
        LLSD(VALUE v): LLSD(Integer(narrow<VALUE>(v))) {}
        // support construction from F32 et al.
        template <typename VALUE,
                  typename std::enable_if<std::is_floating_point<VALUE>::value,
                                          bool>::type = true>
        LLSD(VALUE v): LLSD(Real(narrow<VALUE>(v))) {}
    //@}

    /** @name Scalar Assignment */
    //@{
        void assign(Boolean);
        void assign(Integer);
        void assign(Real);
        void assign(const String&);
        void assign(const UUID&);
        void assign(const Date&);
        void assign(const URI&);
        void assign(const Binary&);
        void assign(String&&);
        void assign(UUID&&);
        void assign(Date&&);
        void assign(URI&&);
        void assign(Binary&&);

        LLSD& operator=(Boolean v)          { assign(v); return *this; }
        LLSD& operator=(Integer v)          { assign(v); return *this; }
        LLSD& operator=(Real v)             { assign(v); return *this; }
        LLSD& operator=(const String& v)    { assign(v); return *this; }
        LLSD& operator=(const UUID& v)      { assign(v); return *this; }
        LLSD& operator=(const Date& v)      { assign(v); return *this; }
        LLSD& operator=(const URI& v)       { assign(v); return *this; }
        LLSD& operator=(const Binary& v)    { assign(v); return *this; }
        LLSD& operator=(String&& v)         { assign(std::move(v)); return *this; }
        LLSD& operator=(UUID&& v)               { assign(std::move(v)); return *this; }
        LLSD& operator=(Date&& v)               { assign(std::move(v)); return *this; }
        LLSD& operator=(URI&& v)                { assign(std::move(v)); return *this; }
        LLSD& operator=(Binary&& v)         { assign(std::move(v)); return *this; }
    //@}

    /**
        @name Scalar Accessors
        @brief Fetch a scalar value, converting if needed and possible

        Conversion among the basic types, Boolean, Integer, Real and String, is
        fully defined.  Each type can be converted to another with a reasonable
        interpretation.  These conversions can be used as a convenience even
        when you know the data is in one format, but you want it in another.  Of
        course, many of these conversions lose information.

        Note: These conversions are not the same as Perl's.  In particular, when
        converting a String to a Boolean, only the empty string converts to
        false.  Converting the String "0" to Boolean results in true.

        Conversion to and from UUID, Date, and URI is only defined to and from
        String.  Conversion is defined to be information preserving for valid
        values of those types.  These conversions can be used when one needs to
        convert data to or from another system that cannot handle these types
        natively, but can handle strings.

        Conversion to and from Binary isn't defined.

        Conversion of the Undefined value to any scalar type results in a
        reasonable null or zero value for the type.
    */
    //@{
        Boolean asBoolean() const;
        Integer asInteger() const;
        Real    asReal() const;
        String  asString() const;
        UUID    asUUID() const;
        Date    asDate() const;
        URI     asURI() const;
        const Binary& asBinary() const;

        // asStringRef on any non-string type will return a ref to an empty string.
        const String& asStringRef() const;

        // Return "<value><((type))>((scalar value or recursive calls))</((type))></value>"
        // See http://xmlrpc.com/spec.md
        String asXMLRPCValue() const;

        operator Boolean() const    { return asBoolean(); }
        operator Integer() const    { return asInteger(); }
        operator Real() const       { return asReal(); }
        operator String() const     { return asString(); }
        operator UUID() const       { return asUUID(); }
        operator Date() const       { return asDate(); }
        operator URI() const        { return asURI(); }
        operator Binary() const     { return asBinary(); }

        // This is needed because most platforms do not automatically
        // convert the boolean negation as a bool in an if statement.
        bool operator!() const { return !asBoolean(); }
    //@}

    /** @name Character Pointer Helpers
        These are helper routines to make working with char* as easy as
        working with strings.
     */
    //@{
        LLSD(const char*);
        void assign(const char*);
        LLSD& operator=(const char* v) { assign(v); return *this; }
    //@}

    /** @name Map Values */
    //@{
        static LLSD emptyMap();

        bool has(const std::string_view) const;
        LLSD get(const std::string_view) const;
        LLSD getKeys() const;               // Return an LLSD array with keys as strings
        void insert(std::string_view, const LLSD&);
        void erase(const String&);
        LLSD& with(std::string_view, const LLSD&);

        LLSD& operator[](const std::string_view);
        LLSD& operator[](const char* c)
        {
            return c ? (*this)[std::string_view(c)] : *this;
        }
        const LLSD& operator[](const std::string_view) const;
        const LLSD& operator[](const char* c) const
        {
            return c ? (*this)[std::string_view(c)] : *this;
        }
    //@}

    /** @name Array Values */
    //@{
        static LLSD emptyArray();

        LLSD get(Integer) const;
        void set(Integer, const LLSD&);
        void insert(Integer, const LLSD&);
        LLSD& append(const LLSD&);
        void erase(Integer);
        LLSD& with(Integer, const LLSD&);

        // accept size_t so we can index relative to size()
        const LLSD& operator[](size_t) const;
        LLSD& operator[](size_t);
        // template overloads to support int literals, U32 et al.
        template <typename IDX,
                  typename std::enable_if<std::is_convertible<IDX, size_t>::value,
                                          bool>::type = true>
        const LLSD& operator[](IDX i) const { return (*this)[size_t(i)]; }
        template <typename IDX,
                  typename std::enable_if<std::is_convertible<IDX, size_t>::value,
                                          bool>::type = true>
        LLSD& operator[](IDX i) { return (*this)[size_t(i)]; }
    //@}

    /** @name Iterators */
    //@{
        size_t size() const;

        typedef std::map<String, LLSD>::iterator        map_iterator;
        typedef std::map<String, LLSD>::const_iterator  map_const_iterator;

        map_iterator        beginMap();
        map_iterator        endMap();
        map_const_iterator  beginMap() const;
        map_const_iterator  endMap() const;

        typedef std::vector<LLSD>::iterator         array_iterator;
        typedef std::vector<LLSD>::const_iterator   array_const_iterator;
        typedef std::vector<LLSD>::reverse_iterator reverse_array_iterator;

        array_iterator          beginArray();
        array_iterator          endArray();
        array_const_iterator    beginArray() const;
        array_const_iterator    endArray() const;

        reverse_array_iterator  rbeginArray();
        reverse_array_iterator  rendArray();
    //@}

    /** @name Type Testing */
    //@{
        enum Type {
            TypeUndefined = 0,
            TypeBoolean,
            TypeInteger,
            TypeReal,
            TypeString,
            TypeUUID,
            TypeDate,
            TypeURI,
            TypeBinary,
            TypeMap,
            TypeArray,
            TypeLLSDTypeEnd,
            TypeLLSDTypeBegin = TypeUndefined,
            TypeLLSDNumTypes = (TypeLLSDTypeEnd - TypeLLSDTypeBegin)
        };

        Type type() const;

        bool isUndefined() const    { return type() == TypeUndefined; }
        bool isDefined() const      { return type() != TypeUndefined; }
        bool isBoolean() const      { return type() == TypeBoolean; }
        bool isInteger() const      { return type() == TypeInteger; }
        bool isReal() const         { return type() == TypeReal; }
        bool isString() const       { return type() == TypeString; }
        bool isUUID() const         { return type() == TypeUUID; }
        bool isDate() const         { return type() == TypeDate; }
        bool isURI() const          { return type() == TypeURI; }
        bool isBinary() const       { return type() == TypeBinary; }
        bool isMap() const          { return type() == TypeMap; }
        bool isArray() const        { return type() == TypeArray; }
    //@}

    /** @name Automatic Cast Protection
        These are not implemented on purpose.  Without them, C++ can perform
        some conversions that are clearly not what the programmer intended.

        If you get a linker error about these being missing, you have made
        mistake in your code.  DO NOT IMPLEMENT THESE FUNCTIONS as a fix.

        All of these problems stem from trying to support char* in LLSD or in
        std::string.  There are too many automatic casts that will lead to
        using an arbitrary pointer or scalar type to std::string.
     */
    //@{
        LLSD(const void*);              ///< construct from aribrary pointers
        void assign(const void*);       ///< assign from arbitrary pointers
        LLSD& operator=(const void*);   ///< assign from arbitrary pointers

        bool has(Integer) const;        ///< has() only works for Maps
    //@}

    /** @name Implementation */
    //@{
public:
        class Impl;
private:
        Impl* impl;
        friend class LLSD::Impl;
    //@}

private:
    /** @name Debugging Interface */
    //@{
        /// Returns XML version of llsd -- only to be called from debugger
        static const char *dumpXML(const LLSD &llsd);

        /// Returns Notation version of llsd -- only to be called from debugger
        static const char *dump(const LLSD &llsd);
    //@}

public:

    static std::string      typeString(Type type);      // Return human-readable type as a string
};

struct llsd_select_bool
{
    LLSD::Boolean operator()(const LLSD& sd) const
    {
        return sd.asBoolean();
    }
};
struct llsd_select_integer
{
    LLSD::Integer operator()(const LLSD& sd) const
    {
        return sd.asInteger();
    }
};
struct llsd_select_real
{
    LLSD::Real operator()(const LLSD& sd) const
    {
        return sd.asReal();
    }
};
struct llsd_select_float
{
    F32 operator()(const LLSD& sd) const
    {
        return (F32)sd.asReal();
    }
};
struct llsd_select_uuid
{
    LLSD::UUID operator()(const LLSD& sd) const
    {
        return sd.asUUID();
    }
};
struct llsd_select_string
{
    LLSD::String operator()(const LLSD& sd) const
    {
        return sd.asString();
    }
};

LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLSD& llsd);

namespace llsd
{

#ifdef LLSD_DEBUG_INFO
/** @name Unit Testing Interface */
//@{
    LL_COMMON_API void dumpStats(const LLSD&);  ///< Output information on object and usage

    /// @warn THE FOLLOWING COUNTS WILL NOT BE ACCURATE IN A MULTI-THREADED
    /// ENVIRONMENT.
    ///
    /// These counts track LLSD::Impl (hidden) objects.
    LL_COMMON_API U32 allocationCount();    ///< how many Impls have been made
    LL_COMMON_API U32 outstandingCount();   ///< how many Impls are still alive

    /// These counts track LLSD (public) objects.
    LL_COMMON_API extern S32 sLLSDAllocationCount;  ///< Number of LLSD objects ever created
    LL_COMMON_API extern S32 sLLSDNetObjects;       ///< Number of LLSD objects that exist
#endif
//@}

} // namespace llsd

/** QUESTIONS & TO DOS
    - Would Binary be more convenient as unsigned char* buffer semantics?
    - Should Binary be convertible to/from String, and if so how?
        - as UTF8 encoded strings (making not like UUID<->String)
        - as Base64 or Base96 encoded (making like UUID<->String)
    - Conversions to std::string and LLUUID do not result in easy assignment
        to std::string, std::string or LLUUID due to non-unique conversion paths
*/

#endif // LL_LLSD_NEW_H