/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    QC_Serializable.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/QC_Serializable.h"
#include "qore/intern/BinaryOutputStream.h"
#include "qore/intern/BinaryInputStream.h"
#include "qore/intern/StringInputStream.h"
#include "qore/intern/QC_TimeZone.h"

static QoreHashNode* Serializable_serializer(const QoreObject& self, const TimeZoneData& z, QoreSerializationContext& context, ExceptionSink* xsink) {
    return nullptr;
}

static void Serializable_deserializer(QoreObject& self, const QoreHashNode* sdata, QoreDeserializationContext& context, ExceptionSink* xsink) {
    if (sdata) {
        xsink->raiseException("DESERIALIZATION-ERROR", "deserialization data provided for the 'Serializable' class where none was expected");
        return;
    }
    self.setPrivate(CID_SERIALIZABLE, new QoreSerializable);
}

//! serialization information
/**
    @since %Qore 0.9
*/
hashdecl SerializationInfo {
    //! list of modules to load to support the data
    *list<string> _modules;

    //! unique object index
    *hash<string, hash<ObjectSerializationInfo>> _index;

    //! serialized data
    auto _data;
}

//! object serialization information
/**
    @since %Qore 0.9
*/
hashdecl ObjectSerializationInfo {
    //! class path name
    string _class;

    //! member values per class; class path -> member hash
    *hash<string, hash<string, auto>> _class_data;
}

//! object serialization information for an indexed object
/**
    @since %Qore 0.9
*/
hashdecl IndexedObjectSerializationInfo {
    //! index value
    string _index;
}

//! hash serialization information for @ref hashdecl "typed hashes" or untyped hashes
/**
    @since %Qore 0.9
*/
hashdecl HashSerializationInfo {
    //! @ref hashdecl "typed hash" path name, if any; otherwise for an untyped hash: \c "^..." where ... is the complex value type info
    string _hash;

    //! member values
    *hash<string, auto> _members;
}

//! list serialization information
/**
    @since %Qore 0.9.1
*/
hashdecl ListSerializationInfo {
    //! the complex list element type info
    string _list;

    //! the data-serialized list elements
    *list<auto> _elements;
}

//! The Serializable class can be used to mark a class as being serializable.
/** This class serves two purposes; to mark classes as serializable and then to implement
    the actual serialization and deserialization methods.

    This class may also be inherited as @ref inheritance "private:internal" to mark it as
    serializable (in this case the serialization methods are only available inside the class
    itself).

    Objects are serialized very efficiently; each object in a data structure will only be
    serialized once, even if it appears multiple times in the data structure.  Recursive
    references and cyclic graphs of objects are also supported for serialization.

    Classes that do not inherit this class cannot be serialized; if any non-transient member
    of a serializable class holds an object from a class that does not inherit this class,
    a \c SERIALIZATION-ERROR will be thrown when the object is attempted to be serialized.

    Furthermore any attempt to serialize a @ref closure "closures",
    @ref call_reference "call references", or a @ref lvalue_references "reference" will
    result in a \c SERIALIZATION-ERROR as well.

    @since %Qore 0.9
 */
qclass Serializable [arg=QoreSerializable* s; serializer=reinterpret_cast<q_serializer_t>(Serializable_serializer); deserializer=Serializable_deserializer];

//! The constructor does not perform any action; this class is just used to mark a class as serializable by inheriting this class
/**
 */
Serializable::constructor() {
    self->setPrivate(CID_SERIALIZABLE, new QoreSerializable);
}

//! The copy constructor does not perform any action; this class is just used to mark a class as serializable by inheriting this class
/**
 */
Serializable::copy() {
    self->setPrivate(CID_SERIALIZABLE, new QoreSerializable);
}

