/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


#include <iostream> 
#include <string>
#include <map>

#include "guiConfigureWorld.h"
#include "guiMessageMenu.h"
#include <IGUIButton.h>
#include <IGUICheckBox.h>
#include <IGUIListBox.h>
#include <IGUIStaticText.h>
#include <IGUITreeView.h>
#include "gettext.h"
#include "util/string.h"
#include "settings.h"
#include "filesys.h"

enum
{
	GUI_ID_MOD_TREEVIEW = 101,
	GUI_ID_ENABLED_CHECKBOX,
	GUI_ID_ENABLEALL,
	GUI_ID_DISABLEALL,
	GUI_ID_DEPENDS_LISTBOX,
	GUI_ID_RDEPENDS_LISTBOX,
	GUI_ID_CANCEL,
	GUI_ID_SAVE
};

#define QUESTIONMARK_STR L"?"
#define CHECKMARK_STR    L"\411"
#define CROSS_STR        L"\403"

GUIConfigureWorld::GUIConfigureWorld(gui::IGUIEnvironment* env,
		gui::IGUIElement* parent, s32 id,
		IMenuManager *menumgr, WorldSpec wspec):
	GUIModalMenu(env, parent, id, menumgr),
	m_wspec(wspec),
	m_gspec(findWorldSubgame(m_wspec.path)),
	m_menumgr(menumgr)
{
	//will be initialized in regenerateGUI()
	m_treeview=NULL;

	// game mods
	m_gamemods = flattenModTree(getModsInPath(m_gspec.gamemods_path));

	// world mods
	std::string worldmods_path = wspec.path + DIR_DELIM + "worldmods";
	m_worldmods = flattenModTree(getModsInPath(worldmods_path));

	// fill m_addontree with add-on mods
	std::set<std::string> paths = m_gspec.addon_mods_paths;
	for(std::set<std::string>::iterator it=paths.begin();
		it != paths.end(); ++it)
	{
		std::map<std::string,ModSpec> mods = getModsInPath(*it);
		m_addontree.insert(mods.begin(), mods.end());
	}

	// expand modpacks
	m_addonmods = flattenModTree(m_addontree);

	// collect reverse dependencies
	for(std::map<std::string, ModSpec>::iterator it = m_addonmods.begin();
		it != m_addonmods.end(); ++it)
	{
		std::string modname = (*it).first;
		ModSpec mod = (*it).second;
		for(std::set<std::string>::iterator dep_it = mod.depends.begin();
			dep_it != mod.depends.end(); ++dep_it)
		{
			m_reverse_depends.insert(std::make_pair((*dep_it),modname));
		}
	}

	m_settings.readConfigFile((m_wspec.path + DIR_DELIM + "world.mt").c_str());
	std::vector<std::string> names = m_settings.getNames();

	// mod_names contains the names of mods mentioned in the world.mt file
	std::set<std::string> mod_names;
	for(std::vector<std::string>::iterator it = names.begin(); 
		it != names.end(); ++it)
	{	
		std::string name = *it;  
		if (name.compare(0,9,"load_mod_")==0)
			mod_names.insert(name.substr(9));
	}

	// find new mods (installed but not mentioned in world.mt)
	for(std::map<std::string, ModSpec>::iterator it = m_addonmods.begin();
		it != m_addonmods.end(); ++it)
	{
		std::string modname = (*it).first;
		ModSpec mod = (*it).second;
		// a mod is new if it is not a modpack, and does not occur in
		// mod_names
		if(!mod.is_modpack &&
		   mod_names.count(modname) == 0)
			m_settings.setBool("load_mod_"+modname, false);
	}
	// find missing mods (mentioned in world.mt, but not installed)
	for(std::set<std::string>::iterator it = mod_names.begin();
		it != mod_names.end(); ++it)
	{
		std::string modname = *it;
		if(m_addonmods.count(modname) == 0)
			m_settings.remove("load_mod_"+modname);
	}
	std::string worldmtfile = m_wspec.path+DIR_DELIM+"world.mt";
	m_settings.updateConfigFile(worldmtfile.c_str());
}

void GUIConfigureWorld::drawMenu()
{
	gui::IGUISkin* skin = Environment->getSkin();
	if (!skin)
		return;
	video::IVideoDriver* driver = Environment->getVideoDriver();
	
	video::SColor bgcolor(140,0,0,0);
	driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);

	gui::IGUIElement::draw();
}


