#include "File.h"
#include <Encoding/LZMA.h>
#include "Generic/Image.h"
#include "Generic/DataArray.h"
#include "Game/Animation.h"
#include "Extended/Sound.h"
#include "Game/Pointer.h"
#include "Link.h"
#include "Game/BitmapFont.h"
#include "Game/FontTheme.h"
#include "Widgets/Line.h"
#include <Widgets/Definitions.h>
#include "Widgets/Rectangle.h"
#include "Widgets/ResizableObject.h"
#include "Widgets/BorderData.h"
#include "Widgets/Placeholder.h"
#include "Widgets/Textbox.h"
#include "Widgets/Checkbox.h"
#include "Widgets/Listbox.h"
#include "Widgets/Panel.h"
#include "Widgets/Slider.h"
#include "Widgets/Combobox.h"
#include "Widgets/Tabpanel.h"

using namespace gge::resource;
using namespace std;

namespace gorgon {

	File::~File() {
		gge::utils::CheckAndDelete(root);
	}

	void File::LoadFile(const std::string &filename, bool destroy) {
		if(destroy)
			gge::utils::CheckAndDelete(root);

		if(!gge::os::filesystem::IsFileExists(filename) && gge::os::filesystem::IsFileExists(filename+".lzma")) {
			ifstream ifile(filename+".lzma",ios::binary);
			ofstream ofile(filename,ios::binary);
			gge::encoding::Lzma.Decode(ifile, ofile);
			gge::os::filesystem::DeleteFile(filename+".lzma");
		}

		char sgn[7];

		this->filename=filename;

		///*Check file existence

		ifstream data;
		data.open(filename, ios::in | ios::binary);
		if(data.fail())
			throw load_error(load_error::FileNotFound, load_error::strings::FileNotFound+"\n"+filename);


		///*Check file signature
		data.read(sgn, 6);
		sgn[6]=0;
		if(string("GORGON")!=sgn)
			throw load_error(load_error::Signature, load_error::strings::Signature);

		///*Check file version
		ReadFrom(data, version);
		if(version>CurrentVersion)
			throw load_error(load_error::VersionMismatch, load_error::strings::VersionMismatch);

		///*Load file type
		ReadFrom(data, type);

		///*Check first element
		if(ReadFrom<int>(data)!=gge::resource::GID::Folder)
			throw load_error(load_error::Containment, load_error::strings::Containment);

		int size;
		ReadFrom(data, size);

		///*Load first element
		if(root) {
			Folder *lroot=LoadFolder(NULL, *this, data, size);
			if(!lroot)
				throw load_error(load_error::Containment, load_error::strings::Containment);

			for(auto it=lroot->Subitems.First();it.IsValid();it.Next()) {
				root->Add(*it);
			}
			lroot->Subitems.Clear();
			delete lroot;
		}
		else {
			root=LoadFolder(NULL, *this, data, size);
			if(!root)
				throw load_error(load_error::Containment, load_error::strings::Containment);
		}

		///*Close file
		data.close();

		root->Resolve(*this);
	}

	void File::SaveFile(const std::string &filename) {
		if(type==0)
			throw save_error(-1, "File type is not specified.");

		this->filename=filename;

		std::ofstream file(filename, std::ios::binary);

		if(!file.is_open()) {
			throw save_error(-2, "Cannot open file for writing");
		}

		int sig=0x00010000;

		file.write("GORGON", 6);
		file.write((char*)&sig, 4);
		file.write((char*)&type, 4);

		root->Save(file);
	}

	void File::AddBasicLoaders() {
		Loaders.Add(new Loader(gge::resource::GID::Folder, LoadFolder));
		Loaders.Add(new Loader(gge::resource::GID::Image, LoadImage)); 
		Loaders.Add(new Loader(gge::resource::GID::Data, LoadData));
		Loaders.Add(new Loader(gge::resource::GID::LinkNode, LoadLink));
	}
	void File::AddExtendedLoaders() {
		AddBasicLoaders();
		Loaders.Add(new Loader(gge::resource::GID::Sound, LoadSound)); 
	}

	void File::AddGameLoaders() {
		AddExtendedLoaders();
		Loaders.Add(new Loader(gge::resource::GID::Animation, LoadAnimation)); 
		Loaders.Add(new Loader(gge::resource::GID::Pointer, LoadPointer)); 
		Loaders.Add(new Loader(gge::resource::GID::Font, LoadBitmapFont));
		Loaders.Add(new Loader(gge::resource::GID::FontTheme, LoadFontTheme)); 
	}

