import std.stdio;
import std.file;
import std.string;
import std.conv : to;


import gtk.Main;
import gtk.MainWindow;
import gtk.TreeView;
import gtk.ListStore;
import gtk.TreeViewColumn;
import gtk.TreeIter;
import gtk.Label;
import gtk.Entry;
import gtk.CellRenderer;
import gtk.CellRendererText;
import gtk.VBox;
import gtk.Statusbar;
import gtk.Button;
import gtk.ScrolledWindow;
import gtk.AccelGroup;

import gtk.HBox;

MainWindow window;

void main(string[] args)
{
	Main.init(args);

	//Window
	window = new MainWindow("2DA-Edit");

	auto accel = new AccelGroup();
	window.addAccelGroup(accel);

	auto cont = new VBox(false, 0);
	window.add(cont);
	cont.setSizeRequest(300, 200);

	version(Windows){
		auto buttonSave = new Button(StockID.SAVE, true);
		auto buttonOpen = new Button(StockID.OPEN, true);
		auto buttonInsert = new Button(StockID.JUMP_TO, true);
		auto buttonDelete = new Button(StockID.DELETE, true);
		auto buttonRenumber = new Button(StockID.INDEX, true);
		auto buttonNewCol = new Button(StockID.NEW, true);
	}
	else{
		auto buttonSave = new Button("document-save-symbolic", GtkIconSize.MENU);
		auto buttonOpen = new Button("document-open-symbolic", GtkIconSize.MENU);
		auto buttonInsert = new Button("format-text-direction-ltr-symbolic", GtkIconSize.SMALL_TOOLBAR);
		auto buttonDelete = new Button("user-trash-symbolic", GtkIconSize.SMALL_TOOLBAR);
		auto buttonRenumber = new Button("view-list-symbolic", GtkIconSize.SMALL_TOOLBAR);
		auto buttonNewCol = new Button("tab-new-symbolic", GtkIconSize.SMALL_TOOLBAR);
	}
	buttonSave.setTooltipText("Save");
	enum GDK_KEY_S = 0x053;
	buttonSave.addAccelerator("clicked", accel, GDK_KEY_S, GdkModifierType.CONTROL_MASK, GtkAccelFlags.VISIBLE);
	buttonOpen.setTooltipText("Open 2DA");
	buttonInsert.setTooltipText("Insert row after");
	buttonDelete.setTooltipText("Delete row");
	buttonRenumber.setTooltipText("Renumber all rows");
	buttonNewCol.setTooltipText("Add new column");

	version(Windows){
		//Menu bar
		import gtk.HBox;

		auto cont2 = new HBox(false, 0);
		cont.packStart(cont2, false, false, 0);

		cont2.packStart(buttonOpen, false, false, 0);
		cont2.packEnd(buttonSave, false, false, 0);
	}
	else{
		//Header bar
		import gtk.HeaderBar;

		auto header = new HeaderBar();
		window.setTitlebar(header);
		header.setTitle("2DAEdit");
		header.setProperty("show-close-button", true);

		header.packStart(buttonOpen);
		header.packEnd(buttonSave);
	}

	//Status bar
	auto statusbar = new HBox(false, 0);
	cont.packEnd(statusbar, false, false, 5);
	statusbar.packStart(buttonRenumber, false, false, 5);
	statusbar.packStart(buttonInsert, false, false, 5);
	statusbar.packStart(buttonDelete, false, false, 5);
	statusbar.packEnd(buttonNewCol, false, false, 5);

	//TreeView to display database
	auto scroll = new ScrolledWindow(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
	cont.packEnd(scroll, true, true, 0);

	auto tree = new TreeView();
	scroll.add(tree);

	tree.setHeadersVisible(true);
	tree.setEnableSearch(true);
	tree.setProperty("enable-grid-lines", GtkTreeViewGridLines.VERTICAL);
	tree.setProperty("tooltip-column", 0);
	tree.setProperty("reorderable", true);
	tree.setProperty("headers-clickable", true);
	tree.addOnColumnsChanged((TreeView tree){
		auto store = cast(ListStore)tree.getModel();

		int si = GetColumnStoreIndex(tree, 0);
		if(si>=0 && store.getColumnType(si) != GType.INT){
			foreach(i ; 0..store.getNColumns()){
				si = GetColumnStoreIndex(tree, i);
				if(si>=0 && store.getColumnType(si)==GType.INT){
					tree.moveColumnAfter(tree.getColumn(i), null);
					break;
				}
			}
		}
	});


	//Configure button callbacks
	buttonSave.addOnClicked((Button){
		Save(tree);

		import core.thread;
		new Thread({
			Thread.getThis.sleep(dur!"msecs"(100));

			auto lbl = new Label("");
			lbl.setMarkup("<i>File saved</i>");
			statusbar.packEnd(lbl, false, false, 5);

			//Wow, much animation, very badass
			lbl.setOpacity(0.0);
			lbl.show();
			foreach(i ; 0..20){
				lbl.setOpacity(i/20.0);
				Thread.getThis.sleep(dur!"msecs"(10));
			}
			Thread.getThis.sleep(dur!"msecs"(1500));
			foreach(i ; 1..20){
				lbl.setOpacity(1.0-i/20.0);
				Thread.getThis.sleep(dur!"msecs"(10));
			}
			//Destroy
			lbl.destroy();
		}).start();

	});

	buttonOpen.addOnClicked((Button){
		import gtk.Dialog;
		import gtk.FileChooserDialog;

		auto fc = new FileChooserDialog("Open 2DA", window, FileChooserAction.OPEN);
		auto res = fc.run();
		if(res==GtkResponseType.OK){
			string filename = fc.getFilename();
			version(Windows)	Open(filename, tree, cast(Object)window);
			else 				Open(filename, tree, cast(Object)header);
		}
		fc.destroy();
	});

	buttonInsert.addOnClicked((Button){
		TreeIter it = tree.getSelectedIter();
		auto store = cast(ListStore)tree.getModel();

		if(store !is null){
			store.insertAfter(it, it);
			store.setValue(it, 0, 0);
			foreach(i ; 1..store.getNColumns()){
				store.setValue(it, cast(int)i, "_");
			}
		}
	});

	buttonDelete.addOnClicked((Button){
		TreeIter it = tree.getSelectedIter();
		auto store = cast(ListStore)tree.getModel();

		if(store !is null)
			store.remove(it);
	});

	buttonRenumber.addOnClicked((Button){
		auto store = cast(ListStore)tree.getModel();

		if(store !is null){
			TreeIter it = new TreeIter();
			if(store.getIterFirst(it)){
				int id = 0;
				do{
					store.setValue(it, 0, id++);
					
				}while(store.iterNext(it));
			}
		}
	});

	buttonNewCol.addOnClicked((Button){
		auto oldstore = cast(ListStore)tree.getModel();
		int newColIndex = oldstore.getNColumns();
		

		GType[] types;
		string[] titles;
		foreach(i ; 0..newColIndex){
			if(i==0)types~= GType.INT;
			else 	types~= GType.STRING;
			titles~= tree.getColumn(0).getTitle;
			tree.removeColumn(tree.getColumn(0));
		}
		types~=GType.STRING;
		titles~="new_col";

		writeln(types);

		auto store = new ListStore(types);
		tree.setModel(store);

		//Fill them
		TreeIter oldit = new TreeIter();
		TreeIter newit = new TreeIter();
		if(oldstore.getIterFirst(oldit)){
			do{
				store.append(newit);

				foreach(i ; 0..newColIndex+1){
					if(i<newColIndex){
						if(types[i]==GType.INT)	store.setValue(newit, cast(int)i, oldstore.getValueInt(oldit, i));
						else 					store.setValue(newit, cast(int)i, oldstore.getValueString(oldit, i));

					}
					else
						store.setValue(newit, cast(int)i, "_");
				}
				
			}while(oldstore.iterNext(oldit));
		}

		//setup cols
		foreach(i ; 0..newColIndex+1){
			tree.appendColumn(SetupColumn(tree, titles[i], i));
		}

		oldstore.destroy();
	});



	//Open if exists
	if(args.length>=2 && exists(args[1])){
		version(Windows)	Open(args[1], tree, cast(Object)window);
		else 				Open(args[1], tree, cast(Object)header);
	}

	window.showAll();
	
	Main.run();

}

int GetColumnStoreIndex(TreeView tree, int colindex){
	auto col = tree.getColumn(colindex);
	if(col !is null)
		return cast(int)(col.getData("colnumber"));
	return -1; 
}

void Save(ref TreeView tree){
	auto store = cast(ListStore)tree.getModel();
	if(store !is null){

		auto file = File(openedFile, "w");

		foreach(i ; 0..tree.getNColumns)
			file.write(tree.getColumn(i).getTitle, "\t");
		file.write("\n");

		TreeIter it = new TreeIter();
		if(store.getIterFirst(it)){
			do{
				file.write(store.getValueInt(it, 0));
				foreach(i ; 1..store.getNColumns()){
					file.write("\t\"", store.getValueString(it, GetColumnStoreIndex(tree, i)), "\"");
				}
				file.write("\n");
			}while(store.iterNext(it));

			file.flush();
			file.close();
			writeln("File written: ",openedFile);
		}
	}
	else
		writeln("Nothing to save !");
}

string openedFile;
void Open(string file, ref TreeView tree, Object header){

	auto twoda = new TwoDA(file);

	openedFile = file;

	version(Windows)	(cast(MainWindow)header).setTitle(file);
	else{
		import gtk.HeaderBar;
		(cast(HeaderBar)header).setSubtitle(file);
	} 				

	//Delete old store
	auto oldstore = cast(ListStore)tree.getModel();
	if(oldstore !is null)
		oldstore.destroy();

	//Remove columns from TreeView
	foreach(i ; 0..tree.getNColumns)
		tree.removeColumn(tree.getColumn(0));

	//Set store types
	GType type[];
	type~=GType.INT;
	foreach(i;1..twoda.header.length)type~=GType.STRING;

	//Create new store
	auto store = new ListStore(type);
	tree.setModel(store);


	//Setup TreeView columns
	foreach(index, s ; twoda.header){

		auto col = SetupColumn(tree, s, index);
		tree.appendColumn(col);
	}

	//Fill database
	TreeIter iter = new TreeIter();

	for(int i=0 ; i<=twoda.lastLine ; i++){
		store.append(iter);
		store.setValue(iter, 0, i);

		if(i in twoda.values){
			foreach(index, v ; twoda.values[i]){
				store.setValue(iter, cast(int)index+1, v);
			}
		}
		else{
			foreach(index ; 1..twoda.header.length){
				store.setValue(iter, cast(int)index+1, "_");
			}
		}
	}

	//Autosize columns
	tree.columnsAutosize();

	tree.setSizeRequest(50, 50);
}


//Note: tree is only used on events (click)
auto ref SetupColumn(TreeView tree, string sName, size_t index){
	auto store = cast(ListStore)tree.getModel();

	CellRendererText cr = new CellRendererText();
	cr.setProperty("editable", true);
	if(index==0){
		cr.setProperty("background-rgba", cast(ulong)(new GdkRGBA(0.36, 0.13, 0.4, 0.5)));
		cr.setProperty("background-set", true);

		cr.addOnEdited((string path, string newval, CellRendererText crt){
			try{
				int n = newval.to!int;
				TreeIter t = new TreeIter(tree.getModel(), path);
				store.setValue(t, cast(int)crt.getData("colnumber"), n);
			}
			catch(Exception e){
				writeln("Not a number");
			}

		});
	}
	else{
		cr.addOnEdited((string path, string newval, CellRendererText crt){
			TreeIter t = new TreeIter(tree.getModel(), path);
			store.setValue(t, cast(int)crt.getData("colnumber"), newval);
		});
	}

	cr.setData("colnumber", cast(void*)cast(int)index);


	auto col = new TreeViewColumn(sName, cr, "text", cast(int)index);
	col.setData("colnumber", cast(void*)cast(int)index);
	col.setResizable(true);
	col.setMinWidth(10);
	col.setClickable(true);
	col.setReorderable(true);
	col.addOnClicked((TreeViewColumn col){
		 import gtk.Dialog;
		 auto dlg = new Dialog("Rename column", window, GtkDialogFlags.MODAL, ["Cancel","Rename"], [ResponseType.CANCEL, ResponseType.OK]);
		 auto entry = new Entry(col.getTitle);
		 dlg.getContentArea.packStart(entry, false, false, 5);
		 entry.show();

		 if(dlg.run()==ResponseType.OK){
		 	auto newname = entry.getText.strip;
		 	if(newname.countchars(" \t\n\r")==0)
		 		col.setTitle(newname);
		 	else
		 		writeln("Unauthorized caracters in column name");
		 }

		 dlg.destroy();

	});

	return col;
}




class TwoDA{
	import std.regex;
	import std.file;

	this(string filepath){
		lastLine = 0;
		foreach(index, line ; readText(filepath).splitLines()){
			
			string data[]; 

			auto results = matchAll(line, rgxField);
			foreach(res ; results){
				string s;
				if(res[0][0]=='"')	data~= res[2];
				else				data~= res[1];
			}

			if(index==0){
				header = data;
				//writeln(header);
			}
			else{
				int nLine = data[0].to!int;
				values[nLine] = data[1..$];
				//writeln(values[nLine]);

				if(nLine > lastLine)
					lastLine = nLine;
			}

		}

	}


	string[] header;
	string[][uint] values;
	uint lastLine;
	enum rgxField = ctRegex!"(?:\\b([^\\s]+?)\\b|\"([^\"]+?)\")";
}

