#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 <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 <Gorgon/Extended/Sound.h>
#include <Gorgon/Link.h>
#include <Widgets/InlinePanel.h>
#include <Widgets/Dialogs/Message.h>

#include "../../../Source/Assets/Item.h"
#include <Utils/StringEnum.h>
#include <Widgets/Combobox.h>

class ItemType {
public:
	typedef assets::Item::Type Type;

	static const Type end;

	static const char* Names[];
};

class BuildingType {
public:
	enum Type {
		Housing,
		Tools,
		Storage,
		MeleeTraining,
		RangedTraining,
		end
	};

	static const char* Names[];
};

typedef utils::StringEnum<ItemType> ItemTypeEnum;
typedef utils::StringEnum<BuildingType> BuildingTypeEnum;

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();

		militarybuildings.SetContainer(navigation("List"));
		militarybuildings.SetHeight(navigation("List").GetUsableHeight()-newmilitarybuilding.GetHeight()-2);
		militarybuildings.SetWidth(180);
		militarybuildings.ItemClickedEvent.Register(this,&UI::SelMilitaryBuilding);

		newmilitarybuilding.Text="New";
		newmilitarybuilding.SetContainer(navigation("List"));
		newmilitarybuilding.Move(10, militarybuildings.GetHeight());
		newmilitarybuilding.SetWidth(80);
		newmilitarybuilding.ClickEvent.Register(this, &UI::NewMilitaryBuilding);

		delmilitarybuilding.Text="Delete";
		delmilitarybuilding.SetContainer(navigation("List"));
		delmilitarybuilding.Move(newmilitarybuilding.GetBounds().TopRight());
		delmilitarybuilding.SetWidth(80);
		delmilitarybuilding.ClickEvent.Register(this, &UI::DelMilitaryBuilding);

		militarybuildingedit.SetContainer(toplevel);
		militarybuildingedit.Move(200, 20);
		militarybuildingedit.Resize(toplevel.GetUsableWidth()-200, toplevel.GetUsableHeight()-militarybuildingedit.GetY()-1);
		militarybuildingedit.SetOrganizer(militarybuildingeditorganizer);
		militarybuildingedit.Hide();

		Type.SetValue(BuildingTypeEnum::end);
		Storage.SetValue(ItemTypeEnum::end);

		BuildingTypeEnum::AddAll(Type);
		ItemTypeEnum::AddAll(Storage);

		auto nl=militarybuildingeditorganizer.nextline;

		help.Text="* For map, 0 non passable, you may use , to separate rows.\n** Relative to the start of map. Use multiples like (2,3) (3,4). No checking is performed. They can be out of the map but should be on a passible tile.\nNot all fields are used for all types.";

		militarybuildingeditorganizer
			<<"Name: "<<Name<<nl
			<<"Type: "<<Type<<nl
			<<"Size: "<<Size<<nl
			<<"Map: *"<<Map<<nl
			<<"HP: "<<HP<<nl
			<<"Armor: "<<Armor<<nl
			<<"Constr cost: "<<ConstrCost<<nl
			<<"Constr time: "<<ConstrTime<<nl
			<<"Usable points: **"<<Points<<nl
			<<"Capacity: "<<Capacity<<nl
			<<"Storage type: "<<Storage<<nl
			<<"Max level: "<<MaxLevel<<nl
			<<"XP/Hit: "<<XPHit<<nl
			<<""<<nl
			<<help
		;
		militarybuildingeditorganizer.Reorganize();

		Name.ChangeEvent.RegisterLambda([&]{
			if(CurrentMilitaryBuilding) {
				CurrentMilitaryBuilding->SetCaption(Name.Text);
				CurrentMilitaryBuilding->Get<resource::StringData>(0).value=Name.Text;
				militarybuildings.GetItem()->Value=Name.Text;
			}
		});
		Size.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding) {
				CurrentMilitaryBuilding->Get<resource::SizeData>(1).value=(utils::Point)Size.Value;
				int ch=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;

				if(mh>ch) {
					for(int i=0;i<mh-ch;i++)
						CurrentMilitaryBuilding->Delete(2);
				}
				else if(mh<ch) {
					//as data array does not support reordering, we remove the rest and add again
					for(int i=0;CurrentMilitaryBuilding->GetCount()>2;i++)
						CurrentMilitaryBuilding->Delete(2);

					for(int i=0;i<ch;i++) {
						std::stringstream ss;
						ss<<"Map "<<i+1;
						CurrentMilitaryBuilding->Add("").name=ss.str();
					}

					CurrentMilitaryBuilding->Add(0).name="Type";
					CurrentMilitaryBuilding->Add(0.f).name="HP";
					CurrentMilitaryBuilding->Add(0.f).name="Armor";
					CurrentMilitaryBuilding->Add(0.f).name="Constr cost";
					CurrentMilitaryBuilding->Add(0.f).name="Constr time";
					CurrentMilitaryBuilding->Add("").name="Usable points";
					CurrentMilitaryBuilding->Add(0).name="Capacity";
					CurrentMilitaryBuilding->Add(0).name="Storage type";
					CurrentMilitaryBuilding->Add(0).name="Max level";
					CurrentMilitaryBuilding->Add(0).name="XP/Hit";

					Map.ChangeEvent();
					HP.ChangeEvent();
					this->Armor.ChangeEvent();
					ConstrCost.ChangeEvent();
					ConstrTime.ChangeEvent();
					Points.ChangeEvent();
					Capacity.ChangeEvent();
					Storage.ChangeEvent();
					MaxLevel.ChangeEvent();
					XPHit.ChangeEvent();
				}
			}
		});
		Map.ChangeEvent.RegisterLambda([&]{
			int mw=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Width;
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding) {
				unsigned ind=0;
				std::string s=Map.Text;
				s.erase(std::remove(s.begin(), s.end(), '|'), s.end());
				s.erase(std::remove(s.begin(), s.end(), ','), s.end());
				s.erase(std::remove(s.begin(), s.end(), ' '), s.end());
				for(int h=0;h<mh;h++) {
					std::string line="";
					for(int w=0;w<mw;w++) {
						char t='1';
						if(ind<s.length()) {
							t=s[ind++];
						}
						line.append(1, t);
					}
					CurrentMilitaryBuilding->Get<resource::StringData>(2+h).value=line;
				}
			}
		});
		Type.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::IntegerData>(2+mh).value=Type.GetValue();
		});
		HP.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::FloatData>(3+mh).value=HP.Value;
		});
		Armor.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::FloatData>(4+mh).value=this->Armor.Value;
		});
		ConstrCost.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::FloatData>(5+mh).value=ConstrCost.Value;
		});
		ConstrTime.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::FloatData>(6+mh).value=ConstrTime.Value;
		});
		Points.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::StringData>(7+mh).value=Points.Text;
		});
		Capacity.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::IntegerData>(8+mh).value=Capacity.Value;
		});
		Storage.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::IntegerData>(9+mh).value=Storage.GetValue();
		});
		MaxLevel.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::IntegerData>(10+mh).value=MaxLevel.Value;
		});
		XPHit.ChangeEvent.RegisterLambda([&]{
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			if(CurrentMilitaryBuilding)
				CurrentMilitaryBuilding->Get<resource::IntegerData>(11+mh).value=XPHit.Value;
		});
	}

	~UI() {
		militarybuildingedit.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() {
		militarybuildingedit.Hide();
		CurrentFile.New();
		CurrentFile.Root().Add(*new gorgon::Folder("Info"));

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

		CurrentFile.Root().Get<gorgon::Folder>(0).Add(dinf);
		CurrentMilitaryBuilding=NULL;
		militarybuildings.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:milbld").name="ID";

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

		ListMilitaryBuildings();

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

	void ListMilitaryBuildings() {
		militarybuildings.Destroy();
		for(int i=1; i<CurrentFile.Root().GetCount();i++) {
			auto &d=CurrentFile.Root().Get<gorgon::DataArray>(i);

			militarybuildings.Add(d.getString(0));
		}

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

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

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

		d.SetCaption(ss.str());
		d.Add(ss.str()).name="Name";
		d.Add(utils::Size(2,2)).name="Size";
		d.Add("00").name="Map 1";
		d.Add("10").name="Map 2";
		d.Add((int)BuildingTypeEnum::Housing).name="Type";
		d.Add(1000.f).name="HP";
		d.Add(10.f).name="Armor";
		d.Add(25.f).name="Constr cost";
		d.Add(12.f).name="Constr time";
		d.Add("(1, 2)").name="Usable points";
		d.Add(3).name="Capacity";
		d.Add((int)assets::Item::Armor).name="Storage type";
		d.Add(0).name="Max level";
		d.Add(0).name="XP/Hit";


		CurrentFile.Root().Add(d);

		militarybuildings.Add(ss.str());
		militarybuildings.GetItem(militarybuildings.GetCount()-1)->Accessed();

		changed=true;
	}

	void SelMilitaryBuilding() {
		if(militarybuildings.GetSelectedItem()) {
			militarybuildingedit.Show();
			CurrentMilitaryBuilding=&CurrentFile.Root().Get<gorgon::DataArray>(militarybuildings.FindLocation(militarybuildings.GetSelectedItem())+1);
			Name.Text=CurrentMilitaryBuilding->getString(0);
			int mh=CurrentMilitaryBuilding->Get<resource::SizeData>(1).value.Height;
			std::string s="";
			for(int h=0;h<mh;h++)
				s+=CurrentMilitaryBuilding->getString(2+h)+",";
			Map.Text=s;
			Size.Value=CurrentMilitaryBuilding->getSize(1);
			Type.SetValue((BuildingTypeEnum::Type)CurrentMilitaryBuilding->getInt(2+mh));
			HP.Value=CurrentMilitaryBuilding->getFloat(3+mh);
			Armor.Value=CurrentMilitaryBuilding->getFloat(4+mh);
			ConstrCost.Value=CurrentMilitaryBuilding->getFloat(5+mh);
			ConstrTime.Value=CurrentMilitaryBuilding->getFloat(6+mh);
			Points.Text=CurrentMilitaryBuilding->getString(7+mh);
			Capacity.Value=CurrentMilitaryBuilding->getInt(8+mh);
			Storage.SetValue((ItemTypeEnum::Type)CurrentMilitaryBuilding->getInt(9+mh));
			MaxLevel.Value=CurrentMilitaryBuilding->getInt(10+mh);
			XPHit.Value=CurrentMilitaryBuilding->getInt(11+mh);
		}
		else {
			CurrentMilitaryBuilding=NULL;
			militarybuildingedit.Hide();
		}
	}

	void DelMilitaryBuilding() {
		if(CurrentMilitaryBuilding)
			CurrentFile.Root().Remove(CurrentMilitaryBuilding);

		ListMilitaryBuildings();

		changed=true;
	}

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

	widgets::Listbox<> militarybuildings;
	widgets::Button newmilitarybuilding;
	widgets::Button delmilitarybuilding;

	widgets::InlinePanel militarybuildingedit;

	widgets::Textbox Name;
	widgets::Combobox<BuildingTypeEnum::Type, BuildingTypeEnum::ConvertToString> Type;
	widgets::Numberbox<utils::Point> Size;
	widgets::Textbox Map;
	widgets::Numberbox<float> HP, Armor;
	widgets::Numberbox<float> ConstrCost, ConstrTime;
	widgets::Numberbox<int> Capacity;
	widgets::Combobox<ItemTypeEnum::Type, ItemTypeEnum::ConvertToString> Storage;
	widgets::Numberbox<int> MaxLevel;
	widgets::Numberbox<int> XPHit;
	widgets::Textbox Points;
	widgets::Label help;

	widgets::LinearOrganizer militarybuildingeditorganizer;

	gorgon::File CurrentFile;
	gorgon::DataArray *CurrentMilitaryBuilding;

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