#pragma once

#include <vector>
#include <string>
#include <Widgets/Main.h>
#include <Widgets/FullscreenPanel.h>
#include <Widgets/Tabpanel.h>
#include <Widgets/Listbox.h>
#include "../Common/FileHandling.h"
#include "../Common/ImageImport.h"
#include "../Common/ImageList.h"
#include <iosfwd>
#include <Widgets/Dialogs/TextInput.h>
#include <Gorgon/Folder.h>
#include <Gorgon/Base.h>
#include <Gorgon/Game/Animation.h>
#include <Gorgon/Generic/DataArray.h>
#include <Widgets/Dialogs/Query.h>
#include <Widgets/Numberbox.h>
#include <Widgets/Checkbox.h>
#include <Widgets/Combobox.h>
#include <Gorgon/Extended/Sound.h>
#include <Gorgon/Link.h>
#include <Widgets/InlinePanel.h>
#include <Widgets/Dialogs/Message.h>
#include <Utils/StringEnum.h>

class Rolee {
public:
	enum Type {
		Regular,
		Single1,
		Single2,
		Single3,
		Single4,
		TopLeft,
		Top,
		TopRight,
		Left,
		Right,
		BottomLeft,
		Bottom,
		BottomRight,
		D1,D2,D3,D4,
		Random1,
		Random2,
		Random3,
		Random4,
		Random5,
		Random6,
		end
	};

	static const char* Names[];
};

typedef utils::StringEnum<Rolee> RoleEnum;

class UI {
public:

