#include "FileZilla.h"
#include "Options.h"
#include "../tinyxml/tinyxml.h"
#include "xmlfunctions.h"
#include "filezillaapp.h"
#include <wx/tokenzr.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

COptions* COptions::m_theOptions = 0;

enum Type
{
	string,
	number
};

struct t_Option
{
	char name[30];
	enum Type type;
	wxString defaultValue; // Default values are stored as string even for numerical options
};

static const t_Option options[OPTIONS_NUM] =
{
	// Engine settings
	{ "Use Pasv mode", number, _T("1") },
	{ "Limit local ports", number, _T("0") },
	{ "Limit ports low", number, _T("6000") },
	{ "Limit ports high", number, _T("7000") },
	{ "External IP mode", number, _T("0") },
	{ "External IP", string, _T("") },
	{ "External address resolver", string, _T("http://ip.filezilla-project.org/ip.php") },
	{ "Last resolved IP", string, _T("") },
	{ "No external ip on local conn", number, _T("1") },
	{ "Pasv reply fallback mode", number, _T("0") },
	{ "Timeout", number, _T("15") },
	{ "Logging Debug Level", number, _T("0") },
	{ "Logging Raw Listing", number, _T("0") },
	{ "fzsftp executable", string, _T("") },
	{ "Allow transfermode fallback", number, _T("1") },
	{ "Reconnect count", number, _T("2") },
	{ "Reconnect delay", number, _T("5") },
	{ "Speedlimit inbound", number, _T("0") },
	{ "Speedlimit outbound", number, _T("0") },
	{ "Speedlimit burst tolerance", number, _T("0") },
	{ "View hidden files", number, _T("0") },

	// Interface settings
	{ "Number of Transfers", number, _T("2") },
	{ "Ascii Binary mode", number, _T("0") },
	{ "Auto Ascii files", string, _T("am|asp|bat|c|cfm|cgi|conf|cpp|css|dhtml|diz|h|hpp|htm|html|in|inc|js|m4|mak|nfo|nsi|pas|patch|php|phtml|pl|po|py|qmail|sh|shtml|sql|tcl|tpl|txt|vbs|xml|xrc") },
	{ "Auto Ascii no extension", number, _T("1") },
	{ "Auto Ascii dotfiles", number, _T("1") },
	{ "Theme", string, _T("") },
	{ "Language", string, _T("") },
	{ "Last Server Path", string, _T("") },
	{ "Max Concurrent Uploads", number, _T("0") },
	{ "Max Concurrent Downloads", number, _T("0") },
	{ "Update Check", number, _T("1") },
	{ "Update Check Interval", number, _T("7") },
	{ "Last automatic update check", string, _T("") },
	{ "Update Check New Version", string, _T("") },
	{ "Update Check Package URL", string, _T("") },
	{ "Show debug menu", number, _T("0") },
	{ "File exists action download", number, _T("0") },
	{ "File exists action upload", number, _T("0") },
	{ "Allow ascii resume", number, _T("0") },
	{ "Greeting version", string, _T("") },
	{ "Onetime Dialogs", string, _T("") },
	{ "Show Tree Local", number, _T("1") },
	{ "Show Tree Remote", number, _T("1") },
	{ "File Pane Layout", number, _T("0") },
	{ "File Pane Swap", number, _T("0") },
	{ "Last local directory", string, _T("") },
	{ "Filelist directory sort", number, _T("0") },
	{ "Queue successful autoclear", number, _T("0") },
	{ "Queue column widths", string, _T("") },
	{ "Local filelist colwidths", string, _T("") },
	{ "Remote filelist colwidths", string, _T("") },
	{ "Window position and size", string, _T("") },
	{ "Local filelist sortorder", string, _T("") },
	{ "Remote filelist sortorder", string, _T("") },
	{ "Date format", string, _T("") },
	{ "Time format", string, _T("") },
	{ "Show message log", number, _T("1") },
	{ "Show queue", number, _T("1") },
	{ "Size format", number, _T("0") },
	{ "Size thousands separator", number, _T("1") }
};

