#include "FileZilla.h"
#include "xmlfunctions.h"
#include "filezillaapp.h"

CXmlFile::CXmlFile(const wxString& fileName)
{
	m_pPrinter = 0;
	SetFileName(fileName);
	m_pDocument = 0;
}

CXmlFile::CXmlFile(const wxFileName& fileName /*=wxFileName()*/)
{
	m_pPrinter = 0;
	SetFileName(fileName);
	m_pDocument = 0;
}

void CXmlFile::SetFileName(const wxString& name)
{
	m_fileName = wxFileName(wxGetApp().GetSettingsDir(), name + _T(".xml"));
	m_modificationTime = wxDateTime();
}

void CXmlFile::SetFileName(const wxFileName& fileName)
{
	m_fileName = fileName;
	m_modificationTime = wxDateTime();
}

CXmlFile::~CXmlFile()
{
	delete m_pPrinter;
	delete m_pDocument;
}

TiXmlElement* CXmlFile::Load(const wxString& name)
{
	wxFileName fileName(wxGetApp().GetSettingsDir(), name + _T(".xml"));
	return Load(fileName);
}

TiXmlElement* CXmlFile::Load(const wxFileName& fileName)
{
	if (fileName.IsOk())
		SetFileName(fileName);

	wxCHECK(m_fileName.IsOk(), 0);

	delete m_pDocument;
	m_pDocument = 0;

	TiXmlElement* pElement = GetXmlFile(m_fileName);
	if (!pElement)
	{
		m_modificationTime = wxDateTime();
		return 0;
	}

	{
		wxLogNull log;
		m_modificationTime = m_fileName.GetModificationTime();
	}

	m_pDocument = pElement->GetDocument();
	return pElement;
}

TiXmlElement* CXmlFile::GetElement()
{
	if (!m_pDocument)
		return 0;

	TiXmlElement* pElement = m_pDocument->FirstChildElement("FileZilla3");
	if (!pElement)
	{
		delete m_pDocument;
		m_pDocument = 0;
		return 0;
	}
	else
		return pElement;
}

const TiXmlElement* CXmlFile::GetElement() const
{
	if (!m_pDocument)
		return 0;

	const TiXmlElement* pElement = m_pDocument->FirstChildElement("FileZilla3");
	return pElement;
}

bool CXmlFile::Modified()
{
	wxCHECK(m_fileName.IsOk(), false);

	if (!m_modificationTime.IsValid())
		return false;

	wxLogNull log;
	wxDateTime modificationTime;
	if (!m_fileName.FileExists())
		return true;

	modificationTime = m_fileName.GetModificationTime();
	if (modificationTime.IsValid() && modificationTime == m_modificationTime)
		return false;

	return true;
}

void CXmlFile::Close()
{
	delete m_pDocument;
	m_pDocument = 0;
}

bool CXmlFile::Save(wxString* error)
{
	wxCHECK(m_fileName.IsOk(), false);

	wxCHECK(m_pDocument, false);

	bool res = SaveXmlFile(m_fileName, GetElement(), error);
	wxLogNull log;
	m_modificationTime = m_fileName.GetModificationTime();
	return res;
}

TiXmlElement* CXmlFile::CreateEmpty()
{
	delete m_pDocument;

	m_pDocument = new TiXmlDocument();
	m_pDocument->SetCondenseWhiteSpace(false);
	m_pDocument->InsertEndChild(TiXmlDeclaration("1.0", "UTF-8", "yes"));

	return m_pDocument->InsertEndChild(TiXmlElement("FileZilla3"))->ToElement();
}

char* ConvUTF8(const wxString& value)
{
	// First convert the string into unicode if neccessary.
	const wxWCharBuffer buffer = wxConvCurrent->cWX2WC(value);

	// Calculate utf-8 string length
	wxMBConvUTF8 conv;
	int len = conv.WC2MB(0, buffer, 0);

	// Not convert the string
	char *utf8 = new char[len + 1];
	conv.WC2MB(utf8, buffer, len + 1);

	return utf8;
}

wxString ConvLocal(const char *value)
{
	return wxString(wxConvUTF8.cMB2WC(value), *wxConvCurrent);
}

void AddTextElement(TiXmlElement* node, const char* name, const wxString& value)
{
	wxASSERT(node);

	TiXmlElement element(name);

	char* utf8 = ConvUTF8(value);
	if (!utf8)
		return;
	
    element.InsertEndChild(TiXmlText(utf8));
	delete [] utf8;

	node->InsertEndChild(element);
}

void AddTextElement(TiXmlElement* node, const char* name, int value)
{
	AddTextElement(node, name, wxString::Format(_T("%d"), value));
}

