#include "plugin_hexlang.h"
#include <QBoxLayout>

template<typename T> QByteArray valueToByteArray(T value, bool rev) {
	QByteArray ret(sizeof(T), 0);
	if (rev) piLetobe(&value, sizeof(value));
	memcpy(ret.data(), &value, ret.size());
	return ret;
}
template<typename T> T byteArrayToValue(const QByteArray & ba, bool rev) {
	T ret;
	memcpy(&ret, ba.constData(), sizeof(T));
	if (rev) piLetobe(&ret, sizeof(ret));
	return ret;
}


HexLang::CommandArgument HexLang::getArgument(QString a) {
	if (a == ".") return HexLang::CommandArgument("", 0, HexLang::UChar, 1, 0, false, true);
	HexLang::CommandArgumentType type = HexLang::UChar;
	int bytes = 1;
	bool be = false;
	QString tn;
	if (a.contains('(')) {
		int fi = a.indexOf('(');
		int ei = a.indexOf(')', fi + 1);
		if (ei > 0) {
			tn = a.mid(fi + 1, ei - fi - 1).trimmed().toLower();
			a.remove(fi, ei - fi + 1);
		}
		if (!tn.isEmpty()) {
			if (tn.startsWith('~')) {tn.remove(0, 1); be = true;}
			if (tn.endsWith('~'))   {tn.chop(1);      be = true;}
				 if (tn == "sbyte"  || tn == "char")    type = HexLang::Char;
			else if (tn == "word"   || tn == "ushort") {type = HexLang::UShort; bytes = 2;}
			else if (tn == "sword"  || tn == "short")  {type = HexLang::Short;  bytes = 2;}
			else if (tn == "dword"  || tn == "uint")   {type = HexLang::UInt;   bytes = 4;}
			else if (tn == "sdword" || tn == "int")    {type = HexLang::Int;    bytes = 4;}
			else if (tn == "qword"  || tn == "ullong") {type = HexLang::ULLong; bytes = 8;}
			else if (tn == "sqword" || tn == "llong")  {type = HexLang::LLong;  bytes = 8;}
			else if (tn == "float")                    {type = HexLang::Float;  bytes = 4;}
			else if (tn == "double")                   {type = HexLang::Double; bytes = 8;}
		}
	}
	if (a.startsWith("%")) {
		a.remove(0, 1);
		if (a.size() > 0) {
			if (a[0].isDigit()) {
				QString name;
				if (a.contains('[')) {
					int fi = a.indexOf('[');
					int ei = a.indexOf(']', fi + 1);
					if (ei > 0) {
						name = a.mid(fi + 1, ei - fi - 1).trimmed();
						a.remove(fi, ei - fi + 1);
					}
				}
				int ind = 0;
				bool av = false;
				if (a == ".") av = true;
				else ind = -a.toInt() - 1;
				if (ind < 0 && name.isEmpty()) name = QString("a%1").arg(-ind);
				return HexLang::CommandArgument(name, av ? 0 : ind, type, bytes, 0, be, av);
			}
		}
	}
	return HexLang::CommandArgument("", a.toInt(0, 16), type, bytes, 0, be);
}


QByteArray argumentToByteArray(const HexLang::CommandArgument & type, const QVariant & value) {
	switch (type.type) {
	case HexLang::UChar : return valueToByteArray<uchar >(value.toInt(), type.bigEndian);
	case HexLang::Char  : return valueToByteArray<char  >(value.toInt(), type.bigEndian);
	case HexLang::UShort: return valueToByteArray<ushort>(value.toInt(), type.bigEndian);
	case HexLang::Short : return valueToByteArray<short >(value.toInt(), type.bigEndian);
	case HexLang::UInt  : return valueToByteArray<uint  >(value.toInt(), type.bigEndian);
	case HexLang::Int   : return valueToByteArray<int   >(value.toInt(), type.bigEndian);
	case HexLang::ULLong: return valueToByteArray<ullong>(value.toLongLong(), type.bigEndian);
	case HexLang::LLong : return valueToByteArray<llong >(value.toLongLong(), type.bigEndian);
	case HexLang::Float : return valueToByteArray<float >(value.toFloat(), type.bigEndian);
	case HexLang::Double: return valueToByteArray<double>(value.toDouble(), type.bigEndian);
	}
	return QByteArray();
}


QVariant argumentFromByteArray(const HexLang::CommandArgument & type, const QByteArray & value) {
	switch (type.type) {
	case HexLang::UChar : return QVariant(byteArrayToValue<uchar >(value, type.bigEndian));
	case HexLang::Char  : return QVariant(byteArrayToValue<char  >(value, type.bigEndian));
	case HexLang::UShort: return QVariant(byteArrayToValue<ushort>(value, type.bigEndian));
	case HexLang::Short : return QVariant(byteArrayToValue<short >(value, type.bigEndian));
	case HexLang::UInt  : return QVariant(byteArrayToValue<uint  >(value, type.bigEndian));
	case HexLang::Int   : return QVariant(byteArrayToValue<int   >(value, type.bigEndian));
	case HexLang::ULLong: return QVariant(byteArrayToValue<ullong>(value, type.bigEndian));
	case HexLang::LLong : return QVariant(byteArrayToValue<llong >(value, type.bigEndian));
	case HexLang::Float : return QVariant(byteArrayToValue<float >(value, type.bigEndian));
	case HexLang::Double: return QVariant(byteArrayToValue<double>(value, type.bigEndian));
	}
	return QVariant();
}


