/*
 * Oak Video Editor - Non-Linear Video Editor
 * Copyright (C) 2025 Olive CE Team
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include "value.h"

#include <QCoreApplication>
#include <QMatrix4x4>
#include <QVector2D>
#include <QVector3D>
#include <QVector4D>

#include "common/tohex.h"
#include "render/subtitleparams.h"
#include "render/videoparams.h"

namespace olive
{

QString NodeValue::ValueToString(Type data_type, const QVariant &value,
								 bool value_is_a_key_track)
{
	if (!value_is_a_key_track && data_type == kVec2) {
		QVector2D vec = value.value<QVector2D>();

		return QStringLiteral("%1:%2").arg(QString::number(vec.x()),
										   QString::number(vec.y()));
	} else if (!value_is_a_key_track && data_type == kVec3) {
		QVector3D vec = value.value<QVector3D>();

		return QStringLiteral("%1:%2:%3")
			.arg(QString::number(vec.x()), QString::number(vec.y()),
				 QString::number(vec.z()));
	} else if (!value_is_a_key_track && data_type == kVec4) {
		QVector4D vec = value.value<QVector4D>();

		return QStringLiteral("%1:%2:%3:%4")
			.arg(QString::number(vec.x()), QString::number(vec.y()),
				 QString::number(vec.z()), QString::number(vec.w()));
	} else if (!value_is_a_key_track && data_type == kColor) {
		Color c = value.value<Color>();

		return QStringLiteral("%1:%2:%3:%4")
			.arg(QString::number(c.red()), QString::number(c.green()),
				 QString::number(c.blue()), QString::number(c.alpha()));
	} else if (!value_is_a_key_track && data_type == kBezier) {
		Bezier b = value.value<Bezier>();

		return QStringLiteral("%1:%2:%3:%4:%5:%6")
			.arg(QString::number(b.x()), QString::number(b.y()),
				 QString::number(b.cp1_x()), QString::number(b.cp1_y()),
				 QString::number(b.cp2_x()), QString::number(b.cp2_y()));
	} else if (data_type == kRational) {
		return QString::fromStdString(value.value<rational>().toString());
	} else if (data_type == kTexture || data_type == kSamples ||
			   data_type == kNone) {
		// These data types need no XML representation
		return QString();
	} else if (data_type == kInt) {
		return QString::number(value.value<int64_t>());
	} else if (data_type == kBinary) {
		return value.toByteArray().toBase64();
	} else {
		if (value.canConvert<QString>()) {
			return value.toString();
		}

		if (!value.isNull()) {
			qWarning()
				<< "Failed to convert type" << ToHex(data_type) << "to string";
		}

		return QString();
	}
}

QVector<QVariant>
NodeValue::split_normal_value_into_track_values(Type type,
												const QVariant &value)
{
	QVector<QVariant> vals(get_number_of_keyframe_tracks(type));

	switch (type) {
	case kVec2: {
		QVector2D vec = value.value<QVector2D>();
		vals.replace(0, vec.x());
		vals.replace(1, vec.y());
		break;
	}
	case kVec3: {
		QVector3D vec = value.value<QVector3D>();
		vals.replace(0, vec.x());
		vals.replace(1, vec.y());
		vals.replace(2, vec.z());
		break;
	}
	case kVec4: {
		QVector4D vec = value.value<QVector4D>();
		vals.replace(0, vec.x());
		vals.replace(1, vec.y());
		vals.replace(2, vec.z());
		vals.replace(3, vec.w());
		break;
	}
	case kColor: {
		Color c = value.value<Color>();
		vals.replace(0, c.red());
		vals.replace(1, c.green());
		vals.replace(2, c.blue());
		vals.replace(3, c.alpha());
		break;
	}
	case kBezier: {
		Bezier b = value.value<Bezier>();
		vals.replace(0, b.x());
		vals.replace(1, b.y());
		vals.replace(2, b.cp1_x());
		vals.replace(3, b.cp1_y());
		vals.replace(4, b.cp2_x());
		vals.replace(5, b.cp2_y());
		break;
	}
	default:
		vals.replace(0, value);
	}

	return vals;
}

QVariant NodeValue::combine_track_values_into_normal_value(
	Type type, const QVector<QVariant> &split)
{
	if (split.isEmpty()) {
		return QVariant();
	}

	switch (type) {
	case kVec2: {
		return QVector2D(split.at(0).toFloat(), split.at(1).toFloat());
	}
	case kVec3: {
		return QVector3D(split.at(0).toFloat(), split.at(1).toFloat(),
						 split.at(2).toFloat());
	}
	case kVec4: {
		return QVector4D(split.at(0).toFloat(), split.at(1).toFloat(),
						 split.at(2).toFloat(), split.at(3).toFloat());
	}
	case kColor: {
		return QVariant::fromValue(
			Color(split.at(0).toFloat(), split.at(1).toFloat(),
				  split.at(2).toFloat(), split.at(3).toFloat()));
	}
	case kBezier:
		return QVariant::fromValue(
			Bezier(split.at(0).toDouble(), split.at(1).toDouble(),
				   split.at(2).toDouble(), split.at(3).toDouble(),
				   split.at(4).toDouble(), split.at(5).toDouble()));
	default:
		return split.first();
	}
}

int NodeValue::get_number_of_keyframe_tracks(Type type)
{
	switch (type) {
	case NodeValue::kVec2:
		return 2;
	case NodeValue::kVec3:
		return 3;
	case NodeValue::kVec4:
	case NodeValue::kColor:
		return 4;
	case NodeValue::kBezier:
		return 6;
	default:
		return 1;
	}
}

QVariant NodeValue::StringToValue(Type data_type, const QString &string,
								  bool value_is_a_key_track)
{
	if (!value_is_a_key_track && data_type == kVec2) {
		QStringList vals = string.split(':');

		ValidateVectorString(&vals, 2);

		return QVector2D(vals.at(0).toFloat(), vals.at(1).toFloat());
	} else if (!value_is_a_key_track && data_type == kVec3) {
		QStringList vals = string.split(':');

		ValidateVectorString(&vals, 3);

		return QVector3D(vals.at(0).toFloat(), vals.at(1).toFloat(),
						 vals.at(2).toFloat());
	} else if (!value_is_a_key_track && data_type == kVec4) {
		QStringList vals = string.split(':');

		ValidateVectorString(&vals, 4);

		return QVector4D(vals.at(0).toFloat(), vals.at(1).toFloat(),
						 vals.at(2).toFloat(), vals.at(3).toFloat());
	} else if (!value_is_a_key_track && data_type == kColor) {
		QStringList vals = string.split(':');

		ValidateVectorString(&vals, 4);

		return QVariant::fromValue(
			Color(vals.at(0).toDouble(), vals.at(1).toDouble(),
				  vals.at(2).toDouble(), vals.at(3).toDouble()));
	} else if (!value_is_a_key_track && data_type == kBezier) {
		QStringList vals = string.split(':');

		ValidateVectorString(&vals, 6);

		return QVariant::fromValue(
			Bezier(vals.at(0).toDouble(), vals.at(1).toDouble(),
				   vals.at(2).toDouble(), vals.at(3).toDouble(),
				   vals.at(4).toDouble(), vals.at(5).toDouble()));
	} else if (data_type == kInt) {
		return QVariant::fromValue(string.toLongLong());
	} else if (data_type == kRational) {
		return QVariant::fromValue(rational::fromString(string.toStdString()));
	} else if (data_type == kBinary) {
		return QByteArray::fromBase64(string.toLatin1());
	} else {
		return string;
	}
}

void NodeValue::ValidateVectorString(QStringList *list, int count)
{
	while (list->size() < count) {
		list->append(QStringLiteral("0"));
	}
}

QString NodeValue::GetPrettyDataTypeName(Type type)
{
	switch (type) {
	case kNone:
		return QCoreApplication::translate("NodeValue", "None");
	case kInt:
	case kCombo:
		return QCoreApplication::translate("NodeValue", "Integer");
	case kFloat:
		return QCoreApplication::translate("NodeValue", "Float");
	case kRational:
		return QCoreApplication::translate("NodeValue", "Rational");
	case kBoolean:
		return QCoreApplication::translate("NodeValue", "Boolean");
	case kColor:
		return QCoreApplication::translate("NodeValue", "Color");
	case kMatrix:
		return QCoreApplication::translate("NodeValue", "Matrix");
	case kText:
		return QCoreApplication::translate("NodeValue", "Text");
	case kFont:
		return QCoreApplication::translate("NodeValue", "Font");
	case kFile:
		return QCoreApplication::translate("NodeValue", "File");
	case kTexture:
		return QCoreApplication::translate("NodeValue", "Texture");
	case kSamples:
		return QCoreApplication::translate("NodeValue", "Samples");
	case kVec2:
		return QCoreApplication::translate("NodeValue", "Vector 2D");
	case kVec3:
		return QCoreApplication::translate("NodeValue", "Vector 3D");
	case kVec4:
		return QCoreApplication::translate("NodeValue", "Vector 4D");
	case kBezier:
		return QCoreApplication::translate("NodeValue", "Bezier");
	case kVideoParams:
		return QCoreApplication::translate("NodeValue", "Video Parameters");
	case kAudioParams:
		return QCoreApplication::translate("NodeValue", "Audio Parameters");
	case kSubtitleParams:
		return QCoreApplication::translate("NodeValue", "Subtitle Parameters");
	case kBinary:
		return QCoreApplication::translate("NodeValue", "Binary");

	case kDataTypeCount:
		break;
	}

	return QCoreApplication::translate("NodeValue", "Unknown");
}

QString NodeValue::GetDataTypeName(Type type)
{
	switch (type) {
	case kNone:
		return QStringLiteral("none");
	case kInt:
		return QStringLiteral("int");
	case kCombo:
		return QStringLiteral("combo");
	case kFloat:
		return QStringLiteral("float");
	case kRational:
		return QStringLiteral("rational");
	case kBoolean:
		return QStringLiteral("bool");
	case kColor:
		return QStringLiteral("color");
	case kMatrix:
		return QStringLiteral("matrix");
	case kText:
		return QStringLiteral("text");
	case kFont:
		return QStringLiteral("font");
	case kFile:
		return QStringLiteral("file");
	case kTexture:
		return QStringLiteral("texture");
	case kSamples:
		return QStringLiteral("samples");
	case kVec2:
		return QStringLiteral("vec2");
	case kVec3:
		return QStringLiteral("vec3");
	case kVec4:
		return QStringLiteral("vec4");
	case kBezier:
		return QStringLiteral("bezier");
	case kVideoParams:
		return QStringLiteral("vparam");
	case kAudioParams:
		return QStringLiteral("aparam");
	case kSubtitleParams:
		return QStringLiteral("sparam");
	case kBinary:
		return QStringLiteral("binary");
	case kDataTypeCount:
		break;
	}

	return QString();
}

NodeValue::Type NodeValue::GetDataTypeFromName(const QString &n)
{
	// Slow but easy to maintain
	for (int i = 0; i < kDataTypeCount; i++) {
		Type t = static_cast<Type>(i);
		if (GetDataTypeName(t) == n) {
			return t;
		}
	}

	return NodeValue::kNone;
}

NodeValue NodeValueTable::Get(const QVector<NodeValue::Type> &type,
							  const QString &tag) const
{
	int value_index = GetValueIndex(type, tag);

	if (value_index >= 0) {
		return values_.at(value_index);
	}

	return NodeValue();
}

NodeValue NodeValueTable::Take(const QVector<NodeValue::Type> &type,
							   const QString &tag)
{
	int value_index = GetValueIndex(type, tag);

	if (value_index >= 0) {
		return values_.takeAt(value_index);
	}

	return NodeValue();
}

bool NodeValueTable::Has(NodeValue::Type type) const
{
	for (int i = values_.size() - 1; i >= 0; i--) {
		const NodeValue &v = values_.at(i);

		if (v.type() & type) {
			return true;
		}
	}

	return false;
}

void NodeValueTable::Remove(const NodeValue &v)
{
	for (int i = values_.size() - 1; i >= 0; i--) {
		const NodeValue &compare = values_.at(i);

		if (compare == v) {
			values_.removeAt(i);
			return;
		}
	}
}

NodeValueTable NodeValueTable::Merge(QList<NodeValueTable> tables)
{
	if (tables.size() == 1) {
		return tables.first();
	}

	int row = 0;

	NodeValueTable merged_table;

	// Slipstreams all tables together
	while (true) {
		bool all_merged = true;

		foreach (const NodeValueTable &t, tables) {
			if (row < t.Count()) {
				all_merged = false;
			} else {
				continue;
			}

			int row_index = t.Count() - 1 - row;

			merged_table.Prepend(t.at(row_index));
		}

		row++;

		if (all_merged) {
			break;
		}
	}

	return merged_table;
}

int NodeValueTable::GetValueIndex(const QVector<NodeValue::Type> &types,
								  const QString &tag) const
{
	int index = -1;

	for (int i = values_.size() - 1; i >= 0; i--) {
		const NodeValue &v = values_.at(i);

		if (types.contains(v.type())) {
			index = i;

			if (tag.isEmpty() || tag == v.tag()) {
				break;
			}
		}
	}

	return index;
}

}