//! converts the object to a serialization hash representing the object
/** @par Example:
    @code{.py}
hash<SerializationInfo> h = obj.serializeToData();
    @endcode

    @return a serialization hash representing the object

    All non-serializable data such as @ref closure "closures", @ref call_reference "call references",
    @ref lvalue_references "references", or non-serializable objects must be tagged as @ref transient
    or a \c SERIALIZATION-ERROR exception will be thrown.

    @throw SERIALIZATION-ERROR the object cannot be serialized as it contains non-serializable data
    (@ref closure "closures", @ref call_reference "call references", @ref lvalue_references "references",
    and non-serializable objects)
*/
hash<SerializationInfo> Serializable::serializeToData() [flags=RET_VALUE_ONLY] {
    return s->serializeToData(self, xsink);
}

//! converts the object to binary data representing the object
/** @par Example:
    @code{.py}
obj.serialize(stream);
    @endcode

    @param stream an output stream where the serialized data will be written

    All non-serializable data such as @ref closure "closures", @ref call_reference "call references",
    @ref lvalue_references "references", or non-serializable objects must be tagged as @ref transient
    or a \c SERIALIZATION-ERROR exception will be thrown.

    @throw SERIALIZATION-ERROR the object cannot be serialized as it contains non-serializable data
    (@ref closure "closures", @ref call_reference "call references", @ref lvalue_references "references",
    and non-serializable objects)
*/
Serializable::serialize(OutputStream[OutputStream] stream) {
    ReferenceHolder<OutputStream> holder(stream, xsink);
    QoreSerializable::serialize(*self, *stream, xsink);
}

//! converts the object to binary data representing the object
/** @par Example:
    @code{.py}
binary b = obj.serialize();
    @endcode

    @return a binary object representing the object

    All non-serializable data such as @ref closure "closures", @ref call_reference "call references",
    @ref lvalue_references "references", or non-serializable objects must be tagged as @ref transient
    or a \c SERIALIZATION-ERROR exception will be thrown.

    @throw SERIALIZATION-ERROR the object cannot be serialized as it contains non-serializable data
    (@ref closure "closures", @ref call_reference "call references", @ref lvalue_references "references",
    and non-serializable objects)
*/
binary Serializable::serialize() {
    ReferenceHolder<BinaryOutputStream> stream(new BinaryOutputStream, xsink);
    QoreSerializable::serialize(*self, **stream, xsink);
    return *xsink ? QoreValue() : stream->getData(xsink);
}

//! overrides the default automatic member retrieval for user classes during data serialization
/** @par Example:
    @code{.py}
class MyClass inherits Qore::Serializable {
    private:internal {
        transient string my_special_member;
    }

    # if this member is defined, all members retrieved from this class will be returned only
    # by this method, as in this case no automatic member retrieval for the given class will be
    # performed
    hash<auto> serializeMembers() {
        return {
            "special_data": get_special_data(),
        }
    }
}
    @endcode

    This method is special in that it is only called for user classes for each
    class in a class hierarchy if the method is defined.

    This method in the base class does nothing and should not be called from user classes;
    this method is declared \c private:internal to emphasize that it is only called for
    the local class by the internal serialization logic; it may be declared with any
    access permission, but in any case the method only has an effect for the local class
    and not for the hierarchy, which makes it different than other %Qore class methods
    (other than the similar @ref deserializeMembers() method).

    This method can be used to implement special serialization logic when serializing
    members of a particular class in a class hierarchy.

    @param members locally-defined serialized non-@ref transient "transient" members, if any

    @return member information to be passed to the class during deserialization; overrides
    the default member retrieval logic during object serialization

    @note If this method is defined, then the class must return its own members, as
    automatic member retrieval will not be performed for the local class by the
    serialization logic in this case.

    @see deserializeMembers()
*/
private:internal *hash<auto> Serializable::serializeMembers(*hash<auto>[doc] members) {
}

