{%MainUnit generics.collections.pas}

{
    This file is part of the Free Pascal run time library.
    Copyright (c) 2014 by Maciej Izak (hnb)
    member of the Free Sparta development team (http://freesparta.com)

    Copyright(c) 2004-2014 DaThoX

    It contains the Free Pascal generics library

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}

{ TPair<TKey,TValue> }

class function TPair<TKey, TValue>.Create(AKey: TKey;
  AValue: TValue): TPair<TKey, TValue>;
begin
  Result.Key := AKey;
  Result.Value := AValue;
end;

{ TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS> }

procedure TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.PairNotify(constref APair: TPair<TKey, TValue>;
  ACollectionNotification: TCollectionNotification);
begin
  KeyNotify(APair.Key, ACollectionNotification);
  ValueNotify(APair.Value, ACollectionNotification);
end;

procedure TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.KeyNotify(constref AKey: TKey;
  ACollectionNotification: TCollectionNotification);
begin
  if Assigned(FOnKeyNotify) then
    FOnKeyNotify(Self, AKey, ACollectionNotification);
end;

procedure TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.SetValue(var AValue: TValue; constref ANewValue: TValue);
var
  LOldValue: TValue;
begin
  LOldValue := AValue;
  AValue := ANewValue;

  ValueNotify(LOldValue, cnRemoved);
  ValueNotify(ANewValue, cnAdded);
end;

procedure TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.ValueNotify(constref AValue: TValue;
  ACollectionNotification: TCollectionNotification);
begin
  if Assigned(FOnValueNotify) then
    FOnValueNotify(Self, AValue, ACollectionNotification);
end;

constructor TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.Create;
begin
  Create(0);
end;

constructor TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.Create(ACapacity: SizeInt); overload;
begin
  Create(ACapacity, TEqualityComparer<TKey>.Default(THashFactory));
end;

constructor TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.Create(ACapacity: SizeInt;
  const AComparer: IEqualityComparer<TKey>);
begin
  FEqualityComparer := AComparer;
  SetCapacity(ACapacity);
end;

constructor TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.Create(const AComparer: IEqualityComparer<TKey>);
begin
  Create(0, AComparer);
end;

constructor TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.Create(ACollection: TEnumerable<TDictionaryPair>);
begin
  Create(ACollection, TEqualityComparer<TKey>.Default(THashFactory));
end;

constructor TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.Create(ACollection: TEnumerable<TDictionaryPair>;
  const AComparer: IEqualityComparer<TKey>); overload;
var
  LItem: TPair<TKey, TValue>;
begin
  Create(AComparer);
  for LItem in ACollection do
    Add(LItem);
end;

destructor TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.Destroy;
begin
  Clear;
  FKeys.Free;
  FValues.Free;
  inherited;
end;

function TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.ToArray(ACount: SizeInt): TArray<TDictionaryPair>;
var
  i: SizeInt;
  LEnumerator: TEnumerator<TDictionaryPair>;
begin
  SetLength(Result, ACount);
  LEnumerator := DoGetEnumerator;

  i := 0;
  while LEnumerator.MoveNext do
  begin
    Result[i] := LEnumerator.Current;
    Inc(i);
  end;
  LEnumerator.Free;
end;

function TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>.ToArray: TArray<TDictionaryPair>;
begin
  Result := ToArray(Count);
end;

{ TCustomDictionaryEnumerator<T, CUSTOM_DICTIONARY_CONSTRAINTS> }

constructor TCustomDictionaryEnumerator<T, CUSTOM_DICTIONARY_CONSTRAINTS>.Create(
  ADictionary: TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>);
begin
  inherited Create;
  FIndex := -1;
  FDictionary := ADictionary;
end;

function TCustomDictionaryEnumerator<T, CUSTOM_DICTIONARY_CONSTRAINTS>.DoGetCurrent: T;
begin
  Result := GetCurrent;
end;

{ TDictionaryEnumerable<TDictionaryEnumerator, T, CUSTOM_DICTIONARY_CONSTRAINTS> }

constructor TDictionaryEnumerable<TDictionaryEnumerator, T, CUSTOM_DICTIONARY_CONSTRAINTS>.Create(
  ADictionary: TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>);
begin
  FDictionary := ADictionary;
end;

function TDictionaryEnumerable<TDictionaryEnumerator, T, CUSTOM_DICTIONARY_CONSTRAINTS>.
  DoGetEnumerator: TDictionaryEnumerator;
begin
  Result := TDictionaryEnumerator(TDictionaryEnumerator.NewInstance);
  TCustomDictionaryEnumerator<T, CUSTOM_DICTIONARY_CONSTRAINTS>(Result).Create(FDictionary);
end;

function TDictionaryEnumerable<TDictionaryEnumerator, T, CUSTOM_DICTIONARY_CONSTRAINTS>.GetCount: SizeInt;
begin
  Result := TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>(FDictionary).Count;
end;

function TDictionaryEnumerable<TDictionaryEnumerator, T, CUSTOM_DICTIONARY_CONSTRAINTS>.ToArray: TArray<T>;
begin
  Result := ToArrayImpl(FDictionary.Count);
end;

{ TOpenAddressingEnumerator<T, DICTIONARY_CONSTRAINTS> }

function TOpenAddressingEnumerator<T, OPEN_ADDRESSING_CONSTRAINTS>.DoMoveNext: Boolean;
var
  LLength: SizeInt;
begin
  Inc(FIndex);

  LLength := Length(TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>(FDictionary).FItems);

  if FIndex >= LLength then
    Exit(False);

  // maybe related to bug #24098
  // compiler error for (TDictionary<DICTIONARY_CONSTRAINTS>(FDictionary).FItems[FIndex].Hash and UInt32.GetSignMask) = 0
  while ((TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>(FDictionary).FItems[FIndex].Hash) and UInt32.GetSignMask) = 0 do
  begin
    Inc(FIndex);
    if FIndex = LLength then
      Exit(False);
  end;

  Result := True;
end;

{ TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS> }

constructor TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.Create(ACapacity: SizeInt;
  const AComparer: IEqualityComparer<TKey>);
begin
  inherited Create(ACapacity, AComparer);

  FMaxLoadFactor := TProbeSequence.DEFAULT_LOAD_FACTOR;
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.GetKeys: TKeyCollection;
begin
  if not Assigned(FKeys) then
    FKeys := TKeyCollection.Create(Self);
  Result := TKeyCollection(FKeys);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.GetValues: TValueCollection;
begin
  if not Assigned(FValues) then
    FValues := TValueCollection.Create(Self);
  Result := TValueCollection(FValues);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.FindBucketIndex(constref AKey: TKey): SizeInt;
var
  LHash: UInt32;
begin
  Result := FindBucketIndex(FItems, AKey, LHash);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.PrepareAddingItem: SizeInt;
begin
  if RealItemsLength > FItemsThreshold then
    Rehash(Length(FItems) shl 1)
  else if FItemsThreshold = 0 then
  begin
    SetLength(FItems, 8);
    UpdateItemsThreshold(8);
  end
  else if FItemsLength = $40000001 then // High(TIndex) ... Error: Type mismatch
    OutOfMemoryError;

  Result := FItemsLength;
  Inc(FItemsLength);
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.UpdateItemsThreshold(ASize: SizeInt);
begin
  if ASize = $40000000 then
    FItemsThreshold := $40000001
  else
    FItemsThreshold := Pred(Round(ASize * FMaxLoadFactor));
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.AddItem(var AItem: TItem; constref AKey: TKey;
  constref AValue: TValue; const AHash: UInt32);
begin
  AItem.Hash := AHash;
  AItem.Pair.Key := AKey;
  AItem.Pair.Value := AValue;

  PairNotify(AItem.Pair, cnAdded);
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.Add(constref AKey: TKey; constref AValue: TValue);
begin
  DoAdd(AKey, AValue);
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.Add(constref APair: TPair<TKey, TValue>);
begin
  DoAdd(APair.Key, APair.Value);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.DoAdd(constref AKey: TKey; constref AValue: TValue): SizeInt;
var
  LHash: UInt32;
begin
  PrepareAddingItem;

  Result := FindBucketIndex(FItems, AKey, LHash);
  if Result >= 0 then
    raise EListError.CreateRes(@SDuplicatesNotAllowed);

  Result := not Result;
  AddItem(FItems[Result], AKey, AValue, LHash);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.DoRemove(AIndex: SizeInt;
  ACollectionNotification: TCollectionNotification): TValue;
var
  LItem: PItem;
  LPair: TPair<TKey, TValue>;
begin
  LItem := @FItems[AIndex];
  LItem.Hash := 0;
  Result := LItem.Pair.Value;
  LPair := LItem.Pair;
  LItem.Pair := Default(TPair<TKey, TValue>);
  Dec(FItemsLength);
  PairNotify(LPair, ACollectionNotification);
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.Remove(constref AKey: TKey);
var
  LIndex: SizeInt;
begin
  LIndex := FindBucketIndex(AKey);
  if LIndex  < 0 then
    Exit;

  DoRemove(LIndex, cnRemoved);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.ExtractPair(constref AKey: TKey): TPair<TKey, TValue>;
var
  LIndex: SizeInt;
begin
  LIndex := FindBucketIndex(AKey);
  if LIndex  < 0 then
    Exit(Default(TPair<TKey, TValue>));

  Result.Key := AKey;
  Result.Value := DoRemove(LIndex, cnExtracted);
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.Clear;
var
  LItem: PItem;
  i: SizeInt;
  LOldItems: array of TItem;
begin
  FItemsLength := 0;
  FItemsThreshold := 0;
  // ClearTombstones;
  LOldItems := FItems;
  FItems := nil;

  for i := 0 to High(LOldItems) do
  begin
    LItem := @LOldItems[i];
    if (LItem.Hash and UInt32.GetSignMask = 0) then
      Continue;

    PairNotify(LItem.Pair, cnRemoved);
  end;
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.RealItemsLength: SizeInt;
begin
  Result := FItemsLength;
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.Rehash(ASizePow2: SizeInt; AForce: Boolean): Boolean;
var
  LNewItems: TArray<TItem>;
  LHash: UInt32;
  LIndex: SizeInt;
  i: SizeInt;
  LItem, LNewItem: PItem;
begin
  if (ASizePow2 = Length(FItems)) and not AForce then
    Exit(False);
  if ASizePow2 < 0 then
    OutOfMemoryError;

  SetLength(LNewItems, ASizePow2);
  UpdateItemsThreshold(ASizePow2);

  for i := 0 to High(FItems) do
  begin
    LItem := @FItems[i];

    if (LItem.Hash and UInt32.GetSignMask) <> 0 then
    begin
      LIndex := FindBucketIndex(LNewItems, LItem.Pair.Key, LHash);
      LIndex := not LIndex;

      LNewItem := @LNewItems[LIndex];
      LNewItem.Hash := LHash;
      LNewItem.Pair := LItem.Pair;
    end;
  end;

  FItems := LNewItems;
  Result := True;
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.DoGetEnumerator: TEnumerator<TDictionaryPair>;
begin
  Result := GetEnumerator;
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.SetCapacity(ACapacity: SizeInt);
begin
  if ACapacity < FItemsLength then
    raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);

  Resize(ACapacity);
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.SetMaxLoadFactor(AValue: single);
var
  LItemsLength: SizeInt;
