#ifndef XNA_CONTENT_READER_HPP
#define XNA_CONTENT_READER_HPP

#include "../common/color.hpp"
#include "../common/numerics.hpp"
#include "../csharp/binary.hpp"
#include "../csharp/type.hpp"
#include "../default.hpp"
#include "typereadermanager.hpp"
#include <any>

namespace xna {
	//A worker object that implements most of ContentManager.Load.
	class ContentReader : public BinaryReader, public std::enable_shared_from_this<ContentReader> {
	public:
		static sptr<ContentReader> Create(sptr<ContentManager> const& contentManager, sptr<Stream>& input, String const& assetName);		

		// Reads a single object from the current stream.
		template <typename T>
		auto ReadObject();

		// Reads a single object from the current stream.
		template <typename T>
		auto ReadObject(T& existingInstance);

		// Reads a single object from the current stream.
		template <typename T>
		auto ReadObject(ContentTypeReader& typeReader);

		// Reads a single object from the current stream.
		template <typename T>
		auto ReadObject(ContentTypeReader& typeReader, T& existingInstance);

		//Reads a Vector2 value from the current stream. 
		Vector2 ReadVector2();
		//Reads a Vector3 value from the current stream. 
		Vector3 ReadVector3();
		//Reads a Vector4 value from the current stream. 
		Vector4 ReadVector4();
		//Reads a Matrix value from the currently open stream.
		Matrix ReadMatrix();
		//Reads a Quaternion value from the current stream. 
		Quaternion ReadQuaternion();
		//Reads a Color value from the currently open stream.
		Color ReadColor();
		//Reads a float value from the currently open stream.
		float ReadSingle() override;
		//Reads a double value from the currently open stream.
		double ReadDouble() override;

		//Gets the name of the asset currently being read by this ContentReader.
		constexpr String AssetName() const {
			return _assetName;
		}

		//Gets the ContentManager associated with the ContentReader.
		sptr<xna::ContentManager> ContentManager() const;

		//
		// Internal methods
		//

		template <typename T>
		auto ReadAsset();

		std::vector<Byte> ReadByteBuffer(size_t size);		

	private:
		ContentReader(sptr<xna::ContentManager> const& contentManager, sptr<Stream>& input, String const& assetName, Int graphicsProfile)
			: BinaryReader(input), _contentManager(contentManager), _assetName(assetName) {}

		static sptr<Stream> PrepareStream(sptr<Stream>& input, String const& assetName, Int& graphicsProfile);

		Int ReadHeader();

		template <typename T>
		auto ReadObjectInternal(std::any& existingInstance);
		
		template <typename T>
		auto ReadObjectInternal(ContentTypeReader& typeReader, Object& existingInstance);

		template <typename T>
		auto InvokeReader(ContentTypeReader& reader, Object& existingInstance);

	private:
		sptr<xna::ContentManager> _contentManager = nullptr;
		String _assetName;
		std::vector<sptr<ContentTypeReader>> typeReaders;
		Int graphicsProfile{ 0 };
		std::vector<Byte> byteBuffer;

		static constexpr Ushort XnbVersionProfileMask = 32512;
		static constexpr Ushort XnbCompressedVersion = 32773;
		static constexpr Ushort XnbVersion = 5;
		static constexpr Int XnbVersionProfileShift = 8;
		static constexpr Char PlatformLabel = 'w';
		static constexpr Int XnbPrologueSize = 10;
		static constexpr Int XnbCompressedPrologueSize = 14;
	};

	template<typename T>
	inline auto ContentReader::ReadObjectInternal(Object& existingInstance)
	{
		const auto num = Read7BitEncodedInt();

		if (num == 0) {
			XnaHelper::ReturnDefaultOrNull<T>();
		}

		const auto index = num - 1;

		if (index >= typeReaders.size()) {
			Exception::Throw(Exception::BAD_XNB);
		}		
		
		auto& reader = typeReaders[index];		
		
		return InvokeReader<T>(*reader, existingInstance);
	}

	template<typename T>
	inline auto ContentReader::InvokeReader(ContentTypeReader& reader, Object& existingInstance)
	{
		auto contentTypeReader = reinterpret_cast<ContentTypeReaderT<T>*>(&reader);
		T objB;

		if (contentTypeReader) {
			auto existingInstance1 = existingInstance.has_value() ? std::any_cast<T>(existingInstance) : T();
			objB = contentTypeReader->Read(*this, existingInstance1);
			return objB;
		}
		else {
			Exception::Throw(Exception::NOT_IMPLEMENTED);
		}

		return XnaHelper::ReturnDefaultOrNull<T>();
	}

	template<typename T>
	inline auto ContentReader::ReadAsset()
	{
		const auto sharedResourceCount = ReadHeader();
		auto obj = ReadObject<T>();
		return obj;
	}

	template<typename T>
	inline auto ContentReader::ReadObject()
	{
		auto a = std::any();
		return ReadObjectInternal<T>(a);
	}

	template<typename T>
	inline auto ContentReader::ReadObject(T& existingInstance)
	{
		return ReadObjectInternal<T>(Object(existingInstance));
	}

	template<typename T>
	inline auto ContentReader::ReadObject(ContentTypeReader& typeReader)
	{
		auto obj = std::any();
		return ReadObjectInternal<T>(typeReader, obj);
	}

	template<typename T>
	inline auto ContentReader::ReadObject(ContentTypeReader& typeReader, T& existingInstance)
	{
		return ReadObjectInternal<T>(typeReader, std::any(existingInstance));
	}

	template<typename T>
	inline auto ContentReader::ReadObjectInternal(ContentTypeReader& typeReader, Object& existingInstance)
	{
		if (typeReader.TargetIsValueType)
			return InvokeReader<T>(typeReader, existingInstance);

		ReadObjectInternal<T>(existingInstance);
	}	
}

#endif