void GUIConfigureWorld::regenerateGui(v2u32 screensize)
{

	/*
		Remove stuff
	*/
	removeChildren();
	
	/*
		Calculate new sizes and positions
	*/
	core::rect<s32> rect(
			screensize.X/2 - 580/2,
			screensize.Y/2 - 300/2,
			screensize.X/2 + 580/2,
			screensize.Y/2 + 300/2
	);
	
	DesiredRect = rect;
	recalculateAbsolutePosition(false);

	v2s32 topleft = v2s32(10, 10);

	/*
		Add stuff
	*/
	changeCtype("");
	{
		core::rect<s32> rect(0, 0, 200, 20);
		rect += topleft;
		//proper text is set below, when a mod is selected
		m_modname_text = Environment->addStaticText(L"Mod: N/A", rect, false, 
													false, this, -1);
	}
	{
		core::rect<s32> rect(0, 0, 200, 20);
		rect += v2s32(0, 25) + topleft;
		wchar_t* text = wgettext("enabled");
		m_enabled_checkbox = 
			Environment->addCheckBox(false, rect, this, GUI_ID_ENABLED_CHECKBOX, 
									 text);
		delete[] text;
		m_enabled_checkbox->setVisible(false);
	}
	{
		core::rect<s32> rect(0, 0, 85, 30);
		rect = rect + v2s32(0, 25) + topleft;
		wchar_t* text = wgettext("Enable All");
		m_enableall = Environment->addButton(rect, this, GUI_ID_ENABLEALL,
											 text);
		delete[] text;
		m_enableall->setVisible(false);
	}
	{
		core::rect<s32> rect(0, 0, 85, 30);
		rect = rect + v2s32(115, 25) + topleft;
		wchar_t* text = wgettext("Disable All");
		m_disableall = Environment->addButton(rect, this, GUI_ID_DISABLEALL, text );
		delete[] text;
		m_disableall->setVisible(false);
	}
	{
		core::rect<s32> rect(0, 0, 200, 20);
		rect += v2s32(0, 60) + topleft;
		wchar_t* text = wgettext("depends on:");
		Environment->addStaticText(text, rect, false, false, this, -1);
		delete[] text;
	}
	{
		core::rect<s32> rect(0, 0, 200, 85);
		rect += v2s32(0, 80) + topleft;
		m_dependencies_listbox = 
			Environment->addListBox(rect, this, GUI_ID_DEPENDS_LISTBOX, true);
	}
	{
		core::rect<s32> rect(0, 0, 200, 20);
		rect += v2s32(0, 175) + topleft;
		wchar_t* text = wgettext("is required by:");
		Environment->addStaticText( text, rect, false, false, this, -1);
		delete[] text;
	}
	{
		core::rect<s32> rect(0, 0, 200, 85);
		rect += v2s32(0, 195) + topleft;
		m_rdependencies_listbox = 
			Environment->addListBox(rect,this, GUI_ID_RDEPENDS_LISTBOX,true);
	}
	{
		core::rect<s32> rect(0, 0, 340, 250);
		rect += v2s32(220, 0) + topleft;
		m_treeview = Environment->addTreeView(rect, this,
											  GUI_ID_MOD_TREEVIEW,true);
		gui::IGUITreeViewNode* node 
			= m_treeview->getRoot()->addChildBack(L"Add-Ons");
		buildTreeView(m_addontree, node);
	}
	{
		core::rect<s32> rect(0, 0, 120, 30);
		rect = rect + v2s32(330, 270) - topleft;
		wchar_t* text = wgettext("Cancel");
		Environment->addButton(rect, this, GUI_ID_CANCEL, text);
		delete[] text;
	}
	{
		core::rect<s32> rect(0, 0, 120, 30);
		rect = rect + v2s32(460, 270) - topleft;
		wchar_t* text = wgettext("Save");
		Environment->addButton(rect, this, GUI_ID_SAVE, text);
		delete[] text;
	}
	changeCtype("C");

	// at start, none of the treeview nodes is selected, so we select
	// the first element in the treeview of mods manually here.
	if(m_treeview->getRoot()->hasChilds())
	{
		m_treeview->getRoot()->getFirstChild()->setExpanded(true);
		m_treeview->getRoot()->getFirstChild()->setSelected(true);
		// Because a manual ->setSelected() doesn't cause an event, we
		// have to do this here:
		adjustSidebar();
	}
}