void AddTextElement(TiXmlElement* node, const wxString& value)
{
	wxASSERT(node);

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

	for (TiXmlNode* pChild = node->FirstChild(); pChild; pChild = pChild->NextSibling())
	{
		if (!pChild->ToText())
			continue;

		node->RemoveChild(pChild);
		break;
	}
	
    node->InsertEndChild(TiXmlText(utf8));
	delete [] utf8;
}

void AddTextElement(TiXmlElement* node, int value)
{
	AddTextElement(node, wxString::Format(_T("%d"), value));
}

wxString GetTextElement(TiXmlElement* node, const char* name)
{
	wxASSERT(node);

	TiXmlElement* element = node->FirstChildElement(name);
	if (!element)
		return _T("");

	TiXmlNode* textNode = element->FirstChild();
	if (!textNode || !textNode->ToText())
		return _T("");

	return ConvLocal(textNode->Value());
}

wxString GetTextElement(TiXmlElement* node)
{
	wxASSERT(node);

	for (TiXmlNode* pChild = node->FirstChild(); pChild; pChild = pChild->NextSibling())
	{
		if (!pChild->ToText())
			continue;

		return ConvLocal(pChild->Value());
	}

	return _T("");
}

int GetTextElementInt(TiXmlElement* node, const char* name, int defValue /*=0*/)
{
	wxASSERT(node);

	TiXmlElement* element = node->FirstChildElement(name);
	if (!element)
		return defValue;

	TiXmlNode* textNode = element->FirstChild();
	if (!textNode || !textNode->ToText())
		return defValue;

	const char* str = textNode->Value();
	const char* p = str;

	int value = 0;
	bool negative = false;
	if (*p == '-')
	{
		negative = true;
		p++;
	}
	while (*p)
	{
		if (*p < '0' || *p > '9')
			return defValue;

		value *= 10;
		value += *p - '0';

		p++;
	}

	return negative ? -value : value;
}

wxLongLong GetTextElementLongLong(TiXmlElement* node, const char* name, int defValue /*=0*/)
{
	wxASSERT(node);

	TiXmlElement* element = node->FirstChildElement(name);
	if (!element)
		return defValue;

	TiXmlNode* textNode = element->FirstChild();
	if (!textNode || !textNode->ToText())
		return defValue;

	const char* str = textNode->Value();
	const char* p = str;

	wxLongLong value = 0;
	bool negative = false;
	if (*p == '-')
	{
		negative = true;
		p++;
	}
	while (*p)
	{
		if (*p < '0' || *p > '9')
			return defValue;

		value *= 10;
		value += *p - '0';

		p++;
	}

	return negative ? -value : value;
}

// Opens the specified XML file if it exists or creates a new one otherwise.
// Returns 0 on error.
TiXmlElement* GetXmlFile(wxFileName file)
{
	if (wxFileExists(file.GetFullPath()) && file.GetSize() > 0)
	{
		// File does exist, open it

		TiXmlDocument* pXmlDocument = new TiXmlDocument();
		pXmlDocument->SetCondenseWhiteSpace(false);
		if (!pXmlDocument->LoadFile(file.GetFullPath().mb_str()))
		{
			delete pXmlDocument;
			return 0;
		}
		if (!pXmlDocument->FirstChildElement("FileZilla3"))
		{
			delete pXmlDocument;
			return 0;
		}

		TiXmlElement* pElement = pXmlDocument->FirstChildElement("FileZilla3");
		if (!pElement)
		{
			delete pXmlDocument;
			return 0;
		}
		else
			return pElement;
	}
	else
	{
		// File does not exist, create new XML document

		TiXmlDocument* pXmlDocument = new TiXmlDocument();
		pXmlDocument->SetCondenseWhiteSpace(false);
		pXmlDocument->InsertEndChild(TiXmlDeclaration("1.0", "UTF-8", "yes"));
	
		pXmlDocument->InsertEndChild(TiXmlElement("FileZilla3"));

		if (!pXmlDocument->SaveFile(file.GetFullPath().mb_str()))
		{
			delete pXmlDocument;
			return 0;
		}
		
		return pXmlDocument->FirstChildElement("FileZilla3");
	}
}

bool SaveXmlFile(const wxFileName& file, TiXmlNode* node, wxString* error /*=0*/)
{
	if (!node)
		return true;

	const wxString& fullPath = file.GetFullPath();

	TiXmlDocument* pDocument = node->GetDocument();

	bool exists = false;
	if (wxFileExists(fullPath))
	{
		exists = true;
		if (!wxCopyFile(fullPath, fullPath + _T("~")))
		{
			const wxString msg = _("Failed to create backup copy of xml file");
			if (error)
				*error = msg;
			else
				wxMessageBox(msg);
			return false;
		}
	}

	if (!pDocument->SaveFile(fullPath.mb_str()))
	{
		wxRemoveFile(fullPath);
		if (exists)
			wxRenameFile(fullPath + _T("~"), fullPath);
		const wxString msg = _("Failed to write xml file");
		if (error)
			*error = msg;
		else
			wxMessageBox(msg);
		return false;
	}

	if (exists)
		wxRemoveFile(fullPath + _T("~"));

	return true;
}

