/**
 *	Semantic version library
 *
 *	Check http://semver.org/ for more info about Semantic Versionning
 */
#Const	Version			"2016-04-20"
#Const	ScriptName	"Libs/Nadeo/Semver.Script.txt"

// ---------------------------------- //
// Libraries
// ---------------------------------- //
#Include "TextLib" as TL
#Include "MathLib" as ML

// ---------------------------------- //
// Constants
// ---------------------------------- //
#Const C_Main	0 ///< The main part of the version
#Const C_Prerelease 1 ///< The prerelease part of the version
#Const C_Build 2 ///< The build part of the version
#Const C_CacheSize 100 ///< Size of the comparison cache

// ---------------------------------- //
// Globales
// ---------------------------------- //
declare Boolean[Text] G_ComparisonCache;

// ---------------------------------- //
// Functions
// ---------------------------------- //
// ---------------------------------- //
// Private
// ---------------------------------- //
// ---------------------------------- //
/** Check if the given Text is an Integer
 * 
 *	@param	_Text											The Text to check
 *
 *	@return														True if it is an Integer, False otherwise
 */
Boolean Private_IsInteger(Text _Text) {
	return (_Text == "-1" || TL::ToInteger(_Text) != -1);
}

// ---------------------------------- //
/** Split the version into three parts
 *	Main, Prerelease and Build metadata
 *
 *	@param	_Version									The version to split
 *
 *	@return														The three parts of the version
 */
Text[Integer] Private_SplitVersion(Text _Version) {
	declare Parts = [C_Main => "", C_Prerelease => "", C_Build => ""];
	
	declare SplitMain = TL::Split("-", _Version);
	Parts[C_Main] = SplitMain[0];
	if (SplitMain.count >= 2) {
		declare SplitPre = TL::Split("+", SplitMain[1]);
		Parts[C_Prerelease] = SplitPre[0];
		if (SplitPre.count >= 2) {
			Parts[C_Build] = SplitPre[1];
		}
	} else {
		SplitMain = TL::Split("+", _Version);
		if (SplitMain.count >= 2) {
			Parts[C_Main] = SplitMain[0];
			Parts[C_Build] = SplitMain[1];
		}
	}
	
	return Parts;
}

// ---------------------------------- //
/** Return the first value that is not a zero
 *	If no value are found, then return zero
 *
 *	@param	_Input										The values to check
 *
 *	@return														The first not zero value, zero otherwise
 */
Integer Private_ReturnFirstNotZero(Integer[] _Input) {
	foreach (Value in _Input) {
		if (Value != 0) return Value;
	}
	return 0;
}

// ---------------------------------- //
// Public
// ---------------------------------- //
// ---------------------------------- //
/**	Return the version number of the script
 *
 *	@return														The version number of the script
 */
Text GetScriptVersion() {
	return Version;
}

// ---------------------------------- //
/**	Return the name of the script
 *
 *	@return														The name of the script
 */
Text GetScriptName() {
	return ScriptName;
}

// ---------------------------------- //
/** Compare two identifiers
 *
 *	@param	_A												The first identifier
 *	@param	_B												The second identifier
 *
 *	@return														1 if _A > _B, -1 if _A < _B, 0 otherwise
 */
Integer CompareIdentifiers(Text _A, Text _B) {
	declare IsIntegerA = Private_IsInteger(_A);
	declare IsIntegerB = Private_IsInteger(_B);
	// Numbers come before letters
	if (IsIntegerA && !IsIntegerB) return -1;
	else if (!IsIntegerA && IsIntegerB) return 1;
	// Compare numbers
	else if (IsIntegerA && IsIntegerB) {
		declare A = TL::ToInteger(_A);
		declare B = TL::ToInteger(_B);
		if (A < B) return -1;
		else if (A > B) return 1;
	}
	// When comparing Text use ASCII order
	else if (_A < _B) return -1;
	else if (_A > _B) return 1;
	return 0;
}

// ---------------------------------- //
/** Compare the main part of the version
 *
 *	@param	_VersionA									The first version to compare
 *	@param	_VersionB									The second version to compare
 *
 *	@return														1 if _VersionA > _VersionB, -1 if _VersionA < _VersionB, 0 otherwise
 */