begin
  if (AValue > TProbeSequence.MAX_LOAD_FACTOR) or (AValue <= 0) then
    raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);

  FMaxLoadFactor := AValue;

  repeat
    LItemsLength := Length(FItems);
    UpdateItemsThreshold(LItemsLength);
    if RealItemsLength > FItemsThreshold then
      Rehash(LItemsLength shl 1);
  until RealItemsLength <= FItemsThreshold;
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.GetLoadFactor: single;
begin
  Result := FItemsLength / Length(FItems);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.GetCapacity: SizeInt;
begin
  Result := Length(FItems);
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.Resize(ANewSize: SizeInt);
var
  LNewSize: SizeInt;
begin
  if ANewSize < 0 then
    raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);

  LNewSize := 0;
  if ANewSize > 0 then
  begin
    LNewSize := 8;
    while LNewSize < ANewSize do
      LNewSize := LNewSize shl 1;
  end;

  Rehash(LNewSize);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.GetEnumerator: TPairEnumerator;
begin
  Result := TPairEnumerator.Create(Self);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.GetItem(const AKey: TKey): TValue;
var
  LIndex: SizeInt;
begin
  LIndex := FindBucketIndex(AKey);
  if LIndex < 0 then
    raise EListError.CreateRes(@SDictionaryKeyDoesNotExist);
  Result := FItems[LIndex].Pair.Value;
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.TrimExcess;
begin
  SetCapacity(Succ(FItemsLength));
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.SetItem(const AKey: TKey; const AValue: TValue);
var
  LIndex: SizeInt;
begin
  LIndex := FindBucketIndex(AKey);
  if LIndex < 0 then
    raise EListError.CreateRes(@SItemNotFound);

  SetValue(FItems[LIndex].Pair.Value, AValue);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.TryGetValue(constref AKey: TKey; out AValue: TValue): Boolean;
var
  LIndex: SizeInt;
begin
  LIndex := FindBucketIndex(AKey);
  Result := LIndex >= 0;

  if Result then
    AValue := FItems[LIndex].Pair.Value
  else
    AValue := Default(TValue);
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.AddOrSetValue(constref AKey: TKey; constref AValue: TValue);
var
  LIndex: SizeInt;
  LHash: UInt32;
begin
  LIndex := FindBucketIndex(FItems, AKey, LHash);

  if LIndex < 0 then
    DoAdd(AKey, AValue)
  else
    SetValue(FItems[LIndex].Pair.Value, AValue);
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.ContainsKey(constref AKey: TKey): Boolean;
var
  LIndex: SizeInt;
begin
  LIndex := FindBucketIndex(AKey);
  Result := LIndex >= 0;
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.ContainsValue(constref AValue: TValue): Boolean;
begin
  Result := ContainsValue(AValue, TEqualityComparer<TValue>.Default(THashFactory));
end;

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.ContainsValue(constref AValue: TValue;
  const AEqualityComparer: IEqualityComparer<TValue>): Boolean;
var
  i: SizeInt;
  LItem: PItem;
begin
  if Length(FItems) = 0 then
    Exit(False);

  for i := 0 to High(FItems) do
  begin
    LItem := @FItems[i];
    if (LItem.Hash and UInt32.GetSignMask) = 0 then
      Continue;

    if AEqualityComparer.Equals(AValue, LItem.Pair.Value) then
      Exit(True);
  end;
  Result := False;
end;

procedure TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.GetMemoryLayout(
  const AOnGetMemoryLayoutKeyPosition: TOnGetMemoryLayoutKeyPosition);
var
  i: SizeInt;
begin
  for i := 0 to High(FItems) do
    if (FItems[i].Hash and UInt32.GetSignMask) <> 0 then
      AOnGetMemoryLayoutKeyPosition(Self, i);
end;

{ TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.TPairEnumerator }

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.TPairEnumerator.GetCurrent: TPair<TKey, TValue>;
begin
  Result := TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>(FDictionary).FItems[FIndex].Pair;