bool GetServer(TiXmlElement *node, CServer& server)
{
	wxASSERT(node);
	
	wxString host = GetTextElement(node, "Host");
	if (host == _T(""))
		return false;

	int port = GetTextElementInt(node, "Port");
	if (port < 1 || port > 65535)
		return false;

	if (!server.SetHost(host, port))
		return false;
	
	int protocol = GetTextElementInt(node, "Protocol");
	if (protocol < 0)
		return false;

	server.SetProtocol((enum ServerProtocol)protocol);
	
	int type = GetTextElementInt(node, "Type");
	if (type < 0)
		return false;

	server.SetType((enum ServerType)type);

	int logonType = GetTextElementInt(node, "Logontype");
	if (logonType < 0)
		return false;

	server.SetLogonType((enum LogonType)logonType);
	
	if (server.GetLogonType() != ANONYMOUS)
	{
		wxString user = GetTextElement(node, "User");
		if (user == _T(""))
			return false;
	
		wxString pass;
		if ((long)NORMAL == logonType || (long)ACCOUNT == logonType)
			pass = GetTextElement(node, "Pass");

		if (!server.SetUser(user, pass))
			return false;
		
		if ((long)ACCOUNT == logonType)
		{
			wxString account = GetTextElement(node, "Account");
			if (account == _T(""))
				return false;
			if (!server.SetAccount(account))
				return false;
		}
	}

	int timezoneOffset = GetTextElementInt(node, "TimezoneOffset");
	if (!server.SetTimezoneOffset(timezoneOffset))
		return false;
	
	wxString pasvMode = GetTextElement(node, "PasvMode");
	if (pasvMode == _T("MODE_PASSIVE"))
		server.SetPasvMode(MODE_PASSIVE);
	else if (pasvMode == _T("MODE_ACTIVE"))
		server.SetPasvMode(MODE_ACTIVE);
	else
		server.SetPasvMode(MODE_DEFAULT);
	
	int maximumMultipleConnections = GetTextElementInt(node, "MaximumMultipleConnections");
	server.MaximumMultipleConnections(maximumMultipleConnections);

	wxString encodingType = GetTextElement(node, "EncodingType");
	if (encodingType == _T("Auto"))
		server.SetEncodingType(ENCODING_AUTO);
	else if (encodingType == _T("UTF-8"))
		server.SetEncodingType(ENCODING_UTF8);
	else if (encodingType == _T("Custom"))
	{
		wxString customEncoding = GetTextElement(node, "CustomEncoding");
		if (customEncoding == _T(""))
			return false;
		if (!server.SetEncodingType(ENCODING_CUSTOM, customEncoding))
			return false;
	}
	else
		server.SetEncodingType(ENCODING_AUTO);

	if (protocol == FTP || protocol == FTPS || protocol == FTPES)
	{
		std::vector<wxString> postLoginCommands;
		TiXmlElement* pElement = node->FirstChildElement("PostLoginCommands");
		if (pElement)
		{
			TiXmlElement* pCommandElement = pElement->FirstChildElement("Command");
			while (pCommandElement)
			{
				TiXmlNode* textNode = pCommandElement->FirstChild();
				if (textNode && textNode->ToText())
				{
					wxString command = ConvLocal(textNode->Value());
					if (command != _T(""))
						postLoginCommands.push_back(command);
				}

				pCommandElement = pCommandElement->NextSiblingElement("Command");
			}
		}
		if (!server.SetPostLoginCommands(postLoginCommands))
			return false;
	}
	
	return true;
}