bool GUIConfigureWorld::OnEvent(const SEvent& event)
{

	gui::IGUITreeViewNode* selected_node = NULL;
	if(m_treeview != NULL)
		selected_node = m_treeview->getSelected();

	if(event.EventType==EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
	{
		switch (event.KeyInput.Key) {
		case KEY_ESCAPE: {
			quitMenu();
			return true;
		}
		//	irrlicht's built-in TreeView gui has no keyboard control,
		//	so we do it here: up/down to select prev/next node,
		//	left/right to collapse/expand nodes, space to toggle
		//	enabled/disabled.
		case KEY_DOWN: {
			if(selected_node != NULL)
			{
				gui::IGUITreeViewNode* node = selected_node->getNextVisible();
				if(node != NULL)
				{
					node->setSelected(true);
					adjustSidebar();
				}
			}
			return true;
		}
		case KEY_UP: {
			if(selected_node != NULL)
			{
				gui::IGUITreeViewNode* node = selected_node->getPrevSibling();
				if(node!=NULL)
				{
					node->setSelected(true);
					adjustSidebar();
				}
				else 
				{
					gui::IGUITreeViewNode* parent = selected_node->getParent();
					if(selected_node == parent->getFirstChild() &&
					   parent != m_treeview->getRoot()) 
					{
						parent->setSelected(true);
						adjustSidebar();
					}
				}
			}
			return true;
		}
		case KEY_RIGHT: {
			if(selected_node != NULL && selected_node->hasChilds())
				selected_node->setExpanded(true);
			return true;
		}
		case KEY_LEFT: {
			if(selected_node != NULL && selected_node->hasChilds())
				selected_node->setExpanded(false);
			return true;
		}
		case KEY_SPACE: {
			if(selected_node != NULL && !selected_node->hasChilds() && 
			   selected_node->getText() != NULL)
			{
				std::string modname = wide_to_narrow(selected_node->getText());
				bool checked = m_enabled_checkbox->isChecked();
				m_enabled_checkbox->setChecked(!checked);
				setEnabled(modname,!checked);
			}
			return true;
		}
		default: {}
		}
	}
	if(event.EventType==EET_GUI_EVENT)
	{
		if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST
				&& isVisible())
		{
			if(!canTakeFocus(event.GUIEvent.Element))
			{
				dstream<<"GUIConfigureWorld: Not allowing focus change."
						<<std::endl;
				// Returning true disables focus change
				return true;
			}
		}
		if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED){
			switch(event.GUIEvent.Caller->getID()){
			case GUI_ID_CANCEL: {
				quitMenu();
				return true;
			}
			case GUI_ID_SAVE: {
				std::string worldmtfile = m_wspec.path+DIR_DELIM+"world.mt";
				m_settings.updateConfigFile(worldmtfile.c_str());

				// The trailing spaces are because there seems to be a
				// bug in the text-size calculation. if the trailing
				// spaces are removed from the message text, the
				// message gets wrapped and parts of it are cut off:
				wchar_t* text = wgettext("Configuration saved.  ");
				GUIMessageMenu *menu = 
					new GUIMessageMenu(Environment, Parent, -1, m_menumgr, 
									 text );
				delete[] text;
				menu->drop();

				try
				{
					ModConfiguration modconf(m_wspec.path);
					if(!modconf.isConsistent())
					{
						wchar_t* text = wgettext("Warning: Configuration not consistent.  ");
						GUIMessageMenu *menu =
							new GUIMessageMenu(Environment, Parent, -1, m_menumgr, 
										 text );
						delete[] text;
						menu->drop();
					}
				}
				catch(ModError &err)
				{
					errorstream<<err.what()<<std::endl;
					std::wstring text = narrow_to_wide(err.what()) + wgettext("\nCheck debug.txt for details.");
					GUIMessageMenu *menu =
						new GUIMessageMenu(Environment, Parent, -1, m_menumgr, 
									 text );
					menu->drop();
				}

				quitMenu();	
				return true;
			}
			case GUI_ID_ENABLEALL: {
				if(selected_node != NULL && selected_node->getParent() == m_treeview->getRoot())
				{  
					enableAllMods(m_addonmods,true);
				} 
				else if(selected_node != NULL && selected_node->getText() != NULL)
				{  
					std::string modname = wide_to_narrow(selected_node->getText());
					std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname);
					if(mod_it != m_addonmods.end())
						enableAllMods(mod_it->second.modpack_content,true);
				}
				return true;
			}
			case GUI_ID_DISABLEALL: {
				if(selected_node != NULL && selected_node->getParent() == m_treeview->getRoot())
				{
					enableAllMods(m_addonmods,false);
				} 
				if(selected_node != NULL && selected_node->getText() != NULL)
				{
					std::string modname = wide_to_narrow(selected_node->getText());
					std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname);
					if(mod_it != m_addonmods.end())
						enableAllMods(mod_it->second.modpack_content,false);
				}
				return true;
			}
			}
		}	
		if(event.GUIEvent.EventType==gui::EGET_CHECKBOX_CHANGED &&
			event.GUIEvent.Caller->getID() == GUI_ID_ENABLED_CHECKBOX)
		{
			if(selected_node != NULL && !selected_node->hasChilds() && 
			   selected_node->getText() != NULL)
			{
				std::string modname = wide_to_narrow(selected_node->getText());
				setEnabled(modname, m_enabled_checkbox->isChecked());
			}
			return true;
		}
		if(event.GUIEvent.EventType==gui::EGET_TREEVIEW_NODE_SELECT &&
		   event.GUIEvent.Caller->getID() == GUI_ID_MOD_TREEVIEW)
		{
			selecting_dep = -1;
			selecting_rdep = -1;
			adjustSidebar();
			return true;
		}
		if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED &&
		   event.GUIEvent.Caller->getID() == GUI_ID_DEPENDS_LISTBOX)
		{
			selecting_dep = m_dependencies_listbox->getSelected();
			selecting_rdep = -1;
			return true;
		}
		if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED &&
		   event.GUIEvent.Caller->getID() == GUI_ID_RDEPENDS_LISTBOX)
		{
			selecting_dep = -1;
			selecting_rdep = m_rdependencies_listbox->getSelected();
			return true;
		}

		//double click in a dependency listbox: find corresponding
		//treeviewnode and select it:
		if(event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN)
		{
			gui::IGUIListBox* box = NULL;
			if(event.GUIEvent.Caller->getID() == GUI_ID_DEPENDS_LISTBOX)
			{
				box = m_dependencies_listbox;
				if(box->getSelected() != selecting_dep)
					return true;
			}
			if(event.GUIEvent.Caller->getID() == GUI_ID_RDEPENDS_LISTBOX)
			{
				box = m_rdependencies_listbox;
				if(box->getSelected() != selecting_rdep)
					return true;
			}
			if(box != NULL && box->getSelected() != -1 &&
			   box->getListItem(box->getSelected()) != NULL)
			{
				std::string modname = 
					wide_to_narrow(box->getListItem(box->getSelected()));
				std::map<std::string, gui::IGUITreeViewNode*>::iterator it =
					m_nodes.find(modname);
				if(it != m_nodes.end())
				{
					// select node and make sure node is visible by
					// expanding all parents
					gui::IGUITreeViewNode* node = (*it).second;
					node->setSelected(true);
					while(!node->isVisible() && 
						  node->getParent() != m_treeview->getRoot())
					{
						node = node->getParent();
						node->setExpanded(true);
					}
					adjustSidebar();
				}
			}
			return true;
		}
	}

	return Parent ? Parent->OnEvent(event) : false;
}