//! overrides the default automatic member assignment logic for user classes during data deserialization
/** @par Example:
    @code{.py}
class MyClass inherits Qore::Serializable {
    private:internal {
        transient string my_special_member;
    }

    # when defining this member, all member assignment during deserialization must be performed by this method,
    # even for transient members, as no automatic member assignment will be performed
    deserializeMembers(*hash<auto> members) {
        my_special_member = get_special_value();
        self += members;
    }
}
    @endcode

    This method is special in that it is only called during data deserialization for user
    classes for each class in a class hierarchy if the method is defined.

    This method in the base class does nothing and should not be called from user classes;
    this method is declared \c private:internal to emphasize that it is only called for
    the local class by the internal deserialization logic; it may be declared with any
    access permission, but in any case the method only has an effect for the local class
    and not for the hierarchy, which makes it different than other %Qore class methods
    (other than the similar @ref serializeMembers() method.

    This method can be used to implement special deserialization logic when deserializing
    members of a particular class in a class hierarchy.

    @param members locally-defined deserialized members to assign to the local class to be
    assigned manually in this method; this hash will be empty if no members were serialized

    @note If this method is defined, then the class must assign its own members including
    transient members, as automatic member initialization will not be performed for the
    local class by the deserialization logic in this case.

    @see serializeMembers()
*/
private:internal nothing Serializable::deserializeMembers(hash<auto>[doc] members) {
}

//! Deserializes data produced with serialize() and returns the value represented by the data
/** @par Example:
    @code{.py}
hash<SerializationInfo> h = Serializable::deserializeToData(stream);
    @endcode

    @param stream an input stream providing serialized binary data as generated by @ref serialize()

    @return the intermediate SerializationInfo hash value represented by the serialization stream

    @throw DESERIALIZATION-ERROR the data cannot be deserialized due to an error in the serialization format or a reference to an unknown class or @ref hashdecl
 */
static hash<SerializationInfo> Serializable::deserializeToData(InputStream[InputStream] stream) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<InputStream> holder(stream, xsink);
    return QoreSerializable::deserializeToData(*stream, xsink);
}

//! Deserializes data produced with serialize() and returns the value represented by the data
/** @par Example:
    @code{.py}
hash<SerializationInfo> h = Serializable::deserializeToData(b);
    @endcode

    @param bin binary data representing serialized data as generated by @ref serialize()

    @return the intermediate SerializationInfo hash value represented by the input data

    @throw DESERIALIZATION-ERROR the data cannot be deserialized due to an error in the serialization format or a reference to an unknown class or @ref hashdecl
 */
static hash<SerializationInfo> Serializable::deserializeToData(binary bin) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<BinaryInputStream> stream(new BinaryInputStream(bin), xsink);
    return QoreSerializable::deserializeToData(**stream, xsink);
}

//! Deserializes data produced with serialize() and returns the value represented by the data
/** @par Example:
    @code{.py}
auto val = Serializable::deserialize(stream);
    @endcode

    @param stream an input stream providing serialized binary data as generated by @ref serialize()

    @return the value represented by the serialization stream

    @throw DESERIALIZATION-ERROR the data cannot be deserialized due to an error in the serialization format or a reference to an unknown class or @ref hashdecl
 */
static auto Serializable::deserialize(InputStream[InputStream] stream) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<InputStream> holder(stream, xsink);
    return QoreSerializable::deserialize(*stream, xsink);
}

//! Deserializes data produced with serialize() and returns the value represented by the data
/** @par Example:
    @code{.py}
auto val = Serializable::deserialize(bin);    @endcode

    @param bin binary data representing serialized data as generated by @ref serialize()

    @return the value represented by the data

    @throw DESERIALIZATION-ERROR the data cannot be deserialized due to an error in the serialization format or a reference to an unknown class or @ref hashdecl
 */
static auto Serializable::deserialize(binary bin) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<BinaryInputStream> stream(new BinaryInputStream(bin), xsink);
    return QoreSerializable::deserialize(**stream, xsink);
}