COptions::COptions()
{
	m_pLastServer = 0;

	m_acquired = false;

	for (unsigned int i = 0; i < OPTIONS_NUM; i++)
		m_optionsCache[i].cached = false;

	m_pXmlFile = new CXmlFile(_T("filezilla"));
	if (!m_pXmlFile->Load())
	{
		wxString msg = wxString::Format(_("Could not load \"%s\", make sure the file is valid.\nFor this session, default settings will be used and any changes to the settings are not persistent."), m_pXmlFile->GetFileName().GetFullPath().c_str());
		wxMessageBox(msg, _("Error loading xml file"), wxICON_ERROR);
		delete m_pXmlFile;
		m_pXmlFile = 0;
	}
	else
		CreateSettingsXmlElement();
}

COptions::~COptions()
{
	delete m_pLastServer;
	delete m_pXmlFile;
}

int COptions::GetOptionVal(unsigned int nID)
{
	if (nID >= OPTIONS_NUM)
		return 0;

	if (options[nID].type != number)
		return 0;

	if (m_optionsCache[nID].cached)
		return m_optionsCache[nID].numValue;

	wxString value;
	long numValue = 0;
	if (!GetXmlValue(nID, value))
		options[nID].defaultValue.ToLong(&numValue);
	else
	{
		value.ToLong(&numValue);
		numValue = Validate(nID, numValue);
	}

	m_optionsCache[nID].numValue = numValue;
	m_optionsCache[nID].cached = true;

	return numValue;
}

wxString COptions::GetOption(unsigned int nID)
{
	if (nID >= OPTIONS_NUM)
		return _T("");

	if (options[nID].type != string)
		return wxString::Format(_T("%d"), GetOptionVal(nID));

	if (m_optionsCache[nID].cached)
		return m_optionsCache[nID].strValue;

	wxString value;
	if (!GetXmlValue(nID, value))
		value = options[nID].defaultValue;
	else
		Validate(nID, value);

	m_optionsCache[nID].strValue = value;
	m_optionsCache[nID].cached = true;

	return value;
}

bool COptions::SetOption(unsigned int nID, int value)
{
	if (!SetOptionNoSave(nID, value))
		return false;

	if (m_pXmlFile)
		m_pXmlFile->Save();

	return true;
}

bool COptions::SetOptionNoSave(unsigned int nID, int value)
{
	if (nID >= OPTIONS_NUM)
		return false;

	if (options[nID].type != number)
		return false;

	value = Validate(nID, value);

	m_optionsCache[nID].cached = true;
	m_optionsCache[nID].numValue = value;

	if (m_pXmlFile)
		SetXmlValue(nID, wxString::Format(_T("%d"), value));

	return true;
}

bool COptions::SetOption(unsigned int nID, wxString value)
{
	if (!SetOptionNoSave(nID, value))
		return false;
	
	if (m_pXmlFile)
		m_pXmlFile->Save();

	return true;
}

bool COptions::SetOptionNoSave(unsigned int nID, wxString value)
{
	if (nID >= OPTIONS_NUM)
		return false;

	if (options[nID].type != string)
	{
		long tmp;
		if (!value.ToLong(&tmp))
			return false;

		return SetOption(nID, tmp);
	}

	Validate(nID, value);

	m_optionsCache[nID].cached = true;
	m_optionsCache[nID].strValue = value;

	if (m_pXmlFile)
		SetXmlValue(nID, value);


	return true;
}

void COptions::CreateSettingsXmlElement()
{
	if (!m_pXmlFile)
		return;

	TiXmlElement *element = m_pXmlFile->GetElement();
	if (element->FirstChildElement("Settings"))
		return;

	TiXmlElement settings("Settings");
	element->InsertEndChild(settings);

	for (int i = 0; i < OPTIONS_NUM; i++)
	{
		m_optionsCache[i].cached = true;
		if (options[i].type == string)
			m_optionsCache[i].strValue = options[i].defaultValue;
		else
		{
			long numValue = 0;
			options[i].defaultValue.ToLong(&numValue);
			m_optionsCache[i].numValue = numValue;
		}
		SetXmlValue(i, options[i].defaultValue);
	}

	m_pXmlFile->Save();
}

