#pragma once

#include <Resource/File.h>
#include "Folder.h"
#include "Definitions.h"
#include <fstream>
#include <cstring>

namespace gorgon {

	class File;

	////This class defines a resource loader
	class Loader {
	public:
		////This is Resource loader function prototype
		typedef std::function<Base* (Base *parent, File&, std::istream&, int)> LoaderFunction;

		////Gorgon ID of the resource
		GID::Type GId;

		////Load handler function
		LoaderFunction Handler;

		////Filling constructor
		Loader(GID::Type gid, LoaderFunction handler) : GId(gid), Handler(handler) 
		{ }
	};

	class save_error : public std::runtime_error {
	public:

		save_error(int number, const std::string &text) : runtime_error(text), number(number) {

		}

		save_error(int number, const char *text) : runtime_error(text), number(number) {

		}

		int number;
	};

	class File {
	public:

		File() : root(new Folder), version(1), type()
		{
			root->file=this;
		}

		~File();

		void New() {
			gge::utils::CheckAndDelete(root);
			root=new Folder;
		}

		void LoadFile(const std::string &filename, bool destroy=true);

		void SaveFile(const std::string &filename);

		void SetFileType(GID::Type type) {
			this->type=type;
		}
		GID::Type GetFileType() const {
			return type;
		}

		std::string GetFilename() const { return filename; }

		void SetFilename(const std::string &value) { filename=value; }

		////Loads a resource object from the given file, GID and size
		Base *LoadObject(std::istream &Data, Base &base, int GID, int Size);

		////Searches the given resource object within this file
		Base *FindObject(gge::utils::SGuid guid) {
			return root->FindObject(guid);
		}

		template<class T_>
		void FindObject(gge::utils::SGuid guid, T_ *&object) {
			object=dynamic_cast<T_*>(FindObject(guid));
		}

		////Adds basic resource loaders 
		void AddBasicLoaders();

		////Adds extended resource loaders
		void AddExtendedLoaders();

		////Adds game resource loaders
		void AddGameLoaders();

		////Adds game resource loaders
		void AddWidgetLoaders();

		void Prepare() {
			root->Prepare(gge::Main, *this);
		}

		Folder &Root() {
			return *root;
		}

		gge::utils::Collection<Loader>	Loaders;

	protected:
		std::string filename;

		////File type
		GID::Type type;

		////File version
		int version;

		Folder *root;
	};

	template<class T_>
	void WriteTo(std::ostream &out, const T_ &data) {
		out.write((const char*)&data, sizeof(T_));
	}

	inline void WriteTo(std::ostream &out, const std::string &data) {
		out.write(data.c_str(), data.length());
	}

	inline void WriteTo(std::ostream &out, const char *data) {
		out.write(data, std::strlen(data));
	}

	void WriteNames(std::ostream &out, const Base &base, GID::Type names, GID::Type name);

	inline std::streampos WriteObjectStart(std::ostream &out, const Base &base) {
		WriteTo(out, base.GetGID());
		auto pos=out.tellp();
		WriteTo(out, 0);

		WriteTo(out, gge::resource::GID::SGuid);
		WriteTo(out, 0x08);
		base.GetGuid().Save(out);

		return pos;
	}

	inline std::streampos WriteObjectStart(std::ostream &out, const Base &base, GID::Type gid) {
		WriteTo(out, gid);
		auto pos=out.tellp();
		WriteTo(out, 0);

		WriteTo(out, gge::resource::GID::SGuid);
		WriteTo(out, 0x08);
		base.GetGuid().Save(out);

		return pos;
	}

	inline std::streampos WriteSectionStart(std::ostream &out, GID::Type gid) {
		WriteTo(out, gid);
		auto pos=out.tellp();
		WriteTo(out, 0);

		return pos;
	}

	inline void WriteSubItems(std::ostream &out, Base &base) {
		for(auto resource=base.Subitems.First();resource.IsValid();resource.Next()) {
			resource->Save(out);
		}
	}

	inline void WriteEnd(std::ostream &out, std::streampos start) {
		auto pos=out.tellp();
		out.seekp(start);
		WriteTo(out, (int)(pos-start)-4);
		out.seekp(pos);
	}

	template<class T_>
	void WriteLink(T_ *obj, std::ostream &out) {
		if(dynamic_cast<Base*>(obj))
			dynamic_cast<Base*>(obj)->GetGuid().Save(out);
		else
			gge::utils::SGuid::Empty.Save(out);
	}

	//template<class T_>
	//void WriteLinkDirect(T_ *obj, std::ostream &out) {
	//	if(obj)
	//		((Base*)obj)->GetGuid().Save(out);
	//	else
	//		gge::utils::SGuid::Empty.Save(out);
	//}

	struct namecaption {
		namecaption() {}
		namecaption(const std::string &name, const std::string &caption) : name(name), caption(caption) {}
		std::string name, caption;
	};

	void LoadNames(std::istream &in, int size, std::vector<namecaption> &list, GID::Type name);
	
	//Loads entries by extracting GID and size and calls reader function
	//if this function returns false, it will check the loaders and tries to load
	//if that fails as well, this function will throw load_error
	//This function assumes name list will not be used for internal entries
	void LoadEntries(std::istream &in, int size, File &file, Base &base, std::function<bool(GID::Type&, int)> reader, const std::vector<namecaption> &namelist);
}