	UI() {
		changed=false;

		CurrentFile.AddGameLoaders();
		CurrentFile.SetFileType(gorgon::GID::File::Game);

		os::window::Closing.RegisterLambda([&](bool &reply){
			if(changed) {
				AskQuit();
				reply=false;
			}
		});

		toplevel.Padding=0;


		navigation.SetContainer(toplevel);
		navigation.FillContainer();
		navigation.SetWidth(200);
		navigation.Add("Files");
		navigation.Add("List");
		navigation("List").Disable();
		navigation.reorganize();


		file.SetPanel(navigation("Files"));
		file.SetPanel(navigation("Files"));
		file.Quit.ClickEvent.RegisterLambda([&]{
			if(changed) AskQuit();
			else        Main.Exit(0);
		});
		file.New.ClickEvent.Register(this, &UI::NewFile);
		file.Load.ClickEvent.Register(this, &UI::LoadFile);
		file.Save.ClickEvent.RegisterLambda([&]{ actionafter=action_none; Save(); });
		file.SaveAs.ClickEvent.RegisterLambda([&]{ actionafter=action_none; AskSavename(); });
		file.RefreshFiles();

		tiles.SetContainer(navigation("List"));
		tiles.SetHeight(250);
		tiles.SetWidth(180);
		tiles.ItemClickedEvent.Register(this,&UI::SelTile);

		tilepartedit.SetContainer(toplevel);
		tilepartedit.Move(200, 0);
		tilepartedit.Padding=0;
		tilepartedit.Resize(toplevel.GetWidth()-201, toplevel.GetHeight()-1);
		tilepartedit.Hide();


		ImageBank.SelectionType=ImageBank.MultiSelect;
		ImageBank.AllowReorder=true;
		ImageBank.SetItemsize(38, 38);
		ImageBank.SetContainer(tilepartedit);
		ImageBank.Move(0, 0);
		ImageBank.Resize((tilepartedit.GetWidth()-61)/2, tilepartedit.GetHeight()-1);

		ImgAdd.SetContainer(tilepartedit);
		ImgAdd.Move(ImageBank.GetBounds().TopRight()+utils::Point(0,94));
		ImgAdd.Text=">>";
		ImgAdd.SetWidth(60);
		ImgAdd.ClickEvent.Register(this, &UI::AddSelectedTileParts);

		ImgRemove.SetContainer(tilepartedit);
		ImgRemove.Move(ImgAdd.GetBounds().BottomLeft());
		ImgRemove.Text="<<";
		ImgRemove.SetWidth(60);
		ImgRemove.ClickEvent.Register(this, &UI::RemoveSelectedTiles);

		ImgAddSet.SetContainer(tilepartedit);
		ImgAddSet.Move(ImgRemove.GetBounds().BottomLeft()+utils::Point(0,10));
		ImgAddSet.Text="1st set>>";
		ImgAddSet.SetWidth(60);
		ImgAddSet.ClickEvent.Register(this, &UI::AddTilePart1Set);

		ImgAddAnother.SetContainer(tilepartedit);
		ImgAddAnother.Move(ImgAddSet.GetBounds().BottomLeft());
		ImgAddAnother.Text="set>>";
		ImgAddAnother.SetWidth(60);
		ImgAddAnother.ClickEvent.Register(this, &UI::AddTilePartSet);

		ImgClear.SetContainer(tilepartedit);
		ImgClear.Move(ImgAddAnother.GetBounds().BottomLeft());
		ImgClear.Text="all >> del";
		ImgClear.SetWidth(60);
		ImgClear.ClickEvent.Register(this, &UI::RemoveAll);

		tilepartdataedit.SetContainer(tilepartedit);
		tilepartdataedit.SetOrganizer(tileparteditorganizer);
		tilepartdataedit.Move(ImageBank.GetBounds().Right+60,0);
		tilepartdataedit.Resize(TileParts.GetWidth(),tilepartedit.GetHeight()-1);
		tilepartdataedit.Padding=0;


		TileParts.SelectionType=TileParts.MultiSelect;
		TileParts.SetItemsize(38, 38);
		//TileParts.SetContainer(tilepartedit);
		//TileParts.Move(ImageBank.GetBounds().Right+60, 90);
		TileParts.Resize((tilepartedit.GetWidth()-61)/2, tilepartedit.GetHeight()-91);
		TileParts.ItemClickedEvent.RegisterLambda([&]{
			if(!CurrentTileFolder) return;
			int ind=TileParts.FindLocation(TileParts.GetSelectedItem());
			auto &d=CurrentTileFolder->Get<gorgon::Folder>(ind+1).Get<gorgon::DataArray>(0);
			if(d.getInt(0)!=0)
				ForceNonWalkable.Check();
			else
				ForceNonWalkable.Uncheck();
			Role.SetValue((RoleEnum::Type)d.getInt(1));
			Against.SetValue(d.getString(2));
		});

		newtile.Text="New";
		newtile.SetContainer(navigation("List"));
		newtile.Move(10, tiles.GetHeight());
		newtile.SetWidth(80);
		newtile.ClickEvent.Register(this, &UI::NewTile);

		deltile.Text="Delete";
		deltile.SetContainer(navigation("List"));
		deltile.Move(newtile.GetBounds().TopRight());
		deltile.SetWidth(80);
		deltile.ClickEvent.Register(this, &UI::DelTile);

		tileedit.SetContainer(navigation("List"));
		tileedit.Move(newtile.GetBounds().BottomLeft()+utils::Point(0,5));
		tileedit.Resize(180, navigation("List").GetUsableHeight()-tileedit.GetY()-1);
		tileedit.SetOrganizer(tileeditorganizer);
		tileedit.Padding=0;
		tileedit.Hide();

		auto nl=tileeditorganizer.nextline;

		Walkable.Text="Is walkable";

		tileeditorganizer
			<<"Name: "<<Name<<nl
			<<"Movement: "<<Movement<<nl
			<<Walkable<<nl
		;

		RoleEnum::AddAll(Role);

		ForceNonWalkable.Text="Force non-walkable";

		tileparteditorganizer
			<<ForceNonWalkable<<nl
			<<"Role: "<<Role<<nl
			<<"Against: "<<Against<<nl
			<<TileParts<<nl
		;

		Name.ChangeEvent.RegisterLambda([&]{
			if(CurrentTile) {
				CurrentTile->SetCaption(Name.Text);
				CurrentTile->Get<resource::StringData>(0).value=Name.Text;
				tiles.GetItem()->Value=Name.Text;
				CurrentTileFolder->SetCaption(Name.Text);
				CurrentTileFolder->SetName(Name.Text);
				int ind=CurrentFile.Root().Subitems.FindLocation(CurrentTileFolder);
				Against.GetItem(ind)->Value=Name.Text;

				changed=true;
			}
		});
		Movement.ChangeEvent.RegisterLambda([&]{
			if(CurrentTile) {
				CurrentTile->Get<resource::FloatData>(1).value=Movement.Value;

				changed=true;
			}
		});
		Walkable.ChangeEvent.RegisterLambda([&]{
			if(CurrentTile) {
				CurrentTile->Get<resource::IntegerData>(2).value=Walkable ? 1 : 0;

				changed=true;
			}
		});
		ForceNonWalkable.ChangeEvent.RegisterLambda([&]{
			for(auto it=TileParts.GetSelectedItems().First();it.IsValid();it.Next()) {
				int i=1;
				for(auto it2=CurrentTileFolder->Subitems.First()+1;it2.IsValid();it2.Next()) {
					auto &f=dynamic_cast<gorgon::Folder&>(*it2);
					if(&f.Get<gorgon::Image>(1)==*it)
						break;
					i++;
				}
				if(i>=CurrentTileFolder->GetCount()) continue;
				CurrentTileFolder->Get<gorgon::Folder>(i).Get<gorgon::DataArray>(0).Get<resource::IntegerData>(0).value=ForceNonWalkable ? 1 : 0;

				changed=true;
			}
		});
		Role.ChangeEvent.RegisterLambda([&]{
			for(auto it=TileParts.GetSelectedItems().First();it.IsValid();it.Next()) {
				int i=1;
				for(auto it2=CurrentTileFolder->Subitems.First()+1;it2.IsValid();it2.Next()) {
					auto &f=dynamic_cast<gorgon::Folder&>(*it2);
					if(&f.Get<gorgon::Image>(1)==*it)
						break;
					i++;
				}
				if(i>=CurrentTileFolder->GetCount()) continue;
				CurrentTileFolder->Get<gorgon::Folder>(i).Get<gorgon::DataArray>(0).Get<resource::IntegerData>(1).value=Role.GetValue();

				changed=true;
			}
		});
		Against.ChangeEvent.RegisterLambda([&]{
			for(auto it=TileParts.GetSelectedItems().First();it.IsValid();it.Next()) {
				int i=1;
				for(auto it2=CurrentTileFolder->Subitems.First()+1;it2.IsValid();it2.Next()) {
					auto &f=dynamic_cast<gorgon::Folder&>(*it2);
					if(&f.Get<gorgon::Image>(1)==*it)
						break;
					i++;
				}
				if(i>=CurrentTileFolder->GetCount()) continue;
				CurrentTileFolder->Get<gorgon::Folder>(i).Get<gorgon::DataArray>(0).Get<resource::StringData>(2).value=Against.GetValue();

				changed=true;
			}
		});
		
		imgimport.Ok.ClickEvent.Register(this, &UI::image_import);
	}

