/*
 * Copyright © 2004-2008 Jens Oknelid, paskharen@gmail.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU 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.
 *
 * In addition, as a special exception, compiling, linking, and/or
 * using OpenSSL with this program is allowed.
 */

#include "WulforUtil.hh"
#include "IntlUtil.hh"
#include <glib/gstdio.h>
#include <glib.h>
#include <dcpp/ClientManager.h>
#include <dcpp/Util.h>
#include <iostream>
#include <arpa/inet.h>
#include <fcntl.h>
#include "settingsmanager.hh"

#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>
#include <net/if.h>
#endif

using namespace std;
using namespace dcpp;

const string WulforUtil::ENCODING_LOCALE = C_("Character encoding", "System default");
std::vector<std::string> WulforUtil::charsets;
const std::string WulforUtil::magnetSignature = "magnet:?xt=urn:tree:tiger:";
GtkIconFactory* WulforUtil::iconFactory = NULL;

vector<int> WulforUtil::splitString(const string &str, const string &delimiter)
{
	string::size_type loc, len, pos = 0;
	vector<int> array;

	if (!str.empty() && !delimiter.empty())
	{
		while ((loc = str.find(delimiter, pos)) != string::npos)
		{
			len = loc - pos;
			array.push_back(Util::toInt(str.substr(pos, len)));
			pos = loc + delimiter.size();
		}
		len = str.size() - pos;
		array.push_back(Util::toInt(str.substr(pos, len)));
	}
	return array;
}

string WulforUtil::linuxSeparator(const string &ps)
{
	string str = ps;
	for (string::iterator it = str.begin(); it != str.end(); ++it)
		if ((*it) == '\\')
			(*it) = '/';
	return str;
}

string WulforUtil::windowsSeparator(const string &ps)
{
	string str = ps;
	for (string::iterator it = str.begin(); it != str.end(); ++it)
		if ((*it) == '/')
			(*it) = '\\';
	return str;
}

vector<string> WulforUtil::getLocalIPs()
{
	vector<string> addresses;

#ifdef HAVE_IFADDRS_H
	struct ifaddrs *ifap;

	if (getifaddrs(&ifap) == 0)
	{
		for (struct ifaddrs *i = ifap; i != NULL; i = i->ifa_next)
		{
			struct sockaddr *sa = i->ifa_addr;

			// If the interface is up, is not a loopback and it has an address
			if ((i->ifa_flags & IFF_UP) && !(i->ifa_flags & IFF_LOOPBACK) && sa != NULL)
			{
				void* src = NULL;
				socklen_t len;

				// IPv4 address
				if (sa->sa_family == AF_INET)
				{
					struct sockaddr_in* sai = (struct sockaddr_in*)sa;
					src = (void*) &(sai->sin_addr);
					len = INET_ADDRSTRLEN;
				}
				// IPv6 address
				else if (sa->sa_family == AF_INET6)
				{
					struct sockaddr_in6* sai6 = (struct sockaddr_in6*)sa;
					src = (void*) &(sai6->sin6_addr);
					len = INET6_ADDRSTRLEN;
				}

				// Convert the binary address to a string and add it to the output list
				if (src != NULL)
				{
					char address[len];
					inet_ntop(sa->sa_family, src, address, len);
					addresses.push_back(address);
				}
			}
		}
		freeifaddrs(ifap);
	}
#endif

	return addresses;
}

string WulforUtil::getNicks(const string &cid)
{
	return getNicks(CID(cid));
}

string WulforUtil::getNicks(const CID& cid)
{
	return Util::toString(ClientManager::getInstance()->getNicks(cid));
}

string WulforUtil::getNicks(const UserPtr& user)
{
	return getNicks(user->getCID());
}

string WulforUtil::getHubNames(const string &cid)
{
	return getHubNames(CID(cid));
}

string WulforUtil::getHubNames(const CID& cid)
{
	StringList hubs = ClientManager::getInstance()->getHubNames(cid);
	if (hubs.empty())
		return C_("User", "Offline");
	else
		return Util::toString(hubs);
}

string WulforUtil::getHubNames(const UserPtr& user)
{
	return getHubNames(user->getCID());
}

StringList WulforUtil::getHubAddress(const CID& cid)
{
	return ClientManager::getInstance()->getHubs(cid);
}