end;

{ TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.TValueEnumerator }

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.TValueEnumerator.GetCurrent: TValue;
begin
  Result := TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>(FDictionary).FItems[FIndex].Pair.Value;
end;

{ TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.TKeyEnumerator }

function TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.TKeyEnumerator.GetCurrent: TKey;
begin
  Result := TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>(FDictionary).FItems[FIndex].Pair.Key;
end;

{ TOpenAddressingLP<DICTIONARY_CONSTRAINTS> }

procedure TOpenAddressingLP<OPEN_ADDRESSING_CONSTRAINTS>.NotifyIndexChange(AFrom, ATo: SizeInt);
begin
end;

function TOpenAddressingLP<OPEN_ADDRESSING_CONSTRAINTS>.DoRemove(AIndex: SizeInt;
  ACollectionNotification: TCollectionNotification): TValue;
var
  LItem: PItem;
  LPair: TPair<TKey, TValue>;
  LLengthMask: SizeInt;
  i, LIndex, LGapIndex: SizeInt;
  LHash, LBucket: UInt32;
begin
  LItem := @FItems[AIndex];
  LPair := LItem.Pair;

  // try fill gap
  LHash := LItem.Hash;
  LItem.Hash := 0; // prevents an infinite searching loop
  LLengthMask := Length(FItems) - 1;
  i := Succ(AIndex - (LHash and LLengthMask));
  LGapIndex := AIndex;
  repeat
    LIndex := TProbeSequence.Probe(i, LHash) and LLengthMask;
    LItem := @FItems[LIndex];

    // Empty position
    if (LItem.Hash and UInt32.GetSignMask) = 0 then
      Break; // breaking bad!

    LBucket := LItem.Hash and LLengthMask;
    if not InCircularRange(LGapIndex, LBucket, LIndex) then
    begin
      NotifyIndexChange(LIndex, LGapIndex);
      FItems[LGapIndex] := LItem^;
      LItem.Hash := 0; // new gap
      LGapIndex := LIndex;
    end;
    Inc(i);
  until false;

  LItem := @FItems[LGapIndex];
  LItem.Hash := 0;
  LItem.Pair := Default(TPair<TKey, TValue>);
  Dec(FItemsLength);

  Result := LPair.Value;
  PairNotify(LPair, ACollectionNotification);
end;

function TOpenAddressingLP<OPEN_ADDRESSING_CONSTRAINTS>.FindBucketIndex(constref AItems: TArray<TItem>;
  constref AKey: TKey; out AHash: UInt32): SizeInt;
var
  LItem: {TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.}_TItem; // for workaround Lazarus bug #25613
  LLengthMask: SizeInt;
  i, m: SizeInt;
  LHash: UInt32;
begin
  m := Length(AItems);
  LLengthMask := m - 1;

  LHash := FEqualityComparer.GetHashCode(AKey);

  i := 0;
  AHash := LHash or UInt32.GetSignMask;

  if m = 0 then
    Exit(-1);

  Result := AHash and LLengthMask;

  repeat
    LItem := _TItem(AItems[Result]);

    // Empty position
    if (LItem.Hash and UInt32.GetSignMask) = 0 then
      Exit(not Result); // insert!

    // Same position?
    if LItem.Hash = AHash then
      if FEqualityComparer.Equals(AKey, LItem.Pair.Key) then
        Exit;

    Inc(i);

    Result := TProbeSequence.Probe(i, AHash) and LLengthMask;

  until false;
end;

{ TOpenAddressingTombstones<OPEN_ADDRESSING_CONSTRAINTS> }

function TOpenAddressingTombstones<OPEN_ADDRESSING_CONSTRAINTS>.Rehash(ASizePow2: SizeInt; AForce: Boolean): Boolean;
begin
  if inherited then
    FTombstonesCount := 0;
end;

function TOpenAddressingTombstones<OPEN_ADDRESSING_CONSTRAINTS>.RealItemsLength: SizeInt;
begin
  Result := FItemsLength + FTombstonesCount
end;

procedure TOpenAddressingTombstones<OPEN_ADDRESSING_CONSTRAINTS>.ClearTombstones;
begin
  Rehash(Length(FItems), True);
end;

procedure TOpenAddressingTombstones<OPEN_ADDRESSING_CONSTRAINTS>.Clear;
begin
  FTombstonesCount := 0;
  inherited;
end;

function TOpenAddressingTombstones<OPEN_ADDRESSING_CONSTRAINTS>.DoRemove(AIndex: SizeInt;
  ACollectionNotification: TCollectionNotification): TValue;
begin
  Result := inherited;

  FItems[AIndex].Hash := 1;
  Inc(FTombstonesCount);
end;

function TOpenAddressingTombstones<OPEN_ADDRESSING_CONSTRAINTS>.DoAdd(constref AKey: TKey;
  constref AValue: TValue): SizeInt;
var
  LHash: UInt32;
begin
  PrepareAddingItem;

  Result := FindBucketIndexOrTombstone(FItems, AKey, LHash);
  if Result >= 0 then
    raise EListError.CreateRes(@SDuplicatesNotAllowed);

  Result := not Result;
  // Can't ovverride because we lost info about old hash
  if FItems[Result].Hash <> 0 then
    Dec(FTombstonesCount);

  AddItem(FItems[Result], AKey, AValue, LHash);
end;

{ TOpenAddressingSH<OPEN_ADDRESSING_CONSTRAINTS> }

function TOpenAddressingSH<OPEN_ADDRESSING_CONSTRAINTS>.FindBucketIndex(constref AItems: TArray<TItem>;
  constref AKey: TKey; out AHash: UInt32): SizeInt;
var
  LItem: {TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.}_TItem; // for workaround Lazarus bug #25613
  LLengthMask: SizeInt;
  i, m: SizeInt;
  LHash: UInt32;
begin
  m := Length(AItems);
  LLengthMask := m - 1;

  LHash := FEqualityComparer.GetHashCode(AKey);

  i := 0;
  AHash := LHash or UInt32.GetSignMask;

  if m = 0 then
    Exit(-1);

  Result := AHash and LLengthMask;

  repeat
    LItem := _TItem(AItems[Result]);
    // Empty position
    if LItem.Hash = 0 then
      Exit(not Result); // insert!

    // Same position?
    if LItem.Hash = AHash then
      if FEqualityComparer.Equals(AKey, LItem.Pair.Key) then
        Exit;

    Inc(i);

    Result := TProbeSequence.Probe(i, AHash) and LLengthMask;

  until false;
end;

function TOpenAddressingSH<OPEN_ADDRESSING_CONSTRAINTS>.FindBucketIndexOrTombstone(constref AItems: TArray<TItem>;
  constref AKey: TKey; out AHash: UInt32): SizeInt;
var
  LItem: {TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.}_TItem; // for workaround Lazarus bug #25613
  LLengthMask: SizeInt;
  i, m: SizeInt;
  LHash: UInt32;
begin
  m := Length(AItems);
  LLengthMask := m - 1;

  LHash := FEqualityComparer.GetHashCode(AKey);

  i := 0;
  AHash := LHash or UInt32.GetSignMask;

  if m = 0 then
    Exit(-1);

  Result := AHash and LLengthMask;

  repeat
    LItem := _TItem(AItems[Result]);

    // Empty position or tombstone
    if LItem.Hash and UInt32.GetSignMask = 0 then
      Exit(not Result); // insert!

    // Same position?
    if LItem.Hash = AHash then
      if FEqualityComparer.Equals(AKey, LItem.Pair.Key) then
        Exit;

    Inc(i);

    Result := TProbeSequence.Probe(i, AHash) and LLengthMask;

  until false;
