using ZB.MOM.WW.CBDD.Bson;
using System;
using System.Linq;
namespace ZB.MOM.WW.CBDD.Core.Indexing;
///
/// Represents a key in an index.
/// Implemented as struct for efficient index operations.
/// Note: Contains byte array so cannot be readonly struct.
///
public struct IndexKey : IEquatable, IComparable
{
private readonly byte[] _data;
private readonly int _hashCode;
///
/// Gets the minimum possible index key.
///
public static IndexKey MinKey => new IndexKey(Array.Empty());
///
/// Gets the maximum possible index key.
///
public static IndexKey MaxKey => new IndexKey(Enumerable.Repeat((byte)0xFF, 32).ToArray());
///
/// Initializes a new instance of the struct from raw key bytes.
///
/// The key bytes.
public IndexKey(ReadOnlySpan data)
{
_data = data.ToArray();
_hashCode = ComputeHashCode(data);
}
///
/// Initializes a new instance of the struct from an object identifier.
///
/// The object identifier value.
public IndexKey(ObjectId objectId)
{
_data = new byte[12];
objectId.WriteTo(_data);
_hashCode = ComputeHashCode(_data);
}
///
/// Initializes a new instance of the struct from a 32-bit integer.
///
/// The integer value.
public IndexKey(int value)
{
_data = BitConverter.GetBytes(value);
_hashCode = ComputeHashCode(_data);
}
///
/// Initializes a new instance of the struct from a 64-bit integer.
///
/// The integer value.
public IndexKey(long value)
{
_data = BitConverter.GetBytes(value);
_hashCode = ComputeHashCode(_data);
}
///
/// Initializes a new instance of the struct from a string.
///
/// The string value.
public IndexKey(string value)
{
_data = System.Text.Encoding.UTF8.GetBytes(value);
_hashCode = ComputeHashCode(_data);
}
///
/// Initializes a new instance of the struct from a GUID.
///
/// The GUID value.
public IndexKey(Guid value)
{
_data = value.ToByteArray();
_hashCode = ComputeHashCode(_data);
}
///
/// Gets the raw byte data for this key.
///
public readonly ReadOnlySpan Data => _data;
///
/// Compares this key to another key.
///
/// The key to compare with.
///
/// A value less than zero if this key is less than , zero if equal, or greater than zero if greater.
///
public readonly int CompareTo(IndexKey other)
{
if (_data == null) return other._data == null ? 0 : -1;
if (other._data == null) return 1;
var minLength = Math.Min(_data.Length, other._data.Length);
for (int i = 0; i < minLength; i++)
{
var cmp = _data[i].CompareTo(other._data[i]);
if (cmp != 0)
return cmp;
}
return _data.Length.CompareTo(other._data.Length);
}
///
/// Determines whether this key equals another key.
///
/// The key to compare with.
/// if the keys are equal; otherwise, .
public readonly bool Equals(IndexKey other)
{
if (_hashCode != other._hashCode)
return false;
if (_data == null) return other._data == null;
if (other._data == null) return false;
return _data.AsSpan().SequenceEqual(other._data);
}
///
public override readonly bool Equals(object? obj) => obj is IndexKey other && Equals(other);
///
public override readonly int GetHashCode() => _hashCode;
public static bool operator ==(IndexKey left, IndexKey right) => left.Equals(right);
public static bool operator !=(IndexKey left, IndexKey right) => !left.Equals(right);
public static bool operator <(IndexKey left, IndexKey right) => left.CompareTo(right) < 0;
public static bool operator >(IndexKey left, IndexKey right) => left.CompareTo(right) > 0;
public static bool operator <=(IndexKey left, IndexKey right) => left.CompareTo(right) <= 0;
public static bool operator >=(IndexKey left, IndexKey right) => left.CompareTo(right) >= 0;
private static int ComputeHashCode(ReadOnlySpan data)
{
var hash = new HashCode();
hash.AddBytes(data);
return hash.ToHashCode();
}
///
/// Creates an from a supported CLR value.
///
/// The CLR type of the value.
/// The value to convert.
/// The created index key.
public static IndexKey Create(T value)
{
if (value == null) return default;
if (typeof(T) == typeof(ObjectId)) return new IndexKey((ObjectId)(object)value);
if (typeof(T) == typeof(int)) return new IndexKey((int)(object)value);
if (typeof(T) == typeof(long)) return new IndexKey((long)(object)value);
if (typeof(T) == typeof(string)) return new IndexKey((string)(object)value);
if (typeof(T) == typeof(Guid)) return new IndexKey((Guid)(object)value);
if (typeof(T) == typeof(byte[])) return new IndexKey((byte[])(object)value);
throw new NotSupportedException($"Type {typeof(T).Name} is not supported as an IndexKey. Provide a custom mapping.");
}
///
/// Converts this key to a CLR value of type .
///
/// The CLR type to read from this key.
/// The converted value.
public readonly T As()
{
if (_data == null) return default!;
if (typeof(T) == typeof(ObjectId)) return (T)(object)new ObjectId(_data);
if (typeof(T) == typeof(int)) return (T)(object)BitConverter.ToInt32(_data);
if (typeof(T) == typeof(long)) return (T)(object)BitConverter.ToInt64(_data);
if (typeof(T) == typeof(string)) return (T)(object)System.Text.Encoding.UTF8.GetString(_data);
if (typeof(T) == typeof(Guid)) return (T)(object)new Guid(_data);
if (typeof(T) == typeof(byte[])) return (T)(object)_data;
throw new NotSupportedException($"Type {typeof(T).Name} cannot be extracted from IndexKey. Provide a custom mapping.");
}
}