/**
 * @file     	IObject.h
 * @brief    	Object system types and interfaces.
 * @copyright	VisionLabs LLC
 * @date     	25.06.2014
 * */

#pragma once

#include "IRefCounted.h"
#include "Types.h"

namespace fsdk {

/**
 * @addtogroup CoreGroup SDK core interfaces
 * @brief Common interfaces and macros shared across all SDK objects.
 * @{
 * */

	struct ISettingsProvider;

	/**
	 * @brief Archive interface.
	 * @details Archives abstract data storage and IO operations.
	 *
	 * @note IArchive is not derived from IRefCounted and does not force any lifetime control. It is up to user to 
	 * implement it.
	 *
	 * @note SDK ojects that use IArchive for serialization purposes do call only write() (during saving) or only 
	 * read() (during loading) but never both during the same process unless otherwise is explicitly stated.
	 *
	 * @note During saving or loading SDK objects are free to write or read their data in chunks; e.g. there may be
	 * several sequential calls to write() in scope of a single serialization request. Any IArchive implementation 
	 * should be aware of this.
	 * */
	struct IArchive {

		/**
		 * @brief Write bytes to archive.
		 * @param [in] data pointer to memory to write.
		 * @param [in] size data size.
		 * @return true if succeeded, false otherwise.
		 * */
		virtual bool write(const void* data, size_t size) = 0;

		/**
		 * @brief Read bytes from archive.
		 * @param [in] data pointer to memory to read to.
		 * @param [in] size data size.
		 * @return true if succeeded, false otherwise.
		 * */
		virtual bool read(void* data, size_t size) = 0;

		/**
		 * @brief Set size hint.
		 * @param [in] hint size hint.
		 * */
		virtual void setSizeHint(size_t hint) { ((void)hint); }

		virtual ~IArchive() = default;
	};

	/**
	 * @brief Serializable object interface.
	 * @details Provides common functions for all serializable objects.
	 * */
	struct ISerializableObject : IRefCounted {

		/**
		 * @brief Serialization nerror codes.
		 * */
		enum class Error : uint32_t {
			Ok,          	//!< Ok
			Size,        	//!< Not enough space
			Signature,   	//!< Invalid signature
			ArchiveRead, 	//!< Error during archive reading,
			InputArchive,	//!< Input archive is nullptr
			ArchiveWrite,	//!< Error during archive writing,
		};

		/**
		 * @brief Serialization flags.
		 * @details These flags control advanced options and should not be set to anything else from Default in
		 * normal conditions.
		 * @note The same set of flags should be specified for both save() and load().
		 * */
		enum Flags {

			/** @brief Default serialization mode. */
			Default = 0,

			/**
			 * @brief Omit object signature.
			 * @details Helps to save space if there are several objects of the same type coming in a stream or 
			 * where type is known for sure.
			 * @note This effectively disables signature check on loading. This may cause undefined results in 
			 * case of mismatched input data.
			 * */
			NoSignature = 1
		};

		/**	
		 * @brief Estimate size of this object binary data.
		 * @param [inout] sizer sizer object to append result to.
		 * @param [in] flags [optional] serialization flags @see Flags.
		 * */
		virtual void getSize(Sizer& sizer, uint32_t flags = Default) const noexcept = 0;

		/**
		 * @brief Load object from archive.
		 * @param [in] archive archive to read from.
		 * @param [in] flags [optional] serialization flags @see Flags.
		 * @return Result with error code specified by ISerializableObject::SerializationError.
		 * @note This method pass exceptions from user defined IArchive, but doesnt throw its own
		 * @see Result and ISerializableObject::SerializationError.
		 * */
		virtual Result<Error> load(IArchive* archive, uint32_t flags = Default) = 0;

		/**
		 * @brief Save object to archive.
		 * @param [in] archive archive to write to.
		 * @param [in] flags [optional] serialization flags @see Flags.
		 * @return Result with error code specified by ISerializableObject::SerializationError.
		 * @note This method pass exceptions from user defined IArchive, but doesnt throw its own
		 * @see Result and ISerializableObject::SerializationError.
		 * */
		virtual Result<Error> save(IArchive* archive, uint32_t flags = Default) const = 0;
	};

	/**
	 * @brief Specialized for ISerializableObject::SerializationError.
	 * */
	template<>
	struct ErrorTraits<ISerializableObject::Error> {

		static bool isOk(ISerializableObject::Error error) noexcept {
			return error == ISerializableObject::Error::Ok;
		}

		static const char* toString (ISerializableObject::Error error) noexcept {
			switch(error) {
				case ISerializableObject::Error::Ok : return "Ok";
				case ISerializableObject::Error::Size: return "Size error";
				case ISerializableObject::Error::Signature: return "Signature error";
				case ISerializableObject::Error::ArchiveRead: return "Error during archive reading";
				case ISerializableObject::Error::InputArchive: return "Input archive is nullptr";
				case ISerializableObject::Error::ArchiveWrite: return "Error during archive writing";
				default: return "Unknown error";
			}
		}
	};

	/** 
	 * @brief Data storage object interface helper.
	 * */
	struct IDataStorageObject : ISerializableObject {

		/** 
		 * @brief Clear object data.
		 * @note This does not necessarily mean deallocation; it is defined by implementation how to manage 
		 * data memory.
		 * */
		virtual void clear() noexcept = 0;

		/**
		 * @brief Get parent object (one that has created this).
		 * @note Any returned interfaces will have their reference count incremented by one, so be sure to 
		 * call release() on the returned pointer(s) before they are freed or else you will have a memory 
		 * leak.
		 * @return pointer to the parent object.
		 * */
		virtual IRefCounted* getParentObject() const noexcept = 0;
	};

/** @} */
}