end;

{ TOpenAddressingQP<OPEN_ADDRESSING_CONSTRAINTS> }

procedure TOpenAddressingQP<OPEN_ADDRESSING_CONSTRAINTS>.UpdateItemsThreshold(ASize: SizeInt);
begin
  if ASize = $40000000 then
    FItemsThreshold := $40000001
  else
    begin
      FPrimaryNumberAsSizeApproximation := PrimaryNumbersJustLessThanPowerOfTwo[
        MultiplyDeBruijnBitPosition[UInt32(((ASize and -ASize) * $077CB531)) shr 27]];

      FItemsThreshold := Pred(Round(FPrimaryNumberAsSizeApproximation * FMaxLoadFactor));
    end;
end;

function TOpenAddressingQP<OPEN_ADDRESSING_CONSTRAINTS>.FindBucketIndex(constref AItems: TArray<TItem>;
  constref AKey: TKey; out AHash: UInt32): SizeInt;
var
  LItem: {TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.}_TItem; // for workaround Lazarus bug #25613
  i: SizeInt;
  LHash: UInt32;
begin
  LHash := FEqualityComparer.GetHashCode(AKey);

  i := 0;
  AHash := LHash or UInt32.GetSignMask;

  if Length(AItems) = 0 then
    Exit(-1);

  for i := 0 to FPrimaryNumberAsSizeApproximation - 1 do
  begin
    Result := TProbeSequence.Probe(i, AHash) mod FPrimaryNumberAsSizeApproximation;
    LItem := _TItem(AItems[Result]);

    // Empty position
    if LItem.Hash = 0 then
      Exit(not Result); // insert!

    // Same position?
    if LItem.Hash = AHash then
      if FEqualityComparer.Equals(AKey, LItem.Pair.Key) then
        Exit;
  end;

  Result := -1;
end;


function TOpenAddressingQP<OPEN_ADDRESSING_CONSTRAINTS>.FindBucketIndexOrTombstone(constref AItems: TArray<TItem>;
  constref AKey: TKey; out AHash: UInt32): SizeInt;
var
  LItem: {TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.}_TItem; // for workaround Lazarus bug #25613
  i: SizeInt;
  LHash: UInt32;
begin
  LHash := FEqualityComparer.GetHashCode(AKey);

  i := 0;
  AHash := LHash or UInt32.GetSignMask;

  if Length(AItems) = 0 then
    Exit(-1);

  for i := 0 to FPrimaryNumberAsSizeApproximation - 1 do
  begin
    Result := TProbeSequence.Probe(i, AHash) mod FPrimaryNumberAsSizeApproximation;
    LItem := _TItem(AItems[Result]);

    // Empty position or tombstone
    if LItem.Hash and UInt32.GetSignMask = 0 then
      Exit(not Result); // insert!

    // Same position?
    if LItem.Hash = AHash then
      if FEqualityComparer.Equals(AKey, LItem.Pair.Key) then
        Exit;
  end;

  Result := -1;
end;

{ TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS> }

constructor TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.Create(ACapacity: SizeInt;
  const AComparer: IEqualityComparer<TKey>);
begin
end;

constructor TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.Create(const AComparer: IEqualityComparer<TKey>);
begin
end;

constructor TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.Create(ACollection: TEnumerable<TDictionaryPair>;
  const AComparer: IEqualityComparer<TKey>);
begin
end;

constructor TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.Create(ACapacity: SizeInt);
begin
  Create(ACapacity, TExtendedEqualityComparer<TKey>.Default(THashFactory));
end;

constructor TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.Create(ACollection: TEnumerable<TDictionaryPair>);
begin
  Create(ACollection, TExtendedEqualityComparer<TKey>.Default(THashFactory));
end;

constructor TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.Create(ACapacity: SizeInt;
  const AComparer: IExtendedEqualityComparer<TKey>);
begin
  FMaxLoadFactor := TProbeSequence.DEFAULT_LOAD_FACTOR;
  FEqualityComparer := AComparer;
  SetCapacity(ACapacity);
end;

constructor TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.Create(const AComparer: IExtendedEqualityComparer<TKey>);
begin
  Create(0, AComparer);
end;

constructor TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.Create(ACollection: TEnumerable<TDictionaryPair>;
  const AComparer: IExtendedEqualityComparer<TKey>);
var
  LItem: TPair<TKey, TValue>;
begin
  Create(AComparer);
  for LItem in ACollection do
    Add(LItem);
end;

procedure TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.UpdateItemsThreshold(ASize: SizeInt);
begin
  inherited;
  R :=
    PrimaryNumbersJustLessThanPowerOfTwo[
      MultiplyDeBruijnBitPosition[UInt32(((ASize and -ASize) * $077CB531)) shr 27]]
end;

function TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.FindBucketIndex(constref AItems: TArray<TItem>;
  constref AKey: TKey; out AHash: UInt32): SizeInt;
var
  LItem: {TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.}_TItem; // for workaround Lazarus bug #25613
  LLengthMask: SizeInt;
  i, m: SizeInt;
  LHash: array[-1..1] of UInt32;
  LHash1: UInt32 absolute LHash[0];
  LHash2: UInt32 absolute LHash[1];
begin
  m := Length(AItems);
  LLengthMask := m - 1;
  LHash[-1] := 2; // number of hashes

  IExtendedEqualityComparer<TKey>(FEqualityComparer).GetHashList(AKey, @LHash[-1]);

  i := 0;
  AHash := LHash1 or UInt32.GetSignMask;

  if m = 0 then
    Exit(-1);

  Result := LHash1 and LLengthMask;
  // second hash function must be special
  LHash2 := (R - (LHash2 mod R)) or 1;

  repeat
    LItem := _TItem(AItems[Result]);

    // Empty position
    if LItem.Hash = 0 then
      Exit(not Result);

    // Same position?
    if LItem.Hash = AHash then
      if FEqualityComparer.Equals(AKey, LItem.Pair.Key) then
        Exit;

    Inc(i);

    Result := TProbeSequence.Probe(i, AHash, LHash2) and LLengthMask;
  until false;
end;

function TOpenAddressingDH<OPEN_ADDRESSING_CONSTRAINTS>.FindBucketIndexOrTombstone(constref AItems: TArray<TItem>;
  constref AKey: TKey; out AHash: UInt32): SizeInt;
var
  LItem: {TOpenAddressing<OPEN_ADDRESSING_CONSTRAINTS>.}_TItem; // for workaround Lazarus bug #25613
  LLengthMask: SizeInt;
  i, m: SizeInt;
  LHash: array[-1..1] of UInt32;
  LHash1: UInt32 absolute LHash[0];
  LHash2: UInt32 absolute LHash[1];
begin
  m := Length(AItems);
  LLengthMask := m - 1;
  LHash[-1] := 2; // number of hashes

  IExtendedEqualityComparer<TKey>(FEqualityComparer).GetHashList(AKey, @LHash[-1]);

  i := 0;
  AHash := LHash1 or UInt32.GetSignMask;

  if m = 0 then
    Exit(-1);

  Result := LHash1 and LLengthMask;
  // second hash function must be special
  LHash2 := (R - (LHash2 mod R)) or 1;

  repeat
    LItem := _TItem(AItems[Result]);

    // Empty position or tombstone
    if LItem.Hash and UInt32.GetSignMask = 0 then
      Exit(not Result);

    // Same position?
    if LItem.Hash = AHash then
      if FEqualityComparer.Equals(AKey, LItem.Pair.Key) then
        Exit;

    Inc(i);

    Result := TProbeSequence.Probe(i, AHash, LHash2) and LLengthMask;
  until false;