void COptions::SetXmlValue(unsigned int nID, wxString value)
{
	if (!m_pXmlFile)
		return;

	// No checks are made about the validity of the value, that's done in SetOption

	char *utf8 = ConvUTF8(value);
	if (!utf8)
		return;

	TiXmlElement *settings = m_pXmlFile->GetElement()->FirstChildElement("Settings");
	if (!settings)
	{
		TiXmlNode *node = m_pXmlFile->GetElement()->InsertEndChild(TiXmlElement("Settings"));
		if (!node)
		{
			delete [] utf8;
			return;
		}
		settings = node->ToElement();
		if (!settings)
		{
			delete [] utf8;
			return;
		}
	}
	else
	{
		TiXmlNode *node = 0;
		while ((node = settings->IterateChildren("Setting", node)))
		{
			TiXmlElement *setting = node->ToElement();
			if (!setting)
				continue;

			const char *attribute = setting->Attribute("name");
			if (!attribute)
				continue;
			if (strcmp(attribute, options[nID].name))
				continue;

			setting->RemoveAttribute("type");
			setting->Clear();
			setting->SetAttribute("type", (options[nID].type == string) ? "string" : "number");
			setting->InsertEndChild(TiXmlText(utf8));

			delete [] utf8;
			return;
		}
	}
	TiXmlElement setting("Setting");
	setting.SetAttribute("name", options[nID].name);
	setting.SetAttribute("type", (options[nID].type == string) ? "string" : "number");
	setting.InsertEndChild(TiXmlText(utf8));
	settings->InsertEndChild(setting);

	delete [] utf8;
}

bool COptions::GetXmlValue(unsigned int nID, wxString &value, TiXmlElement *settings /*=0*/)
{
	if (!settings)
	{
		if (!m_pXmlFile)
			return false;

		settings = m_pXmlFile->GetElement()->FirstChildElement("Settings");
		if (!settings)
		{
			TiXmlNode *node = m_pXmlFile->GetElement()->InsertEndChild(TiXmlElement("Settings"));
			if (!node)
				return false;
			settings = node->ToElement();
			if (!settings)
				return false;
		}
	}

	TiXmlNode *node = 0;
	while ((node = settings->IterateChildren("Setting", node)))
	{
		TiXmlElement *setting = node->ToElement();
		if (!setting)
			continue;

		const char *attribute = setting->Attribute("name");
		if (!attribute)
			continue;
		if (strcmp(attribute, options[nID].name))
			continue;

		TiXmlNode *text = setting->FirstChild();
		if (!text || !text->ToText())
			return false;

		value = ConvLocal(text->Value());

		return true;
	}

	return false;
}

int COptions::Validate(unsigned int nID, int value)
{
	switch (nID)
	{
	case OPTION_UPDATECHECK_INTERVAL:
		if (value < 7 || value > 9999)
			value = 7;
		break;
	case OPTION_LOGGING_DEBUGLEVEL:
		if (value < 0 || value > 4)
			value = 0;
		break;
	case OPTION_RECONNECTCOUNT:
		if (value < 0 || value > 99)
			value = 5;
		break;
	case OPTION_RECONNECTDELAY:
		if (value < 0 || value > 999)
			value = 5;
		break;
	case OPTION_FILEPANE_LAYOUT:
		if (value < 0 || value > 3)
			value = 0;
		break;
	case OPTION_SPEEDLIMIT_INBOUND:
	case OPTION_SPEEDLIMIT_OUTBOUND:
		if (value < 0)
			value = 0;
		break;
	case OPTION_SPEEDLIMIT_BURSTTOLERANCE:
		if (value < 0 || value > 2)
			value = 0;
		break;
	case OPTION_FILELIST_DIRSORT:
		if (value < 0 || value > 2)
			value = 0;
		break;
	}
	return value;
}

wxString COptions::Validate(unsigned int nID, wxString value)
{
	return value;
}