//! Deserializes data produced with serialize() and returns the value represented by the data
/** @par Example:
    @code{.py}
auto val = Serializable::deserialize(bin);
    @endcode

    @param bin binary data representing serialized data as generated by @ref serialize()

    @return the value represented by the data

    @throw DESERIALIZATION-ERROR the data cannot be deserialized due to an error in the serialization format or a reference to an unknown class or @ref hashdecl

    @since %Qore 0.9.2
 */
static auto Serializable::deserialize(string bin) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<StringInputStream> stream(new StringInputStream(bin), xsink);
    return QoreSerializable::deserialize(**stream, xsink);
}

//! Deserializes data produced with serializeToData() and returns the value represented by the data
/** @par Example:
    @code{.py}
auto val = Serializable::deserialize(h);
    @endcode

    @param data the data representing the object as generated by @ref serializeToData()

    @return the value represented by the data

    @throw DESERIALIZATION-ERROR the data cannot be deserialized due to an error in the serialization format or a reference to an unknown class or @ref hashdecl
 */
static auto Serializable::deserialize(hash<SerializationInfo> data) [flags=RET_VALUE_ONLY] {
    return QoreSerializable::deserialize(*data, xsink);
}

//! converts the value to a serialization hash representing the value
/** @par Example:
    @code{.py}
hash<SerializationInfo> h = serializeToData(val);
    @endcode

    @param val the value to serialize to a serialization data hash

    @return a serialization hash representing the value

    All non-serializable data such as @ref closure "closures", @ref call_reference "call references",
    @ref lvalue_references "references", or non-serializable objects must be tagged as @ref transient
    or a \c SERIALIZATION-ERROR exception will be thrown.

    @throw SERIALIZATION-ERROR the object cannot be serialized as it contains non-serializable data
    (@ref closure "closures", @ref call_reference "call references", @ref lvalue_references "references",
    and non-serializable objects)
*/
static hash<SerializationInfo> Serializable::serializeToData(auto val) [flags=RET_VALUE_ONLY] {
    return QoreSerializable::serializeToData(val, xsink);
}

//! serializes the data and writes the serialized data to the given output stream
/** @par Example:
    @code{.py}
Serializable::serialize(val, stream);
    @endcode

    @param val the data to serialize
    @param stream an output stream where the serialized data will be written

    All non-serializable data such as @ref closure "closures", @ref call_reference "call references",
    @ref lvalue_references "references", or non-serializable objects must be tagged as @ref transient
    in object members or a \c SERIALIZATION-ERROR exception will be thrown.

    @throw SERIALIZATION-ERROR the object cannot be serialized as it contains non-serializable data
    (@ref closure "closures", @ref call_reference "call references", @ref lvalue_references "references",
    and non-serializable objects)
*/
static Serializable::serialize(auto val, OutputStream[OutputStream] stream) {
    ReferenceHolder<OutputStream> holder(stream, xsink);
    QoreSerializable::serialize(val, *stream, xsink);
}

//! serializes the data and returns the serialized data as a binary object
/** @par Example:
    @code{.py}
binary bin = Serializable::serialize(val);
    @endcode

    @param val the data to serialize

    @return serialized data representing the input data

    All non-serializable data such as @ref closure "closures", @ref call_reference "call references",
    @ref lvalue_references "references", or non-serializable objects must be tagged as @ref transient
    in object members or a \c SERIALIZATION-ERROR exception will be thrown.

    @throw SERIALIZATION-ERROR the object cannot be serialized as it contains non-serializable data
    (@ref closure "closures", @ref call_reference "call references", @ref lvalue_references "references",
    and non-serializable objects)
*/
static binary Serializable::serialize(auto val) {
    ReferenceHolder<BinaryOutputStream> stream(new BinaryOutputStream, xsink);
    QoreSerializable::serialize(val, **stream, xsink);
    return *xsink ? QoreValue() : stream->getData(xsink);
}