end;

{ TDeamortizedDArrayCuckooMapEnumerator<T, CUCKOO_CONSTRAINTS> }

constructor TDeamortizedDArrayCuckooMapEnumerator<T, CUCKOO_CONSTRAINTS>.Create(
  ADictionary: TCustomDictionary<CUSTOM_DICTIONARY_CONSTRAINTS>);
begin
  inherited;
  if ADictionary.Count = 0 then
    FMainIndex := TCuckooCfg.D
  else
    FMainIndex := 0;
end;

function TDeamortizedDArrayCuckooMapEnumerator<T, CUCKOO_CONSTRAINTS>.DoMoveNext: Boolean;
var
  LLength: SizeInt;
  LArray: TItemsArray;
begin
  Inc(FIndex);

  if (FMainIndex = TCuckooCfg.D) then // queue
  begin
    LLength := Length(TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>(FDictionary).FQueue.FItems);
    if FIndex >= LLength then
      Exit(False);

    while ((TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>(FDictionary).FQueue.FItems[FIndex].Hash)
      and UInt32.GetSignMask) = 0 do
    begin
      Inc(FIndex);
      if FIndex = LLength then
        Exit(False);
    end;
  end
  else // d-array
  begin
    LArray := TItemsArray(TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>(FDictionary).FItems[FMainIndex]);
    LLength := Length(LArray);
    if FIndex >= LLength then
    begin
      Inc(FMainIndex);
      FIndex := -1;
      Exit(DoMoveNext);
    end;

    while ((LArray[FIndex].Hash) and UInt32.GetSignMask) = 0 do
    begin
      Inc(FIndex);
      if FIndex = LLength then
      begin
        Inc(FMainIndex);
        FIndex := -1;
        Exit(DoMoveNext);
      end;
    end;
  end;

  Result := True;
end;

{ TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS> }

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TQueueDictionary.Rehash(ASizePow2: SizeInt;
  AForce: boolean): Boolean;
var
  FOldIdx: array of TKey;
  i: SizeInt;
begin
  SetLength(FOldIdx, FIdx.Count);
  for i := 0 to FIdx.Count - 1 do
    FOldIdx[i] := FItems[FIdx[i]].Pair.Key;

  Result := inherited Rehash(ASizePow2, AForce);

  for i := 0 to FIdx.Count - 1 do
    FIdx[i] := FindBucketIndex(FOldIdx[i]);
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TQueueDictionary.NotifyIndexChange(AFrom, ATo: SizeInt);
var
  i: SizeInt;
begin
  // notify change position
  for i := 0 to FIdx.Count-1 do
    if FIdx[i] = AFrom then
    begin
      FIdx[i] := ATo;
      Exit;
    end;
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TQueueDictionary.InsertIntoBack(AItem: Pointer);
//var
//  LItem: TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.PItem; absolute AItem; !!! bug #25917
var
  LItem: TQueueDictionary.PValue absolute AItem;
  LIndex: SizeInt;
begin
  LIndex := DoAdd(LItem.Pair.Key, LItem^);
  FIdx.Insert(0, LIndex);
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TQueueDictionary.InsertIntoHead(AItem: Pointer);
//var
//  LItem: TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.PItem absolute AItem; !!! bug #25917
var
  LItem: TQueueDictionary.PValue absolute AItem;
  LIndex: SizeInt;
begin
  LIndex := DoAdd(LItem.Pair.Key, LItem^);
  FIdx.Add(LIndex);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TQueueDictionary.IsEmpty: Boolean;
begin
  Result := FIdx.Count = 0;
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TQueueDictionary.Pop: Pointer;
var
  AIndex, LGap: SizeInt;
  //LResult: TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TItem; !!!bug #25917
begin
  AIndex := FIdx.DoRemove(FIdx.Count - 1, cnExtracted);

  Result := New(TQueueDictionary.PValue);
  TQueueDictionary.PValue(Result)^ := DoRemove(AIndex, cnExtracted);
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TQueueDictionary.Create(ACapacity: SizeInt;
  const AComparer: IEqualityComparer<TKey>);
begin
  FIdx := TList<UInt32>.Create;
  inherited Create(ACapacity, AComparer);
end;

destructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TQueueDictionary.Destroy;
begin
  FIdx.Free;
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.GetQueueCount: SizeInt;
begin
  Result := FQueue.Count;
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(ACapacity: SizeInt;
  const AComparer: IEqualityComparer<TKey>);
begin
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(const AComparer: IEqualityComparer<TKey>);
begin
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(ACollection: TEnumerable<TDictionaryPair>;
  const AComparer: IEqualityComparer<TKey>);
begin
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create;
begin
  Create(0);
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(ACapacity: SizeInt);
begin
  Create(ACapacity, TExtendedEqualityComparer<TKey>.Default(THashFactory));
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(ACollection: TEnumerable<TDictionaryPair>);
begin
  Create(ACollection, TExtendedEqualityComparer<TKey>.Default(THashFactory));
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(ACapacity: SizeInt;
  const AComparer: IExtendedEqualityComparer<TKey>);
begin
  FMaxLoadFactor := TCuckooCfg.MAX_LOAD_FACTOR;
  FQueue := TQueueDictionary.Create;
  FCDM   := TCDM.Create;

  // to do - check constraint consts

  if TCuckooCfg.D > THashFactory.MAX_HASHLIST_COUNT then
    raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);

  // should be moved to class constructor, but bug #24848
  CUCKOO_SIGN := UInt32.GetSizedSignMask(THashFactory.HASH_FUNCTIONS_MASK_SIZE + 1);
  CUCKOO_INDEX_SIZE := UInt32.GetBitsLength - (THashFactory.HASH_FUNCTIONS_MASK_SIZE + 1);
  CUCKOO_HASH_SIGN := THashFactory.HASH_FUNCTIONS_MASK shl CUCKOO_INDEX_SIZE;

  FEqualityComparer := AComparer;
  SetCapacity(ACapacity);
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(const AComparer: IExtendedEqualityComparer<TKey>);
begin
  Create(0, AComparer);
end;

constructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(ACollection: TEnumerable<TDictionaryPair>;
  const AComparer: IExtendedEqualityComparer<TKey>);
var
  LItem: TPair<TKey, TValue>;
begin
  Create(AComparer);
  for LItem in ACollection do
    Add(LItem);
end;

destructor TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Destroy;
begin
  inherited;
  FQueue.Free;
  FCDM.Free;
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.GetKeys: TKeyCollection;
begin
  if not Assigned(FKeys) then
    FKeys := TKeyCollection.Create(Self);
  Result := TKeyCollection(FKeys);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.GetValues: TValueCollection;
begin
  if not Assigned(FValues) then
    FValues := TValueCollection.Create(Self);
  Result := TValueCollection(FValues);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Lookup(constref AKey: TKey;
  var AHashListOrIndex: PUInt32): SizeInt;
begin
  Result := Lookup(FItems, AKey, AHashListOrIndex);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Lookup(constref AItems: TItemsDArray; constref AKey: TKey;
  var AHashListOrIndex: PUInt32): SizeInt;
var
  LLengthMask: SizeInt;
  i, j, k: SizeInt;
  AHashList: PUInt32 absolute AHashListOrIndex;
  AHashListParams: PUInt16 absolute AHashListOrIndex;
  AIndex: PtrInt absolute AHashListOrIndex;
  // LBloomFilter: UInt32; // to rethink. now is useless