	void File::AddWidgetLoaders() {
		AddGameLoaders();
		Loaders.Add(new Loader(gge::widgets::GID::Line, widgets::LoadLine)); 
		Loaders.Add(new Loader(gge::widgets::GID::Rectangle, widgets::LoadRectangle)); 
		Loaders.Add(new Loader(gge::widgets::GID::ResizableObj, widgets::LoadResizableObject)); 
		Loaders.Add(new Loader(gge::widgets::GID::BorderData, widgets::LoadBorderData)); 
		Loaders.Add(new Loader(gge::widgets::GID::Placeholder, widgets::LoadPlaceholder)); 

		Loaders.Add(new Loader(gge::widgets::GID::Textbox, widgets::LoadTextbox)); 
		Loaders.Add(new Loader(gge::widgets::GID::Textbox_Element, widgets::LoadTextboxElement)); 

		Loaders.Add(new Loader(gge::widgets::GID::Checkbox, widgets::LoadCheckbox)); 
		Loaders.Add(new Loader(gge::widgets::GID::Checkbox_Element, widgets::LoadCheckboxElement)); 
		Loaders.Add(new Loader(gge::widgets::GID::Checkbox_Group, widgets::LoadCheckboxGroup)); 
		Loaders.Add(new Loader(gge::widgets::GID::Checkbox_Line, widgets::LoadCheckboxLine)); 

		Loaders.Add(new Loader(gge::widgets::GID::Slider, widgets::LoadSlider)); 
		Loaders.Add(new Loader(gge::widgets::GID::Slider_Element, widgets::LoadSliderElement)); 
		Loaders.Add(new Loader(gge::widgets::GID::Slider_Group, widgets::LoadSliderGroup)); 

		Loaders.Add(new Loader(gge::widgets::GID::Panel, widgets::LoadPanel)); 
		Loaders.Add(new Loader(gge::widgets::GID::Panel_Element, widgets::LoadPanelElement)); 

		Loaders.Add(new Loader(gge::widgets::GID::Listbox, widgets::LoadListbox)); 

		Loaders.Add(new Loader(gge::widgets::GID::Combobox, widgets::LoadCombobox)); 

		Loaders.Add(new Loader(gge::widgets::GID::Tabpanel, widgets::LoadTabpanel)); 
	}

	Base * File::LoadObject(std::istream &in, Base &base, int GID, int Size) {
		for(auto loader=Loaders.First();loader.IsValid(); loader.Next()) {

				if(loader->GId==GID) {
					return loader->Handler(&base, *this, in, Size);
				}
		}

		EatChunk(in,Size);
		return NULL;
	}

	void WriteNames(std::ostream &out, const Base &base, GID::Type names, GID::Type name) {
		WriteTo(out, names);
		auto innersizepos=out.tellp();
		WriteTo(out, 0);
		for(auto resource=base.Subitems.First();resource.IsValid();resource.Next()) {
			WriteTo(out, name);

			int size=resource->GetName().length()+resource->GetCaption().length()+1;

			WriteTo(out, size);

			out.write(resource->GetName().c_str(), resource->GetName().length());
			out.put(1);
			out.write(resource->GetCaption().c_str(), resource->GetCaption().length());
		}
		auto innernewpos=out.tellp();
		out.seekp(innersizepos);
		WriteTo(out, (int)(innernewpos-innersizepos)-4);
		out.seekp(innernewpos);
	}

	void LoadNames(std::istream &in, int size, std::vector<namecaption> &list, GID::Type name) {
		auto pos=in.tellg();

		while(in.tellg()-pos<size) {
			GID::Type gid;
			int tsize;

			gid=gge::resource::ReadFrom<int>(in);
			tsize=gge::resource::ReadFrom<int>(in);

			if(gid==name) {
				namecaption nc;
				if(tsize) {
					nc.caption.resize(tsize);
					in.read(&nc.caption[0], tsize);
				}

				auto p=nc.caption.find_first_of(1);

				if(p!=nc.caption.npos) {
					nc.name=nc.caption.substr(0, p);
					nc.caption=nc.caption.substr(p+1);
				}

				list.push_back(nc);
			}
			else {
				in.seekg(tsize, std::ios::cur);
			}
		}

		in.seekg(pos+size, std::ios::beg);
	}

	void LoadEntries(std::istream &in, int size, File &file, Base &base, std::function<bool(GID::Type&, int)> reader, const std::vector<namecaption> &list) {
		auto pos=in.tellg();
		unsigned nameind=0;

		while(in.tellg()-pos<size) {
			GID::Type gid;
			int osize;
			auto cpos=in.tellg();

			gid=gge::resource::ReadFrom<int>(in);
			osize=gge::resource::ReadFrom<int>(in);

			if(gid==gge::resource::GID::SGuid) {
				gge::utils::SGuid g;
				g.Load(in);

				base.SetGuid(g);
			}
			else if(gid==gge::resource::GID::Guid) {
				gge::utils::SGuid g;
				g.LoadLong(in);

				base.SetGuid(g);
			}
			else if( !reader(gid, osize) ) {
				Base *obj=file.LoadObject(in, base, gid, osize);

				if(obj==NULL)
					throw gge::resource::load_error(gge::resource::load_error::UnknownNode, gge::resource::load_error::strings::UnknownNode);

				if(nameind<list.size()) {
					obj->SetCaption(list[nameind].caption);
					obj->SetName(list[nameind].name);

					nameind++;
				}

				obj->SetParent(base);
				base.Subitems.Add(obj);
			}

			in.seekg(cpos+osize+8, std::ios::beg);
		}
	}

}