QVector<HexLang::CommandArgument> HexLang::parseArgument(QString a) {
	QVector<HexLang::CommandArgument> ret;
	HexLang::CommandArgument arg = HexLang::getArgument(a);
	for (int i = 0; i < arg.bytes; ++i) {
		arg.byte = i;
		ret << arg;
	}
	return ret;
}


QVector<HexLang::CommandArgument> HexLang::compileCommand(QString cmd) {
	QStringList sl = replaceStringToHex(cmd).split(" ");
	QVector<HexLang::CommandArgument> ret;
	sl.removeAll("");
	sl.removeAll(" ");
	sl.removeAll("\t");
	foreach (const QString & s, sl) {
		ret << HexLang::parseArgument(s);
	}
	return ret;
}


QStringList HexLang::getUserArguments(QString cmd) {
	QStringList sl = replaceStringToHex(cmd).split(" "), ret;
	sl.removeAll("");
	sl.removeAll(" ");
	sl.removeAll("\t");
	foreach (const QString & s, sl) {
		HexLang::CommandArgument arg = HexLang::getArgument(s);
		if (!arg.name.isEmpty())
			ret << arg.name;
	}
	return ret;
}


QByteArray HexLang::prepareCommand(const QVector<HexLang::CommandArgument> & cmd, const QVector<QVariant> & args) {
	QByteArray ret, ba;
	ret.reserve(cmd.size());
	for (int i = 0; i < cmd.size(); ++i) {
		const HexLang::CommandArgument & arg(cmd[i]);
		if (arg.index >= 0) {
			ba = argumentToByteArray(arg, arg.index);
		} else {
			int ind = -(arg.index + 1);
			if (args.size() <= ind) {
				qDebug() << "Invalid arguments count!";
				return QByteArray();
			}
			ba = argumentToByteArray(arg, args[ind]);
			if (arg.bytes != ba.size()) {
				qDebug() << "Invalid value!";
				return QByteArray();
			}
		}
		ret.append(ba);
		i += ba.size() - 1;
	}
	return ret;
}


bool HexLang::parseCommand(const QByteArray & rec, const QVector<HexLang::CommandArgument> & cmd_args, QVariantMap & extracted_args) {
	if (cmd_args.size() > rec.size()) return false;
	QByteArray ba;
	int eai = 0;
	for (int i = 0; i < cmd_args.size(); ++i) {
		if (cmd_args[i].any_value) continue;
		if (cmd_args[i].index < 0) {
			if (cmd_args[i].byte == 0)
				ba.resize(cmd_args[i].bytes);
			ba[cmd_args[i].byte] = rec[i];
			if (cmd_args[i].byte == cmd_args[i].bytes - 1) {
				eai = -1 - cmd_args[i].index;
				if (eai < 0) return false;
				extracted_args[cmd_args[i].name] = argumentFromByteArray(cmd_args[i], ba);
			}
		} else
			if (cmd_args[i].index != int(uchar(rec[i]))) {
				return false;
			}
	}
	return true;
}




HexLangWidget::HexLangWidget(QWidget * parent): SHPacketWidgetBase(parent) {
	line = new QLineEdit();
	setLayout(new QBoxLayout(QBoxLayout::LeftToRight));
	layout()->setContentsMargins(0, 0, 0, 0);
	layout()->addWidget(line);
}


SHS::Command HexLangWidget::saveCommand() {
	SHS::Command ret;
	ret.arguments["__text__"] = line->text();
	ret.user_arguments = HexLang::getUserArguments(line->text());
	return ret;
}


bool HexLangWidget::loadCommand(const SHS::Command & cmd) {
	line->setText(cmd.arguments["__text__"].toString());
	return true;
}


QString HexLangWidget::commandToString(const SHS::Command & command) {
	return command.arguments["__text__"].toString();
}




void PluginHexLang::prepareCommand(SHS::Object & root, SHS::Object & object, SHS::Command & command) {
	QString cmd = command.arguments["__text__"].toString();
	QMapIterator<QString, QVariant> it(root.properties);
	while (it.hasNext()) {
		it.next();
		cmd.replace("%" + it.key(), QString::number(it.value().toInt(), 16));
	}
	auto cc = HexLang::compileCommand(cmd);
	command.arguments["_cmd"] = qSerialize(cc, QDataStream::Qt_5_13);
	command.arguments["_cmd_size"] = cc.size();
}


QByteArray PluginHexLang::sendPacket(SHS::Object & root, SHS::Object & object, SHS::Command & command) {
	auto cc = qDeserialize<QVector<HexLang::CommandArgument>>(command.arguments.value("_cmd").toByteArray(), QDataStream::Qt_5_13);
	return HexLang::prepareCommand(cc, command.obtainUserArguments());
}


SHPluginBasePacket::ReceiveResults PluginHexLang::receivePacket(SHS::Object & root, SHS::Object & object, SHS::Command & command, QByteArray packet, QVariantMap & args) {
	int sz = command.arguments.value("_cmd_size").toInt();
	if (sz != packet.size()) return rrFailure;
	auto cc = qDeserialize<QVector<HexLang::CommandArgument>>(command.arguments.value("_cmd").toByteArray(), QDataStream::Qt_5_13);
	if (!HexLang::parseCommand(packet, cc, args))
		return rrFailure;
	return rrSuccess;
}
