using System;
using System.Collections.Generic;
namespace PKHeX.Core;
///
/// block accessor, where blocks are ordered by ascending .
///
public abstract class SCBlockAccessor : ISaveBlockAccessor
{
public abstract IReadOnlyList BlockInfo { get; }
/// Checks if there is any with the requested .
public bool HasBlock(uint key) => FindIndex(BlockInfo, key) != -1;
#region Direct Block Accessing
/// Returns the reference with the corresponding .
public SCBlock GetBlock(uint key) => Find(BlockInfo, key);
///
public object GetBlockValue(uint key) => GetBlock(key).GetValue();
///
public T GetBlockValue(uint key) where T : struct
{
var value = GetBlockValue(key);
if (value is T v)
return v;
throw new ArgumentException($"Incorrect type request! Expected {typeof(T).Name}, received {value.GetType().Name}", nameof(T));
}
///
public void SetBlockValue(uint key, object value) => GetBlock(key).SetValue(value);
#endregion
#region Ease of Use Overloads
///
/// Block name (un-hashed)
public SCBlock GetBlock(string name) => GetBlock(Hash(name.AsSpan()));
///
public SCBlock GetBlock(ReadOnlySpan name) => GetBlock(Hash(name));
private static uint Hash(ReadOnlySpan name) => (uint)FnvHash.HashFnv1a_64(name);
///
public SCBlock GetBlock(ReadOnlySpan name) => GetBlock(Hash(name));
private static uint Hash(ReadOnlySpan name) => (uint)FnvHash.HashFnv1a_64(name);
#endregion
#region Safe Block Operations (no exceptions thrown)
///
/// Tries to grab the actual block, and returns a new dummy if the block does not exist.
///
/// Block Key
/// Block if exists, dummy if not. Dummy key will not match requested key.
public SCBlock GetBlockSafe(uint key) => FindOrDefault(BlockInfo, key);
/// If the block does not exist, the method will return the default value.
///
public T GetBlockValueSafe(uint key) where T : struct
{
var index = FindIndex(BlockInfo, key);
if (index == -1)
return default;
var block = BlockInfo[index];
if (block.Type != SCTypeCode.None) // not fake block
return (T)block.GetValue();
return default;
}
/// If the block does not exist, the method will do nothing.
///
public void SetBlockValueSafe(uint key, object value)
{
var index = FindIndex(BlockInfo, key);
if (index == -1)
return;
var block = BlockInfo[index];
if (block.Type != SCTypeCode.None) // not fake block
block.SetValue(value);
}
#endregion
#region Block Fetching
private static SCBlock Find(IReadOnlyList array, uint key)
{
var index = FindIndex(array, key);
if (index != -1)
return array[index];
throw new KeyNotFoundException(nameof(key));
}
private static SCBlock FindOrDefault(IReadOnlyList array, uint key)
{
var index = FindIndex(array, key);
if (index != -1)
return array[index];
return new SCBlock(0, SCTypeCode.None);
}
///
/// Finds a specified within the .
///
///
/// Rather than storing a dictionary of keys, we can abuse the fact that the is stored in order of ascending block key.
///
/// Binary Search doesn't require extra memory like a Dictionary would; also, we usually only need to find a few blocks.
///
/// Index-able collection
/// to find.
/// Returns -1 if no match found.
private static int FindIndex(IReadOnlyList array, uint key)
{
int min = 0;
int max = array.Count - 1;
do
{
int mid = min + ((max - min) >> 1);
var entry = array[mid];
var ek = entry.Key;
if (key == ek)
return mid;
if (key < ek)
max = mid - 1;
else
min = mid + 1;
} while (min <= max);
return -1;
}
#endregion
}