	~UI() {
		tileedit.RemoveOrganizer();
		tilepartdataedit.RemoveOrganizer();
	}


	void SetDirectory(const std::string &dir) {
		file.SetDirectory(dir);
	}

	void AskQuit() {
		auto &d=widgets::dialog::AskConfirm("You have changed the file, do you wish to save?", "Quit");
		d.RepliedEvent.RegisterLambda([&](bool reply){
			if(reply) {
				actionafter=action_quit;
				Save();
				return;
			}
			else {
				Main.Exit(0);
			}
		});
		d.ShowCancel=true;
	}

	void AskSavename() {
		widgets::dialog::AskText("Enter the name for the file.", CurrentFile.GetFilename(), 
			"Save").RepliedEvent.RegisterLambda([&](const std::string &str) {
				CurrentFile.SetFilename(file.directory.Text+"/"+str);
				Save();
		});
	}

	bool Save() {
		if(CurrentFile.GetFilename()=="") {
			AskSavename();
			return false;
		}

		try {
			CurrentFile.SaveFile(CurrentFile.GetFilename());
			file.RefreshFiles();
		}
		catch(std::exception ex) {
			widgets::dialog::ShowMessage(std::string(ex.what())+"\nCheck if file path is valid.", "Save error");
		}
		changed=false;

		if(actionafter==action_newfile) {
			NewFile();
		}
		else if(actionafter==action_loadfile) {
			LoadFile();
		}
		else if(actionafter==action_quit) {
			Main.Exit(0);
		}

		return true;
	}

	void AskNew() {
		auto &d=widgets::dialog::AskConfirm("You have changed the file, do you wish to save?", "New file");
		d.RepliedEvent.RegisterLambda([&](bool reply){
			if(reply) {
				actionafter=action_newfile;
				Save();
				return;
			}
			else {
				ForceNewFile();
			}
		});
		d.ShowCancel=true;
	}

	void NewFile() {
		if(changed) {
			AskNew();
		}
		else {
			ForceNewFile();
		}
	}

	void ForceNewFile() {
		tileedit.Hide();
		tilepartedit.Hide();
		CurrentFile.New();
		CurrentFile.Root().Add(*new gorgon::Folder("Info"));

		gorgon::DataArray &dinf=*new gorgon::DataArray;
		dinf.SetCaption("Info");
		dinf.Add("trinlibr:tiles").name="ID";

		CurrentTile=NULL;
		tiles.Destroy();
		navigation("List").Enable();
		file.Save.Enable();
		file.SaveAs.Enable();
		navigation.reorganize();
		navigation.Activate(navigation("List"));
	}