begin
  if Length(AItems[0]) = 0 then
    Exit(LR_NIL);

  LLengthMask := Length(AItems[0]) - 1;
  AHashListParams[0] := TCuckooCfg.D; // number of hashes

  i := 1; // ineks iteracji iteracji haszy
  k := 1; // indeks iteracji haszy
  // LBloomFilter := 0;
  repeat
    AHashListParams[1] := i; // iteration
    IExtendedEqualityComparer<TKey>(FEqualityComparer).GetHashList(AKey, AHashList);
    for j := 0 to THashFactory.HASHLIST_COUNT_PER_FUNCTION[i] - 1 do
    begin
      AHashList[k] := AHashList[k] or CUCKOO_SIGN;
      // LBloomFilter := LBloomFilter or AHashList[k];

      with AItems[k-1][AHashList[k] and LLengthMask] do
        if (Hash and UInt32.GetSignMask) <> 0 then
          if (AHashList[k] = Hash or CUCKOO_SIGN) and FEqualityComparer.Equals(AKey, Pair.Key) then
            Exit(k-1);

      Inc(k);
    end;
    Inc(i);
  until k > TCuckooCfg.D;

  i := FQueue.FindBucketIndex(AKey);
  if i >= 0 then
  begin
    AIndex := i;
    Exit(LR_QUEUE);
  end;

{  LBloomFilter := not LBloomFilter;
  for i := 0 to FDicQueueList.Count - 1 do
    // with FQueue[i] do
    if LBloomFilter and FQueue[i].Hash = 0 then
      for j := 1 to TCuckooCfg.D do
        if (FQueue[i].Hash or CUCKOO_SIGN = AHashList[j]) then
          if FEqualityComparer.Equals(AKey, FQueue[i].Pair.Key) then
          begin
            AIndex := i;
            Exit(LR_QUEUE);
          end;     }

  Result := LR_NIL;
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.PrepareAddingItem: SizeInt;
var
  i: SizeInt;
begin
  if FItemsLength > FItemsThreshold then
    Rehash(Length(FItems[0]) shl 1)
  else if FItemsThreshold = 0 then
  begin
    for i := 0 to TCuckooCfg.D - 1 do
      SetLength(FItems[i], 4);
    UpdateItemsThreshold(4);
  end
  else if FItemsLength = $40000001 then // High(TIndex) ... Error: Type mismatch
    OutOfMemoryError;

  Result := FItemsLength;
  Inc(FItemsLength);
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.UpdateItemsThreshold(ASize: SizeInt);
var
  LLength: SizeInt;
begin
  LLength := ASize*TCuckooCfg.D;
  if LLength = $40000000 then
    FItemsThreshold := $40000001
  else
    FItemsThreshold := Pred(Round(LLength * FMaxLoadFactor));
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.AddItem(constref AItems: TItemsDArray; constref AKey: TKey;
  constref AValue: TValue; const AHashList: PUInt32);
var
  LNewItem: TItem;
  LPNewItem: PItem;
  y: boolean = false;
  b: UInt32;
  LIndex: UInt32;
  i, j, LLengthMask: SizeInt;
  LTempItem: TItem;
  LHashList: array[0..1] of UInt32;
  LHashListParams: array[0..3] of UInt16 absolute LHashList;
begin
  LLengthMask := Length(AItems[0]) - 1;

  LNewItem.Pair.Key := AKey;
  LNewItem.Pair.Value := AValue;
  // by concept already sign bit is set
  LNewItem.Hash := ((not CUCKOO_HASH_SIGN) and AHashList[1]) or UInt32.GetSignMask; // start at array [0]
  FQueue.InsertIntoBack(@LNewItem);

  for i := 0 to TCuckooCfg.L - 1 do
  begin
    if not y then
      if FQueue.IsEmpty then
        Exit
      else
      begin
        LPNewItem := FQueue.Pop; // bug #25917 workaround
        LNewItem := LPNewItem^;
        Dispose(LPNewItem);
        b := (LNewItem.Hash and CUCKOO_HASH_SIGN) shr CUCKOO_INDEX_SIZE;
        y := true;
      end;
    LIndex := LNewItem.Hash and LLengthMask;
    if (AItems[b][LIndex].Hash and UInt32.GetSignMask) = 0 then // insert!
    begin
      AItems[b][LIndex] := LNewItem;
      FCDM.Clear;
      y := false;
    end
    else
    begin
      if FCDM.ContainsKey(LNewItem.Pair.Key) then // found second cycle
      begin
        FQueue.InsertIntoBack(@LNewItem);
        FCDM.Clear;
        y := false;
      end
      else
      begin
        LTempItem := AItems[b][LIndex];
        AItems[b][LIndex] := LNewItem;
        LNewItem.Hash := LNewItem.Hash or CUCKOO_SIGN;
        FCDM.AddOrSetValue(LNewItem.Pair.Key, EmptyRecord);

        LNewItem := LTempItem;
        b := b + 1;
        if b >= TCuckooCfg.D then
          b := 0;
        LHashListParams[0] := -Succ(b);
        IExtendedEqualityComparer<TKey>(FEqualityComparer).GetHashList(LNewItem.Pair.Key, @LHashList[0]);
        LNewItem.Hash := (LHashList[1] and not CUCKOO_SIGN) or (b shl CUCKOO_INDEX_SIZE) or UInt32.GetSignMask;
        //  y := True; // always true in this place
      end;
    end;
  end;
  if y then
    FQueue.InsertIntoHead(@LNewItem);
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.DoAdd(constref AKey: TKey; constref AValue: TValue;
  const AHashList: PUInt32);
begin
  AddItem(FItems, AKey, AValue, AHashList);
  KeyNotify(AKey, cnAdded);
  ValueNotify(AValue, cnAdded);
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Add(constref AKey: TKey; constref AValue: TValue);
var
  LHashList: array[0..TCuckooCfg.D] of UInt32;
  LHashListOrIndex: PUint32;
begin
  PrepareAddingItem;
  LHashListOrIndex := @LHashList[0];
  if Lookup(AKey, LHashListOrIndex) <> LR_NIL then
    raise EListError.CreateRes(@SDuplicatesNotAllowed);

  DoAdd(AKey, AValue, LHashListOrIndex);
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Add(constref APair: TPair<TKey, TValue>);
begin
  Add(APair.Key, APair.Value);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.DoRemove(const AHashListOrIndex: PUInt32;
  ALookupResult: SizeInt; ACollectionNotification: TCollectionNotification): TValue;
var
  LItem: PItem;
  LIndex: UInt32;
  LQueueIndex: SizeInt absolute AHashListOrIndex;
  LPair: TPair<TKey, TValue>;
begin
  case ALookupResult of
    LR_QUEUE:
      LPair := FQueue.FItems[LQueueIndex].Pair.Value.Pair;
    LR_NIL:
      raise ERangeError.Create(SItemNotFound);
  else
    LIndex := AHashListOrIndex[ALookupResult + 1] and (Length(FItems[0]) - 1);
    LItem := @FItems[ALookupResult][LIndex];
    LItem.Hash := 0;
    LPair := LItem.Pair;
    LItem.Pair := Default(TPair<TKey, TValue>);
  end;

  Result := LPair.Value;
  Dec(FItemsLength);
  if ALookupResult = LR_QUEUE then
  begin
    FQueue.FIdx.Remove(LQueueIndex);
    FQueue.DoRemove(LQueueIndex, cnRemoved);
  end;

  FCDM.Remove(LPair.Key); // item can exist in CDM

  PairNotify(LPair, ACollectionNotification);
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Remove(constref AKey: TKey);
var
  LHashList: array[0..TCuckooCfg.D] of UInt32;
  LHashListOrIndex: PUint32;
  LLookupResult: SizeInt;