void COptions::SetServer(wxString path, const CServer& server)
{
	if (!m_pXmlFile)
		return;

	if (path == _T(""))
		return;

	TiXmlElement *element = m_pXmlFile->GetElement();
	
	while (path != _T(""))
	{
		wxString sub;
		int pos = path.Find('/');
		if (pos != -1)
		{
			sub = path.Left(pos);
			path = path.Mid(pos + 1);
		}
		else
		{
			sub = path;
			path = _T("");
		}
		char *utf8 = ConvUTF8(sub);
		if (!utf8)
			return;
		TiXmlElement *newElement = element->FirstChildElement(utf8);
		delete [] utf8;
		if (newElement)
			element = newElement;
		else
		{
			char *utf8 = ConvUTF8(sub);
			if (!utf8)
				return;
			TiXmlNode *node = element->InsertEndChild(TiXmlElement(utf8));
			delete [] utf8;
			if (!node || !node->ToElement())
				return;
			element = node->ToElement();
		}
	}

	::SetServer(element, server);

	m_pXmlFile->Save();
}

bool COptions::GetServer(wxString path, CServer& server)
{
	if (path == _T(""))
		return false;

	if (!m_pXmlFile)
		return false;
	TiXmlElement *element = m_pXmlFile->GetElement();

	while (path != _T(""))
	{
		wxString sub;
		int pos = path.Find('/');
		if (pos != -1)
		{
			sub = path.Left(pos);
			path = path.Mid(pos + 1);
		}
		else
		{
			sub = path;
			path = _T("");
		}
		char *utf8 = ConvUTF8(sub);
		if (!utf8)
			return false;
		element = element->FirstChildElement(utf8);
		delete [] utf8;
		if (!element)
			return false;
	}

	bool res = ::GetServer(element, server);

	return res;
}

void COptions::SetLastServer(const CServer& server)
{
	if (!m_pLastServer)
		m_pLastServer = new CServer(server);
	else
		*m_pLastServer = server;
	SetServer(_T("Settings/LastServer"), server);
}

bool COptions::GetLastServer(CServer& server)
{
	if (!m_pLastServer)
	{
		bool res = GetServer(_T("Settings/LastServer"), server);
		if (res)
			m_pLastServer = new CServer(server);
		return res;
	}
	else
	{
		server = *m_pLastServer;
		if (server == CServer())
			return false;

		return true;
	}
}

void COptions::Init()
{
	if (!m_theOptions)
		m_theOptions = new COptions();
}

void COptions::Destroy()
{
	if (!m_theOptions)
		return;

	delete m_theOptions;
	m_theOptions = 0;
}

COptions* COptions::Get()
{
	return m_theOptions;
}

void COptions::Import(TiXmlElement* pElement)
{
	for (int i = 0; i < OPTIONS_NUM; i++)
	{
		wxString value;
		if (!GetXmlValue(i, value, pElement))
			continue;

		SetOptionNoSave(i, value);
	}

	if (m_pXmlFile)
		m_pXmlFile->Save();
}

void COptions::SaveColumnWidths(const wxListCtrl* const pListCtrl, unsigned int optionId)
{
	wxCHECK_RET(pListCtrl, _T("SaveColumnWidths called with !pListCtrl"));

	wxString widths;
	for (int i = 0; i < pListCtrl->GetColumnCount(); i++)
		widths += wxString::Format(_T("%d "), pListCtrl->GetColumnWidth(i));
	widths.RemoveLast();

	SetOption(optionId, widths);
}

bool COptions::ReadColumnWidths(unsigned int optionId, unsigned int count, unsigned long* widths)
{
	wxString savedWidths = GetOption(optionId);
	wxStringTokenizer tokens(savedWidths, _T(" "));
	if (tokens.CountTokens() != count)
		return false;

	unsigned long* newWidths = new unsigned long[count];
	for (unsigned int i = 0; i < count; i++)
	{
		wxString token = tokens.GetNextToken();
		if (!token.ToULong(newWidths + i) || newWidths[i] > 5000)
		{
			delete [] newWidths;
			return false;
		}
	}

	for (unsigned int i = 0; i < count; i++)
		widths[i] = newWidths[i];

	delete [] newWidths;
	return true;
}


syntax highlighted by Code2HTML, v. 0.9.1