void SetServer(TiXmlElement *node, const CServer& server)
{
	if (!node)
		return;
	
	node->Clear();
	
	AddTextElement(node, "Host", server.GetHost());
	AddTextElement(node, "Port", server.GetPort());
	AddTextElement(node, "Protocol", server.GetProtocol());
	AddTextElement(node, "Type", server.GetType());
	AddTextElement(node, "Logontype", server.GetLogonType());
	
	if (server.GetLogonType() != ANONYMOUS)
	{
		AddTextElement(node, "User", server.GetUser());

		if (server.GetLogonType() == NORMAL || server.GetLogonType() == ACCOUNT)
			AddTextElement(node, "Pass", server.GetPass());
		
		if (server.GetLogonType() == ACCOUNT)
			AddTextElement(node, "Account", server.GetAccount());
	}

	AddTextElement(node, "TimezoneOffset", server.GetTimezoneOffset());
	switch (server.GetPasvMode())
	{
	case MODE_PASSIVE:
		AddTextElement(node, "PasvMode", _T("MODE_PASSIVE"));
		break;
	case MODE_ACTIVE:
		AddTextElement(node, "PasvMode", _T("MODE_ACTIVE"));
		break;
	default:
		AddTextElement(node, "PasvMode", _T("MODE_DEFAULT"));
		break;
	}
	AddTextElement(node, "MaximumMultipleConnections", server.MaximumMultipleConnections());

	switch (server.GetEncodingType())
	{
	case ENCODING_AUTO:
		AddTextElement(node, "EncodingType", _T("Auto"));
		break;
	case ENCODING_UTF8:
		AddTextElement(node, "EncodingType", _T("UTF-8"));
		break;
	case ENCODING_CUSTOM:
		AddTextElement(node, "EncodingType", _T("Custom"));
		AddTextElement(node, "CustomEncoding", server.GetCustomEncoding());
		break;
	}

	const enum ServerProtocol protocol = server.GetProtocol();
	if (protocol == FTP || protocol == FTPS || protocol == FTPES)
	{
		const std::vector<wxString>& postLoginCommands = server.GetPostLoginCommands();
		if (!postLoginCommands.empty())
		{
			TiXmlElement* pElement = node->InsertEndChild(TiXmlElement("PostLoginCommands"))->ToElement();
			for (std::vector<wxString>::const_iterator iter = postLoginCommands.begin(); iter != postLoginCommands.end(); iter++)
				AddTextElement(pElement, "Command", *iter);
		}
	}
}

void SetTextAttribute(TiXmlElement* node, const char* name, const wxString& value)
{
	wxASSERT(node);

	char* utf8 = ConvUTF8(value);
	if (!utf8)
		return;
	
    node->SetAttribute(name, utf8);
	delete [] utf8;
}

wxString GetTextAttribute(TiXmlElement* node, const char* name)
{
	wxASSERT(node);

	const char* value = node->Attribute(name);
	if (!value)
		return _T("");
	
	return ConvLocal(value);
}

TiXmlElement* FindElementWithAttribute(TiXmlElement* node, const char* element, const char* attribute, const char* value)
{
	TiXmlElement* child;
	if (element)
		child = node->FirstChildElement(element);
	else
		child = node->FirstChildElement();

	while (child)
	{
		const char* nodeVal = child->Attribute(attribute);
		if (nodeVal && !strcmp(value, nodeVal))
			return child;			

		if (element)
			child = child->NextSiblingElement(element);
		else
			child = child->NextSiblingElement();
	}

	return 0;
}

TiXmlElement* FindElementWithAttribute(TiXmlElement* node, const char* element, const char* attribute, int value)
{
	TiXmlElement* child;
	if (element)
		child = node->FirstChildElement(element);
	else
		child = node->FirstChildElement();

	while (child)
	{
		int nodeValue;
		const char* nodeVal = child->Attribute(attribute, &nodeValue);
		if (nodeVal && nodeValue == value)
			return child;			

		if (element)
			child = child->NextSiblingElement(element);
		else
			child = child->NextSiblingElement();
	}

	return 0;
}

int GetAttributeInt(TiXmlElement* node, const char* name)
{
	int value;
	if (!node->Attribute(name, &value))
		return 0;

	return value;
}

void SetAttributeInt(TiXmlElement* node, const char* name, int value)
{
	node->SetAttribute(name, value);
}

int CXmlFile::GetRawDataLength()
{
	if (!m_pDocument)
		return 0;

	delete m_pPrinter;
	m_pPrinter = new TiXmlPrinter;
	m_pPrinter->SetStreamPrinting();

	m_pDocument->Accept(m_pPrinter);
	return m_pPrinter->Size();
}

void CXmlFile::GetRawDataHere(char* p) // p has to big enough to hold at least GetRawDataLength() bytes
{
	if (!m_pPrinter)
	{
		wxFAIL;
		return;
	}

	memcpy(p, m_pPrinter->CStr(), m_pPrinter->Size());
}

bool CXmlFile::ParseData(char* data)
{
	delete m_pDocument;
	m_pDocument = new TiXmlDocument;
	m_pDocument->SetCondenseWhiteSpace(false);
	m_pDocument->Parse(data);
	
	if (!m_pDocument->FirstChildElement("FileZilla3"))
	{
		delete m_pDocument;
		m_pDocument = 0;
		return false;
	}
	
	return true;
}


syntax highlighted by Code2HTML, v. 0.9.1