#include <QApplication>
#include <QCryptographicHash>
#include <QMessageBox>
#include <QStandardPaths>
#include <QDir>
#include <QUrl>
#include <picrypt.h>
#include <pisysteminfo.h>
#include <piqt_connection_edit.h>
#include <piqt.h>
#include <aboutwindow.h>
#include "SH_network_types.h"
#include "SH_base.h"
#include "SH_types.h"
#include "SH_version_gen.h"
#include "gserial.h"


QString toCName(QString s) {
	QString ret(s.trimmed());
	ret.replace(':', '_').replace(',', '_').replace('.', '_').replace(' ', '_').replace('\t', '_');
	if (!ret.isEmpty())
		if (!ret[0].isLetter() && ret[0].toLatin1() != '_')
			ret.push_front('_');
	return ret;
}


bool isCName(QString s) {
	QString ret(s.trimmed());
	if (ret.isEmpty()) return false;
	for (int i = 0; i < ret.size(); ++i) {
		if (i == 0) {
			if (!(ret[i].isLetter() || ret[i].toLatin1() == '_')) return false;
		} else {
			if (!(ret[i].isLetter() || ret[i].isDigit() || ret[i].toLatin1() == '_')) return false;
		}
	}
	return true;
}


QString findFile(QString p, QString ext, const QString & dir) {
	QFileInfo fi(p);
	if (fi.exists()) return p;
	QString np = fi.filePath();
	np.chop(fi.completeSuffix().size());
	if (np.endsWith(".")) np.chop(1);
	np += "." + ext;
	fi.setFile(np);
	if (fi.exists()) return p;
	fi.setFile(p);
	QStringList dirs;
	if (!dir.isEmpty())
		dirs << dir;
	dirs << "include";
	//qDebug() << "find" << p << "in" << dirs;
	if (!fi.isAbsolute()) {
		foreach (QString d, dirs) {
			np = fi.filePath();
			if (!np.startsWith("\\") && !np.startsWith("/"))
				np.prepend("/");
			np.prepend(d);
			fi.setFile(np);
			if (fi.exists()) return fi.filePath();
			np.chop(fi.completeSuffix().size());
			if (np.endsWith(".")) np.chop(1);
			np += "." + ext;
			fi.setFile(np);
			if (fi.exists()) return fi.filePath();
		}
	}
	return QString();
}


QString findExecutable(QString p) {
	QFileInfo fi(p);
	//if (fi.exists()) return fi.absoluteFilePath();
	QString np = fi.filePath();
	np.chop(fi.completeSuffix().size());
	if (np.endsWith(".")) np.chop(1);
#ifdef WINDOWS
	np += ".exe";
#endif
	fi.setFile(np);
	if (!fi.isAbsolute()) {
		QString lp = QApplication::applicationDirPath();
		if (lp.endsWith("/") || lp.endsWith("\\")) lp.chop(1);
		QFileInfo lfi(lp + "/" + np);
		if (lfi.exists()) return lfi.absoluteFilePath();
	}
	if (fi.exists()) return fi.absoluteFilePath();
	return QString();
}


