#pragma once

#include "BsPrerequisitesUtil.h"

namespace BansheeEngine
{
	/**
	 * @brief	Represents an interface RTTI objects need to implement if they
	 *			want to provide custom "diff" generation and applying.
	 */
	class BS_UTILITY_EXPORT IDiff
	{
	public:
		virtual ~IDiff() { }

		/**
		 * @brief	Generates per-field differences between the provided original and new object. Any field
		 *			or array entry that is different in the new object compared to the original will be output
		 *			in the resulting object, with a full hierarchy of that field.
		 *
		 *			Will return null if there is no difference.
		 */
		SPtr<SerializedObject> generateDiff(const SPtr<SerializedObject>& orgObj, const SPtr<SerializedObject>& newObj);

		/**
		 * @brief	Applies a previously generated per-field differences to the provided object. This will
		 *			essentially transform the original object the differences were generated for into the modified
		 *			version.
		 */
		void applyDiff(const SPtr<IReflectable>& object, const SPtr<SerializedObject>& diff);

	protected:
		typedef UnorderedMap<SPtr<SerializedObject>, SPtr<SerializedObject>> ObjectMap;
		typedef UnorderedMap<SPtr<SerializedObject>, SPtr<IReflectable>> DiffObjectMap;

		/**
		 * @brief	Types of commands that are used when applying difference field values.
		 */
		enum DiffCommandType
		{
			Diff_Plain = 0x01,
			Diff_Reflectable = 0x02,
			Diff_ReflectablePtr = 0x03,
			Diff_DataBlock = 0x04,
			Diff_ArraySize = 0x05,
			Diff_ObjectStart = 0x06,
			Diff_ObjectEnd = 0x07,
			Diff_ArrayFlag = 0x10
		};
		
		/**
		 * @brief	A command that is used for delaying writing to an object, it contains
		 *			all necessary information for setting RTTI field values on an object.
		 */
		struct DiffCommand
		{
			RTTIField* field;
			UINT32 type;
			SPtr<IReflectable> object;
			UINT8* value;
			UINT32 size;

			union
			{
				UINT32 arrayIdx;
				UINT32 arraySize;
			};
		};

		/**
		 * @brief	Recursive version of generateDiff(const SPtr<SerializedObject>&, const SPtr<SerializedObject>&).
		 *
		 * @see		generateDiff(const SPtr<SerializedObject>&, const SPtr<SerializedObject>&)
		 */
		virtual SPtr<SerializedObject> generateDiff(const SPtr<SerializedObject>& orgObj, const SPtr<SerializedObject>& newObj, ObjectMap& objectMap) = 0;

		/**
		 * @brief	Generates a difference between data of a specific field type indiscriminately of the
		 *			specific field type.
		 *
		 * @see		generateDiff(const SPtr<SerializedObject>&, const SPtr<SerializedObject>&)
		 */
		SPtr<SerializedInstance> generateDiff(RTTITypeBase* rtti, UINT32 fieldType, const SPtr<SerializedInstance>& orgData,
			const SPtr<SerializedInstance>& newData, ObjectMap& objectMap);

		/**
		 * @brief	Recursive version of applyDiff(const SPtr<IReflectable>& object, const SPtr<SerializedObject>& diff).
		 *			Outputs a set of commands that then must be executed in order to actually apply the difference to the
		 *			provided object.
		 *
		 * @see		applyDiff(const SPtr<IReflectable>& object, const SPtr<SerializedObject>& diff)
		 */
		virtual void applyDiff(const SPtr<IReflectable>& object, const SPtr<SerializedObject>& diff, DiffObjectMap& objectMap, Vector<DiffCommand>& diffCommands) = 0;

		/**
		 * @brief	Applies diff according to the diff handler retrieved from the provided RTTI object.
		 *
		 * @see		applyDiff(const SPtr<IReflectable>& object, const SPtr<SerializedObject>& diff)
		 */
		void applyDiff(RTTITypeBase* rtti, const SPtr<IReflectable>& object, const SPtr<SerializedObject>& diff, DiffObjectMap& objectMap, Vector<DiffCommand>& diffCommands);
	};

	/**
	 * @brief	Generates and applies "diffs". Diffs contain per-field differences between
	 *			an original and new object. These differences can be saved and then applied
	 *			to an original object to transform it to the new version.
	 *
	 * @note	Objects must be in intermediate serialized format generated by BinarySerializer.
	 */
	class BS_UTILITY_EXPORT BinaryDiff : public IDiff
	{
	private:
		/**
		 * @copydoc	IDiff::generateDiff(const SPtr<SerializedObject>&, const SPtr<SerializedObject>&, ObjectMap&)
		 */
		SPtr<SerializedObject> generateDiff(const SPtr<SerializedObject>& orgObj, const SPtr<SerializedObject>& newObj, ObjectMap& objectMap) override;

		/**
		 * @copydoc	IDiff::applyDiff(const SPtr<IReflectable>&, const SPtr<SerializedObject>&, DiffObjectMap&, Vector<DiffCommand>&)
		 */
		void applyDiff(const SPtr<IReflectable>& object, const SPtr<SerializedObject>& diff, DiffObjectMap& objectMap, Vector<DiffCommand>& diffCommands) override;
	};
}