begin
  LHashListOrIndex := @LHashList[0];
  LLookupResult := Lookup(AKey, LHashListOrIndex);
  if LLookupResult = LR_NIL then
    Exit;

  DoRemove(LHashListOrIndex, LLookupResult, cnRemoved);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.ExtractPair(constref AKey: TKey): TPair<TKey, TValue>;
var
  LHashList: array[0..TCuckooCfg.D] of UInt32;
  LHashListOrIndex: PUint32;
  LLookupResult: SizeInt;
begin
  LHashListOrIndex := @LHashList[0];
  LLookupResult := Lookup(AKey, LHashListOrIndex);
  if LLookupResult = LR_NIL then
    Exit(Default(TPair<TKey, TValue>));

  Result.Key := AKey;
  Result.Value := DoRemove(LHashListOrIndex, LLookupResult, cnExtracted);
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Clear;
var
  LItem: PItem;
  i, j: SizeInt;
  LOldItems: TItemsDArray;
  LOldQueueItems: TQueueDictionary.TItemsArray;
  LQueueItem: TQueueDictionary._TItem;
begin
  FItemsLength := 0;
  FItemsThreshold := 0;
  LOldItems := FItems;
  for i := 0 to TCuckooCfg.D - 1 do
    FItems[i] := nil;

  for i := 0 to TCuckooCfg.D - 1 do
  begin
    for j := 0 to High(LOldItems[0]) do
    begin
      LItem := @LOldItems[i][j];
      if (LItem.Hash and UInt32.GetSignMask <> 0) then
        PairNotify(LItem.Pair, cnRemoved);
    end;
  end;

  FCDM.Clear;

  // queue
  FQueue.FItemsLength := 0;
  FQueue.FItemsThreshold := 0;
  LOldQueueItems := FQueue.FItems;
  FQueue.FItems := nil;

  for i := 0 to High(LOldQueueItems) do
  begin
    LQueueItem := TQueueDictionary._TItem(LOldQueueItems[i]);
    if (LQueueItem.Hash and UInt32.GetSignMask = 0) then
      Continue;

    PairNotify(LQueueItem.Pair.Value.Pair, cnRemoved);
  end;
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Rehash(ASizePow2: SizeInt);
var
  LNewItems: TItemsDArray;
  LHash: UInt32;
  LIndex: SizeInt;
  i, j: SizeInt;
  LItem, LNewItem: PItem;
  LOldQueue: TQueueDictionary;
var
  LHashList: array[0..1] of UInt32;
  LHashListParams: array[0..3] of Int16 absolute LHashList;
begin
  if ASizePow2 = Length(FItems[0]) then
    Exit;
  if ASizePow2 < 0 then
    OutOfMemoryError;

  for i := 0 to TCuckooCfg.D - 1 do
    SetLength(LNewItems[i], ASizePow2);

  LHashListParams[0] := -1;

  // opportunity to clear the queue
  LOldQueue := FQueue;
  FCDM.Clear;
  FQueue := TQueueDictionary.Create;
  for i := 0 to LOldQueue.FIdx.Count - 1 do
  begin
   LItem := @LOldQueue.FItems[LOldQueue.FIdx[i]].Pair.Value;
   LHashList[1] := FEqualityComparer.GetHashCode(LItem.Pair.Key);
   AddItem(LNewItems, LItem.Pair.Key, LItem.Pair.Value, @LHashList[0]);
  end;
  LOldQueue.Free;

  // copy the old elements
  for i := 0 to TCuckooCfg.D - 1 do
    for j := 0 to High(FItems[0]) do
    begin
      LItem := @FItems[i][j];
      if (LItem.Hash and UInt32.GetSignMask) = 0 then
        Continue;

      // small optimization. most of items exist in table 0
      if LItem.Hash and CUCKOO_HASH_SIGN = 0 then
      begin
        LHashList[1] := LItem.Hash;
        AddItem(LNewItems, LItem.Pair.Key, LItem.Pair.Value, @LHashList[0]);
      end
      else
      begin
        LHashList[1] := FEqualityComparer.GetHashCode(LItem.Pair.Key);
        AddItem(LNewItems, LItem.Pair.Key, LItem.Pair.Value, @LHashList[0]);
      end;
    end;

  FItems := LNewItems;
  UpdateItemsThreshold(ASizePow2);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.DoGetEnumerator: TEnumerator<TDictionaryPair>;
begin
  Result := GetEnumerator;
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.SetCapacity(ACapacity: SizeInt);
begin
  if ACapacity < FItemsLength then
    raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);

  Resize(ACapacity);
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.SetMaxLoadFactor(AValue: single);
var
  LItemsLength: SizeInt;
begin
  if (AValue > TCuckooCfg.MAX_LOAD_FACTOR) or (AValue <= 0) then
    raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);

  FMaxLoadFactor := AValue;

  repeat
    LItemsLength := Length(FItems[0]);
    UpdateItemsThreshold(LItemsLength);
    if FItemsLength > FItemsThreshold then
      Rehash(LItemsLength shl 1);
  until FItemsLength <= FItemsThreshold;
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.GetLoadFactor: single;
begin
  Result := FItemsLength / (Length(FItems[0]) * TCuckooCfg.D);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.GetCapacity: SizeInt;
begin
  Result := Length(FItems[0]) * TCuckooCfg.D;
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Resize(ANewSize: SizeInt);
var
  LNewSize: SizeInt;
begin
  if ANewSize < 0 then
    raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);

  LNewSize := 0;
  if ANewSize > 0 then
  begin
    LNewSize := 4;
    while LNewSize * TCuckooCfg.D < ANewSize do
      LNewSize := LNewSize shl 1;
  end;

  Rehash(LNewSize);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.GetEnumerator: TPairEnumerator;
begin
  Result := TPairEnumerator.Create(Self);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.GetItem(const AKey: TKey): TValue;
var
  LHashList: array[0..TCuckooCfg.D] of UInt32;
  LHashListOrIndex: PUint32;
  LLookupResult: SizeInt;
  LIndex: UInt32;
begin
  LHashListOrIndex := @LHashList[0];
  LLookupResult := Lookup(AKey, LHashListOrIndex);

  case LLookupResult of
    LR_QUEUE:
      Result := FQueue.FItems[PtrInt(LHashListOrIndex)].Pair.Value.Pair.Value;
    LR_NIL:
      raise EListError.CreateRes(@SDictionaryKeyDoesNotExist);
  else
    LIndex := LHashListOrIndex[LLookupResult + 1] and (Length(FItems[0]) - 1);
    Result := FItems[LLookupResult][LIndex].Pair.Value;
  end;
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TrimExcess;
begin
  SetCapacity(Succ(FItemsLength));
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.SetItem(constref AValue: TValue;
  const AHashListOrIndex: PUInt32; ALookupResult: SizeInt);
var
  LIndex: UInt32;
begin
  case ALookupResult of
    LR_QUEUE:
      SetValue(FQueue.FItems[PtrInt(AHashListOrIndex)].Pair.Value.Pair.Value, AValue);
    LR_NIL:
      raise EListError.CreateRes(@SItemNotFound);
  else
    LIndex := AHashListOrIndex[ALookupResult + 1] and (Length(FItems[0]) - 1);
    SetValue(FItems[ALookupResult][LIndex].Pair.Value, AValue);
  end;
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.SetItem(const AKey: TKey; const AValue: TValue);
var
  LHashList: array[0..TCuckooCfg.D] of UInt32;
  LHashListOrIndex: PUint32;
  LLookupResult: SizeInt;
  LIndex: UInt32;