Integer CompareMain(Text _VersionA, Text _VersionB) {
	declare VersionA = TL::Split(".", _VersionA);
	declare VersionB = TL::Split(".", _VersionB);
	for (I, 0, 2) {
		if (!VersionA.existskey(I)) VersionA.add("0");
		if (!VersionB.existskey(I)) VersionB.add("0");
	}
	
	return Private_ReturnFirstNotZero([
		CompareIdentifiers(VersionA[0], VersionB[0]), //< Major
		CompareIdentifiers(VersionA[1], VersionB[1]), //< Minor
		CompareIdentifiers(VersionA[2], VersionB[2]) //< Patch
	]);
}

// ---------------------------------- //
/** Compare the prerelease part of the version
 *
 *	@param	_VersionA									The first version to compare
 *	@param	_VersionB									The second version to compare
 *
 *	@return														1 if _VersionA > _VersionB, -1 if _VersionA < _VersionB, 0 otherwise
 */
Integer ComparePre(Text _VersionA, Text _VersionB) {
	// Not having a prerelease is > having one
	if (_VersionA != "" && _VersionB == "") return -1;
	else if (_VersionA == "" && _VersionB != "") return 1;
	else if (_VersionA == "" && _VersionB == "") return 0;
	
	declare VersionA = TL::Split(".", _VersionA);
	declare VersionB = TL::Split(".", _VersionB);
	declare End = ML::Max(VersionA.count, VersionB.count) - 1;
	for (I, 0, End) {
		if (!VersionA.existskey(I)) return -1;
		else if (!VersionB.existskey(I)) return 1;
		else if (VersionA[I] == VersionB[I]) continue;
		else return CompareIdentifiers(VersionA[I], VersionB[I]);
	}
	
	return 0;
}

// ---------------------------------- //
/** Compare two versions
 *
 *	@param	_VersionA									The first version to compare
 *	@param	_Operator									The comparison to do
 *	@param	_VersionB									The second version to compare
 *
 *	@return														The result of the comparison
 */
Boolean Compare(Text _VersionA, Text _Operator, Text _VersionB) {
	// Hit cache first
	declare Comparison = _VersionA^_Operator^_VersionB;
	if (G_ComparisonCache.existskey(Comparison)) {
		return G_ComparisonCache[Comparison];
	}
	
	declare VersionA = Private_SplitVersion(_VersionA);
	declare VersionB = Private_SplitVersion(_VersionB);
	
	declare Result = False;
	
	switch (_Operator) {
		case "===": {
			Result = (_VersionA == _VersionB);
		}
		case "!==": {
			Result = (_VersionA != _VersionB);
		}
		case "!=": {
			Result = (
				Private_ReturnFirstNotZero([
					CompareMain(VersionA[C_Main], VersionB[C_Main]),
					ComparePre(VersionA[C_Prerelease], VersionB[C_Prerelease])
				]) != 0
			);
		}
		case ">": {
			Result = (
				Private_ReturnFirstNotZero([
					CompareMain(VersionA[C_Main], VersionB[C_Main]),
					ComparePre(VersionA[C_Prerelease], VersionB[C_Prerelease])
				]) > 0
			);
		}
		case "<": {
			Result = (
				Private_ReturnFirstNotZero([
					CompareMain(VersionA[C_Main], VersionB[C_Main]),
					ComparePre(VersionA[C_Prerelease], VersionB[C_Prerelease])
				]) < 0
			);
		}
		case ">=": {
			Result = (
				Private_ReturnFirstNotZero([
					CompareMain(VersionA[C_Main], VersionB[C_Main]),
					ComparePre(VersionA[C_Prerelease], VersionB[C_Prerelease])
				]) >= 0
			);
		}
		case "<=": {
			Result = (
				Private_ReturnFirstNotZero([
					CompareMain(VersionA[C_Main], VersionB[C_Main]),
					ComparePre(VersionA[C_Prerelease], VersionB[C_Prerelease])
				]) <= 0
			);
		}
		default: { //< ==
			Result = (
				CompareMain(VersionA[C_Main], VersionB[C_Main]) == 0 &&
				ComparePre(VersionA[C_Prerelease], VersionB[C_Prerelease]) == 0
			);
		}
	}
	
	// Update cache
	if (G_ComparisonCache.count >= C_CacheSize) {
		declare ComparisonToRemove = "";
		foreach (Comparison => Value in G_ComparisonCache) {
			ComparisonToRemove = Comparison;
			break;
		}
		declare Removed = G_ComparisonCache.removekey(ComparisonToRemove);
	}
	G_ComparisonCache[Comparison] = Result;
	
	return Result;
}