StringList WulforUtil::getHubAddress(const UserPtr& user)
{
	return getHubAddress(user->getCID());
}

string WulforUtil::getTextFromMenu(GtkMenuItem *item)
{
	string text;
	GtkWidget *child = gtk_bin_get_child(GTK_BIN(item));

	if (child && GTK_IS_LABEL(child))
		text = gtk_label_get_text(GTK_LABEL(child));

	return text;
}

vector<string>& WulforUtil::getCharsets()
{
	if (charsets.size() == 0)
	{
		charsets.push_back(ENCODING_LOCALE);
		charsets.push_back(C_("Character encoding", "UTF-8 (Unicode)"));
		charsets.push_back(C_("Character encoding", "CP1252 (Western Europe)"));
		charsets.push_back(C_("Character encoding", "CP1250 (Central Europe)"));
		charsets.push_back(C_("Character encoding", "ISO-8859-2 (Central Europe)"));
		charsets.push_back(C_("Character encoding", "ISO-8859-7 (Greek)"));
		charsets.push_back(C_("Character encoding", "ISO-8859-8 (Hebrew)"));
		charsets.push_back(C_("Character encoding", "ISO-8859-9 (Turkish)"));
		charsets.push_back(C_("Character encoding", "ISO-2022-JP (Japanese)"));
		charsets.push_back(C_("Character encoding", "SJIS (Japanese)"));
		charsets.push_back(C_("Character encoding", "CP949 (Korean)"));
		charsets.push_back(C_("Character encoding", "KOI8-R (Cyrillic)"));
		charsets.push_back(C_("Character encoding", "CP1251 (Cyrillic)"));
		charsets.push_back(C_("Character encoding", "CP1256 (Arabic)"));
		charsets.push_back(C_("Character encoding", "CP1257 (Baltic)"));
		charsets.push_back(C_("Character encoding", "GB18030 (Chinese)"));
		charsets.push_back(C_("Character encoding", "TIS-620 (Thai)"));
	}
	return charsets;
}

void WulforUtil::openURI(const std::string &uri)
{
	GError* error = NULL;
	gchar *argv[3];

#if defined(__APPLE__)
	argv[0] = (gchar *)"open";
#elif defined(_WIN32)
	argv[0] = (gchar *)"start";
#else
	argv[0] = (gchar *)"xdg-open";
#endif
	argv[1] = (gchar *)Text::fromUtf8(uri).c_str();
	argv[2] = NULL;

	g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);

	if (error != NULL)
	{
		cerr << "Failed to open URI: " << error->message << endl;
		g_error_free(error);
	}
}

string WulforUtil::makeMagnet(const string &name, const int64_t size, const string &tth)
{
	if (name.empty() || tth.empty())
		return string();

	// other clients can return paths with different separators, so we should catch both cases
	string::size_type i = name.find_last_of("/\\");
	string path = (i != string::npos) ? name.substr(i + 1) : name;

	return magnetSignature + tth + "&xl=" + Util::toString(size) + "&dn=" + Util::encodeURI(path);
}

bool WulforUtil::splitMagnet(const string &magnet, string &name, int64_t &size, string &tth)
{
	name = _("Unknown");
	size = 0;
	tth = _("Unknown");

	if (!isMagnet(magnet.c_str()) || magnet.size() <= magnetSignature.length())
		return FALSE;

	string::size_type nextpos = 0;

	for (string::size_type pos = magnetSignature.length(); pos < magnet.size(); pos = nextpos + 1)
	{
		nextpos = magnet.find('&', pos);
		if (nextpos == string::npos)
			nextpos = magnet.size();

		if (pos == magnetSignature.length())
			tth = magnet.substr(magnetSignature.length(), nextpos - magnetSignature.length());
		else if (magnet.compare(pos, 3, "xl=") == 0)
			size = Util::toInt64(magnet.substr(pos + 3, nextpos - pos - 3));
		else if (magnet.compare(pos, 3, "dn=") == 0)
			name = Util::encodeURI(magnet.substr(pos + 3, nextpos - pos - 3), TRUE);
	}

	return TRUE;
}

bool WulforUtil::isMagnet(const string &text)
{
	return g_ascii_strncasecmp(text.c_str(), magnetSignature.c_str(), magnetSignature.length()) == 0;
}