begin
  LHashListOrIndex := @LHashList[0];
  LLookupResult := Lookup(AKey, LHashListOrIndex);

  SetItem(AValue, LHashListOrIndex, LLookupResult);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TryGetValue(constref AKey: TKey; out AValue: TValue): Boolean;
var
  LHashList: array[0..TCuckooCfg.D] of UInt32;
  LHashListOrIndex: PUint32;
  LLookupResult: SizeInt;
  LIndex: UInt32;
begin
  LHashListOrIndex := @LHashList[0];
  LLookupResult := Lookup(AKey, LHashListOrIndex);

  Result := LLookupResult <> LR_NIL;

  case LLookupResult of
    LR_QUEUE:
      AValue := FQueue.FItems[PtrInt(LHashListOrIndex)].Pair.Value.Pair.Value;
    LR_NIL:
      AValue := Default(TValue);
  else
    LIndex := LHashListOrIndex[LLookupResult + 1] and (Length(FItems[0]) - 1);
    AValue := FItems[LLookupResult][LIndex].Pair.Value;
  end;
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.AddOrSetValue(constref AKey: TKey; constref AValue: TValue);
var
  LHashList: array[0..TCuckooCfg.D] of UInt32;
  LHashListOrIndex: PUint32;
  LLookupResult: SizeInt;
  LIndex: UInt32;
begin
  LHashListOrIndex := @LHashList[0];
  LLookupResult := Lookup(AKey, LHashListOrIndex);

  if LLookupResult = LR_NIL then
  begin
    PrepareAddingItem;
    DoAdd(AKey, AValue, LHashListOrIndex);
  end
  else
    SetItem(AValue, LHashListOrIndex, LLookupResult);
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.ContainsKey(constref AKey: TKey): Boolean;
var
  LHashList: array[0..TCuckooCfg.D] of UInt32;
  LHashListOrIndex: PUint32;
begin
  LHashListOrIndex := @LHashList[0];
  Result := Lookup(AKey, LHashListOrIndex) <> LR_NIL;
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.ContainsValue(constref AValue: TValue): Boolean;
begin
  Result := ContainsValue(AValue, TEqualityComparer<TValue>.Default(THashFactory));
end;

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.ContainsValue(constref AValue: TValue;
  const AEqualityComparer: IEqualityComparer<TValue>): Boolean;
var
  i, j: SizeInt;
  LItem: PItem;
begin
  if Length(FItems[0]) = 0 then
    Exit(False);

  for i := 0 to TCuckooCfg.D - 1 do
    for j := 0 to High(FItems[0]) do
    begin
      LItem := @FItems[i][j];
      if (LItem.Hash and UInt32.GetSignMask) = 0 then
        Continue;

      if AEqualityComparer.Equals(AValue, LItem.Pair.Value) then
        Exit(True);
    end;
  Result := False;
end;

procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.GetMemoryLayout(
  const AOnGetMemoryLayoutKeyPosition: TOnGetMemoryLayoutKeyPosition);
var
  i, j, k: SizeInt;
begin
  k := 0;
  for i := 0 to TCuckooCfg.D - 1 do
    for j := 0 to High(FItems[0]) do
    begin
      if FItems[i][j].Hash and UInt32.GetSignMask <> 0 then
        AOnGetMemoryLayoutKeyPosition(Self, k);
      inc(k);
    end;
end;

{ TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TPairEnumerator }

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TPairEnumerator.GetCurrent: TPair<TKey, TValue>;
begin
  if FMainIndex = TCuckooCfg.D then
    Result := TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>(FDictionary).FQueue.FItems[FIndex].Pair.Value.Pair
  else
    Result := TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>(FDictionary).FItems[FMainIndex][FIndex].Pair;
end;

{ TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TValueEnumerator }

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TValueEnumerator.GetCurrent: TValue;
begin
  if FMainIndex = TCuckooCfg.D then
    Result := TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>(FDictionary).FQueue.FItems[FIndex].Pair.Value.Pair.Value
  else
    Result := TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>(FDictionary).FItems[FMainIndex][FIndex].Pair.Value;
end;

{ TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TKeyEnumerator }

function TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TKeyEnumerator.GetCurrent: TKey;
begin
  if FMainIndex = TCuckooCfg.D then
    Result := TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>(FDictionary).FQueue.FItems[FIndex].Pair.Value.Pair.Key
  else
    Result := TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>(FDictionary).FItems[FMainIndex][FIndex].Pair.Key;
end;

{ TObjectDictionary<DICTIONARY_CONSTRAINTS> }

procedure TObjectDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.KeyNotify(
  constref AKey: TKey; ACollectionNotification: TCollectionNotification);
begin
  inherited;

  if (doOwnsKeys in FOwnerships) and (ACollectionNotification = cnRemoved) then
    TObject(AKey).Free;
end;

procedure TObjectDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.ValueNotify(constref AValue: TValue;
  ACollectionNotification: TCollectionNotification);
begin
  inherited;

  if (doOwnsValues in FOwnerships) and (ACollectionNotification = cnRemoved) then
    TObject(AValue).Free;
end;

constructor TObjectDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(
  AOwnerships: TDictionaryOwnerships);
begin
  Create(AOwnerships, 0);
end;

constructor TObjectDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(
  AOwnerships: TDictionaryOwnerships; ACapacity: SizeInt);
begin
  inherited Create(ACapacity);

  FOwnerships := AOwnerships;
end;

constructor TObjectDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(
  AOwnerships: TDictionaryOwnerships; const AComparer: IExtendedEqualityComparer<TKey>);
begin
  inherited Create(AComparer);

  FOwnerships := AOwnerships;
end;

constructor TObjectDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.Create(
  AOwnerships: TDictionaryOwnerships; ACapacity: SizeInt; const AComparer: IExtendedEqualityComparer<TKey>);
begin
  inherited Create(ACapacity, AComparer);

  FOwnerships := AOwnerships;
end;

procedure TObjectOpenAddressingLP<OPEN_ADDRESSING_CONSTRAINTS>.KeyNotify(
  constref AKey: TKey; ACollectionNotification: TCollectionNotification);
begin
  inherited;

  if (doOwnsKeys in FOwnerships) and (ACollectionNotification = cnRemoved) then
    TObject(AKey).Free;
end;

procedure TObjectOpenAddressingLP<OPEN_ADDRESSING_CONSTRAINTS>.ValueNotify(
  constref AValue: TValue; ACollectionNotification: TCollectionNotification);
begin
  inherited;

  if (doOwnsValues in FOwnerships) and (ACollectionNotification = cnRemoved) then
    TObject(AValue).Free;
end;

constructor TObjectOpenAddressingLP<OPEN_ADDRESSING_CONSTRAINTS>.Create(AOwnerships: TDictionaryOwnerships);
begin
  Create(AOwnerships, 0);
end;

constructor TObjectOpenAddressingLP<OPEN_ADDRESSING_CONSTRAINTS>.Create(AOwnerships: TDictionaryOwnerships;
  ACapacity: SizeInt);
begin
  inherited Create(ACapacity);

  FOwnerships := AOwnerships;
end;

constructor TObjectOpenAddressingLP<OPEN_ADDRESSING_CONSTRAINTS>.Create(AOwnerships: TDictionaryOwnerships;
  const AComparer: IEqualityComparer<TKey>);
begin
  inherited Create(AComparer);

  FOwnerships := AOwnerships;
end;

constructor TObjectOpenAddressingLP<OPEN_ADDRESSING_CONSTRAINTS>.Create(AOwnerships: TDictionaryOwnerships;
  ACapacity: SizeInt; const AComparer: IEqualityComparer<TKey>);
begin
  inherited Create(ACapacity, AComparer);

  FOwnerships := AOwnerships;
end;