	void AskLoad() {
		auto &d=widgets::dialog::AskConfirm("You have changed the file, do you wish to save?", "Load file");
		d.RepliedEvent.RegisterLambda([&](bool reply){
			if(reply) {
				actionafter=action_loadfile;
				Save();
				return;
			}
			else {
				ForceLoadFile();
			}
		});
		d.ShowCancel=true;
	}

	void LoadFile() {
		if(changed) {
			AskLoad();
		}
		else
			ForceLoadFile();

	}

	void ForceLoadFile() {
		std::string filename=(std::string)file.directory.Text + "/" + file.lst.GetValue().Filename;

		CurrentFile.LoadFile(filename);
		CurrentFile.Prepare();

		if(CurrentFile.Root().Get<gorgon::Folder>(0).GetCount()==0) {
			gorgon::DataArray &dinf=*new gorgon::DataArray;
			dinf.SetCaption("Info");
			dinf.Add("trinlibr:tiles").name="ID";

			CurrentFile.Root().Get<gorgon::Folder>(0).Add(dinf);
		}

		ListTiles();

		navigation("List").Enable();
		file.Save.Enable();
		file.SaveAs.Enable();
		navigation.reorganize();
		navigation.Activate(navigation("List"));
	}

	void ListTiles() {
		tiles.Destroy();
		Against.DeleteAll();
		Against.Add("");
		for(int i=1; i<CurrentFile.Root().GetCount();i++) {
			auto &f=CurrentFile.Root().Get<gorgon::Folder>(i);
			auto &d=f.Get<gorgon::DataArray>(0);

			tiles.Add(d.getString(0));
			Against.Add(d.getString(0));
		}

		if(tiles.GetCount())
			tiles[0].Accessed();
	}

	void NewTile() {
		gorgon::DataArray &d=*new gorgon::DataArray;

		std::stringstream ss;
		ss<<"Tile "<<CurrentFile.Root().GetCount();

		d.SetCaption(ss.str());
		d.Add(ss.str()).name="Name";
		d.Add(1.f).name="Movement";
		d.Add(1).name="Walkable";

		gorgon::Folder &f=*new gorgon::Folder;
		f.Add(d);
		f.SetCaption(ss.str());
		f.SetName(ss.str());

		CurrentFile.Root().Add(f);

		ListTiles();
		tiles.GetItem(tiles.GetCount()-1)->Accessed();

		changed=true;
	}

	void SelTile() {
		if(tiles.GetSelectedItem()) {
			tileedit.Show();
			tilepartedit.Show();
			CurrentTileFolder=&CurrentFile.Root().Get<gorgon::Folder>(tiles.FindLocation(tiles.GetSelectedItem())+1);
			CurrentTile=&CurrentTileFolder->Get<gorgon::DataArray>(0);
			Name.Text=CurrentTile->getString(0);
			Movement.Value=CurrentTile->getFloat(1);
			if(CurrentTile->getInt(2)!=0)
				Walkable.Check();
			else
				Walkable.Uncheck();
		}
		else {
			CurrentTile=NULL;
			CurrentTileFolder=NULL;
			tileedit.Hide();
			tilepartedit.Hide();
		}

		ListTileParts();
	}

	void DelTile() {
		if(CurrentTile)
			CurrentFile.Root().Remove(CurrentTileFolder);

		ListTiles();

		changed=true;
	}

	void AddTilePart1Set() {
		static RoleEnum::Type roles[]={
			RoleEnum::Single1, RoleEnum::D1, RoleEnum::D2,
			RoleEnum::Single2, RoleEnum::D3, RoleEnum::D4,
			RoleEnum::TopLeft, RoleEnum::Top, RoleEnum::TopRight,
			RoleEnum::Left, RoleEnum::Regular, RoleEnum::Right,
			RoleEnum::BottomLeft, RoleEnum::Bottom, RoleEnum::BottomRight,
			RoleEnum::Random1, RoleEnum::Random2, RoleEnum::Random3
		};

		for(int i=0;i<6*3 && ImageBank.First().IsValid();i++) {
			NewTilePart(ImageBank.First()->Value, roles[i]);
			ImageBank.Delete(*ImageBank.First());
		}
	}
	void AddTilePartSet() {
		static RoleEnum::Type roles[]={
			RoleEnum::Single1, RoleEnum::D1, RoleEnum::D2,
			RoleEnum::Single2, RoleEnum::D3, RoleEnum::D4,
			RoleEnum::TopLeft, RoleEnum::Top, RoleEnum::TopRight,
			RoleEnum::Left, RoleEnum::Regular, RoleEnum::Right,
			RoleEnum::BottomLeft, RoleEnum::Bottom, RoleEnum::BottomRight
		};

		for(int i=0;i<6*3 && ImageBank.First().IsValid();i++) {
			if(i!=10 && i<15) {
				NewTilePart(ImageBank.First()->Value, roles[i]);
			}
			ImageBank.Delete(*ImageBank.First());
		}
	}
	void AddSelectedTileParts() {
		for(auto it=ImageBank.GetSelectedItems().First();it.IsValid();it.Next()) {
			NewTilePart(dynamic_cast<gorgon::Image*>((resource::Image*)it->Value));
			ImageBank.Delete(*it);
		}
	}
	void RemoveSelectedTiles() {
		for(auto it=TileParts.GetSelectedItems().Last();it.IsValid();it.Previous()) {
			if(ImageBank.First().IsValid())
				ImageBank.Insert(it->Value, ImageBank.First().CurrentPtr());
			else
				ImageBank.Add(it->Value);
			TileParts.Delete(*it);
		}
	}
	void RemoveAll() {
		for(auto it=ImageBank.First(); it.IsValid();) {
			ImageBank.Delete(*it);
		}
	}

