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 }