using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using static PKHeX.Core.InstructionComparer;
namespace PKHeX.Core;
///
/// Batch Editing instruction
///
///
/// Can be a filter (skip), or a modification instruction (modify)
///
///
///
///
/// Property to modify.
/// Value to set to the property.
/// Filter Comparison Type
public sealed record StringInstruction(string PropertyName, string PropertyValue, InstructionComparer Comparer, InstructionOperation Operation = InstructionOperation.Set)
{
public string PropertyValue { get; private set; } = PropertyValue;
///
/// Sets the to the index of the value in the input , if it exists.
///
/// List of values to search for the .
/// True if the value was found and set, false otherwise.
public bool SetScreenedValue(ReadOnlySpan arr)
{
int index = arr.IndexOf(PropertyValue);
if ((uint)index >= arr.Length)
return false;
PropertyValue = index.ToString();
return true;
}
///
/// Valid prefixes that are recognized for value comparison types.
///
public static ReadOnlySpan Prefixes =>
[
Apply,
FilterEqual, FilterNotEqual, FilterGreaterThan, FilterGreaterThanOrEqual, FilterLessThan, FilterLessThanOrEqual,
ApplyAdd, ApplySubtract, ApplyMultiply, ApplyDivide, ApplyModulo,
ApplyBitwiseAnd, ApplyBitwiseOr, ApplyBitwiseXor, ApplyBitwiseShiftRight, ApplyBitwiseShiftLeft,
];
public static bool IsFilterInstruction(char c) => c switch
{
FilterEqual => true,
FilterNotEqual => true,
FilterGreaterThan => true,
FilterLessThan => true,
FilterGreaterThanOrEqual => true,
FilterLessThanOrEqual => true,
_ => false,
};
public static bool IsMutationInstruction(char c) => !IsFilterInstruction(c);
private const char Apply = '.';
private const char ApplyAdd = '+';
private const char ApplySubtract = '-';
private const char ApplyMultiply = '*';
private const char ApplyDivide = '/';
private const char ApplyModulo = '%';
private const char ApplyBitwiseAnd = '&';
private const char ApplyBitwiseOr = '|';
private const char ApplyBitwiseXor = '^';
private const char ApplyBitwiseShiftRight = '»';
private const char ApplyBitwiseShiftLeft = '«';
private const char SplitRange = ',';
private const char FilterEqual = '=';
private const char FilterNotEqual = '!';
private const char FilterGreaterThan = '>';
private const char FilterLessThan = '<';
private const char FilterGreaterThanOrEqual = '≥';
private const char FilterLessThanOrEqual = '≤';
///
/// Character which divides a property and a value.
///
///
/// Example:
/// =Species=1
/// The second = is the split.
///
public const char SplitInstruction = '=';
// Extra Functionality
private int RandomMinimum, RandomMaximum;
///
/// Apply a instead of fixed value, based on the and values.
///
public bool Random { get; private set; }
///
/// Gets a value, based on the and values.
///
public int RandomValue => Util.Rand.Next(RandomMinimum, RandomMaximum + 1);
///
/// Checks if the input is a valid "random range" specification.
///
public static bool IsRandomRange(ReadOnlySpan str)
{
// Need at least one character on either side of the splitter char.
int index = str.IndexOf(SplitRange);
return index > 0 && index < str.Length - 1;
}
///
/// Sets a "random range" specification to the instruction.
///
/// When the splitter is not present.
public void SetRandomRange(ReadOnlySpan str)
{
var index = str.IndexOf(SplitRange);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(index);
var min = str[..index];
var max = str[(index + 1)..];
_ = int.TryParse(min, out RandomMinimum);
_ = int.TryParse(max, out RandomMaximum);
if (RandomMinimum == RandomMaximum)
{
PropertyValue = RandomMinimum.ToString();
Debug.WriteLine($"{PropertyName} randomization range Min/Max same?");
}
else
{
Random = true;
}
}
///
/// Gets a list of s from the input .
///
public static List GetFilters(ReadOnlySpan text) => GetFilters(text.EnumerateLines());
///
/// Gets a list of filters from the input .
///
public static List GetFilters(ReadOnlySpan lines)
{
var result = new List(lines.Length);
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
///
/// Gets a list of filters from the input .
///
public static List GetFilters(SpanLineEnumerator lines)
{
var result = new List();
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
///
/// Gets a list of filters from the input .
///
public static List GetFilters(IReadOnlyList lines)
{
var result = new List(lines.Count);
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
///
/// Gets a list of filters from the input .
///
public static List GetFilters(IEnumerable lines)
{
var result = new List();
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
///
/// Gets a list of instructions from the input .
///
public static List GetInstructions(ReadOnlySpan text) => GetInstructions(text.EnumerateLines());
///
/// Gets a list of instructions from the input .
///
public static List GetInstructions(ReadOnlySpan lines)
{
var result = new List(lines.Length);
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
///
/// Gets a list of instructions from the input .
///
public static List GetInstructions(SpanLineEnumerator lines)
{
var result = new List();
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
///
/// Gets a list of instructions from the input .
///
public static List GetInstructions(IReadOnlyList lines)
{
var result = new List(lines.Count);
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
///
/// Gets a list of instructions from the input .
///
public static List GetInstructions(IEnumerable lines)
{
var result = new List();
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
///
/// Tries to parse a filter from the input .
///
public static bool TryParseFilter(ReadOnlySpan line, [NotNullWhen(true)] out StringInstruction? entry)
{
entry = null;
if (line.Length is 0)
return false;
var comparer = GetComparer(line[0]);
if (!comparer.IsSupported)
return false;
return TryParseSplitTuple(line[1..], ref entry, comparer);
}
///
/// Tries to parse a instruction from the input .
///
public static bool TryParseInstruction(ReadOnlySpan line, [NotNullWhen(true)] out StringInstruction? entry)
{
entry = null;
if (line.Length is 0 || !TryGetOperation(line[0], out var operation))
return false;
return TryParseSplitTuple(line[1..], ref entry, default, operation);
}
///
/// Tries to split a tuple from the input .
///
public static bool TryParseSplitTuple(ReadOnlySpan tuple, [NotNullWhen(true)] ref StringInstruction? entry, InstructionComparer eval = default, InstructionOperation operation = InstructionOperation.Set)
{
if (!TryParseSplitTuple(tuple, out var name, out var value))
return false;
entry = new StringInstruction(name.ToString(), value.ToString(), eval, operation);
return true;
}
///
/// Tries to split a tuple from the input .
///
public static bool TryParseSplitTuple(ReadOnlySpan tuple, out ReadOnlySpan name, out ReadOnlySpan value)
{
name = default;
value = default;
var splitIndex = tuple.IndexOf(SplitInstruction);
if (splitIndex <= 0)
return false;
name = tuple[..splitIndex];
if (name.IsWhiteSpace())
return false;
value = tuple[(splitIndex + 1)..];
var noExtra = value.IndexOf(SplitInstruction);
return noExtra == -1;
}
///
/// Gets the from the input .
///
public static InstructionComparer GetComparer(char opCode) => opCode switch
{
FilterEqual => IsEqual,
FilterNotEqual => IsNotEqual,
FilterGreaterThan => IsGreaterThan,
FilterLessThan => IsLessThan,
FilterGreaterThanOrEqual => IsGreaterThanOrEqual,
FilterLessThanOrEqual => IsLessThanOrEqual,
_ => None,
};
///
/// Gets the from the input .
///
public static bool TryGetOperation(char opCode, out InstructionOperation operation)
{
switch (opCode)
{
case Apply:
operation = InstructionOperation.Set;
return true;
case ApplyAdd:
operation = InstructionOperation.Add;
return true;
case ApplySubtract:
operation = InstructionOperation.Subtract;
return true;
case ApplyMultiply:
operation = InstructionOperation.Multiply;
return true;
case ApplyDivide:
operation = InstructionOperation.Divide;
return true;
case ApplyModulo:
operation = InstructionOperation.Modulo;
return true;
case ApplyBitwiseAnd:
operation = InstructionOperation.BitwiseAnd;
return true;
case ApplyBitwiseOr:
operation = InstructionOperation.BitwiseOr;
return true;
case ApplyBitwiseXor:
operation = InstructionOperation.BitwiseXor;
return true;
case ApplyBitwiseShiftRight:
operation = InstructionOperation.BitwiseShiftRight;
return true;
case ApplyBitwiseShiftLeft:
operation = InstructionOperation.BitwiseShiftLeft;
return true;
default:
operation = default;
return false;
}
}
}