void GUIConfigureWorld::buildTreeView(std::map<std::string, ModSpec> mods, 
									  gui::IGUITreeViewNode* node)
{
	for(std::map<std::string,ModSpec>::iterator it = mods.begin();
		it != mods.end(); ++it)    
	{
		std::string modname = (*it).first;
		ModSpec mod = (*it).second;
		gui::IGUITreeViewNode* new_node = 
			node->addChildBack(narrow_to_wide(modname).c_str());
		m_nodes.insert(std::make_pair(modname, new_node));
		if(mod.is_modpack)
			buildTreeView(mod.modpack_content, new_node);
		else
		{
			// set icon for node: x for disabled mods, checkmark for enabled mods
			bool mod_enabled = false;
			if(m_settings.exists("load_mod_"+modname))
				mod_enabled = m_settings.getBool("load_mod_"+modname);
			if(mod_enabled)
				new_node->setIcon(CHECKMARK_STR);
			else
				new_node->setIcon(CROSS_STR);
		}
	}
}


void GUIConfigureWorld::adjustSidebar()
{
	gui::IGUITreeViewNode* node = m_treeview->getSelected();
	std::wstring modname_w;
	if(node->getText() != NULL)
		modname_w = node->getText();
	else 
		modname_w = L"N/A";
	std::string modname = wide_to_narrow(modname_w);

	ModSpec mspec;
	std::map<std::string, ModSpec>::iterator it = m_addonmods.find(modname);
	if(it != m_addonmods.end())
		mspec = it->second;

	m_dependencies_listbox->clear();
	m_rdependencies_listbox->clear();

	// if no mods installed, there is nothing to enable/disable, so we
	// don't show buttons or checkbox on the sidebar
	if(node->getParent() == m_treeview->getRoot() && !node->hasChilds())
	{
		m_disableall->setVisible(false);
		m_enableall->setVisible(false);
		m_enabled_checkbox->setVisible(false);
		return;
	} 
	
    // a modpack is not enabled/disabled by itself, only its cotnents
    // are. so we show show enable/disable all buttons, but hide the
    // checkbox
	if(node->getParent() == m_treeview->getRoot() ||
	   mspec.is_modpack)
	{
		m_enabled_checkbox->setVisible(false);
		m_disableall->setVisible(true);
		m_enableall->setVisible(true);
		m_modname_text->setText((L"Modpack: "+modname_w).c_str());
		return;
	}	

	// for a normal mod, we hide the enable/disable all buttons, but show the checkbox.
	m_disableall->setVisible(false);
	m_enableall->setVisible(false);
	m_enabled_checkbox->setVisible(true);
	m_modname_text->setText((L"Mod: "+modname_w).c_str());

	// the mod is enabled unless it is disabled in the world.mt settings. 
	bool mod_enabled = true;
	if(m_settings.exists("load_mod_"+modname))
		mod_enabled = m_settings.getBool("load_mod_"+modname);
	m_enabled_checkbox->setChecked(mod_enabled);

	for(std::set<std::string>::iterator it=mspec.depends.begin();
		it != mspec.depends.end(); ++it)
	{
		// check if it is an add-on mod or a game/world mod. We only
		// want to show add-ons
		std::string dependency = (*it);
		if(m_gamemods.count(dependency) > 0)
			dependency += " (" + m_gspec.id + ")";
		else if(m_worldmods.count(dependency) > 0)
			dependency += " (" + m_wspec.name + ")";
		else if(m_addonmods.count(dependency) == 0)
			dependency += " (missing)";
		m_dependencies_listbox->addItem(narrow_to_wide(dependency).c_str());
	}

	// reverse dependencies of this mod:
	std::pair< std::multimap<std::string, std::string>::iterator, 
			std::multimap<std::string, std::string>::iterator > rdep = 
		m_reverse_depends.equal_range(modname);
	for(std::multimap<std::string,std::string>::iterator it = rdep.first;
		it != rdep.second; ++it)
	{
		// check if it is an add-on mod or a game/world mod. We only
		// want to show add-ons
		std::string rdependency = (*it).second;
		if(m_addonmods.count(rdependency) > 0)
			m_rdependencies_listbox->addItem(narrow_to_wide(rdependency).c_str());
	}
}