bool WulforUtil::isLink(const string &text)
{
	return g_ascii_strncasecmp(text.c_str(), "http://", 7) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "https://", 8) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "www.", 4) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "ftp://", 6) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "sftp://", 7) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "irc://", 6) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "ircs://", 7) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "im:", 3) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "mailto:", 7) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "news:", 5) == 0;
}

bool WulforUtil::isHubURL(const string &text)
{
	return g_ascii_strncasecmp(text.c_str(), "dchub://", 8) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "adc://", 6) == 0 ||
		g_ascii_strncasecmp(text.c_str(), "adcs://", 7) == 0;
}

const string& WulforUtil::getPipePath()
{
	static string pipePath = "";
	if (pipePath != "")
		return pipePath;

	// We really can't use Util::getPath(PATH_USER_CONFIG) for this
	// since it has to stay same even if core hasn't been started.
	// Though it might be better to have Util::getPath working
	// without the core start (if it doesn't work already ==>
	// should be checked)

	char *home = getenv("HOME");
	string configPath = home ? string(home) + "/.dc++/" : "/tmp/";
	pipePath = configPath + "linuxdcpp.pipe";
	return pipePath;
}

int WulforUtil::writeIPCCommand(string cmd)
{
	if (cmd.empty())
		return 0;

	const std::string pipepath = WulforUtil::getPipePath();
	if (cmd[cmd.length() -1] != '\n')
		cmd += '\n';

	int fd = open(pipepath.c_str(), O_WRONLY | O_NONBLOCK);
	if (fd >= 0)
	{
		int wrote = write(fd, cmd.c_str(), cmd.length());
		close(fd);
		if (wrote < 0)
		{
			return wrote;
		}
		return wrote;
	}

	return errno;
}

bool WulforUtil::profileIsLocked()
{
	static bool profileIsLocked = false;

	if (profileIsLocked)
		return TRUE;

	// We can't use Util::getConfigPath() since the core has not been started yet.
	// Also, Util::getConfigPath() is utf8 and we need system encoding for g_open().
	char *home = getenv("HOME");
	string configPath = home ? string(home) + "/.dc++/" : "/tmp/";
	string profileLockingFile = configPath + "profile.lck";
	int flags = O_WRONLY | O_CREAT;
	int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;

	int fd = g_open(profileLockingFile.c_str(), flags, mode);
	if (fd != -1) // read error
	{
		struct flock lock;
		lock.l_start = 0;
		lock.l_len = 0;
		lock.l_type = F_WRLCK;
		lock.l_whence = SEEK_SET;
		struct flock testlock = lock;

		if (fcntl(fd, F_GETLK, &testlock) != -1) // Locking not supported
		{
			if (fcntl(fd, F_SETLK, &lock) == -1)
				profileIsLocked = true;
		}
	}

	return profileIsLocked;
}


gboolean WulforUtil::getNextIter_gui(GtkTreeModel *model, GtkTreeIter *iter, bool children /* = TRUE */, bool parent /* = TRUE */)
{
	gboolean valid = FALSE;
	GtkTreeIter old = *iter;

	if (children && gtk_tree_model_iter_has_child(model, iter))
	{
		valid = gtk_tree_model_iter_children(model, iter, &old);
	}
	else
	{
		valid = gtk_tree_model_iter_next(model, iter);
	}

	// Try to go up one level if next failed
	if (!valid && parent)
	{
		valid = gtk_tree_model_iter_parent(model, iter, &old);
		if (valid)
			valid = gtk_tree_model_iter_next(model, iter);
	}

	return valid;
}

GtkTreeIter WulforUtil::copyRow_gui(GtkListStore *store, GtkTreeIter *fromIter, int position /* = -1 */)
{
	GtkTreeIter toIter;
	gtk_list_store_insert(store, &toIter, position);
	GtkTreeModel *m = GTK_TREE_MODEL(store);
	int count = gtk_tree_model_get_n_columns(m);

	for (int col = 0; col < count; col++)
	{
		copyValue_gui(store, fromIter, &toIter, col);
	}

	return toIter;
}

