using MsgPack;
using Ryujinx.Ava.Systems.AppLibrary;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Systems.PlayReport
{
///
/// A mapping of title IDs to value formatter specs.
///
/// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself.
///
public class GameSpec
{
public static GameSpec Create(string requiredTitleId, params IEnumerable otherTitleIds)
=> new() { TitleIds = otherTitleIds.Prepend(requiredTitleId).ToArray() };
public static GameSpec Create(IEnumerable titleIds)
=> new() { TitleIds = titleIds.ToArray() };
private int _lastPriority;
public required string[] TitleIds { get; init; }
public const string DefaultDescription = "Formats the details on your Discord presence based on logged data from the game.";
private string _valueDescription;
public string Description => _valueDescription ?? DefaultDescription;
public GameSpec WithDescription(string description)
{
_valueDescription = description != null
? $"Formats the details on your Discord presence {description}"
: null;
return this;
}
public List ValueFormatters { get; } = [];
///
/// Add a value formatter to the current
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
///
/// The key name to match.
/// The function which can return a potential formatted value.
/// The current , for chaining convenience.
public GameSpec AddValueFormatter(
string reportKey,
SingleValueFormatter valueFormatter
) => AddValueFormatter(_lastPriority++, reportKey, valueFormatter);
///
/// Add a value formatter at a specific priority to the current
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
///
/// The resolution priority of this value formatter. Higher resolves sooner.
/// The key name to match.
/// The function which can return a potential formatted value.
/// The current , for chaining convenience.
public GameSpec AddValueFormatter(
int priority,
string reportKey,
SingleValueFormatter valueFormatter
) => AddValueFormatter(new FormatterSpec
{
Priority = priority, ReportKeys = [reportKey], Formatter = valueFormatter
});
///
/// Add a multi-value formatter to the current
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
///
/// The key names to match.
/// The function which can format the values.
/// The current , for chaining convenience.
public GameSpec AddMultiValueFormatter(
string[] reportKeys,
MultiValueFormatter valueFormatter
) => AddMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
///
/// Add a multi-value formatter at a specific priority to the current
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
///
/// The resolution priority of this value formatter. Higher resolves sooner.
/// The key names to match.
/// The function which can format the values.
/// The current , for chaining convenience.
public GameSpec AddMultiValueFormatter(
int priority,
string[] reportKeys,
MultiValueFormatter valueFormatter
) => AddValueFormatter(new MultiFormatterSpec
{
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
///
/// Add a multi-value formatter to the current
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
///
/// The 'Sparse' multi-value formatters do not require every key to be present.
/// If you need this requirement, use .
///
/// The key names to match.
/// The function which can format the values.
/// The current , for chaining convenience.
public GameSpec AddSparseMultiValueFormatter(
string[] reportKeys,
SparseMultiValueFormatter valueFormatter
) => AddSparseMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
///
/// Add a multi-value formatter at a specific priority to the current
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
///
/// The 'Sparse' multi-value formatters do not require every key to be present.
/// If you need this requirement, use .
///
/// The resolution priority of this value formatter. Higher resolves sooner.
/// The key names to match.
/// The function which can format the values.
/// The current , for chaining convenience.
public GameSpec AddSparseMultiValueFormatter(
int priority,
string[] reportKeys,
SparseMultiValueFormatter valueFormatter
) => AddValueFormatter(new SparseMultiFormatterSpec
{
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
private GameSpec AddValueFormatter(T formatterSpec) where T : FormatterSpecBase
{
ValueFormatters.Add(formatterSpec);
return this;
}
}
///
/// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
///
public class FormatterSpec : FormatterSpecBase
{
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
{
if (!playReport.ReportData.AsDictionary().TryGetValue(ReportKeys[0], out MessagePackObject valuePackObject))
{
result = null;
return false;
}
result = valuePackObject;
return true;
}
}
///
/// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values.
///
public class MultiFormatterSpec : FormatterSpecBase
{
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
{
List packedObjects = [];
foreach (string reportKey in ReportKeys)
{
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
{
result = null;
return false;
}
packedObjects.Add(valuePackObject);
}
result = packedObjects;
return true;
}
}
///
/// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their sparsely populated potential values.
///
public class SparseMultiFormatterSpec : FormatterSpecBase
{
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
{
Dictionary packedObjects = [];
foreach (string reportKey in ReportKeys)
{
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
continue;
packedObjects.Add(reportKey, valuePackObject);
}
result = packedObjects;
return true;
}
}
public abstract class FormatterSpecBase
{
public abstract bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object data);
public int Priority { get; init; }
public string[] ReportKeys { get; init; }
public Delegate Formatter { get; init; }
public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
out FormattedValue formattedValue)
{
formattedValue = default;
if (!GetData(playReport, out object data))
return false;
if (data is FormattedValue fv)
{
formattedValue = fv;
return true;
}
switch (Formatter)
{
case SingleValueFormatter svf when data is MessagePackObject match:
formattedValue = svf(
new SingleValue(match) { Application = appMeta, PlayReport = playReport }
);
return true;
case MultiValueFormatter mvf when data is List matches:
formattedValue = mvf(
new MultiValue(matches) { Application = appMeta, PlayReport = playReport }
);
return true;
case SparseMultiValueFormatter smvf when data is Dictionary sparseMatches:
formattedValue = smvf(
new SparseMultiValue(sparseMatches) { Application = appMeta, PlayReport = playReport }
);
return true;
default:
throw new InvalidOperationException("Formatter delegate is not of a known type!");
}
}
}
}