void GUIConfigureWorld::enableAllMods(std::map<std::string, ModSpec> mods,bool enable)
{
	for(std::map<std::string, ModSpec>::iterator it = mods.begin();
		it != mods.end(); ++it)
	{
		ModSpec mod = (*it).second;
		if(mod.is_modpack) 
			// a modpack, recursively enable all mods in it
			enableAllMods(mod.modpack_content,enable);
		else // not a modpack
			setEnabled(mod.name, enable);

	}
}

void GUIConfigureWorld::enableMod(std::string modname)
{
	std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname);
	if(mod_it == m_addonmods.end()){
		errorstream << "enableMod() called with invalid mod name \"" << modname << "\"" << std::endl;
		return;
	}
	ModSpec mspec = mod_it->second;
	m_settings.setBool("load_mod_"+modname,true);
	std::map<std::string,gui::IGUITreeViewNode*>::iterator it = 
		m_nodes.find(modname);
	if(it != m_nodes.end())
		(*it).second->setIcon(CHECKMARK_STR);
	//also enable all dependencies
	for(std::set<std::string>::iterator it=mspec.depends.begin();
		it != mspec.depends.end(); ++it)
	{
		std::string dependency = *it;
		// only enable it if it is an add-on mod
		if(m_addonmods.count(dependency) > 0)
			enableMod(dependency);
	}
}

void GUIConfigureWorld::disableMod(std::string modname)
{
	std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname);
	if(mod_it == m_addonmods.end()){
		errorstream << "disableMod() called with invalid mod name \"" << modname << "\"" << std::endl;
		return;
	}

	m_settings.setBool("load_mod_"+modname,false);
	std::map<std::string,gui::IGUITreeViewNode*>::iterator it = 
		m_nodes.find(modname);
 	if(it != m_nodes.end())
		(*it).second->setIcon(CROSS_STR);
	//also disable all mods that depend on this one
	std::pair<std::multimap<std::string, std::string>::iterator, 
			  std::multimap<std::string, std::string>::iterator > rdep = 
		m_reverse_depends.equal_range(modname);
	for(std::multimap<std::string,std::string>::iterator it = rdep.first;
		it != rdep.second; ++it)
	{
		std::string rdependency = (*it).second;
		// only disable it if it is an add-on mod
		if(m_addonmods.count(rdependency) > 0)
			disableMod(rdependency);
	}	
}