void WulforUtil::copyValue_gui(GtkListStore *store, GtkTreeIter *fromIter, GtkTreeIter *toIter, int position)
{
	GValue value = {0, };
	gtk_tree_model_get_value(GTK_TREE_MODEL(store), fromIter, position, &value);
	gtk_list_store_set_value(store, toIter, position, &value);
	g_value_unset(&value);
}

GtkTreeIter WulforUtil::copyRow_gui(GtkTreeStore *store, GtkTreeIter *fromIter, GtkTreeIter *parent /* = NULL */, int position /* = -1 */)
{
	GtkTreeIter toIter;
	gtk_tree_store_insert(store, &toIter, parent, position);
	GtkTreeModel *m = GTK_TREE_MODEL(store);
	int count = gtk_tree_model_get_n_columns(m);

	for (int col = 0; col < count; col++)
	{
		copyValue_gui(store, fromIter, &toIter, col);
	}

	return toIter;
}

void WulforUtil::copyValue_gui(GtkTreeStore *store, GtkTreeIter *fromIter, GtkTreeIter *toIter, int position)
{
	GValue value = {0, };
	gtk_tree_model_get_value(GTK_TREE_MODEL(store), fromIter, position, &value);
	gtk_tree_store_set_value(store, toIter, position, &value);
	g_value_unset(&value);
}

/*
 * Registers either the custom icons or the GTK+ icons as stock icons in
 * GtkIconFactory according to the user's preference. If the icons have
 * previously been loaded, they are removed and re-added.
 */
void WulforUtil::registerIcons()
{
	// Holds a mapping of custom icon names -> stock icon names.
	// Not all icons have stock representations.
	map<string, string> icons;
	icons["linuxdcpp"] = "linuxdcpp";
	icons["linuxdcpp-dc++"] = "linuxdcpp-dc++";
	icons["linuxdcpp-dc++-fw"] = "linuxdcpp-dc++-fw";
	icons["linuxdcpp-dc++-fw-op"] = "linuxdcpp-dc++-fw-op";
	icons["linuxdcpp-dc++-op"] = "linuxdcpp-dc++-op";
	icons["linuxdcpp-normal"] = "linuxdcpp-normal";
	icons["linuxdcpp-normal-fw"] = "linuxdcpp-normal-fw";
	icons["linuxdcpp-normal-fw-op"] = "linuxdcpp-normal-fw-op";
	icons["linuxdcpp-normal-op"] = "linuxdcpp-normal-op";
	icons["linuxdcpp-download"] = GTK_STOCK_GO_DOWN;
	icons["linuxdcpp-favorite-hubs"] = GTK_STOCK_HOME;
	icons["linuxdcpp-favorite-users"] = "linuxdcpp-favorite-users";
	icons["linuxdcpp-finished-downloads"] = GTK_STOCK_GO_DOWN;
	icons["linuxdcpp-finished-uploads"] = GTK_STOCK_GO_UP;
	icons["linuxdcpp-hash"] = GTK_STOCK_CONVERT;
	icons["linuxdcpp-preferences"] = GTK_STOCK_PREFERENCES;
	icons["linuxdcpp-public-hubs"] = GTK_STOCK_NETWORK;
	icons["linuxdcpp-queue"] = GTK_STOCK_DIRECTORY;
	icons["linuxdcpp-search"] = GTK_STOCK_FIND;
	icons["linuxdcpp-upload"] = GTK_STOCK_GO_UP;

	if (iconFactory)
	{
		gtk_icon_factory_remove_default(iconFactory);
		iconFactory = NULL;
	}

	iconFactory = gtk_icon_factory_new();

	for (map<string, string>::const_iterator i = icons.begin(); i != icons.end(); ++i)
	{
		string iconName = WGETI("use-system-icons") ? i->second : i->first;
		GtkIconSource *iconSource = gtk_icon_source_new();
		GtkIconSet *iconSet = gtk_icon_set_new();

		gtk_icon_source_set_icon_name(iconSource, iconName.c_str());
		gtk_icon_set_add_source(iconSet, iconSource);
		gtk_icon_factory_add(iconFactory, i->first.c_str(), iconSet);
		gtk_icon_source_free(iconSource);
		gtk_icon_set_unref(iconSet);
	}

	gtk_icon_factory_add_default(iconFactory);
	g_object_unref(iconFactory);
}