QString parsePath(QString p) {
	p.replace("$home"     , QDir::homePath());
	p.replace("$documents", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
	p.replace("$downloads", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
	p.replace("$desktop"  , QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
	p = QDir::fromNativeSeparators(p);
	if (!p.endsWith("/")) p += "/";
	return p;
}


QString replaceStringToHex(QString cmd) {
	bool is_str = false;
	uchar sbyte = 0;
	QString hex, ret;
	ret.reserve(cmd.size());
	for (int i = 0; i < cmd.size(); ++i) {
		if (is_str) {
			if (cmd[i] == '\\') {
				if (i == cmd.size() - 1) break;
				switch (cmd[i + 1].toLatin1()) {
				case '"' : sbyte = '"' ; break;
				case '\'': sbyte = '\''; break;
				case '\\': sbyte = '\\'; break;
				case 'n' : sbyte = '\n'; break;
				case 'r' : sbyte = '\r'; break;
				case 't' : sbyte = '\t'; break;
				case '0' : sbyte = 0   ; break;
				default: sbyte = cmd[i + 1].toLatin1();
				}
				++i;
			} else {
				if (cmd[i] == '"') {
					ret.append(hex);
					is_str = false;
					continue;
				}
				sbyte = cmd[i].toLatin1();
			}
			hex += QString::number(sbyte, 16).rightJustified(2, '0');
			hex += " ";
		} else {
			if (cmd[i] == '"') {
				is_str = true;
				hex = " ";
			} else
				ret += cmd[i];
		}
	}
	return ret;
}


uint generateID() {
	uint ret = 0;
	PIByteArray ba = PICrypt::generateRandomBuff(sizeof(ret));
	ret = *(uint*)(ba.data());
	return ret;
}


void initAbout() {
	Q_INIT_RESOURCE(shared);
	ADD_ABOUT_VERSION(SHS);
	AboutWindow::addVersion("Ui sync", QString::number(SHS_UISYNC_VERSION));
	ADD_ABOUT_VERSION(QAD);
	AboutWindow::addVersion("Qt", qVersion());
	ADD_ABOUT_VERSION(PIP);
	AboutWindow::addVersion("sodium", PI2QString(PICrypt::version()));
	ADD_ABOUT_BUILD_INFO(SHS);
}


bool __SH_Base_Initializer__::inited = false;
__SH_Base_Initializer__::__SH_Base_Initializer__() {
	if (inited) return;
	inited = true;
	QApplication::setOrganizationDomain("org");
	QApplication::setOrganizationName("SHS");
}


bool isFullpathValid(const QString & p) {
	if (p.isEmpty()) return false;
	int i = p.indexOf("//");
	if (!p.contains(':') || i < 0) return false;
	return !p.mid(i + 2).trimmed().isEmpty();
}


void dirContent(const QString & path, QStringList & dirs, QStringList & files, const QString & prefix) {
	if (path.isEmpty()) return;
	QDir dir(path);
	QStringList sl = dir.entryList(QDir::Files);
	foreach (const QString & i, sl)
		files << (prefix + i);
	sl = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
	foreach (const QString & i, sl) {
		dirs << (prefix + i);
		dirContent(path + "/" + i, dirs, files, prefix + i + "/");
	}
}


bool copyDir(const QString & from, const QString & to) {
	QFileInfo fi(from);
	if (!fi.exists()) return false;
	if (fi == QFileInfo(to)) return true;
	qDebug() << "copy" << from << "->" << to;
	QStringList dirs, files;
	dirContent(from, dirs, files);
	QDir().mkdir(to);
	foreach (const QString & i, dirs)
		QDir().mkdir(to + "/" + i);
	foreach (const QString & i, files) {
		QFile::remove(to + "/" + i);
		QFile::copy(from + "/" + i, to + "/" + i);
	}
	return true;
}


PIByteArray passwordHash(const PIString & p) {
	static PIByteArray seed = PIString("shs_server").toByteArray();
	return PICrypt::passwordHash(p, seed);
}


ServerAddress parseServerAddress(QString addr) {
	ServerAddress ret;
	if (addr.isEmpty()) return ret;
	ret.channel = SHNetworkTypes::TCP;
	if (addr.startsWith("http")) {
		ret.channel = SHNetworkTypes::Cloud;
		QUrl url(addr.toUtf8());
		addr = url.query(QUrl::FullyDecoded);
		if (addr.startsWith("address="))
			addr.remove(0, 8);
	} else if (addr.startsWith("shsserver")) {
		ret.channel = SHNetworkTypes::Cloud;
		addr = QUrl::fromPercentEncoding(addr.toUtf8());
		addr.remove(0, 12);
		if (addr.endsWith("/"))
			addr.chop(1);
	}
	int i = addr.indexOf('@');
	if (i > 0) {
		ret.channel = SHNetworkTypes::Cloud;
		ret.cloud_name = addr.mid(i + 1);
		addr.chop(addr.size() - i);
	}
	i = addr.indexOf(':');
	if (i > 0) {
		ret.port = addr.mid(i + 1).trimmed().toUShort();
		addr.chop(addr.size() - i);
	} else {
		if (ret.channel == SHNetworkTypes::TCP) ret.port = SHNetworkTypes::clientPort;
		else if (ret.channel == SHNetworkTypes::Cloud) ret.port = SHNetworkTypes::cloudPort;
	}
	ret.server = addr;
	return ret;
}


PIString getThisCloudName() {
	PIByteArray hash = PICrypt::hash("shs_server_" + PISystemInfo::machineKey());
	return PIString::fromAscii(hash.toBase64());
}


PropertyStorage mergeProperties(PropertyStorage static_props, PropertyStorage plugin_props) {
	if (plugin_props.isEmpty()) {
		static_props.removePropertiesByFlag(SHS::Plugin);
		return static_props;
	}
	for (int j = 0; j < plugin_props.size(); ++j)
		plugin_props[j].flags |= SHS::Plugin;
	for (int i = 0; i < static_props.size(); ++i) {
		if ((static_props[i].flags & SHS::Internal) != SHS::Internal) {
			bool f = false;
			for (int j = 0; j < plugin_props.size(); ++j)
				if (plugin_props[j].name == static_props[i].name && plugin_props[j].value.userType() == static_props[i].value.userType()) {
					if (plugin_props[j].value.userType() == qMetaTypeId<QAD::Enum>()) {
						QAD::Enum et = plugin_props[j].value.value<QAD::Enum>();
						et.selectName(static_props[i].value.value<QAD::Enum>().selectedName());
						plugin_props[j].value = QVariant::fromValue<QAD::Enum>(et);
					} else{
						plugin_props[j].value = static_props[i].value;
					}
					f = true;
				}
			if (!f) continue;
			static_props.removeAt(i);
			--i;
		}
	}
	for (int i = 0; i < static_props.size(); ++i)
		if (static_props[i].flags & SHS::Plugin) {
			static_props.removeAt(i);
			--i;
		}
	static_props << plugin_props;
	return static_props;
}