	void NewTilePart(resource::Image *imgg, RoleEnum::Type role=RoleEnum::Regular) {
		auto img=dynamic_cast<gorgon::Image*>(imgg);
		TileParts.Add(img);

		std::stringstream ss;
		ss<<"Tile part "<<CurrentTileFolder->GetCount();

		gorgon::DataArray &d=*new gorgon::DataArray;
		d.Add(ForceNonWalkable ? 1 : 0).name="Force non-walkable";
		d.Add((int)role).name="Role";
		d.Add(Against.GetValue()).name="Against";
		d.SetCaption("Info");

		gorgon::Folder &f=*new gorgon::Folder;
		f.Add(d);
		f.Add(img);
		img->SetCaption("Image");
		f.SetCaption(ss.str());

		CurrentTileFolder->Add(f);
	}

	void ListTileParts() {
		TileParts.DeleteAll();
		
		if(!CurrentTile) return;

		for(int i=1;i<CurrentTileFolder->GetCount();i++) {
			TileParts.Add(&CurrentTileFolder->Get<gorgon::Folder>(i).Get<gorgon::Image>(1));
		}
	}

	void BeginImport() {
		if(FilesToImport.size()) {
			imgimport.Show(FilesToImport[FilesToImport.size()-1]);
			FilesToImport.pop_back();
		}
	}

	ImageList ImageBank;
	ImageList TileParts;

	std::vector<std::string> FilesToImport;

	widgets::FullscreenPanel toplevel;
	widgets::Tabpanel navigation;
	FileHandler file;

	widgets::Listbox<> tiles;
	widgets::Button newtile;
	widgets::Button deltile;

	widgets::InlinePanel tileedit;
	widgets::InlinePanel tilepartedit;
	widgets::InlinePanel tilepartdataedit;

	widgets::Textbox Name;
	widgets::Numberbox<float> Movement;
	widgets::Checkbox Walkable;

	widgets::Checkbox ForceNonWalkable;
	widgets::Combobox<RoleEnum::Type, RoleEnum::ConvertToString> Role;
	widgets::Combobox<std::string> Against;

	widgets::LinearOrganizer tileeditorganizer;
	widgets::LinearOrganizer tileparteditorganizer;

	widgets::Button ImgAdd, ImgAddSet, ImgAddAnother, ImgRemove, ImgClear;

	gorgon::File CurrentFile;
	gorgon::DataArray *CurrentTile;
	gorgon::Folder *CurrentTileFolder;
	ImageImport imgimport;

	void image_import() {
		int W=imgimport.Width;
		int H=imgimport.Height;
		auto &img=*imgimport.img;
		int nw=img.GetWidth()/W;
		int nh=img.GetHeight()/H;

		for(int y=0;y<nh;y++) {
			for(int x=0;x<nw;x++) {
				gorgon::Image &nimg=*new gorgon::Image(W,H);
				nimg.SetCompression(resource::Image::PNG);
				::graphics::Canvas c(nimg);
				bool isempty=true;
				for(int yy=0;yy<H;yy++) {
					for(int xx=0;xx<W;xx++) {
						c(xx,yy)=img(x*W+xx,y*H+yy);
						if(c(xx,yy).a) isempty=false;
					}
				}
				if(isempty)
					delete &nimg;
				else {
					nimg.Prepare(Main, CurrentFile);
					ImageBank.Add(&nimg);
				}
			}

			imgimport.Close();
		}

		BeginImport();
	}

	bool changed;
	enum {
		action_none,
		action_newfile,
		action_loadfile,
		action_quit
	} actionafter;
};
