/// Framework Core Low-Level Data Processing Functions
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.core.data;

{
  *****************************************************************************

   Low-Level Data Processing Functions shared by all framework units
    - RTL TPersistent / TInterfacedObject with Custom Constructor
    - TSynPersistent* TSyn*List TSynLocker classes
    - TSynPersistentStore with proper Binary Serialization
    - INI Files and In-memory Access
    - Efficient RTTI Values Binary Serialization and Comparison
    - TDynArray and TDynArrayHashed Wrappers
    - Integer Arrays Extended Process
    - RawUtf8 String Values Interning and TRawUtf8List
    - Abstract Radix Tree Classes

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

interface

{$I ..\mormot.defines.inc}

uses
  classes,
  contnrs,
  types,
  sysutils,
  {$ifdef ISDELPHI}
  typinfo,  // circumvent Delphi inlining issues
  {$endif ISDELPHI}
  mormot.core.base,
  mormot.core.os,
  mormot.core.rtti,
  mormot.core.datetime,
  mormot.core.unicode,
  mormot.core.text,
  mormot.core.buffers;


{ ************ RTL TPersistent / TInterfacedObject with Custom Constructor }

type
    /// abstract parent class with a virtual constructor, ready to be overridden
  // to initialize the instance
  // - you can specify such a class if you need an object including published
  // properties (like TPersistent) with a virtual constructor (e.g. to
  // initialize some nested class properties)
  TPersistentWithCustomCreate = class(TPersistent)
  public
    /// this virtual constructor will be called at instance creation
    // - this constructor does nothing, but is declared as virtual so that
    // inherited classes may safely override this default void implementation
    constructor Create; virtual;
  end;

  {$M+}
  /// abstract parent class with threadsafe implementation of IInterface and
  // a virtual constructor
  // - you can specify e.g. such a class to TRestServer.ServiceRegister() if
  // you need an interfaced object with a virtual constructor, ready to be
  // overridden to initialize the instance
  TInterfacedObjectWithCustomCreate = class(TInterfacedObject)
  public
    /// this virtual constructor will be called at instance creation
    // - this constructor does nothing, but is declared as virtual so that
    // inherited classes may safely override this default void implementation
    constructor Create; virtual;
    /// used to mimic TInterfacedObject reference counting
    // - Release=true will call TInterfacedObject._Release
    // - Release=false will call TInterfacedObject._AddRef
    // - could be used to emulate proper reference counting of the instance
    // via interfaces variables, but still storing plain class instances
    // (e.g. in a global list of instances) - warning: use with extreme caution!
    procedure RefCountUpdate(Release: boolean); virtual;
  end;
  {$M-}


  /// an abstract ancestor, for implementing a custom TInterfacedObject like class
  // - by default, will do nothing: no instance would be retrieved by
  // QueryInterface unless the VirtualQueryInterface protected method is
  // overriden, and _AddRef/_Release methods would call VirtualAddRef and
  // VirtualRelease pure abstract methods
  // - using this class will leverage the signature difference between Delphi
  // and FPC, among all supported platforms
  // - the class includes a RefCount integer field
  TSynInterfacedObject = class(TObject, IUnknown)
  protected
    fRefCount: integer;
    // returns E_NOINTERFACE by default
    function VirtualQueryInterface(IID: PGuid; out Obj): TIntQry; virtual;
    // always return 1 for a "non allocated" instance (0 triggers release)
    function VirtualAddRef: integer;  virtual; abstract;
    function VirtualRelease: integer; virtual; abstract;
    function QueryInterface({$ifdef FPC_HAS_CONSTREF}constref{$else}const{$endif}
      IID: TGuid; out Obj): TIntQry; {$ifdef OSWINDOWS}stdcall{$else}cdecl{$endif};
    function _AddRef: TIntCnt;       {$ifdef OSWINDOWS}stdcall{$else}cdecl{$endif};
    function _Release: TIntCnt;      {$ifdef OSWINDOWS}stdcall{$else}cdecl{$endif};
  public
    /// this virtual constructor will be called at instance creation
    // - this constructor does nothing, but is declared as virtual so that
    // inherited classes may safely override this default void implementation
    constructor Create; virtual;
    /// the associated reference count
    property RefCount: integer
      read fRefCount write fRefCount;
  end;

  /// any TCollection used between client and server shall inherit from this class
  // - you should override the GetClass virtual method to provide the
  // expected collection item class to be used on server side
  // - another possibility is to register a TCollection/TCollectionItem pair
  // via a call to Rtti.RegisterCollection()
  TInterfacedCollection = class(TCollection)
  public
    /// you shall override this abstract method
    class function GetClass: TCollectionItemClass; virtual; abstract;
    /// this constructor will call GetClass to initialize the collection
    constructor Create; reintroduce; virtual;
  end;

  /// used to determine the exact class type of a TInterfacedObjectWithCustomCreate
  // - could be used to create instances using its virtual constructor
  TInterfacedObjectWithCustomCreateClass = class of TInterfacedObjectWithCustomCreate;

  /// used to determine the exact class type of a TPersistentWithCustomCreateClass
  // - could be used to create instances using its virtual constructor
  TPersistentWithCustomCreateClass = class of TPersistentWithCustomCreate;

  /// class-reference type (metaclass) of a TInterfacedCollection kind
  TInterfacedCollectionClass = class of TInterfacedCollection;


  /// interface for TAutoFree to register another TObject instance
  // to an existing IAutoFree local variable
  // - WARNING: both FPC and Delphi 10.4+ don't keep the IAutoFree instance
  // up to the end-of-method -> you should not use TAutoFree for new projects :(
  IAutoFree = interface
    procedure Another(var objVar; obj: TObject);
    /// do-nothing method to circumvent the Delphi 10.4 IAutoFree early release
    procedure ForMethod;
  end;

  /// simple reference-counted storage for local objects
  // - WARNING: both FPC and Delphi 10.4+ don't keep the IAutoFree instance
  // up to the end-of-method -> you should not use TAutoFree for new projects :(
  // - be aware that it won't implement a full ARC memory model, but may be
  // just used to avoid writing some try ... finally blocks on local variables
  // - use with caution, only on well defined local scope
  TAutoFree = class(TInterfacedObject, IAutoFree)
  protected
    fObject: TObject;
    fObjectList: array of TObject;
    // do-nothing method to circumvent the Delphi 10.4 IAutoFree early release
    procedure ForMethod;
  public
    /// initialize the TAutoFree class for one local variable
    // - do not call this constructor, but class function One() instead
    constructor Create(var localVariable; obj: TObject); reintroduce; overload;
    /// initialize the TAutoFree class for several local variables
    // - do not call this constructor, but class function Several() instead
    constructor Create(const varObjPairs: array of pointer); reintroduce; overload;
    /// protect one local TObject variable instance life time
    // - for instance, instead of writing:
    // !var
    // !  myVar: TMyClass;
    // !begin
    // !  myVar := TMyClass.Create;
    // !  try
    // !    ... use myVar
    // !  finally
    // !    myVar.Free;
    // !  end;
    // !end;
    // - you may write:
    // !var
    // !  myVar: TMyClass;
    // !begin
    // !  TAutoFree.One(myVar,TMyClass.Create);
    // !  ... use myVar
    // !end; // here myVar will be released
    // - warning: under FPC, you should assign the result of this method to a local
    // IAutoFree variable - see bug http://bugs.freepascal.org/view.php?id=26602
    // - Delphi 10.4 also did change it and release the IAutoFree before the
    // end of the current method, so we inlined a void method call trying to
    // circumvent this problem - https://quality.embarcadero.com/browse/RSP-30050
    // - for both Delphi 10.4+ and FPC, you may use with TAutoFree.One() do
    class function One(var localVariable; obj: TObject): IAutoFree;
      {$ifdef ISDELPHI104} inline; {$endif}
    /// protect several local TObject variable instances life time
    // - specified as localVariable/objectInstance pairs
    // - you may write:
    // !var
    // !  var1, var2: TMyClass;
    // !begin
    // !  TAutoFree.Several([
    // !    @var1,TMyClass.Create,
    // !    @var2,TMyClass.Create]);
    // !  ... use var1 and var2
    // !end; // here var1 and var2 will be released
    // - warning: under FPC, you should assign the result of this method to a local
    // IAutoFree variable - see bug http://bugs.freepascal.org/view.php?id=26602
    // - Delphi 10.4 also did change it and release the IAutoFree before the
    // end of the current method, and an "array of pointer" cannot be inlined
    // by the Delphi compiler, so you should explicitly call ForMethod:
    // !  TAutoFree.Several([
    // !    @var1,TMyClass.Create,
    // !    @var2,TMyClass.Create]).ForMethod;
    class function Several(const varObjPairs: array of pointer): IAutoFree;
    /// protect another TObject variable to an existing IAutoFree instance life time
    // - you may write:
    // !var
    // !  var1, var2: TMyClass;
    // !  auto: IAutoFree;
    // !begin
    // !  auto := TAutoFree.One(var1,TMyClass.Create);,
    // !  .... do something
    // !  auto.Another(var2,TMyClass.Create);
    // !  ... use var1 and var2
    // !end; // here var1 and var2 will be released
    procedure Another(var localVariable; obj: TObject);
    /// will finalize the associated TObject instances
    // - note that releasing the TObject instances won't be protected, so
    // any exception here may induce a memory leak: use only with "safe"
    // simple objects, e.g. mORMot's TOrm
    destructor Destroy; override;
  end;


  /// an interface used by TAutoLocker to protect multi-thread execution
  IAutoLocker = interface
    ['{97559643-6474-4AD3-AF72-B9BB84B4955D}']
    /// enter the mutex
    // - any call to Enter should be ended with a call to Leave, and
    // protected by a try..finally block, as such:
    // !begin
    // !  ... // unsafe code
    // !  fSharedAutoLocker.Enter;
    // !  try
    // !    ... // thread-safe code
    // !  finally
    // !    fSharedAutoLocker.Leave;
    // !  end;
    // !end;
    procedure Enter;
    /// leave the mutex
    // - any call to Leave should be preceded with a call to Enter
    procedure Leave;
    /// will enter the mutex until the IUnknown reference is released
    // - using an IUnknown interface to let the compiler auto-generate a
    // try..finally block statement to release the lock for the code block
    // - could be used as such under Delphi:
    // !begin
    // !  ... // unsafe code
    // !  fSharedAutoLocker.ProtectMethod;
    // !  ... // thread-safe code
    // !end; // local hidden IUnknown will release the lock for the method
    // - warning: under FPC, you should assign its result to a local variable -
    // see bug http://bugs.freepascal.org/view.php?id=26602
    // !var
    // !  LockFPC: IUnknown;
    // !begin
    // !  ... // unsafe code
    // !  LockFPC := fSharedAutoLocker.ProtectMethod;
    // !  ... // thread-safe code
    // !end; // LockFPC will release the lock for the method
    // or
    // !begin
    // !  ... // unsafe code
    // !  with fSharedAutoLocker.ProtectMethod do
    // !  begin
    // !    ... // thread-safe code
    // !  end; // local hidden IUnknown will release the lock for the method
    // !end;
    function ProtectMethod: IUnknown;
    /// gives an access to the internal low-level TSynLocker instance used
    function Safe: PSynLocker;
  end;

  /// reference-counted block code critical section
  // - you can use one instance of this to protect multi-threaded execution
  // - the main class may initialize a IAutoLocker property in Create, then call
  // IAutoLocker.ProtectMethod in any method to make its execution thread safe
  // - this class inherits from TInterfacedObjectWithCustomCreate so you
  // could define one published property of a mormot.core.interface.pas
  // TInjectableObject as IAutoLocker so that this class may be automatically
  // injected
  // - consider inherit from high-level TSynPersistentLock or call low-level
  // fSafe := NewSynLocker / fSafe^.DoneAndFreemem instead
  TAutoLocker = class(TInterfacedObjectWithCustomCreate, IAutoLocker)
  protected
    fSafe: TSynLocker;
  public
    /// initialize the mutex
    constructor Create; override;
    /// finalize the mutex
    destructor Destroy; override;
    /// will enter the mutex until the IUnknown reference is released
    // - as expected by IAutoLocker interface
    // - could be used as such under Delphi:
    // !begin
    // !  ... // unsafe code
    // !  fSharedAutoLocker.ProtectMethod;
    // !  ... // thread-safe code
    // !end; // local hidden IUnknown will release the lock for the method
    // - warning: under FPC, you should assign its result to a local variable -
    // see bug http://bugs.freepascal.org/view.php?id=26602
    // !var
    // !  LockFPC: IUnknown;
    // !begin
    // !  ... // unsafe code
    // !  LockFPC := fSharedAutoLocker.ProtectMethod;
    // !  ... // thread-safe code
    // !end; // LockFPC will release the lock for the method
    // or
    // !begin
    // !  ... // unsafe code
    // !  with fSharedAutoLocker.ProtectMethod do
    // !  begin
    // !    ... // thread-safe code
    // !  end; // local hidden IUnknown will release the lock for the method
    // !end;
    function ProtectMethod: IUnknown;
    /// enter the mutex
    // - as expected by IAutoLocker interface
    // - any call to Enter should be ended with a call to Leave, and
    // protected by a try..finally block, as such:
    // !begin
    // !  ... // unsafe code
    // !  fSharedAutoLocker.Enter;
    // !  try
    // !    ... // thread-safe code
    // !  finally
    // !    fSharedAutoLocker.Leave;
    // !  end;
    // !end;
    procedure Enter; virtual;
    /// leave the mutex
    // - as expected by IAutoLocker interface
    procedure Leave; virtual;
    /// access to the locking methods of this instance
    // - as expected by IAutoLocker interface
    function Safe: PSynLocker;
    /// direct access to the locking methods of this instance
    // - faster than IAutoLocker.Safe function
    property Locker: TSynLocker
      read fSafe;
  end;



{ ************ TSynPersistent* TSyn*List TSynLocker classes }

type
  /// our own empowered TPersistent-like parent class
  // - TPersistent has an unexpected speed overhead due a giant lock introduced
  // to manage property name fixup resolution (which we won't use outside the UI)
  // - this class has a virtual constructor, so is a preferred alternative
  // to both TPersistent and TPersistentWithCustomCreate classes
  // - features some protected methods to customize its JSON serialization
  // - for best performance, any type inheriting from this class will bypass
  // some regular steps: do not implement interfaces or use TMonitor with them!
  TSynPersistent = class(TObjectWithCustomCreate)
  protected
    // this default implementation will call AssignError()
    procedure AssignTo(Dest: TSynPersistent); virtual;
    procedure AssignError(Source: TSynPersistent);
  public
    /// allows to implement a TPersistent-like assignement mechanism
    // - inherited class should override AssignTo() protected method
    // to implement the proper assignment
    procedure Assign(Source: TSynPersistent); virtual;
  end;

  /// used to determine the exact class type of a TSynPersistent
  TSynPersistentClass = class of TSynPersistent;


  {$ifdef HASITERATORS}
  /// abstract pointer Enumerator
  TPointerEnumerator = record
  private
    Curr, After: PPointer;
    function GetCurrent: pointer; inline;
  public
    procedure Init(Values: PPointerArray; Count: PtrUInt); inline;
    function MoveNext: Boolean; inline;
    function GetEnumerator: TPointerEnumerator; inline;
    /// returns the current pointer value
    property Current: pointer
      read GetCurrent;
  end;
  {$endif HASITERATORS}

  {$M+}
  /// simple and efficient TList, without any notification
  // - regular TList has an internal notification mechanism which slows down
  // basic process, and can't be easily inherited
  // - stateless methods (like Add/Clear/Exists/Remove) are defined as virtual
  // since can be overriden e.g. by TSynObjectListLocked to add a TSynLocker
  TSynList = class(TObject)
  protected
    fCount: integer;
    fList: TPointerDynArray;
    function Get(index: integer): pointer;
      {$ifdef HASINLINE}inline;{$endif}
  public
    /// virtual constructor called at instance creation
    constructor Create; virtual;
    /// add one item to the list
    function Add(item: pointer): PtrInt; virtual;
    /// insert one item to the list at a given position
    function Insert(item: pointer; index: PtrInt): PtrInt;
    /// delete all items of the list
    procedure Clear; virtual;
    /// delete one item from the list
    procedure Delete(index: integer; dontfree: boolean = false); virtual;
    /// fast retrieve one item in the list
    function IndexOf(item: pointer): PtrInt; virtual;
    /// fast check if one item exists in the list
    function Exists(item: pointer): boolean; virtual;
    /// fast delete one item in the list
    function Remove(item: pointer): PtrInt; virtual;
    {$ifdef HASITERATORS}
    /// an enumerator able to compile "for .. in list do" statements
    function GetEnumerator: TPointerEnumerator;
    {$endif HASITERATORS}
    /// how many items are stored in this TList instance
    property Count: integer
      read fCount;
    /// low-level access to the items stored in this TList instance
    property List: TPointerDynArray
      read fList;
    /// low-level array-like access to the items stored in this TList instance
    // - warning: if index is out of range, will return nil and won't raise
    // any exception
    property Items[index: integer]: pointer
      read Get; default;
  end;
  {$M-}
  PSynList = ^TSynList;

  /// simple and efficient TObjectList, without any notification
  TSynObjectList = class(TSynList)
  protected
    fOwnObjects: boolean;
    fItemClass: TClass;
  public
    /// initialize the object list
    // - can optionally specify an item class for efficient JSON serialization
    constructor Create(aOwnObjects: boolean = true;
      aItemClass: TClass = nil); reintroduce; virtual;
    /// delete one object from the list
    // - will also Free the item if OwnObjects was set, and dontfree is false
    procedure Delete(index: integer; dontfree: boolean = false); override;
    /// delete all objects of the list
    procedure Clear; override;
    /// delete all objects of the list in reverse order
    // - for some kind of processes, owned objects should be removed from the
    // last added to the first
    // - will use slower but safer FreeAndNilSafe() instead of plain Free
    procedure ClearFromLast; virtual;
    /// finalize the store items
    destructor Destroy; override;
    /// create a new ItemClass instance, Add() it and return it
    function NewItem: pointer;
    /// optional class of the stored items
    // - used e.g. by _JL_TSynObjectList() when unserializing from JSON
    property ItemClass: TClass
      read fItemClass write fItemClass;
    /// flag set if this list will Free its items on Delete/Clear/Destroy
    property OwnObjects: boolean
      read fOwnObjects write fOwnObjects;
  end;
  PSynObjectList = ^TSynObjectList;

  /// meta-class of TSynObjectList type
  TSynObjectListClass = class of TSynObjectList;

  /// adding locking methods to a TSynPersistent with virtual constructor
  // - you may use this class instead of the RTL TCriticalSection, since it
  // would use a TSynLocker which does not suffer from CPU cache line conflit,
  // and is cross-compiler whereas TMonitor is Delphi-specific and buggy (at
  // least before XE5)
  // - if you don't need TSynPersistent overhead, consider plain TSynLocked class
  TSynPersistentLock = class(TSynPersistent)
  protected
    // TSynLocker would increase inherited fields offset -> managed PSynLocker
    fSafe: PSynLocker;
    // will lock/unlock the instance during JSON serialization of its properties
    function RttiBeforeWriteObject(W: TTextWriter;
      var Options: TTextWriterWriteObjectOptions): boolean; override;
    procedure RttiAfterWriteObject(W: TTextWriter;
      Options: TTextWriterWriteObjectOptions); override;
    // set the rcfHookWrite flag to call RttiBeforeWriteObject
    class procedure RttiCustomSetParser(Rtti: TRttiCustom); override;
  public
    /// initialize the instance, and its associated lock
    constructor Create; override;
    /// finalize the instance, and its associated lock
    destructor Destroy; override;
    /// access to the associated instance critical section
    // - call Safe.Lock/UnLock to protect multi-thread access on this storage
    property Safe: PSynLocker
      read fSafe;
    /// could be used as a short-cut to Safe.Lock
    procedure Lock;
      {$ifdef HASINLINE}inline;{$endif}
    /// could be used as a short-cut to Safe.UnLock
    procedure Unlock;
      {$ifdef HASINLINE}inline;{$endif}
  end;

  /// adding light non-upgradable multiple Read / exclusive Write locking
  // methods to a TSynPersistent with virtual constructor
  TSynPersistentRWLightLock = class(TSynPersistent)
  protected
    fSafe: TRWLightLock;
  public
    /// access to the associated non-upgradable TRWLightLock instance
    // - call Safe methods to protect multi-thread access on this storage
    property Safe: TRWLightLock
      read fSafe;
  end;

  /// adding light upgradable multiple Read / exclusive Write locking methods
  // to a TSynPersistent with virtual constructor
  TSynPersistentRWLock = class(TSynPersistent)
  protected
    fSafe: TRWLock;
  public
    /// access to the associated upgradable TRWLock instance
    // - call Safe methods to protect multi-thread access on this storage
    property Safe: TRWLock
      read fSafe;
  end;

  {$ifndef PUREMORMOT2}

  /// used for backward compatibility only with existing code
  TSynPersistentLocked = class(TSynPersistentLock);

  {$endif PUREMORMOT2}

  /// adding locking methods to a TInterfacedObject with virtual constructor
  TInterfacedObjectLocked = class(TInterfacedObjectWithCustomCreate)
  protected
    fSafe: PSynLocker; // TSynLocker would increase inherited fields offset
  public
    /// initialize the object instance, and its associated lock
    constructor Create; override;
    /// release the instance (including the locking resource)
    destructor Destroy; override;
    /// access to the locking methods of this instance
    // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block
    property Safe: PSynLocker
      read fSafe;
  end;

  /// adding light locking methods to a TInterfacedObject with virtual constructor
  TInterfacedObjectRWLocked = class(TInterfacedObjectWithCustomCreate)
  protected
    fSafe: TRWLock;
  public
    /// access to the multiple Read / exclusive Write locking methods of this instance
    property Safe: TRWLock
      read fSafe;
  end;

  /// add TRWLightLock non-upgradable methods to a TSynObjectList
  // - this class expands the regular TSynObjectList to include a TRWLightLock
  // - you need to call the Safe locking methods by hand to protect the
  // execution of all methods, since even Add/Clear/ClearFromLast/Remove/Exists
  // have not been overriden because TRWLighLock.WriteLock is not reentrant
  TSynObjectListLightLocked = class(TSynObjectList)
  protected
    fSafe: TRWLightLock;
  public
    /// the light single Read / exclusive Write LightLock associated to this list
    // - could be used to protect shared resources within the internal process,
    // for index-oriented methods like Delete/Items/Count...
    // - use Safe LightLock methods with a try ... finally bLightLock
    property Safe: TRWLightLock
      read fSafe;
  end;

  /// add TRWLock upgradable methods to a TSynObjectList
  // - this class expands the regular TSynObjectList to include a TRWLock
  // - you need to call the Safe locking methods by hand to protect the
  // execution of index-oriented methods (like Delete/Items/Count...): the
  // list content may change in the background, so using indexes is thread-safe
  // - on the other hand, Add/Clear/ClearFromLast/Remove stateless methods have
  // been overriden in this class to call Safe lock methods, and therefore are
  // thread-safe and protected to any background change
  TSynObjectListLocked = class(TSynObjectList)
  protected
    fSafe: TRWLock;
  public
    /// add one item to the list using Safe.WriteLock
    function Add(item: pointer): PtrInt; override;
    /// delete all items of the list using Safe.WriteLock
    procedure Clear; override;
    /// delete all items of the list in reverse order, using Safe.WriteLock
    procedure ClearFromLast; override;
    /// fast delete one item in the list, using Safe.WriteLock
    function Remove(item: pointer): PtrInt; override;
    /// check an item using Safe.ReadOnlyLock
    function Exists(item: pointer): boolean; override;
    /// the light single Read / exclusive Write lock associated to this list
    // - could be used to protect shared resources within the internal process,
    // for index-oriented methods like Delete/Items/Count...
    // - use Safe lock methods within a try ... finally block
    property Safe: TRWLock
      read fSafe;
  end;

  /// event used by TSynObjectListSorted to compare its instances
  TOnObjectCompare = function(A, B: TObject): integer;

  /// an ordered thread-safe TSynObjectList
  // - items will be stored in order, for O(log(n)) fast search
  TSynObjectListSorted = class(TSynObjectListLocked)
  protected
    fCompare: TOnObjectCompare;
    // returns TRUE and the index of existing Item, or FALSE and the index
    // where the Item is to be inserted so that the array remains sorted
    function Locate(item: pointer; out index: PtrInt): boolean;
  public
    /// initialize the object list to be sorted with the supplied function
    constructor Create(const aCompare: TOnObjectCompare;
      aOwnsObjects: boolean = true); reintroduce;
    /// add in-order one item to the list using Safe.WriteLock
    // - returns the sorted index when item was inserted
    // - returns < 0 if item was found, as -(existingindex + 1)
    function Add(item: pointer): PtrInt; override;
    /// fast retrieve one item in the list using O(log(n)) binary search
    // - this overriden version won't search for the item pointer itself,
    // but will use the Compare() function until it is 0
    function IndexOf(item: pointer): PtrInt; override;
    /// fast retrieve one item in the list using O(log(n)) binary search
    // - supplied item should have enough information for fCompare to work
    function Find(item: TObject): TObject;
    /// how two stored objects are stored
    property Compare: TOnObjectCompare
      read fCompare write fCompare;
  end;


{ ************ TSynPersistentStore with proper Binary Serialization }

type
  /// abstract high-level handling of (SynLZ-)compressed persisted storage
  // - LoadFromReader/SaveToWriter abstract methods should be overriden
  // with proper binary persistence implementation
  TSynPersistentStore = class(TSynPersistentRWLock)
  protected
    fName: RawUtf8;
    fReader: TFastReader;
    fReaderTemp: PRawByteString;
    fLoadFromLastUncompressed, fSaveToLastUncompressed: integer;
    fLoadFromLastAlgo: TAlgoCompress;
    /// low-level virtual methods implementing the persistence reading
    procedure LoadFromReader; virtual;
    procedure SaveToWriter(aWriter: TBufferWriter); virtual;
  public
    /// initialize a void storage with the supplied name
    constructor Create(const aName: RawUtf8); reintroduce; overload; virtual;
    /// initialize a storage from a SaveTo persisted buffer
    // - raise a EFastReader exception on decoding error
    constructor CreateFrom(const aBuffer: RawByteString;
      aLoad: TAlgoCompressLoad = aclNormal);
    /// initialize a storage from a SaveTo persisted buffer
    // - raise a EFastReader exception on decoding error
    constructor CreateFromBuffer(aBuffer: pointer; aBufferLen: integer;
      aLoad: TAlgoCompressLoad = aclNormal);
    /// initialize a storage from a SaveTo persisted buffer
    // - raise a EFastReader exception on decoding error
    constructor CreateFromFile(const aFileName: TFileName;
      aLoad: TAlgoCompressLoad = aclNormal);
    /// fill the storage from a SaveTo persisted buffer
    // - actually call the LoadFromReader() virtual method for persistence
    // - raise a EFastReader exception on decoding error
    procedure LoadFrom(const aBuffer: RawByteString;
      aLoad: TAlgoCompressLoad = aclNormal); overload;
    /// initialize the storage from a SaveTo persisted buffer
    // - actually call the LoadFromReader() virtual method for persistence
    // - raise a EFastReader exception on decoding error
    procedure LoadFrom(aBuffer: pointer; aBufferLen: integer;
      aLoad: TAlgoCompressLoad = aclNormal); overload; virtual;
    /// initialize the storage from a SaveToFile content
    // - actually call the LoadFromReader() virtual method for persistence
    // - returns false if the file is not found, true if the file was loaded
    // without any problem, or raise a EFastReader exception on decoding error
    function LoadFromFile(const aFileName: TFileName;
      aLoad: TAlgoCompressLoad = aclNormal): boolean;
    /// persist the content as a SynLZ-compressed binary blob
    // - to be retrieved later on via LoadFrom method
    // - actually call the SaveToWriter() protected virtual method for persistence
    // - you can specify ForcedAlgo if you want to override the default AlgoSynLZ
    // - BufferOffset could be set to reserve some bytes before the compressed buffer
    procedure SaveTo(out aBuffer: RawByteString; nocompression: boolean = false;
      BufLen: integer = 65536; ForcedAlgo: TAlgoCompress = nil;
      BufferOffset: integer = 0); overload; virtual;
    /// persist the content as a SynLZ-compressed binary blob
    // - just an overloaded wrapper
    function SaveTo(nocompression: boolean = false; BufLen: integer = 65536;
      ForcedAlgo: TAlgoCompress = nil; BufferOffset: integer = 0): RawByteString; overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// persist the content as a SynLZ-compressed binary file
    // - to be retrieved later on via LoadFromFile method
    // - returns the number of bytes of the resulting file
    // - actually call the SaveTo method for persistence
    function SaveToFile(const aFileName: TFileName; nocompression: boolean = false;
      BufLen: integer = 65536; ForcedAlgo: TAlgoCompress = nil): PtrUInt;
    /// one optional text associated with this storage
    // - you can define this field as published to serialize its value in log/JSON
    property Name: RawUtf8
      read fName;
    /// after a LoadFrom(), contains the uncompressed data size read
    property LoadFromLastUncompressed: integer
      read fLoadFromLastUncompressed;
    /// after a SaveTo(), contains the uncompressed data size written
    property SaveToLastUncompressed: integer
      read fSaveToLastUncompressed;
  end;



{ ********** Efficient RTTI Values Binary Serialization and Comparison }

type
  /// possible options for a TDocVariant JSON/BSON document storage
  // - defined in this unit to avoid circular reference with mormot.core.variants
  // - dvoIsArray and dvoIsObject will store the "Kind: TDocVariantKind" state -
  // you should never have to define these two options directly
  // - dvoNameCaseSensitive will be used for every name lookup - here
  // case-insensitivity is restricted to a-z A-Z 0-9 and _ characters
  // - dvoCheckForDuplicatedNames will be used for method
  // TDocVariantData.AddValue(), but not when setting properties at
  // variant level: for consistency, "aVariant.AB := aValue" will replace
  // any previous value for the name "AB"
  // - dvoReturnNullForUnknownProperty will be used when retrieving any value
  // from its name (for dvObject kind of instance), or index (for dvArray or
  // dvObject kind of instance)
  // - by default, internal values will be copied by-value from one variant
  // instance to another, to ensure proper safety - but it may be too slow:
  // if you set dvoValueCopiedByReference, the internal
  // TDocVariantData.VValue/VName instances will be copied by-reference,
  // to avoid memory allocations, BUT it may break internal process if you change
  // some values in place (since VValue/VName and VCount won't match) - as such,
  // if you set this option, ensure that you use the content as read-only
  // - any registered custom types may have an extended JSON syntax (e.g.
  // TBsonVariant does for MongoDB types), and will be searched during JSON
  // parsing, unless dvoJsonParseDoNotTryCustomVariants is set (slightly faster)
  // - the parser will try to guess the array or object size by pre-fetching
  // some content: you can set dvoJsonParseDoNotGuessCount if your input has
  // a lot of nested documents, and manual resize is preferred - this option
  // will be forced by InitJson if a huge nest of objects is detected
  // - by default, it will only handle direct JSON [array] of {object}: but if
  // you define dvoJsonObjectParseWithinString, it will also try to un-escape
  // a JSON string first, i.e. handle "[array]" or "{object}" content (may be
  // used e.g. when JSON has been retrieved from a database TEXT column) - is
  // used for instance by VariantLoadJson()
  // - JSON serialization will follow the standard layout, unless
  // dvoSerializeAsExtendedJson is set so that the property names would not
  // be escaped with double quotes, writing '{name:"John",age:123}' instead of
  // '{"name":"John","age":123}': this extended json layout is compatible with
  // http://docs.mongodb.org/manual/reference/mongodb-extended-json and with
  // TDocVariant JSON unserialization, also our SynCrossPlatformJSON unit, but
  // NOT recognized by most JSON clients, like AJAX/JavaScript or C#/Java
  // - by default, only integer/Int64/currency number values are allowed, unless
  // dvoAllowDoubleValue is set and 32-bit floating-point conversion is tried,
  // with potential loss of precision during the conversion
  // - dvoInternNames and dvoInternValues will use shared TRawUtf8Interning
  // instances to maintain a list of RawUtf8 names/values for all TDocVariant,
  // so that redundant text content will be allocated only once on heap
  // - see JSON_[TDocVariantModel] and all JSON_* constants as useful sets, and
  // TDocVariantData.IsObject/IsArray/IsCaseSensitive/Has wrapper methods
  TDocVariantOption = (
    dvoIsArray,
    dvoIsObject,
    dvoNameCaseSensitive,
    dvoCheckForDuplicatedNames,
    dvoReturnNullForUnknownProperty,
    dvoValueCopiedByReference,
    dvoJsonParseDoNotTryCustomVariants,
    dvoJsonParseDoNotGuessCount,
    dvoJsonObjectParseWithinString,
    dvoSerializeAsExtendedJson,
    dvoAllowDoubleValue,
    dvoInternNames,
    dvoInternValues);

  /// set of options for a TDocVariant storage
  // - defined in this unit to avoid circular reference with mormot.core.variants
  // - see JSON_[TDocVariantModel] and all JSON_* constants (e.g. JSON_FAST or
  // JSON_FAST_FLOAT) as potential values
  // - when specifying the options, you should not include dvoIsArray nor
  // dvoIsObject directly in the set, but explicitly define TDocVariantDataKind
  // - see TDocVariantData.IsObject/IsArray/IsCaseSensitive/Has wrapper methods
  // which are faster than 16-bit "if dvo* in VOptions" on Intel
  TDocVariantOptions = set of TDocVariantOption;

  /// pointer to a set of options for a TDocVariant storage
  // - defined in this unit to avoid circular reference with mormot.core.variants
  // - use e.g. @JSON_[mFast], @JSON_[mDefault], or any other TDocVariantModel
  PDocVariantOptions = ^TDocVariantOptions;

  /// a boolean array of TDocVariant storage options
  TDocVariantOptionsBool = array[boolean] of TDocVariantOptions;
  PDocVariantOptionsBool = ^TDocVariantOptionsBool;


type
  /// internal function handler for binary persistence of any RTTI type value
  // - i.e. the kind of functions called via RTTI_BINARYSAVE[] lookup table
  // - work with managed and unmanaged types
  // - persist Data^ into Dest, returning the size in Data^ as bytes
  TRttiBinarySave = function(Data: pointer; Dest: TBufferWriter;
    Info: PRttiInfo): PtrInt;

  /// the type of RTTI_BINARYSAVE[] efficient lookup table
  TRttiBinarySaves = array[TRttiKind] of TRttiBinarySave;
  PRttiBinarySaves = ^TRttiBinarySaves;

  /// internal function handler for binary persistence of any RTTI type value
  // - i.e. the kind of functions called via RTTI_BINARYLOAD[] lookup table
  // - work with managed and unmanaged types
  // - fill Data^ from Source, returning the size in Data^ as bytes
  TRttiBinaryLoad = function(Data: pointer; var Source: TFastReader;
    Info: PRttiInfo): PtrInt;

  /// the type of RTTI_BINARYLOAD[] efficient lookup table
  TRttiBinaryLoads = array[TRttiKind] of TRttiBinaryLoad;
  PRttiBinaryLoads = ^TRttiBinaryLoads;

  /// internal function handler for fast comparison of any RTTI type value
  // - i.e. the kind of functions called via RTTI_COMPARE[] lookup table
  // - work with managed and unmanaged types
  // - returns the size in Data1/Data2^ as bytes, and the result in Compared
  TRttiCompare = function(Data1, Data2: pointer; Info: PRttiInfo;
    out Compared: integer): PtrInt;

  /// the type of RTTI_COMPARE[] efficient lookup table
  TRttiCompares = array[TRttiKind] of TRttiCompare;
  PRttiCompares = ^TRttiCompares;

  TRttiComparers = array[{CaseInSensitive=}boolean] of TRttiCompares;

var
  /// lookup table for binary persistence of any RTTI type value
  // - for efficient persistence into binary of managed and unmanaged types
  RTTI_BINARYSAVE: TRttiBinarySaves;

  /// lookup table for binary persistence of any RTTI type value
  // - for efficient retrieval from binary of managed and unmanaged types
  RTTI_BINARYLOAD: TRttiBinaryLoads;

  /// lookup table for comparison of any RTTI type value
  // - for efficient search or sorting of managed and unmanaged types
  // - RTTI_COMPARE[false] for case-sensitive comparison
  // - RTTI_COMPARE[true] for case-insensitive comparison
  RTTI_COMPARE: TRttiComparers;

  /// lookup table for comparison of ordinal RTTI type values
  // - slightly faster alternative to RTTI_COMPARE[rkOrdinalTypes]
  RTTI_ORD_COMPARE: array[TRttiOrd] of TRttiCompare;

  /// lookup table for comparison of floating-point RTTI type values
  // - slightly faster alternative to RTTI_COMPARE[rkFloat]
  RTTI_FLOAT_COMPARE: array[TRttiFloat] of TRttiCompare;

/// raw binary serialization of a dynamic array
// - as called e.g. by TDynArray.SaveTo, using ExternalCount optional parameter
// - RTTI_BINARYSAVE[rkDynArray] is a wrapper to this function, with ExternalCount=nil
procedure DynArraySave(Data: PAnsiChar; ExternalCount: PInteger;
  Dest: TBufferWriter; Info: PRttiInfo); overload;

/// serialize a dynamic array content as binary, ready to be loaded by
// DynArrayLoad() / TDynArray.Load()
// - Value shall be set to the source dynamic arry field
// - is a wrapper around BinarySave(rkDynArray)
function DynArraySave(var Value; TypeInfo: PRttiInfo): RawByteString; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// fill a dynamic array content from a binary serialization as saved by
// DynArraySave() / TDynArray.Save()
// - Value shall be set to the target dynamic array field
// - is a wrapper around BinaryLoad(rkDynArray)
function DynArrayLoad(var Value; Source: PAnsiChar; TypeInfo: PRttiInfo;
  {$ifdef PUREMORMOT2} // SourceMax is manadatory for safety
  TryCustomVariants: PDocVariantOptions; SourceMax: PAnsiChar): PAnsiChar;
  {$else}
  TryCustomVariants: PDocVariantOptions = nil; SourceMax: PAnsiChar = nil): PAnsiChar;
  {$endif PUREMORMOT2}
  {$ifdef HASINLINE}inline;{$endif}

/// low-level binary unserialization as saved by DynArraySave/TDynArray.Save
// - as used by DynArrayLoad() and TDynArrayLoadFrom
// - returns the stored length() of the dynamic array, and Source points to
// the stored binary data itself
function DynArrayLoadHeader(var Source: TFastReader;
  ArrayInfo, ItemInfo: PRttiInfo): integer;

/// raw comparison of two dynamic arrays
// - as called e.g. by TDynArray.Equals, using ExternalCountA/B optional parameter
// - RTTI_COMPARE[true/false,rkDynArray] are wrappers to this, with ExternalCount=nil
// - if Info=TypeInfo(TObjectDynArray) then will compare any T*ObjArray
function DynArrayCompare(A, B: PAnsiChar; ExternalCountA, ExternalCountB: PInteger;
  Info: PRttiInfo; CaseInSensitive: boolean): integer; overload;

/// wrapper around TDynArray.Add
// - warning: the Item type is not checked at runtime, so should be as expected
// - not very fast, but could be useful for simple code
function DynArrayAdd(TypeInfo: PRttiInfo; var DynArray; const Item): integer; overload;

/// wrapper around TDynArray.Delete
// - not very fast, but could be useful for simple code
function DynArrayDelete(TypeInfo: PRttiInfo; var DynArray; Index: PtrInt): boolean; overload;

/// compare two dynamic arrays by calling TDynArray.Equals
// - if Info=TypeInfo(TObjectDynArray) then will compare any T*ObjArray
function DynArrayEquals(TypeInfo: PRttiInfo; var Array1, Array2;
  Array1Count: PInteger = nil; Array2Count: PInteger = nil;
  CaseInsensitive: boolean = false): boolean;
  {$ifdef HASINLINE}inline;{$endif}

{$ifdef FPCGENERICS}
/// wrapper around TDynArray.Add
// - warning: the Item type is not checked at runtime, so should be as expected
// - not very fast, but could be useful for simple code
function DynArrayAdd<TArray>(var DynArray: TArray; const Item): integer; overload;

/// wrapper around TDynArray.Delete
// - not very fast, but could be useful for simple code
function DynArrayDelete<TArray>(var DynArray: TArray; Index: PtrInt): boolean; overload;

/// compare two dynamic arrays values
function DynArrayCompare<TArray>(var Array1, Array2: TArray;
  CaseInSensitive: boolean = false): integer; overload;
{$endif FPCGENERICS}

// some low-level comparison methods used by mormot.core.json
function _BC_SQWord(A, B: PInt64; Info: PRttiInfo; out Compared: integer): PtrInt;
function _BC_UQWord(A, B: PQWord; Info: PRttiInfo; out Compared: integer): PtrInt;
function _BC_ObjArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
function _BCI_ObjArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;

/// check equality of two values by content, using RTTI
// - optionally returns the known in-memory PSize of the value
function BinaryEquals(A, B: pointer; Info: PRttiInfo; PSize: PInteger;
  Kinds: TRttiKinds; CaseInSensitive: boolean): boolean;

/// comparison of two values by content, using RTTI
function BinaryCompare(A, B: pointer; Info: PRttiInfo;
  CaseInSensitive: boolean): integer; overload;

/// comparison of two arrays of values by content, using RTTI
function BinaryCompare(A, B: pointer; Info: PRttiInfo; Count: PtrInt;
  CaseInSensitive: boolean): integer; overload;

/// comparison of two TObject published properties, using RTTI
function ObjectCompare(A, B: TObject; CaseInSensitive: boolean): integer; overload;

/// comparison of published properties of several TObject instances, using RTTI
function ObjectCompare(A, B: PObject; Count: PtrInt;
  CaseInsensitive: boolean = false): integer; overload;

/// case-sensitive comparison of two TObject published properties, using RTTI
function ObjectEquals(A, B: TObject): boolean;
  {$ifdef HASINLINE}inline;{$endif}

/// case-insensitive comparison of two TObject published properties, using RTTI
function ObjectEqualsI(A, B: TObject): boolean;
  {$ifdef HASINLINE}inline;{$endif}

{$ifndef PUREMORMOT2}

/// how many bytes a BinarySave() may return
// - deprecated function - use overloaded BinarySave() functions instead
function BinarySaveLength(Data: pointer; Info: PRttiInfo; Len: PInteger;
  Kinds: TRttiKinds): integer; deprecated;

/// binary persistence of any value using RTTI, into a memory buffer
// - deprecated function - use overloaded BinarySave() functions instead
function BinarySave(Data: pointer; Dest: PAnsiChar; Info: PRttiInfo;
  out Len: integer; Kinds: TRttiKinds): PAnsiChar; overload; deprecated;

{$endif PUREMORMOT2}

/// binary persistence of any value using RTTI, into a RawByteString buffer
function BinarySave(Data: pointer; Info: PRttiInfo; Kinds: TRttiKinds;
  WithCrc: boolean = false): RawByteString; overload;

/// binary persistence of any value using RTTI, into a TBytes buffer
function BinarySaveBytes(Data: pointer; Info: PRttiInfo; Kinds: TRttiKinds): TBytes;

/// binary persistence of any value using RTTI, into a TBufferWriter stream
procedure BinarySave(Data: pointer; Info: PRttiInfo; Dest: TBufferWriter); overload;
  {$ifdef HASINLINE}inline;{$endif}

/// binary persistence of any value using RTTI, into a TSynTempBuffer buffer
procedure BinarySave(Data: pointer; var Dest: TSynTempBuffer;
  Info: PRttiInfo; Kinds: TRttiKinds; WithCrc: boolean = false); overload;

/// binary persistence of any value using RTTI, into a Base64-encoded text
// - contains a trailing crc32c hash before the actual data
function BinarySaveBase64(Data: pointer; Info: PRttiInfo; UriCompatible: boolean;
  Kinds: TRttiKinds; WithCrc: boolean = true): RawUtf8;

/// unserialize any value from BinarySave() memory buffer, using RTTI
function BinaryLoad(Data: pointer; Source: PAnsiChar; Info: PRttiInfo;
  Len: PInteger; SourceMax: PAnsiChar; Kinds: TRttiKinds;
  TryCustomVariants: PDocVariantOptions = nil): PAnsiChar; overload;

/// unserialize any value from BinarySave() RawByteString, using RTTI
function BinaryLoad(Data: pointer; const Source: RawByteString; Info: PRttiInfo;
  Kinds: TRttiKinds; TryCustomVariants: PDocVariantOptions = nil): boolean; overload;

/// unserialize any value from BinarySaveBase64() encoding, using RTTI
// - optionally contains a trailing crc32c hash before the actual data
function BinaryLoadBase64(Source: PAnsiChar; Len: PtrInt; Data: pointer;
  Info: PRttiInfo; UriCompatible: boolean; Kinds: TRttiKinds;
  WithCrc: boolean = true; TryCustomVariants: PDocVariantOptions = nil): boolean;


/// check equality of two records by content
// - will handle packed records, with binaries (byte, word, integer...) and
// string types properties
// - will use binary-level comparison: it could fail to match two floating-point
// values because of rounding issues (Currency won't have this problem)
// - is a wrapper around BinaryEquals(rkRecordTypes)
function RecordEquals(const RecA, RecB; TypeInfo: PRttiInfo;
  PRecSize: PInteger = nil; CaseInSensitive: boolean = false): boolean;
  {$ifdef HASINLINE}inline;{$endif}

/// save a record content into a RawByteString
// - will handle packed records, with binaries (byte, word, integer...) and
// string types properties (but not with internal raw pointers, of course)
// - will use a proprietary binary format, with some variable-length encoding
// of the string length - note that if you change the type definition, any
// previously-serialized content will fail, maybe triggering unexpected GPF: you
// may use TypeInfoToHash() if you share this binary data accross executables
// - warning: will encode RTL string fields as AnsiString (one byte per char)
// prior to Delphi 2009, and as UnicodeString (two bytes per char) since Delphi
// 2009: if you want to use this function between UNICODE and NOT UNICODE
// versions of Delphi, you should use some explicit types like RawUtf8,
// WinAnsiString, SynUnicode or even RawUnicode/WideString
// - is a wrapper around BinarySave(rkRecordTypes)
function RecordSave(const Rec; TypeInfo: PRttiInfo): RawByteString; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// save a record content into a TBytes dynamic array
// - could be used as an alternative to RawByteString's RecordSave()
// - is a wrapper around BinarySaveBytes(rkRecordTypes)
function RecordSaveBytes(const Rec; TypeInfo: PRttiInfo): TBytes;
  {$ifdef HASINLINE}inline;{$endif}

{$ifndef PUREMORMOT2}

/// compute the number of bytes needed to save a record content
// using the RecordSave() function
// - deprecated function - use overloaded BinarySave() functions instead
function RecordSaveLength(const Rec; TypeInfo: PRttiInfo;
  Len: PInteger = nil): integer; deprecated;
  {$ifdef HASINLINE}inline;{$endif}

/// save a record content into a destination memory buffer
// - Dest must be at least RecordSaveLength() bytes long
// - deprecated function - use overloaded BinarySave() functions instead
function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: PRttiInfo;
  out Len: integer): PAnsiChar; overload; deprecated;
  {$ifdef HASINLINE}inline;{$endif}

/// save a record content into a destination memory buffer
// - Dest must be at least RecordSaveLength() bytes long
// - deprecated function - use overloaded BinarySave() functions instead
function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: PRttiInfo): PAnsiChar;
  overload; deprecated; {$ifdef HASINLINE}inline;{$endif}

{$endif PUREMORMOT2}

/// save a record content into a destination memory buffer
// - caller should make Dest.Done once finished with Dest.buf/Dest.len buffer
// - is a wrapper around BinarySave(rkRecordTypes)
procedure RecordSave(const Rec; var Dest: TSynTempBuffer; TypeInfo: PRttiInfo); overload;
  {$ifdef HASINLINE}inline;{$endif}

/// save a record content into a Base64 encoded UTF-8 text content
// - will use RecordSave() format, with a left-sided binary CRC
// - is a wrapper around BinarySaveBase64(rkRecordTypes)
function RecordSaveBase64(const Rec; TypeInfo: PRttiInfo;
  UriCompatible: boolean = false): RawUtf8;
  {$ifdef HASINLINE}inline;{$endif}

/// fill a record content from a memory buffer as saved by RecordSave()
// - return nil if the Source buffer is incorrect
// - in case of success, return the memory buffer pointer just after the
// read content, and set the Rec size, in bytes, into Len reference variable
// - will use a proprietary binary format, with some variable-length encoding
// of the string length - note that if you change the type definition, any
// previously-serialized content will fail, maybe triggering unexpected GPF: you
// may use TypeInfoToHash() if you share this binary data accross executables
// - you should provide in SourceMax the first byte after the Source memory
// buffer, which will be used to avoid any unexpected buffer overflow - clearly
// mandatory when decoding the content from any external process (e.g. a
// maybe-forged client) - with no performance penalty
// - is a wrapper around BinaryLoad(rkRecordTypes)
function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: PRttiInfo;
  {$ifdef PUREMORMOT2} // SourceMax is manadatory for safety
  Len: PInteger; SourceMax: PAnsiChar;
  {$else}              // mORMot 1 compatibility mode
  Len: PInteger = nil; SourceMax: PAnsiChar = nil;
  {$endif PUREMORMOT2}
  TryCustomVariants: PDocVariantOptions = nil): PAnsiChar; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// fill a record content from a memory buffer as saved by RecordSave()
// - will use the Source length to detect and avoid any buffer overlow
// - returns false if the Source buffer was incorrect, true on success
// - is a wrapper around BinaryLoad(rkRecordTypes)
function RecordLoad(var Rec; const Source: RawByteString;
  TypeInfo: PRttiInfo; TryCustomVariants: PDocVariantOptions = nil): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// read a record content from a Base64 encoded content
// - expects RecordSaveBase64() format, with a left-sided binary CRC32C
// - is a wrapper around BinaryLoadBase64(rkRecordTypes)
function RecordLoadBase64(Source: PAnsiChar; Len: PtrInt; var Rec; TypeInfo: PRttiInfo;
  UriCompatible: boolean = false; TryCustomVariants: PDocVariantOptions = nil): boolean;
  {$ifdef HASINLINE}inline;{$endif}

/// crc32c-based hash of a variant value
// - complex string types will make up to 255 uppercase characters conversion
// if CaseInsensitive is true
// - you can specify your own hashing function if crc32c is not what you expect
function VariantHash(const value: variant; CaseInsensitive: boolean;
  Hasher: THasher = nil): cardinal;


{ ************ TDynArray and TDynArrayHashed Wrappers }

type
  /// function prototype to be used for TDynArray Sort and Find method
  // - common functions exist for base types: see e.g. SortDynArrayBoolean,
  // SortDynArrayByte, SortDynArrayWord, SortDynArrayInteger, SortDynArrayCardinal,
  // SortDynArrayInt64, SortDynArrayQWord, SordDynArraySingle, SortDynArrayDouble,
  // SortDynArrayAnsiString, SortDynArrayAnsiStringI, SortDynArrayUnicodeString,
  // SortDynArrayUnicodeStringI, SortDynArrayString, SortDynArrayStringI
  // - any custom type (even records) can be compared then sort by defining
  // such a custom function
  // - must return 0 if A=B, -1 if A<B, 1 if A>B
  TDynArraySortCompare = function(const A, B): integer;

  /// event oriented version of TDynArraySortCompare
  TOnDynArraySortCompare = function(const A, B): integer of object;

  /// defined here as forward definition of the TRawUtf8Interning final class
  TRawUtf8InterningAbstract = class(TSynPersistent);

const
  /// redirect to the proper SortDynArrayAnsiString/SortDynArrayAnsiStringI
  SORT_LSTRING: array[{caseins=}boolean] of TDynArraySortCompare = (
    {$ifdef CPUINTEL}
    SortDynArrayAnsiString,
    {$else}
    SortDynArrayRawByteString,
    {$endif CPUINTEL}
    SortDynArrayAnsiStringI);

{$ifndef PUREMORMOT2}

type
  /// internal enumeration used to specify some standard arrays
  // - mORMot 1.18 did have two serialization engines - we unified it
  // - defined only for backward compatible code; use TRttiParserType instead
  TDynArrayKind = TRttiParserType;
  TDynArrayKinds = TRttiParserTypes;

const
  /// deprecated TDynArrayKind enumerate mapping
  // - defined only for backward compatible code; use TRttiParserType instead
  djNone = ptNone;
  djboolean = ptboolean;
  djByte = ptByte;
  djWord = ptWord;
  djInteger = ptInteger;
  djCardinal = ptCardinal;
  djSingle = ptSingle;
  djInt64 = ptInt64;
  djQWord = ptQWord;
  djDouble = ptDouble;
  djCurrency = ptCurrency;
  djTimeLog = ptTimeLog;
  djDateTime = ptDateTime;
  djDateTimeMS = ptDateTimeMS;
  djRawUtf8 = ptRawUtf8;
  djRawJson = ptRawJson;
  djWinAnsi = ptWinAnsi;
  djString = ptString;
  djRawByteString = ptRawByteString;
  djWideString = ptWideString;
  djSynUnicode = ptSynUnicode;
  djHash128 = ptHash128;
  djHash256 = ptHash256;
  djHash512 = ptHash512;
  djVariant = ptVariant;
  djCustom = ptCustom;
  djPointer = ptPtrInt;
  djObject = ptPtrInt;
  djUnmanagedTypes = ptUnmanagedTypes;
  djStringTypes = ptStringTypes;

{$endif PUREMORMOT2}

type
  /// the kind of exceptions raised during TDynArray/TDynArrayHashed process
  EDynArray = class(ESynException);

  /// a pointer to a TDynArray Wrapper instance
  PDynArray = ^TDynArray;

  /// a wrapper around a dynamic array with one dimension
  // - provide TList-like methods using fast RTTI information
  // - can be used to fast save/retrieve all memory content to a TStream
  // - note that the "const Item" is not checked at compile time nor runtime:
  // you must ensure that Item matchs the element type of the dynamic array;
  // all Item*() methods will use pointers for safety
  // - can use external Count storage to make Add() and Delete() much faster
  // (avoid most reallocation of the memory buffer)
  // - Note that TDynArray is just a wrapper around an existing dynamic array:
  // methods can modify the content of the associated variable but the TDynArray
  // doesn't contain any data by itself. It is therefore aimed to initialize
  // a TDynArray wrapper on need, to access any existing dynamic array.
  // - is defined as an object or as a record, due to a bug
  // in Delphi 2009/2010 compiler (at least): this structure is not initialized
  // if defined as an object on the stack, but will be as a record :(
  {$ifdef UNDIRECTDYNARRAY}
  TDynArray = record
  {$else}
  TDynArray = object
  {$endif UNDIRECTDYNARRAY}
  private
    fValue: PPointer;
    fInfo: TRttiCustom;
    fCountP: PInteger;
    fCompare: TDynArraySortCompare;
    fSorted, fNoFinalize: boolean;
    function GetCount: PtrInt;
      {$ifdef HASINLINE}inline;{$endif}
    procedure SetCount(aCount: PtrInt);
    function GetCapacity: PtrInt;
      {$ifdef HASINLINE}inline;{$endif}
    procedure SetCapacity(aCapacity: PtrInt);
    procedure SetCompare(const aCompare: TDynArraySortCompare);
      {$ifdef HASINLINE}inline;{$endif}
    function FindIndex(const Item; aIndex: PIntegerDynArray;
      aCompare: TDynArraySortCompare): PtrInt;
      {$ifdef HASINLINE}inline;{$endif}
    /// faster than RTL + handle T*ObjArray + ensure unique
    procedure InternalSetLength(OldLength, NewLength: PtrUInt);
  public
    /// initialize the wrapper with a one-dimension dynamic array
    // - the dynamic array must have been defined with its own type
    // (e.g. TIntegerDynArray = array of integer)
    // - if aCountPointer is set, it will be used instead of length() to store
    // the dynamic array items count - it will be much faster when adding
    // items to the array, because the dynamic array won't need to be
    // resized each time - but in this case, you should use the Count property
    // instead of length(array) or high(array) when accessing the data: in fact
    // length(array) will store the memory size reserved, not the items count
    // - if aCountPointer is set, its content will be set to 0, whatever the
    // array length is, or the current aCountPointer^ value is - to bypass this
    // behavior and keep an existing Count, call UseExternalCount() after Init()
    // - a sample usage may be:
    // !var
    // !  DA: TDynArray;
    // !  A: TIntegerDynArray;
    // !begin
    // !  DA.Init(TypeInfo(TIntegerDynArray), A);
    // ! (...)
    // - a sample usage may be (using a count variable):
    // !var
    // !  DA: TDynArray;
    // !  A: TIntegerDynArray;
    // !  ACount: integer;
    // !  i: integer;
    // !begin
    // !  DA.Init(TypeInfo(TIntegerDynArray), A, @ACount);
    // !  for i := 1 to 100000 do
    // !    DA.Add(i); // MUCH faster using the ACount variable
    // ! (...)   // now you should use DA.Count or Count instead of length(A)
    procedure Init(aTypeInfo: PRttiInfo; var aValue; aCountPointer: PInteger = nil);
    /// initialize the wrapper with a one-dimension dynamic array
    // - also set the Compare() function from a supplied TRttiParserType
    // - aKind=ptNone will guess the type from Info.ArrayRtti/ArrayFirstField
    // - will raise an exception if there is not enough RTTI available
    // - no RTTI check is made over the corresponding array layout: you shall
    // ensure that the aKind parameter matches at least the first field of
    // the dynamic array item definition
    // - aCaseInsensitive will be used for ptStringTypes
    function InitSpecific(aTypeInfo: PRttiInfo; var aValue; aKind: TRttiParserType;
      aCountPointer: PInteger = nil; aCaseInsensitive: boolean = false): TRttiParserType;
    /// set a specific TRttiParserType for this dynamic array
    // - could be called after InitRtti() to set the Compare() function
    // - as used by InitSpecific() after InitRtti(Rtti.RegisterType(aTypeInfo))
    function SetParserType(aKind: TRttiParserType; aCaseInsensitive: boolean): TRttiParserType;
    /// initialize the wrapper with a one-dimension dynamic array
    // - low-level method, as called by Init() and InitSpecific()
    // - can be called directly for a very fast TDynArray initialization
    // - warning: caller should check that aInfo.Kind=rkDynArray
    procedure InitRtti(aInfo: TRttiCustom; var aValue; aCountPointer: PInteger); overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// initialize the wrapper with a one-dimension dynamic array
    // - low-level method, as called by Init() and InitSpecific()
    // - can be called directly for a very fast TDynArray initialization
    // - warning: caller should check that aInfo.Kind=rkDynArray
    procedure InitRtti(aInfo: TRttiCustom; var aValue); overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// fast initialize a wrapper for an existing dynamic array of the same type
    // - is slightly faster than
    // ! InitRtti(aAnother.Info, aValue, nil);
    procedure InitFrom(aAnother: PDynArray; var aValue);
      {$ifdef HASINLINE}inline;{$endif}
    /// define the reference to an external count integer variable
    // - Init and InitSpecific methods will reset the aCountPointer to 0: you
    // can use this method to set the external count variable without overriding
    // the current value
    procedure UseExternalCount(aCountPointer: PInteger);
      {$ifdef HASINLINE}inline;{$endif}
    /// initialize the wrapper to point to no dynamic array
    // - it won't clear the wrapped array, just reset the fValue internal pointer
    // - in practice, will disable the other methods
    procedure Void;
    /// check if the wrapper points to a dynamic array
    // - i.e. if Void has been called before
    function IsVoid: boolean;
    /// add an element to the dynamic array
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (you can't write Add(i+10) e.g.)
    // - returns the index of the added element in the dynamic array
    // - note that because of dynamic array internal memory managment, adding
    // may reallocate the list every time a record is added, unless an external
    // count variable has been specified in Init(...,@Count) method
    function Add(const Item): PtrInt;
    /// add an element to the dynamic array, returning its index
    // - note: if you use this method to add a new item with a reference to the
    // dynamic array, be aware that the following trigger a GPF on FPC:
    // !    with Values[DynArray.New] do // otherwise Values is nil -> GPF
    // !    begin
    // !      Field1 := 1;
    // !      ...
    // - so you should either use a local variable:
    // !    i := DynArray.New;
    // !    with Values[i] do // otherwise Values is nil -> GPF
    // !    begin
    // - or even better, don't use the dubious "with Values[...] do" but NewPtr
    function New: PtrInt;
    /// add an element to the dynamic array, returning its pointer
    // - a slightly faster alternative to ItemPtr(New)
    function NewPtr: pointer;
    /// add an element to the dynamic array at the position specified by Index
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (you can't write Insert(10,i+10) e.g.)
    procedure Insert(Index: PtrInt; const Item);
    /// get and remove the last element stored in the dynamic array
    // - Add + Pop/Peek will implement a LIFO (Last-In-First-Out) stack
    // - warning: Dest must be of the same exact type than the dynamic array
    // - returns true if the item was successfully copied and removed
    // - use Peek() if you don't want to remove the item, but just get its value
    function Pop(var Dest): boolean;
    /// get the last element stored in the dynamic array
    // - Add + Pop/Peek will implement a LIFO (Last-In-First-Out) stack
    // - warning: Dest must be of the same exact type than the dynamic array
    // - returns true if the item was successfully copied into Dest
    // - use Pop() if you also want to remove the item
    function Peek(var Dest): boolean;
    /// get and remove the first element stored in the dynamic array
    // - Add + PopHead/PeekHead will implement a FIFO (First-In-First-Out) stack
    // - removing from head will move all items so TSynQueue is faster
    // - warning: Dest must be of the same exact type than the dynamic array
    // - returns true if the item was successfully copied and removed
    // - use PeekHead() if you don't want to remove the item, but get its value
    // - first slot will be deleted and all content moved, so may take some time
    function PopHead(var Dest): boolean;
    /// get the first element stored in the dynamic array
    // - Add + PopHead/PeekHead will implement a FIFO (First-In-First-Out) stack
    // - warning: Dest must be of the same exact type than the dynamic array
    // - returns true if the item was successfully copied and removed
    // - use PopHead() if you also want to remove the item
    function PeekHead(var Dest): boolean;
    /// delete the whole dynamic array content
    // - this method will recognize T*ObjArray types and free all instances
    procedure Clear;
      {$ifdef HASINLINE}inline;{$endif}
    /// delete the whole dynamic array content, ignoring exceptions
    // - returns true if no exception occurred when calling Clear, false otherwise
    // - you should better not call this method, which will catch and ignore
    // all exceptions - but it may somewhat make sense in a destructor
    // - this method will recognize T*ObjArray types and free all instances
    function ClearSafe: boolean;
    /// delete one item inside the dynamic array
    // - the deleted element is finalized if necessary
    // - this method will recognize T*ObjArray types and free all instances
    function Delete(aIndex: PtrInt): boolean;
    /// search for an element inside the dynamic array using RTTI
    // - return the index found (0..Count-1), or -1 if Item was not found
    // - will search for all properties content of Item: TList.IndexOf()
    // searches by address, this method searches by content using the RTTI
    // element description (and not the Compare property function)
    // - use the Find() method if you want the search via the Compare property
    // function, or e.g. to search only with some part of the element content
    // - will work with simple types: binaries (byte, word, integer, Int64,
    // Currency, array[0..255] of byte, packed records with no reference-counted
    // type within...), string types (e.g. array of string), and packed records
    // with binary and string types within (like TFileVersion)
    // - won't work with not packed types (like a shorstring, or a record
    // with byte or word fields with {$A+}): in this case, the padding data
    // (i.e. the bytes between the aligned fields) can be filled as random, and
    // there is no way with standard RTTI to identify randomness from values
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (you can't write IndexOf(i+10) e.g.)
    function IndexOf(const Item; CaseInSensitive: boolean = true): PtrInt;
    /// search for an element inside the dynamic array using the Compare function
    // - this method will use the Compare property function, or the supplied
    // aCompare for the search; if none of them are set, it will fallback to
    // IndexOf() to perform a default case-sensitive RTTI search
    // - return the index found (0..Count-1), or -1 if Item was not found
    // - if the array is sorted, it will use fast O(log(n)) binary search
    // - if the array is not sorted, it will use slower O(n) iterating search
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (you can't write Find(i+10) e.g.)
    function Find(const Item; aCompare: TDynArraySortCompare = nil): PtrInt; overload;
    /// search for an element value inside the dynamic array, from an external
    // aIndex[] lookup table - e.g. created by CreateOrderedIndex()
    // - return the index found (0..Count-1), or -1 if Item was not found
    // - if an indexed lookup is supplied, it must already be sorted:
    // this function will then use fast O(log(n)) binary search over aCompare
    // - if the indexed lookup is not correct (e.g. aIndex=nil), iterate O(n)
    // using aCompare - it won't fallback to IndexOf() RTTI search
    // - warning: the lookup aIndex[] should be synchronized if array content
    // is modified (in case of addition or deletion)
    function Find(const Item; const aIndex: TIntegerDynArray;
      aCompare: TDynArraySortCompare): PtrInt; overload;
    /// search for an element value, then fill all properties if match
    // - this method will use the Compare property function for the search,
    // or the supplied indexed lookup table and its associated compare function,
    // and fallback to case-sensitive RTTI search if none is defined
    // - if Item content matches, all Item fields will be filled with the record
    // - can be used e.g. as a simple dictionary: if Compare will match e.g. the
    // first string field (i.e. set to SortDynArrayString), you can fill the
    // first string field with the searched value (if returned index is >= 0)
    // - return the index found (0..Count-1), or -1 if Item was not found
    // - if the array is sorted, it will use fast O(log(n)) binary search
    // - if the array is not sorted, it will use slower O(n) iterating search
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (you can't write Find(i+10) e.g.)
    function FindAndFill(var Item; aIndex: PIntegerDynArray = nil;
      aCompare: TDynArraySortCompare = nil): integer;
    /// search for an element value, then delete it if match
    // - this method will use the Compare property function for the search,
    // or the supplied indexed lookup table and its associated compare function,
    // and fallback to case-sensitive RTTI search if none is defined
    // - if Item content matches, this item will be deleted from the array
    // - can be used e.g. as a simple dictionary: if Compare will match e.g. the
    // first string field (i.e. set to SortDynArrayString), you can fill the
    // first string field with the searched value (if returned index is >= 0)
    // - return the index deleted (0..Count-1), or -1 if Item was not found
    // - if the array is sorted, it will use fast O(log(n)) binary search
    // - if the array is not sorted, it will use slower O(n) iterating search
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (you can't write Find(i+10) e.g.)
    function FindAndDelete(const Item; aIndex: PIntegerDynArray = nil;
      aCompare: TDynArraySortCompare = nil): integer;
    /// search for an element value, then update the item if match
    // - this method will use the Compare property function for the search,
    // or the supplied indexed lookup table and its associated compare function,
    // and fallback to case-sensitive RTTI search if none is defined
    // - if Item content matches, this item will be updated with the supplied value
    // - can be used e.g. as a simple dictionary: if Compare will match e.g. the
    // first string field (i.e. set to SortDynArrayString), you can fill the
    // first string field with the searched value (if returned index is >= 0)
    // - return the index found (0..Count-1), or -1 if Item was not found
    // - if the array is sorted, it will use fast O(log(n)) binary search
    // - if the array is not sorted, it will use slower O(n) iterating search
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (you can't write Find(i+10) e.g.)
    function FindAndUpdate(const Item; aIndex: PIntegerDynArray = nil;
      aCompare: TDynArraySortCompare = nil): integer;
    /// search for an element value, then add it if none matched
    // - this method will use the Compare property function for the search,
    // or the supplied indexed lookup table and its associated compare function,
    // and fallback to case-sensitive RTTI search if none is defined
    // - if no Item content matches, the item will added to the array
    // - can be used e.g. as a simple dictionary: if Compare will match e.g. the
    // first string field (i.e. set to SortDynArrayString), you can fill the
    // first string field with the searched value (if returned index is >= 0)
    // - return the index found (0..Count-1), or -1 if Item was not found and
    // the supplied element has been successfully added
    // - if the array is sorted, it will use fast O(log(n)) binary search
    // - if the array is not sorted, it will use slower O(n) iterating search
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (you can't write Find(i+10) e.g.)
    function FindAndAddIfNotExisting(const Item; aIndex: PIntegerDynArray = nil;
      aCompare: TDynArraySortCompare = nil): integer;
    /// sort the dynamic array items, using the Compare property function
    // - it will change the dynamic array content, and exchange all items
    // in order to be sorted in increasing order according to Compare function
    procedure Sort(aCompare: TDynArraySortCompare = nil); overload;
    /// sort some dynamic array items, using the Compare property function
    // - this method allows to sort only some part of the items
    // - it will change the dynamic array content, and exchange all items
    // in order to be sorted in increasing order according to Compare function
    procedure SortRange(aStart, aStop: integer;
      aCompare: TDynArraySortCompare = nil);
    /// will check all items against aCompare
    function IsSorted(aCompare: TDynArraySortCompare = nil): boolean;
    /// will check all items against aCompare, calling Sort() if needed
    // - faster than plain Sort() if the array is likely to be already sorted
    procedure EnsureSorted(aCompare: TDynArraySortCompare = nil);
    /// sort the dynamic array items, using a Compare method (not function)
    // - it will change the dynamic array content, and exchange all items
    // in order to be sorted in increasing order according to Compare function,
    // unless aReverse is true
    // - it won't mark the array as Sorted, since the comparer is local
    procedure Sort(const aCompare: TOnDynArraySortCompare;
      aReverse: boolean = false); overload;
    /// search the items range which match a given value in a sorted dynamic array
    // - this method will use the Compare property function for the search
    // - returns TRUE and the matching indexes, or FALSE if none found
    // - if the array is not sorted, returns FALSE
    // - warning: FirstIndex/LastIndex parameters should be integer, not PtrInt
    function FindAllSorted(const Item;
      out FirstIndex, LastIndex: integer): boolean; overload;
    /// search the item pointers which match a given value in a sorted dynamic array
    // - this method will use the Compare property function for the search
    // - return nil and FindCount = 0 if no matching item was found
    // - return the a pointer to the first matching item, and FindCount >=1
    // - warning: FindCount out parameter should be integer, not PtrInt
    function FindAllSorted(const Item; out FindCount: integer): pointer; overload;
    /// search for an element value inside a sorted dynamic array
    // - this method will use the Compare property function for the search
    // - will be faster than a manual FindAndAddIfNotExisting+Sort process
    // - returns TRUE and the index of existing Item, or FALSE and the index
    // where the Item is to be inserted so that the array remains sorted
    // - you should then call FastAddSorted() later with the returned Index
    // - if the array is not sorted, returns FALSE and Index=-1
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (no FastLocateSorted(i+10) e.g.)
    // - warning: Index out parameter should be integer, not PtrInt
    function FastLocateSorted(const Item; out Index: integer): boolean;
    /// insert a sorted element value at the proper place
    // - the index should have been computed by FastLocateSorted(): false
    // - you may consider using FastLocateOrAddSorted() instead
    procedure FastAddSorted(Index: PtrInt; const Item);
    /// search and add an element value inside a sorted dynamic array
    // - this method will use the Compare property function for the search
    // - will be faster than a manual FindAndAddIfNotExisting+Sort process
    // - returns the index of the existing Item and wasAdded^=false
    // - returns the sorted index of the inserted Item and wasAdded^=true
    // - if the array is not sorted, returns -1 and wasAdded^=false
    // - is just a wrapper around FastLocateSorted+FastAddSorted
    function FastLocateOrAddSorted(const Item; wasAdded: PBoolean = nil): integer;
    /// delete a sorted element value at the proper place
    // - plain Delete(Index) would reset the fSorted flag to FALSE, so use
    // this method with a FastLocateSorted/FastAddSorted array
    procedure FastDeleteSorted(Index: PtrInt);
    /// will reverse all array items, in place
    procedure Reverse;
    /// will call FillZero() on all items, mainly binaries and strings
    // - could be used on a dynamic array to avoid memory forensic after release
    procedure FillZero;
    /// sort the dynamic array items using a lookup array of indexes
    // - in comparison to the Sort method, this CreateOrderedIndex won't change
    // the dynamic array content, but only create (or update) the supplied
    // integer lookup array, using the specified comparison function
    // - if aCompare is not supplied, the method will use fCompare (if defined)
    // - you should provide either a void either a valid lookup table, that is
    // a table with one to one lookup (e.g. created with FillIncreasing)
    // - if the lookup table has less items than the main dynamic array,
    // its content will be recreated
    procedure CreateOrderedIndex(var aIndex: TIntegerDynArray;
      aCompare: TDynArraySortCompare); overload;
    /// sort the dynamic array items using a lookup array of indexes
    // - this overloaded method will use the supplied TSynTempBuffer for
    // index storage, so use PIntegerArray(aIndex.buf) to access the values
    // - caller should always make aIndex.Done once done
    procedure CreateOrderedIndex(out aIndex: TSynTempBuffer;
      aCompare: TDynArraySortCompare); overload;
    /// sort using a lookup array of indexes, after a Add()
    // - will resize aIndex if necessary, and set aIndex[Count-1] := Count-1
    procedure CreateOrderedIndexAfterAdd(var aIndex: TIntegerDynArray;
      aCompare: TDynArraySortCompare);
    /// save the dynamic array content into a (memory) stream
    // - will handle array of binaries values (byte, word, integer...), array of
    // strings or array of packed records, with binaries and string properties
    // - will use a proprietary binary format, with some variable-length encoding
    // of the string length - note that if you change the type definition, any
    // previously-serialized content will fail, maybe triggering unexpected GPF:
    // use SaveToTypeInfoHash if you share this binary data accross executables
    // - Stream position will be set just after the added data
    // - is optimized for memory streams, but will work with any kind of TStream
    procedure SaveToStream(Stream: TStream);
    /// load the dynamic array content from a (memory) stream
    // - stream content must have been created using SaveToStream method
    // - will handle array of binaries values (byte, word, integer...), array of
    // strings or array of packed records, with binaries and string properties
    // - will use a proprietary binary format, with some variable-length encoding
    // of the string length - note that if you change the type definition, any
    // previously-serialized content will fail, maybe triggering unexpected GPF:
    // use SaveToTypeInfoHash if you share this binary data accross executables
    procedure LoadFromStream(Stream: TCustomMemoryStream);
    /// save the dynamic array content using our binary serialization
    // - will use a proprietary binary format, with some variable-length encoding
    // of the string length - note that if you change the type definition, any
    // previously-serialized content will fail, maybe triggering unexpected GPF
    // - this method will raise an ESynException for T*ObjArray types
    // - use TDynArray.LoadFrom to decode the saved buffer
    // - warning: legacy Hash32 checksum will be stored as 0, so may be refused
    // by mORMot TDynArray.LoadFrom before 1.18.5966
    procedure SaveTo(W: TBufferWriter); overload;
    /// save the dynamic array content into a RawByteString
    // - will use a proprietary binary format, with some variable-length encoding
    // of the string length - note that if you change the type definition, any
    // previously-serialized content will fail, maybe triggering unexpected GPF:
    // use SaveToTypeInfoHash if you share this binary data accross executables
    // - this method will raise an ESynException for T*ObjArray types
    // - use TDynArray.LoadFrom to decode the saved buffer
    // - warning: legacy Hash32 checksum will be stored as 0, so may be refused
    // by mORMot TDynArray.LoadFrom before 1.18.5966
    function SaveTo: RawByteString; overload;
    /// unserialize dynamic array content from binary written by TDynArray.SaveTo
    // - return nil if the Source buffer is incorrect: invalid type, wrong
    // checksum, or SourceMax overflow
    // - return a non nil pointer just after the Source content on success
    // - this method will raise an ESynException for T*ObjArray types
    function LoadFrom(Source: PAnsiChar;
      {$ifdef PUREMORMOT2} // SourceMax is manadatory for safety
      SourceMax: PAnsiChar): PAnsiChar;
      {$else}             // mORMot 1 compatibility mode
      SourceMax: PAnsiChar = nil): PAnsiChar;
      {$endif PUREMORMOT2}
    /// unserialize dynamic array content from binary written by TDynArray.SaveTo
    procedure LoadFromReader(var Read: TFastReader);
    /// unserialize the dynamic array content from a TDynArray.SaveTo binary string
    // - same as LoadFrom, and will check for any buffer overflow since we
    // know the actual end of input buffer
    // - will read mORMot 1.18 binary content, but will ignore the Hash32
    // stored checksum which is not needed any more
    function LoadFromBinary(const Buffer: RawByteString): boolean;
    /// serialize the dynamic array content as JSON
    function SaveToJson(EnumSetsAsText: boolean = false;
      reformat: TTextWriterJsonFormat = jsonCompact): RawUtf8; overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// serialize the dynamic array content as JSON
    procedure SaveToJson(out result: RawUtf8; EnumSetsAsText: boolean = false;
      reformat: TTextWriterJsonFormat = jsonCompact); overload;
    /// serialize the dynamic array content as JSON
    // - is just a wrapper around TTextWriter.AddTypedJson()
    // - this method will therefore recognize T*ObjArray types
    procedure SaveToJson(out result: RawUtf8; Options: TTextWriterOptions;
      ObjectOptions: TTextWriterWriteObjectOptions = [];
      reformat: TTextWriterJsonFormat = jsonCompact); overload;
    /// serialize the dynamic array content as JSON
    // - is just a wrapper around TTextDateWTTextWriterriter.AddTypedJson()
    // - this method will therefore recognize T*ObjArray types
    procedure SaveToJson(W: TTextWriter;
      ObjectOptions: TTextWriterWriteObjectOptions = []); overload;
    /// load the dynamic array content from an UTF-8 encoded JSON buffer
    // - expect the format as saved by TTextWriter.AddDynArrayJson method, i.e.
    // handling TbooleanDynArray, TIntegerDynArray, TInt64DynArray, TCardinalDynArray,
    // TDoubleDynArray, TCurrencyDynArray, TWordDynArray, TByteDynArray,
    // TRawUtf8DynArray, TWinAnsiDynArray, TRawByteStringDynArray,
    // TStringDynArray, TWideStringDynArray, TSynUnicodeDynArray,
    // TTimeLogDynArray and TDateTimeDynArray as JSON array - or any customized
    // Rtti.RegisterFromText/TRttiJson.RegisterCustomSerializer format
    // - or any other kind of array as Base64 encoded binary stream precessed
    // via JSON_BASE64_MAGIC_C (UTF-8 encoded \uFFF0 special code)
    // - typical handled content could be
    // ! '[1,2,3,4]' or '["\uFFF0base64encodedbinary"]'
    // - return a pointer at the end of the data read from P, nil in case
    // of an invalid input buffer
    // - this method will recognize T*ObjArray types, and will first free
    // any existing instance before unserializing, to avoid memory leak
    // - set e.g. @JSON_[mFast] as CustomVariantOptions parameter to handle
    // complex JSON object or arrays as TDocVariant into variant fields
    // - can use an associated TRawUtf8Interning instance for RawUtf8 values
    // - warning: the content of P^ will be modified during parsing: make a
    // local copy if it will be needed later (using e.g. the overloaded method)
    function LoadFromJson(P: PUtf8Char; EndOfObject: PUtf8Char = nil;
      CustomVariantOptions: PDocVariantOptions = nil; Tolerant: boolean = false;
      Interning: TRawUtf8InterningAbstract = nil): PUtf8Char; overload;
    /// load the dynamic array content from an UTF-8 encoded JSON buffer
    // - this method will make a private copy of the JSON for in-place parsing
    // - returns false in case of invalid input buffer, true on success
    function LoadFromJson(const Json: RawUtf8;
      CustomVariantOptions: PDocVariantOptions = nil; Tolerant: boolean = false;
      Interning: TRawUtf8InterningAbstract = nil): boolean; overload;
    ///  select a sub-section (slice) of a dynamic array content
    procedure Slice(var Dest; Limit: cardinal; Offset: cardinal = 0);
    /// assign the current dynamic array content into a variable
    // - by default (Offset=Limit=0), the whole array is set with no memory
    // (re)allocation, just finalize the Dest slot, then make Inc(RefCnt) and
    // force the internal length/Capacity to equal Count
    // - Offset/Limit could be used to create a new dynamic array with some part
    // of the existing content (Offset<0 meaning from the end):
    // ! SliceAsDynArray(DA);         // items 0..Count-1 (assign with refcount)
    // ! SliceAsDynArray(DA, 10);     // items 10..Count-1
    // ! SliceAsDynArray(DA, 0, 10);  // first 0..9 items
    // ! SliceAsDynArray(DA, 10, 20); // items 10..29 - truncated if Count < 20
    // ! SliceAsDynArray(DA, -10);    // last Count-10..Count-1 items
    procedure SliceAsDynArray(Dest: PPointer; Offset: integer = 0;
      Limit: integer = 0);
    /// add items from a given dynamic array variable
    // - the supplied source DynArray MUST be of the same exact type as the
    // current used for this TDynArray - warning: pass here a reference to
    // a "array of ..." variable, not another TDynArray instance; if you
    // want to add another TDynArray, use AddDynArray() method
    // - you can specify the start index and the number of items to take from
    // the source dynamic array (leave as -1 to add till the end)
    // - returns the number of items added to the array
    function AddArray(const DynArrayVar; aStartIndex: integer = 0;
      aCount: integer = -1): integer;
    /// add items from a given TDynArray
    // - the supplied source TDynArray MUST be of the same exact type as the
    // current used for this TDynArray, otherwise it won't do anything
    // - you can specify the start index and the number of items to take from
    // the source dynamic array (leave as -1 to add till the end)
    procedure AddDynArray(aSource: PDynArray; aStartIndex: integer = 0;
      aCount: integer = -1);
    /// compare the content of the two arrays, returning TRUE if both match
    // - use any supplied Compare property (unless ignorecompare=true), or
    // following the RTTI element description on all array items
    // - T*ObjArray kind of arrays will properly compare their properties
    function Equals(B: PDynArray; IgnoreCompare: boolean = false;
      CaseSensitive: boolean = true): boolean;
      {$ifdef HASINLINE}inline;{$endif}
    /// compare the content of the two arrays
    // - use any supplied Compare property (unless ignorecompare=true), or
    // following the RTTI element description on all array items
    // - T*ObjArray kind of arrays will properly compare their properties
    function Compares(B: PDynArray; IgnoreCompare: boolean = false;
      CaseSensitive: boolean = true): integer;
    /// set all content of one dynamic array to the current array
    // - both must be of the same exact type
    // - T*ObjArray will be reallocated and copied by content (using a temporary
    // JSON serialization), unless ObjArrayByRef is true and pointers are copied
    procedure Copy(Source: PDynArray; ObjArrayByRef: boolean = false);
    /// set all content of one dynamic array to the current array
    // - both must be of the same exact type
    // - T*ObjArray will be reallocated and copied by content (using a temporary
    // JSON serialization), unless ObjArrayByRef is true and pointers are copied
    procedure CopyFrom(const Source; MaxItem: integer;
      ObjArrayByRef: boolean = false);
    /// set all content of the current dynamic array to another array variable
    // - both must be of the same exact type
    // - resulting length(Dest) will match the exact items count, even if an
    // external Count integer variable is used by this instance
    // - T*ObjArray will be reallocated and copied by content (using a temporary
    // JSON serialization), unless ObjArrayByRef is true and pointers are copied
    procedure CopyTo(out Dest; ObjArrayByRef: boolean = false);
    /// returns a pointer to an element of the array
    // - returns nil if aIndex is out of range
    // - since TDynArray is just a wrapper around an existing array, you should
    // better use direct access to its wrapped variable, and not this (slightly)
    // slower and more error prone method (such pointer access lacks of strong
    // typing abilities), which is designed for TDynArray abstract/internal use
    function ItemPtr(index: PtrInt): pointer;
      {$ifdef HASINLINE}inline;{$endif}
    /// just a convenient wrapper of Info.Cache.ItemSize
    function ItemSize: PtrUInt;
      {$ifdef HASINLINE}inline;{$endif}
    /// will copy one element content from its index into another variable
    // - do nothing and return false if index is out of range or Dest is nil
    function ItemCopyAt(index: PtrInt; Dest: pointer): boolean;
      {$ifdef FPC}inline;{$endif}
    /// will move one element content from its index into another variable
    // - will erase the internal item after copy
    // - do nothing and return false if index is out of range or Dest is nil
    function ItemMoveTo(index: PtrInt; Dest: pointer): boolean;
    /// will copy one variable content into an indexed element
    // - do nothing if index is out of range
    // - ClearBeforeCopy will call ItemClear() before the copy, which may be safer
    // if the source item is a copy of Values[index] with some dynamic arrays
    procedure ItemCopyFrom(Source: pointer; index: PtrInt;
      ClearBeforeCopy: boolean = false);
      {$ifdef HASINLINE}inline;{$endif}
    /// compare the content of two items, returning TRUE if both values equal
    // - use the Compare() property function (if set) or using
    // Info.Cache.ItemInfoManaged if available - and fallbacks to binary comparison
    function ItemEquals(A, B: pointer; CaseInSensitive: boolean = false): boolean;
      {$ifdef HASINLINE}inline;{$endif}
    /// compare the content of two items, returning -1, 0 or +1s
    // - use the Compare() property function (if set) or using
    // Info.Cache.ItemInfoManaged if available - and fallbacks to binary comparison
    function ItemCompare(A, B: pointer; CaseInSensitive: boolean = false): integer;
    /// will reset the element content
    // - i.e. release any managed type memory, and fill Item with zeros
    procedure ItemClear(Item: pointer);
      {$ifdef HASINLINE}inline;{$endif}
    /// will fill the element with some random content
    // - this method is thread-safe using Rtti.DoLock/DoUnLock
    procedure ItemRandom(Item: pointer);
    /// will copy one element content
    procedure ItemCopy(Source, Dest: pointer);
      {$ifdef HASINLINE}{$ifndef ISDELPHI2009}inline;{$endif}{$endif}
    /// will copy the first field value of an array element
    // - will use the array KnownType to guess the copy routine to use
    // - returns false if the type information is not enough for a safe copy
    function ItemCopyFirstField(Source, Dest: pointer): boolean;
    /// save an array element into a serialized binary content
    // - use the same layout as TDynArray.SaveTo, but for a single item
    // - you can use ItemLoad method later to retrieve its content
    // - warning: Item must be of the same exact type than the dynamic array,
    // and must be a reference to a variable (you can't write ItemSave(i+10) e.g.)
    function ItemSave(Item: pointer): RawByteString;
    /// load an array element as saved by the ItemSave method into Item variable
    // - warning: Item must be of the same exact type than the dynamic array
    procedure ItemLoad(Source, SourceMax: PAnsiChar; Item: pointer);
    /// load an array element as saved by the ItemSave method
    // - this overloaded method will retrieve the element as a memory buffer,
    // which should be cleared by ItemLoadMemClear() before release
    function ItemLoadMem(Source, SourceMax: PAnsiChar): RawByteString;
    /// search for an array element as saved by the ItemSave method
    // - same as ItemLoad() + Find()/IndexOf() + ItemLoadClear()
    // - will call Find() method if Compare property is set
    // - will call generic IndexOf() method if no Compare property is set
    function ItemLoadFind(Source, SourceMax: PAnsiChar): integer;
    /// finalize a temporary buffer used to store an element via ItemLoadMem()
    // - will release any managed type referenced inside the RawByteString,
    // then void the variable
    // - is just a wrapper around ItemClear(pointer(ItemTemp)) + ItemTemp := ''
    procedure ItemLoadMemClear(var ItemTemp: RawByteString);

    /// retrieve or set the number of items of the dynamic array
    // - same as length(DynArray) or SetLength(DynArray)
    // - this property will recognize T*ObjArray types, so will free any stored
    // instance if the array is sized down
    property Count: PtrInt
      read GetCount write SetCount;
    /// the internal buffer capacity
    // - if no external Count pointer was set with Init, is the same as Count
    // - if an external Count pointer is set, you can set a value to this
    // property before a massive use of the Add() method e.g.
    // - if no external Count pointer is set, set a value to this property
    // will affect the Count value, i.e. Add() will append after this count
    // - this property will recognize T*ObjArray types, so will free any stored
    // instance if the array is sized down
    property Capacity: PtrInt
      read GetCapacity write SetCapacity;
    /// the compare function to be used for Sort and Find methods
    // - by default, no comparison function is set
    // - common functions exist for base types: e.g. SortDynArrayByte, SortDynArrayBoolean,
    // SortDynArrayWord, SortDynArrayInteger, SortDynArrayCardinal, SortDynArraySingle,
    // SortDynArrayInt64, SortDynArrayDouble, SortDynArrayAnsiString,
    // SortDynArrayAnsiStringI, SortDynArrayString, SortDynArrayStringI,
    // SortDynArrayUnicodeString, SortDynArrayUnicodeStringI
    property Compare: TDynArraySortCompare
      read fCompare write SetCompare;
    /// must be TRUE if the array is currently in sorted order according to
    // the compare function
    // - Add/Delete/Insert/Load* methods will reset this property to false
    // - Sort method will set this property to true
    // - you MUST set this property to false if you modify the dynamic array
    // content in your code, so that Find() won't try to wrongly use binary
    // search in an unsorted array, and miss its purpose
    property Sorted: boolean
      read fSorted write fSorted;
    /// can be set to TRUE to avoid any item finalization
    // -  e.g. with T*ObjArray - handle with care to avoid memory leaks
    property NoFinalize: boolean
      read fNoFinalize write fNoFinalize;

    /// low-level direct access to the storage variable
    property Value: PPointer
      read fValue;
    /// low-level extended RTTI access
    // - use e.g. Info.ArrayRtti to access the item RTTI, or
    // Info.Cache.ItemInfoManaged to get the managed item TypeInfo()
    property Info: TRttiCustom
      read fInfo;
    /// low-level direct access to the external count (if defined at Init)
    // - use UseExternalCount() after Init to avoid resetting the count to 0
    property CountExternal: PInteger
      read fCountP;
  end;

  /// just a wrapper record to join a TDynArray, its Count and a TRWLightLock
  TDynArrayLocked = record
    /// lightweight multiple Reads / exclusive Write non-upgradable lock
    Safe: TRWLightLock;
    /// the wrapper to a dynamic array
    DynArray: TDynArray;
    /// will store the length of the TDynArray
    Count: integer;
  end;


{.$define DYNARRAYHASHCOLLISIONCOUNT} // to be defined also in test.core.base

{$ifndef CPU32DELPHI} // Delphi Win32 compiler doesn't like Lemire algorithm

  {$define DYNARRAYHASH_LEMIRE}
  // use the Lemire 64-bit multiplication for faster hash reduction
  // see https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction
  // - generate more collisions with crc32c, but is always faster -> enabled

{$endif CPU32DELPHI}

// use Power-Of-Two sizes for smallest HashTables[], to reduce the hash with AND
// - and Delphi Win32 is not efficient at 64-bit multiplication, anyway
{$define DYNARRAYHASH_PO2}

// use 16-bit Hash table when indexes fit in a word (array Capacity < 65535)
// - to reduce memory consumption and slightly enhance CPU cache efficiency
// - e.g. arrays of size 1..127 use only 256*2=512 bytes for their hash table
{$define DYNARRAYHASH_16BIT}

{$ifdef DYNARRAYHASH_PO2}
const
  /// defined for inlining bitwise division in TDynArrayHasher.HashTableIndex
  // - HashTableSize<=HASH_PO2 is expected to be a power of two (fast binary op);
  // limit is set to 262,144 hash table slots (=512KB), for Capacity=131,072 items
  // - above this limit, a set of increasing primes is used; using a prime as
  // hashtable modulo enhances its distribution, especially for a weak hash function
  // - 64-bit CPU and FPC can efficiently compute a prime reduction using Lemire
  // algorithm, but power of two sizes still have a better practical performance
  // for lower (and most common) content until it consumes too much memory
  HASH_PO2 = 1 shl 18;
{$endif DYNARRAYHASH_PO2}

type
  /// function prototype to be used for hashing of a dynamic array element
  // - this function must use the supplied hasher on the Item data
  TDynArrayHashOne = function(const Item; Hasher: THasher): cardinal;

  /// event handler to be used for hashing of a dynamic array element
  // - can be set as an alternative to TDynArrayHashOne
  TOnDynArrayHashOne = function(const Item): cardinal of object;

  TDynArrayHasherState = set of (
    hasHasher
    {$ifdef DYNARRAYHASH_16BIT} , hash16bit {$endif} );

  /// implements O(1) lookup to any dynamic array content
  // - this won't handle the storage process (like add/update), just efficiently
  // maintain a hash table over an existing dynamic array: several TDynArrayHasher
  // could be applied to a single TDynArray wrapper
  // - TDynArrayHashed will use a TDynArrayHasher on its own storage
  {$ifdef USERECORDWITHMETHODS}
  TDynArrayHasher = record
  {$else}
  TDynArrayHasher = object
  {$endif USERECORDWITHMETHODS}
  private
    fDynArray: PDynArray;
    fHashItem: TDynArrayHashOne;       // function
    fEventHash: TOnDynArrayHashOne;    // function of object
    fHashTableStore: TIntegerDynArray; // store 0 for void entry, or Index+1
    fHashTableSize: integer;
    fState: TDynArrayHasherState;
    fCompare: TDynArraySortCompare;        // function
    fEventCompare: TOnDynArraySortCompare; // function of object
    fHasher: THasher;
    function HashTableIndex(aHashCode: PtrUInt): PtrUInt;
      {$ifdef HASINLINE}inline;{$endif}
    function HashTableIndexToIndex(aHashTableIndex: PtrInt): PtrInt;
      {$ifdef HASINLINE}inline;{$endif}
    procedure HashAdd(aHashCode: cardinal; var result: PtrInt);
    procedure HashDelete(aArrayIndex, aHashTableIndex: PtrInt; aHashCode: cardinal);
    function RaiseFatalCollision(const caller: shortstring; aHashCode: cardinal): integer;
    procedure HashTableInit(aHasher: THasher);
    procedure SetEventCompare(const Value: TOnDynArraySortCompare);
    procedure SetEventHash(const Value: TOnDynArrayHashOne);
  public
    {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
    /// low-level access to an hash collisions counter for all instance live
    CountCollisions: cardinal;
    /// low-level access to an hash collisions counter for the last HashTable[]
    CountCollisionsCurrent: cardinal;
    /// low-level access to the size of the internal HashTable[]
    HashTableSize: integer;
    {$endif DYNARRAYHASHCOLLISIONCOUNT}
    /// initialize the hash table for a given dynamic array storage
    // - you can call this method several times, e.g. if aCaseInsensitive changed
    procedure Init(aDynArray: PDynArray; aHashItem: TDynArrayHashOne;
      const aEventHash: TOnDynArrayHashOne; aHasher: THasher; aCompare: TDynArraySortCompare;
      const aEventCompare: TOnDynArraySortCompare; aCaseInsensitive: boolean);
    /// initialize a known hash table for a given dynamic array storage
    // - you can call this method several times, e.g. if aCaseInsensitive changed
    procedure InitSpecific(aDynArray: PDynArray; aKind: TRttiParserType;
      aCaseInsensitive: boolean; aHasher: THasher);
    /// search for an element value inside the dynamic array without hashing
    function Scan(Item: pointer): PtrInt;
    /// search for an element value inside the dynamic array with hashing
    function Find(Item: pointer): PtrInt; overload;
    /// search for a hashed element value inside the dynamic array with hashing
    function Find(Item: pointer; aHashCode: cardinal): PtrInt; overload;
    /// search for a hash position inside the dynamic array with hashing
    function Find(aHashCode: cardinal; aForAdd: boolean): PtrInt; overload;
    /// returns position in array, or next void index in HashTable[] as -(index+1)
    function FindOrNew(aHashCode: cardinal; Item: pointer; aHashTableIndex: PPtrInt): PtrInt;
    /// returns position in array, or -1 if not found with a custom comparer
    function FindOrNewComp(aHashCode: cardinal; Item: pointer;
      Comp: TDynArraySortCompare = nil): PtrInt;
    /// search an hashed element value for adding, updating the internal hash table
    // - trigger hashing if Count reaches CountTrigger
    function FindBeforeAdd(Item: pointer; out wasAdded: boolean;
      aHashCode: cardinal): PtrInt;
    /// search and delete an element value, updating the internal hash table
    function FindBeforeDelete(Item: pointer): PtrInt;
    /// full computation of the internal hash table
    // - to be called after items have been manually updated - e.g. after Clear
    // - can return the number of duplicated values found (likely to be 0)
    procedure ForceReHash(duplicates: PInteger = nil);
    {$ifndef PUREMORMOT2}
    function ReHash(forced: boolean = false): integer;
    {$endif PUREMORMOT2}
    /// compute the hash of a given item
    function HashOne(Item: pointer): cardinal;
      {$ifdef FPC_OR_DELPHIXE4}inline;{$endif}
      { not inlined to circumvent Delphi 2007=C1632, 2010=C1872, XE3=C2130 }
    /// compare one given item from its index with a value
    // - using either EventCompare() or Compare() functions
    function Equals(Item: pointer; ndx: PtrInt): boolean;
       {$ifdef FPC_OR_DELPHIXE4}inline;{$endif}
    /// retrieve the low-level hash of a given item
    function GetHashFromIndex(aIndex: PtrInt): cardinal;
    /// associated item comparison - may differ from DynArray^.Compare
    property Compare: TDynArraySortCompare
      read fCompare;
    /// custom method-based comparison function
    // - should be set just after Init, when no item has been stored
    property EventCompare: TOnDynArraySortCompare
      read fEventCompare write SetEventCompare;
    /// custom method-based hashing function
    // - should be set just after Init, when no item has been stored
    property EventHash: TOnDynArrayHashOne
      read fEventHash write SetEventHash;
    /// associated item hasher
    property Hasher: THasher
      read fHasher;
  end;

  /// pointer to a TDynArrayHasher instance
  PDynArrayHasher = ^TDynArrayHasher;

type
  /// used to access any dynamic arrray items using fast hash
  // - by default, binary sort could be used for searching items for TDynArray:
  // using a hash is faster on huge arrays for implementing a dictionary
  // - in this current implementation, modification (update or delete) of an
  // element is not handled yet: you should rehash all content - only
  // TDynArrayHashed.FindHashedForAdding / FindHashedAndUpdate /
  // FindHashedAndDelete will refresh the internal hash
  // - this object extends the TDynArray type, since presence of Hashs[] dynamic
  // array will increase code size if using TDynArrayHashed instead of TDynArray
  // - in order to have the better performance, you should use an external Count
  // variable, AND set the Capacity property to the expected maximum count (this
  // will avoid most re-hashing for FindHashedForAdding+FindHashedAndUpdate)
  // - consider using TSynDictionary from mormot.core.json for a thread-safe
  // stand-alone storage of key/value pairs
  {$ifdef UNDIRECTDYNARRAY}
  TDynArrayHashed = record
  // pseudo inheritance for most used methods
  private
    function GetCount: PtrInt; inline;
    procedure SetCount(aCount: PtrInt); inline;
    procedure SetCapacity(aCapacity: PtrInt); inline;
    function GetCapacity: PtrInt; inline;
  public
    InternalDynArray: TDynArray;
    function Value: PPointer; inline;
    function ItemSize: PtrUInt; inline;
    function Info: TRttiCustom; inline;
    procedure Clear; inline;
    procedure ItemCopy(Source, Dest: pointer); inline;
    function ItemPtr(index: PtrInt): pointer; inline;
    function ItemCopyAt(index: PtrInt; Dest: pointer): boolean; inline;
    function Add(const Item): PtrInt; inline;
    procedure Delete(aIndex: PtrInt); inline;
    function SaveTo: RawByteString; overload; inline;
    procedure SaveTo(W: TBufferWriter); overload; inline;
    procedure Sort(aCompare: TDynArraySortCompare = nil); inline;
    function SaveToJson(EnumSetsAsText: boolean = false;
      reformat: TTextWriterJsonFormat = jsonCompact): RawUtf8; overload; inline;
    procedure SaveToJson(out result: RawUtf8; EnumSetsAsText: boolean = false;
      reformat: TTextWriterJsonFormat = jsonCompact); overload; inline;
    procedure SaveToJson(W: TTextWriter); overload; inline;
    function LoadFromJson(P: PUtf8Char; aEndOfObject: PUtf8Char = nil;
      CustomVariantOptions: PDocVariantOptions = nil): PUtf8Char; inline;
    function LoadFrom(Source: PAnsiChar; SourceMax: PAnsiChar
      {$ifndef PUREMORMOT2} = nil{$endif}): PAnsiChar; inline;
    function LoadFromBinary(const Buffer: RawByteString): boolean; inline;
    procedure CreateOrderedIndex(var aIndex: TIntegerDynArray;
      aCompare: TDynArraySortCompare);
    property Count: PtrInt read GetCount write SetCount;
    property Capacity: PtrInt read GetCapacity write SetCapacity;
  private
  {$else UNDIRECTDYNARRAY}
  TDynArrayHashed = object(TDynArray)
  protected
  {$endif UNDIRECTDYNARRAY}
    fHash: TDynArrayHasher;
    function GetHashFromIndex(aIndex: PtrInt): cardinal;
      {$ifdef HASINLINE}inline;{$endif}
    procedure SetEventCompare(const cmp: TOnDynArraySortCompare);
    procedure SetEventHash(const hsh: TOnDynArrayHashOne);
  public
    /// initialize the wrapper with a one-dimension dynamic array
    // - this version accepts some hash-dedicated parameters: aHashItem to
    // set how to hash each element, aCompare to handle hash collision
    // - if no aHashItem is supplied, it will hash according to the RTTI, i.e.
    // strings or binary types, and the first field for records (strings included)
    // - if no aCompare is supplied, it will use default Equals() method
    // - if no THasher function is supplied, it will use the one supplied in
    // DefaultHasher global variable, set to crc32c() by default - using
    // SSE4.2 instruction if available
    // - if CaseInsensitive is set to TRUE, it will ignore difference in 7-bit
    // alphabetic characters (e.g. compare 'a' and 'A' as equal)
    procedure Init(aTypeInfo: PRttiInfo; var aValue; aHashItem: TDynArrayHashOne = nil;
      aCompare: TDynArraySortCompare = nil; aHasher: THasher = nil;
      aCountPointer: PInteger = nil; aCaseInsensitive: boolean = false);
    /// initialize the wrapper with a one-dimension dynamic array from our RTTI
    procedure InitRtti(aRtti: TRttiCustom; var aValue; aHashItem: TDynArrayHashOne = nil;
      aCompare: TDynArraySortCompare = nil; aHasher: THasher = nil;
      aCountPointer: PInteger = nil; aCaseInsensitive: boolean = false);
    /// initialize the wrapper with a one-dimension dynamic array
    // - this version accepts to specify how both hashing and comparison should
    // occur, setting the TRttiParserType kind of first/hashed field
    // - djNone and djCustom are too vague, and will raise an exception
    // - no RTTI check is made over the corresponding array layout: you shall
    // ensure that aKind matches the dynamic array element definition
    // - aCaseInsensitive will be used for djRawUtf8..djHash512 text comparison
    procedure InitSpecific(aTypeInfo: PRttiInfo; var aValue; aKind: TRttiParserType;
      aCountPointer: PInteger = nil; aCaseInsensitive: boolean = false;
      aHasher: THasher = nil);
    /// will recompute all hash from the current items of the dynamic array
    // - can be called on purpose, when modifications have been performed on
    // the dynamic array content (e.g. in case of element deletion or update,
    // or after calling LoadFrom/Clear method) - this is not necessary after
    // FindHashedForAdding / FindHashedAndUpdate / FindHashedAndDelete methods
    // - returns the number of duplicated items found - which should be 0
    procedure ForceReHash;
      {$ifdef HASINLINE} inline; {$endif}
    {$ifndef PUREMORMOT2}
    function ReHash(forced: boolean = false): integer;
    {$endif PUREMORMOT2}
    /// search for an element value inside the dynamic array using hashing
    // - Item should be of the type expected by both the hash function and
    // Compare/EventCompare methods: e.g. if the searched/hashed field contains
    // a string as first field, you can safely use a string variable as Item
    // - Item must refer to a variable: e.g. you can't write FindHashed(i+10)
    // - will call fHashItem(Item,fHasher) to compute the needed hash
    // - returns -1 if not found, or the index in the dynamic array if found
    function FindHashed(const Item): PtrInt;
      {$ifdef FPC} inline; {$endif}
    /// search for an element value inside the dynamic array using its hash
    // - returns -1 if not found, or the index in the dynamic array if found
    // - aHashCode parameter constains an already hashed value of the item,
    // to be used e.g. after a call to HashFind()
    function FindFromHash(const Item; aHashCode: cardinal): PtrInt;
    /// search for an element value inside the dynamic array using hashing, and
    // fill ItemToFill with the found content
    // - return the index found (0..Count-1), or -1 if Item was not found
    // - ItemToFill should be of the type expected by the dynamic array, since
    // all its fields will be set on match
    function FindHashedAndFill(var ItemToFill): PtrInt;
    /// search for an element value inside the dynamic array using hashing, and
    // add a void entry to the array if was not found (unless noAddEntry is set)
    // - this method will use hashing for fast retrieval
    // - Item should be of the type expected by both the hash function and
    // Compare/EventCompare methods: e.g. if the searched/hashed field contains
    // a string as first field, you can safely use a string variable as Item
    // - returns either the index in the dynamic array if found (and set wasAdded
    // to false), either the newly created index in the dynamic array (and set
    // wasAdded to true)
    // - for faster process (avoid rehash), please set the Capacity property
    // - warning: in contrast to the Add() method, if an entry is added to the
    // array (wasAdded=true), the entry is left VOID: you must set the field
    // content to expecting value - in short, Item is used only for searching,
    // not copied to the newly created entry in the array  - check
    // FindHashedAndUpdate() for a method actually copying Item fields
    function FindHashedForAdding(const Item; out wasAdded: boolean;
      noAddEntry: boolean = false): PtrInt; overload;
    /// search for an element value inside the dynamic array using hashing, and
    // add a void entry to the array if was not found (unless noAddEntry is set)
    // - overloaded method accepting an already hashed value of the item, to be
    // used e.g. after a call to HashFind()
    function FindHashedForAdding(const Item; out wasAdded: boolean;
      aHashCode: cardinal; noAddEntry: boolean = false): PtrInt; overload;
    /// ensure a given element name is unique, then add it to the array
    // - expected element layout is to have a RawUtf8 field at first position
    // - the aName is searched (using hashing) to be unique, and if not the case,
    // an ESynException.CreateUtf8() is raised with the supplied arguments
    // - use internally FindHashedForAdding method
    // - this version will set the field content with the unique value
    // - returns a pointer to the newly added element (to set other fields)
    function AddUniqueName(const aName: RawUtf8; const ExceptionMsg: RawUtf8;
      const ExceptionArgs: array of const;
      aNewIndex: PPtrInt = nil): pointer; overload;
    /// ensure a given element name is unique, then add it to the array
    // - just a wrapper to AddUniqueName(aName,'',[],aNewIndex)
    function AddUniqueName(const aName: RawUtf8;
      aNewIndex: PPtrInt = nil): pointer; overload;
    /// search for a given element name, make it unique, and add it to the array
    // - expected element layout is to have a RawUtf8 field at first position
    // - the aName is searched (using hashing) to be unique, and if not the case,
    // some suffix is added to make it unique, counting from _1 to _999
    // - use internally FindHashedForAdding method
    // - this version will set the field content with the unique value
    // - returns a pointer to the newly added element (to set other fields)
    function AddAndMakeUniqueName(aName: RawUtf8): pointer;
    /// search for an element value inside the dynamic array using hashing, then
    // update any matching item, or add the item if none matched
    // - by design, hashed field shouldn't have been modified by this update,
    // otherwise the method won't be able to find and update the old hash: in
    // this case, you should first call FindHashedAndDelete(OldItem) then
    // FindHashedForAdding(NewItem) to properly handle the internal hash table
    // - if AddIfNotExisting is FALSE, returns the index found (0..Count-1),
    // or -1 if Item was not found - Update will force slow rehash all content
    // - if AddIfNotExisting is TRUE, returns the index found (0..Count-1),
    // or the index newly created/added is the Item value was not matching -
    // add won't rehash all content - for even faster process (avoid rehash),
    // please set the Capacity property
    // - Item should be of the type expected by the dynamic array, since its
    // content will be copied into the dynamic array, and by design it must
    // refer to a variable: e.g. you can't write FindHashedAndUpdate(i+10)
    function FindHashedAndUpdate(const Item; AddIfNotExisting: boolean): PtrInt;
    /// search for an element value inside the dynamic array using hashing, and
    // delete it if matchs
    // - return the index deleted (0..Count-1), or -1 if Item was not found
    // - can optionally copy the deleted item to FillDeleted^ before erased
    // - Item should be of the type expected by Compare/EventCompare (no need
    // to supply a full array item), and by design must refer to a variable:
    // e.g. you can't write FindHashedAndDelete(i+10)
    // - it won't call slow ForceReHash but refresh the hash table as needed
    function FindHashedAndDelete(const Item; FillDeleted: pointer = nil;
      noDeleteEntry: boolean = false): PtrInt;
    /// search for an element value inside the dynamic array without hashing
    // - is preferred to Find(), since EventCompare would be used if defined
    // - Item should be of the type expected by Compare/EventCompare (no need
    // to supply a full array item), and by design must refer to a variable:
    // e.g. you can't write Scan(i+10)
    // - returns -1 if not found, or the index in the dynamic array if found
    function Scan(const Item): PtrInt;
    /// retrieve the hash value of a given item, from its index
    property Hash[aIndex: PtrInt]: cardinal
      read GetHashFromIndex;
    /// alternative event-oriented Compare function to be used for Sort and Find
    // - will be used instead of Compare, to allow object-oriented callbacks
    // - should be set just after Init, when not item has been stored
    property EventCompare: TOnDynArraySortCompare
      read fHash.fEventCompare write SetEventCompare;
    /// custom hash function used for hashing of a dynamic array element
    property HashItem: TDynArrayHashOne
      read fHash.fHashItem;
    /// alternative event-oriented Hash function
    // - this object-oriented callback will be used instead of HashItem()
    // on each dynamic array entries - HashItem will still be used on
    // const Item values, since they may be just a sub part of the stored entry
    // - should be set just after Init, when not item has been stored
    property EventHash: TOnDynArrayHashOne
      read fHash.fEventHash write SetEventHash;
    /// access to the internal hash table
    // - you can call e.g. Hasher.Clear to invalidate the whole hash table
    property Hasher: TDynArrayHasher
      read fHash;
  end;


/// initialize the structure with a one-dimension dynamic array
// - the dynamic array must have been defined with its own type
// (e.g. TIntegerDynArray = array of integer)
// - if aCountPointer is set, it will be used instead of length() to store
// the dynamic array items count - it will be much faster when adding
// elements to the array, because the dynamic array won't need to be
// resized each time - but in this case, you should use the Count property
// instead of length(array) or high(array) when accessing the data: in fact
// length(array) will store the memory size reserved, not the items count
// - if aCountPointer is set, its content will be set to 0, whatever the
// array length is, or the current aCountPointer^ value is
// - a typical usage could be:
// !var
// !  IntArray: TIntegerDynArray;
// !begin
// !  with DynArray(TypeInfo(TIntegerDynArray), IntArray) do
// !  begin
// !    (...)
// !  end;
// ! (...)
// ! bin := DynArray(TypeInfo(TIntegerDynArray), IntArray).SaveTo;
function DynArray(aTypeInfo: PRttiInfo; var aValue;
  aCountPointer: PInteger = nil): TDynArray;
  {$ifdef HASINLINE}inline;{$endif}

/// get the hash function corresponding to a given standard array type
// - as used internally by TDynArrayHasher.Init and exported here for testing
function DynArrayHashOne(Kind: TRttiParserType;
  CaseInsensitive: boolean = false): TDynArrayHashOne;

/// sort any dynamic array, generating an external array of indexes
// - this function will use the supplied TSynTempBuffer for index storage,
// so use PIntegerArray(Indexes.buf) to access the values
// - caller should always make Indexes.Done once finshed
procedure DynArraySortIndexed(Values: pointer; ItemSize, Count: integer;
  out Indexes: TSynTempBuffer; Compare: TDynArraySortCompare); overload;

/// sort any dynamic array, via a supplied array of indexes
// - this function expects Indexes[] to be already allocated and filled
procedure DynArraySortIndexed(Values: pointer; ItemSize, Count: integer;
  Indexes: PCardinalArray; Compare: TDynArraySortCompare); overload;

/// get the comparison function corresponding to a given standard array type
// - as used e.g. internally by TDynArray
function DynArraySortOne(Kind: TRttiParserType; CaseInsensitive: boolean): TDynArraySortCompare;

/// sort any TObjArray with a given comparison function
procedure ObjArraySort(var aValue; Compare: TDynArraySortCompare;
  CountPointer: PInteger = nil);


{ *************** Integer Arrays Extended Process }

type
  /// event handler called by NotifySortedIntegerChanges()
  // - Sender is an opaque const value, maybe a TObject or any pointer
  TOnNotifySortedIntegerChange = procedure(const Sender; Value: integer) of object;

/// compares two 32-bit signed sorted integer arrays, and call event handlers
// to notify the corresponding modifications in an O(n) time
// - items in both old[] and new[] arrays are required to be sorted
procedure NotifySortedIntegerChanges(old, new: PIntegerArray; oldn, newn: PtrInt;
  const added, deleted: TOnNotifySortedIntegerChange; const sender);

/// copy an integer array, then sort it, low values first
procedure CopyAndSortInteger(Values: PIntegerArray; ValuesCount: integer;
  var Dest: TIntegerDynArray);

/// copy an integer array, then sort it, low values first
procedure CopyAndSortInt64(Values: PInt64Array; ValuesCount: integer;
  var Dest: TInt64DynArray);

/// remove some 32-bit integer from Values[]
// - Excluded is declared as var, since it will be sorted in-place during process
// if it contains more than ExcludedSortSize items (i.e. if the sort is worth it)
procedure ExcludeInteger(var Values, Excluded: TIntegerDynArray;
  ExcludedSortSize: integer = 32);

/// ensure some 32-bit integer from Values[] will only contain Included[]
// - Included is declared as var, since it will be sorted in-place during process
// if it contains more than IncludedSortSize items (i.e. if the sort is worth it)
procedure IncludeInteger(var Values, Included: TIntegerDynArray;
  IncludedSortSize: integer = 32);

/// sort and remove any 32-bit duplicated integer from Values[]
procedure DeduplicateInteger(var Values: TIntegerDynArray); overload;

/// sort and remove any 32-bit duplicated integer from Values[]
// - returns the new Values[] length
function DeduplicateInteger(var Values: TIntegerDynArray; Count: PtrInt): PtrInt; overload;

/// low-level function called by DeduplicateInteger()
function DeduplicateIntegerSorted(val: PIntegerArray; last: PtrInt): PtrInt;

/// create a new 32-bit integer dynamic array with the values from another one
procedure CopyInteger(const Source: TIntegerDynArray; out Dest: TIntegerDynArray);

/// remove some 64-bit integer from Values[]
// - Excluded is declared as var, since it will be sorted in-place during process
// if it contains more than ExcludedSortSize items (i.e. if the sort is worth it)
procedure ExcludeInt64(var Values, Excluded: TInt64DynArray;
  ExcludedSortSize: integer = 32);

/// ensure some 64-bit integer from Values[] will only contain Included[]
// - Included is declared as var, since it will be sorted in-place during process
// if it contains more than IncludedSortSize items (i.e. if the sort is worth it)
procedure IncludeInt64(var Values, Included: TInt64DynArray;
  IncludedSortSize: integer = 32);

/// sort and remove any 64-bit duplicated integer from Values[]
procedure DeduplicateInt64(var Values: TInt64DynArray); overload;

/// sort and remove any 64-bit duplicated integer from Values[]
// - returns the new Values[] length
function DeduplicateInt64(var Values: TInt64DynArray; Count: PtrInt): PtrInt; overload;

/// low-level function called by DeduplicateInt64()
// - warning: caller should ensure that last>0
function DeduplicateInt64Sorted(val: PInt64Array; last: PtrInt): PtrInt;

/// create a new 64-bit integer dynamic array with the values from another one
procedure CopyInt64(const Source: TInt64DynArray; out Dest: TInt64DynArray);

/// find the maximum 32-bit integer in Values[]
function MaxInteger(const Values: TIntegerDynArray; ValuesCount: PtrInt;
  MaxStart: integer = -1): integer;

/// find the maximum 64-bit integer in Values[]
function MaxInt64(const Values: TInt64DynArray; ValuesCount: PtrInt;
  MaxStart: Int64 = -1): Int64;

/// sum all 32-bit integers in Values[]
function SumInteger(const Values: TIntegerDynArray; ValuesCount: PtrInt): integer;

/// fill already allocated Reversed[] so that Reversed[Values[i]]=i
procedure Reverse(const Values: TIntegerDynArray; ValuesCount: PtrInt;
  Reversed: PIntegerArray);

/// copy some Int64 values into an unsigned integer array
procedure Int64ToUInt32(Values64: PInt64Array; Values32: PCardinalArray; Count: PtrInt);

type
  /// comparison function as expected by MedianQuickSelect()
  // - should return TRUE if Values[IndexA]>Values[IndexB]
  TOnValueGreater = function(IndexA, IndexB: PtrInt): boolean of object;

/// compute the median of a serie of values, using "Quickselect"
// - based on the algorithm described in "Numerical recipes in C", Second Edition
// - expect the values information to be available from a comparison callback
// - this version will use a temporary index list to exchange items order
// (supplied as a TSynTempBuffer), so won't change the supplied values themself
// - see also function MedianQuickSelectInteger() for PIntegerArray values
// - returns the index of the median Value
function MedianQuickSelect(const OnCompare: TOnValueGreater; n: integer;
  var TempBuffer: TSynTempBuffer): integer;

/// compute the median of an integer serie of values, using "Quickselect"
// - based on the algorithm described in "Numerical recipes in C", Second Edition,
// translated from Nicolas Devillard's C code: http://ndevilla.free.fr/median/median
// - warning: the supplied integer array is modified in-place during the process,
// and won't be fully sorted on output (this is no QuickSort alternative)
function MedianQuickSelectInteger(Values: PIntegerArray; n: integer): integer;


/// fast search of a binary value position in a fixed-size array
// - Count is the number of entries in P^[]
// - return index of P^[index]=V^, comparing VSize bytes
// - return -1 if Value was not found
function AnyScanIndex(P, V: pointer; Count, VSize: PtrInt): PtrInt;

/// fast search of a binary value position in a fixed-size array
// - Count is the number of entries in P^[]
function AnyScanExists(P, V: pointer; Count, VSize: PtrInt): boolean;
  {$ifdef HASINLINE} inline; {$endif}


{ ************ INI Files and In-memory Access }

/// find a Name= Value in a [Section] of a INI RawUtf8 Content
// - this function scans the Content memory buffer, and is
// therefore very fast (no temporary TMemIniFile is created)
// - if Section equals '', find the Name= value before any [Section]
function FindIniEntry(const Content, Section, Name: RawUtf8;
  const DefaultValue: RawUtf8 = ''): RawUtf8;

/// find a Name= Value in a [Section] of a INI WinAnsi Content
// - same as FindIniEntry(), but the value is converted from WinAnsi into UTF-8
function FindWinAnsiIniEntry(const Content, Section, Name: RawUtf8): RawUtf8;

/// find a Name= numeric Value in a [Section] of a INI RawUtf8 Content and
// return it as an integer, or 0 if not found
// - this function scans the Content memory buffer, and is
// therefore very fast (no temporary TMemIniFile is created)
// - if Section equals '', find the Name= value before any [Section]
function FindIniEntryInteger(const Content, Section, Name: RawUtf8): integer;
  {$ifdef HASINLINE}inline;{$endif}

/// find a Name= Value in a [Section] of a .INI file
// - if Section equals '', find the Name= value before any [Section]
// - use internally fast FindIniEntry() function above
function FindIniEntryFile(const FileName: TFileName;
  const Section, Name: RawUtf8; const DefaultValue: RawUtf8 = ''): RawUtf8;

/// update a Name= Value in a [Section] of a INI RawUtf8 Content
// - this function scans and update the Content memory buffer, and is
// therefore very fast (no temporary TMemIniFile is created)
// - if Section equals '', update the Name= value before any [Section]
procedure UpdateIniEntry(var Content: RawUtf8; const Section, Name, Value: RawUtf8);

/// update a Name= Value in a [Section] of a .INI file
// - if Section equals '', update the Name= value before any [Section]
// - use internally fast UpdateIniEntry() function above
procedure UpdateIniEntryFile(const FileName: TFileName; const Section, Name, Value: RawUtf8);

/// find the position of the [SEARCH] section in source
// - return true if [SEARCH] was found, and store pointer to the line after it in source
function FindSectionFirstLine(var source: PUtf8Char; search: PAnsiChar): boolean;

/// find the position of the [SEARCH] section in source
// - return true if [SEARCH] was found, and store pointer to the line after it in source
// - this version expects source^ to point to an Unicode char array
function FindSectionFirstLineW(var source: PWideChar; search: PUtf8Char): boolean;

/// retrieve the whole content of a section as a string
// - SectionFirstLine may have been obtained by FindSectionFirstLine() function above
function GetSectionContent(SectionFirstLine: PUtf8Char): RawUtf8; overload;

/// retrieve the whole content of a section as a string
// - use SectionFirstLine() then previous GetSectionContent()
function GetSectionContent(const Content, SectionName: RawUtf8): RawUtf8; overload;

/// delete a whole [Section]
// - if EraseSectionHeader is TRUE (default), then the [Section] line is also
// deleted together with its content lines
// - return TRUE if something was changed in Content
// - return FALSE if [Section] doesn't exist or is already void
function DeleteSection(var Content: RawUtf8; const SectionName: RawUtf8;
  EraseSectionHeader: boolean = true): boolean; overload;

/// delete a whole [Section]
// - if EraseSectionHeader is TRUE (default), then the [Section] line is also
// deleted together with its content lines
// - return TRUE if something was changed in Content
// - return FALSE if [Section] doesn't exist or is already void
// - SectionFirstLine may have been obtained by FindSectionFirstLine() function above
function DeleteSection(SectionFirstLine: PUtf8Char; var Content: RawUtf8;
  EraseSectionHeader: boolean = true): boolean; overload;

/// replace a whole [Section] content by a new content
// - create a new [Section] if none was existing
procedure ReplaceSection(var Content: RawUtf8; const SectionName,
  NewSectionContent: RawUtf8); overload;

/// replace a whole [Section] content by a new content
// - create a new [Section] if none was existing
// - SectionFirstLine may have been obtained by FindSectionFirstLine() function above
procedure ReplaceSection(SectionFirstLine: PUtf8Char;
  var Content: RawUtf8; const NewSectionContent: RawUtf8); overload;

/// return TRUE if Value of UpperName does exist in P, till end of current section
// - expect UpperName as 'NAME='
function ExistsIniName(P: PUtf8Char; UpperName: PAnsiChar): boolean;

/// find the Value of UpperName in P, till end of current section
// - expect UpperName as 'NAME='
function FindIniNameValue(P: PUtf8Char; UpperName: PAnsiChar;
  const DefaultValue: RawUtf8 = ''): RawUtf8;

/// return TRUE if one of the Value of UpperName exists in P, till end of
// current section
// - expect UpperName e.g. as 'CONTENT-TYPE: '
// - expect UpperValues to be an array of upper values with left side matching,
// and ending with nil - as expected by IdemPPChar(), i.e. with at least 2 chars
function ExistsIniNameValue(P: PUtf8Char; const UpperName: RawUtf8;
  UpperValues: PPAnsiChar): boolean;

/// find the integer Value of UpperName in P, till end of current section
// - expect UpperName as 'NAME='
// - return 0 if no NAME= entry was found
function FindIniNameValueInteger(P: PUtf8Char; const UpperName: RawUtf8): PtrInt;

/// replace a value from a given set of name=value lines
// - expect UpperName as 'UPPERNAME=', otherwise returns false
// - if no UPPERNAME= entry was found, then Name+NewValue is added to Content
// - a typical use may be:
// ! UpdateIniNameValue(headers,HEADER_CONTENT_TYPE,HEADER_CONTENT_TYPE_UPPER,contenttype);
function UpdateIniNameValue(var Content: RawUtf8;
  const Name, UpperName, NewValue: RawUtf8): boolean;

/// fill a class Instance properties from an .ini content
// - the class property fields are searched in the supplied main SectionName
// - nested objects and multi-line text values are searched in their own section,
// named from their section level and property (e.g. [mainprop.nested1.nested2])
// - returns true if at least one property has been identified
function IniToObject(const Ini: RawUtf8; Instance: TObject;
  const SectionName: RawUtf8 = 'Main'; DocVariantOptions: PDocVariantOptions = nil;
  Level: integer = 0): boolean;

/// serialize a class Instance properties into an .ini content
// - the class property fields are written in the supplied main SectionName
// - nested objects and multi-line text values are written in their own section,
// named from their section level and property (e.g. [mainprop.nested1.nested2])
function ObjectToIni(const Instance: TObject; const SectionName: RawUtf8 = 'Main';
  Options: TTextWriterWriteObjectOptions =
    [woEnumSetsAsText, woRawBlobAsBase64, woHumanReadableEnumSetAsComment];
    Level: integer = 0): RawUtf8;

/// returns TRUE if the supplied HTML Headers contains 'Content-Type: text/...',
// 'Content-Type: application/json' or 'Content-Type: application/xml'
function IsHtmlContentTypeTextual(Headers: PUtf8Char): boolean;
  {$ifdef HASINLINE}inline;{$endif}

/// search if the WebSocketUpgrade() header is present
// - consider checking the hsrConnectionUpgrade flag instead
function IsWebSocketUpgrade(headers: PUtf8Char): boolean;


{ ************ RawUtf8 String Values Interning and TRawUtf8List }

type
  /// store a TRawUtf8DynArray with its efficient hash table
  {$ifdef USERECORDWITHMETHODS}
  TRawUtf8Hashed = record
  {$else}
  TRawUtf8Hashed = object
  {$endif USERECORDWITHMETHODS}
  public
    Count: integer;
    Value: TRawUtf8DynArray;
    Values: TDynArrayHashed;
    /// initialize the RawUtf8 dynamic array and hasher
    procedure Init;
  end;

  /// used to store one list of hashed RawUtf8 in TRawUtf8Interning pool
  // - Delphi "object" is buggy on stack -> also defined as record with methods
  // - each slot has its own TRWLightLock for efficient concurrent reads
  {$ifdef USERECORDWITHMETHODS}
  TRawUtf8InterningSlot = record
  {$else}
  TRawUtf8InterningSlot = object
  {$endif USERECORDWITHMETHODS}
  private
    fSafe: TRWLightLock;
    fHash: TRawUtf8Hashed;
  public
    /// initialize the RawUtf8 slot (and its Safe mutex)
    procedure Init;
    /// returns the interned RawUtf8 value
    procedure Unique(var aResult: RawUtf8; const aText: RawUtf8;
      aTextHash: cardinal);
    /// returns the interned RawUtf8 value
    // - only allocates new aResult string if needed
    procedure UniqueFromBuffer(var aResult: RawUtf8;
      aText: PUtf8Char; aTextLen: PtrInt; aTextHash: cardinal); overload;
    /// returns the interned RawUtf8 value with no pre-computed hash
    procedure UniqueFromBuffer(var aResult: RawUtf8;
      aText: PUtf8Char; aTextLen: PtrInt); overload;
    /// ensure the supplied RawUtf8 value is interned
    procedure UniqueText(var aText: RawUtf8; aTextHash: cardinal);
    /// return the interned value, if any
    function Existing(const aText: RawUtf8; aTextHash: cardinal): pointer;
    /// delete all stored RawUtf8 values
    procedure Clear;
    /// reclaim any unique RawUtf8 values
    // - any string with an usage count <= aMaxRefCount will be removed
    function Clean(aMaxRefCount: TStrCnt): integer;
    /// how many items are currently stored in Value[]
    property Count: integer
      read fHash.Count;
  end;
  PRawUtf8InterningSlot = ^TRawUtf8InterningSlot;

  /// allow to store only one copy of distinct RawUtf8 values
  // - thanks to the Copy-On-Write feature of string variables, this may
  // reduce a lot the memory overhead of duplicated text content
  // - this class is thread-safe and optimized for performance
  TRawUtf8Interning = class(TRawUtf8InterningAbstract)
  protected
    fPool: array of TRawUtf8InterningSlot;
    fPoolLast: integer;
  public
    /// initialize the storage and its internal hash pools
    // - aHashTables is the pool size, and should be a power of two <= 512
    // (1, 2, 4, 8, 16, 32, 64, 128, 256, 512)
    constructor Create(aHashTables: integer = 4); reintroduce;
    /// return a RawUtf8 variable stored within this class
    // - if aText occurs for the first time, add it to the internal string pool
    // - if aText does exist in the internal string pool, return the shared
    // instance (with its reference counter increased), to reduce memory usage
    function Unique(const aText: RawUtf8): RawUtf8; overload;
    /// check if a RawUtf8 value is already stored within this class
    // - if not existing, returns nil and don't add it to the pool
    // - if existing, returns pointer(fValue[i]) of the unique stored RawUtf8
    // - use e.g. for very fast per-pointer lookup of interned property names
    function Existing(const aText: RawUtf8): pointer;
    /// return a RawUtf8 variable stored within this class from a text buffer
    // - if aText occurs for the first time, add it to the internal string pool
    // - if aText does exist in the internal string pool, return the shared
    // instance (with its reference counter increased), to reduce memory usage
    function Unique(aText: PUtf8Char; aTextLen: PtrInt): RawUtf8; overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// return a RawUtf8 variable stored within this class
    // - if aText occurs for the first time, add it to the internal string pool
    // - if aText does exist in the internal string pool, return the shared
    // instance (with its reference counter increased), to reduce memory usage
    procedure Unique(var aResult: RawUtf8; const aText: RawUtf8); overload;
    /// return a RawUtf8 variable stored within this class from a text buffer
    // - if aText occurs for the first time, add it to the internal string pool
    // - if aText does exist in the internal string pool, return the shared
    // instance (with its reference counter increased), to reduce memory usage
    // - this method won't allocate any memory if aText is already interned
    procedure Unique(var aResult: RawUtf8; aText: PUtf8Char; aTextLen: PtrInt); overload;
    /// ensure a RawUtf8 variable is stored within this class
    // - if aText occurs for the first time, add it to the internal string pool
    // - if aText does exist in the internal string pool, set the shared
    // instance (with its reference counter increased), to reduce memory usage
    procedure UniqueText(var aText: RawUtf8);
    /// return a variant containing a RawUtf8 stored within this class
    // - similar to RawUtf8ToVariant(), but with string interning
    // - see also UniqueVariant() from mormot.core.variants if you want to
    // intern only non-numerical values
    procedure UniqueVariant(var aResult: variant; const aText: RawUtf8); overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// return a variant containing a RawUtf8 stored within this class
    // - similar to RawUtf8ToVariant(StringToUtf8()), but with string interning
    // - this method expects the text to be supplied as a RTL string, which will
    // be converted into a variant containing a RawUtf8 varString instance
    procedure UniqueVariantString(var aResult: variant; const aText: string);
    /// ensure a variant contains only RawUtf8 stored within this class
    // - supplied variant should be a varString containing a RawUtf8 value
    procedure UniqueVariant(var aResult: variant); overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// delete any previous storage pool
    procedure Clear;
    /// reclaim any unique RawUtf8 values
    // - i.e. run a garbage collection process of all values with RefCount=1
    // by default, i.e. all string which are not used any more; you may set
    // aMaxRefCount to a higher value, depending on your expecations, i.e. 2 to
    // delete all string which are referenced only once outside of the pool
    // - returns the number of unique RawUtf8 cleaned from the internal pool
    // - to be executed on a regular basis - but not too often, since the
    // process can be time consumming, and void the benefit of interning
    function Clean(aMaxRefCount: TStrCnt = 1): integer;
    /// how many items are currently stored in this instance
    function Count: integer;
  end;

  /// possible values used by TRawUtf8List.Flags
  TRawUtf8ListFlags = set of (
    fObjectsOwned,
    fCaseSensitive,
    fNoDuplicate,
    fOnChangeTrigerred,
    fThreadSafe);

  /// thread-safe TStringList-class optimized for our native UTF-8 string type
  // - can optionally store associated some TObject instances
  // - high-level methods of this class are thread-safe
  // - if fNoDuplicate flag is defined, an internal hash table will be
  // maintained to perform IndexOf() lookups in O(1) linear way
  // - not thread-safe by default, unless fThreadSafe is set to use the TRWLock
  TRawUtf8List = class(TSynPersistentRWLock)
  protected
    fCount: PtrInt;
    fValue: TRawUtf8DynArray;
    fValues: TDynArrayHashed;
    fObjects: TObjectDynArray;
    fFlags: TRawUtf8ListFlags;
    fNameValueSep: AnsiChar;
    fOnChange, fOnChangeBackupForBeginUpdate: TNotifyEvent;
    fOnChangeLevel: integer;
    function GetCount: PtrInt;
      {$ifdef HASINLINE}inline;{$endif}
    procedure SetCapacity(const capa: PtrInt);
    function GetCapacity: PtrInt;
    function Get(Index: PtrInt): RawUtf8;
      {$ifdef HASINLINE}inline;{$endif}
    procedure Put(Index: PtrInt; const Value: RawUtf8);
    function GetS(Index: PtrInt): string;
    procedure PutS(Index: PtrInt; const Value: string);
    function GetObject(Index: PtrInt): pointer;
      {$ifdef HASINLINE}inline;{$endif}
    procedure PutObject(Index: PtrInt; Value: pointer);
    function GetName(Index: PtrInt): RawUtf8;
    function GetValue(const Name: RawUtf8): RawUtf8;
    procedure SetValue(const Name, Value: RawUtf8);
    function GetTextCRLF: RawUtf8;
    procedure SetTextCRLF(const Value: RawUtf8);
    procedure SetTextPtr(P, PEnd: PUtf8Char; const Delimiter: RawUtf8);
    function GetTextPtr: PPUtf8CharArray;
      {$ifdef HASINLINE}inline;{$endif}
    function GetNoDuplicate: boolean;
      {$ifdef HASINLINE}inline;{$endif}
    function GetObjectPtr: PPointerArray;
      {$ifdef HASINLINE}inline;{$endif}
    function GetCaseSensitive: boolean;
      {$ifdef HASINLINE}inline;{$endif}
    procedure SetCaseSensitive(Value: boolean); virtual;
    procedure Changed; virtual;
    procedure InternalDelete(Index: PtrInt);
    procedure OnChangeHidden(Sender: TObject);
    {$ifndef PUREMORMOT2}
    procedure SetDefaultFlags; virtual;
    {$endif PUREMORMOT2}
  public
    /// initialize the RawUtf8/Objects storage with [fCaseSensitive] flags
    constructor Create; overload; override;
    /// initialize the RawUtf8/Objects storage with extended flags
    // - by default, any associated Objects[] are just weak references;
    // you may supply fOwnObjects flag to force object instance management
    // - if you want the stored text items to be unique, set fNoDuplicate
    // and then an internal hash table will be maintained for fast IndexOf()
    // - you can set fCaseSensitive to let the UTF-8 lookup be case-sensitive
    // - not thread-safe by default, unless fThreadSafe is set to use a R/W lock
    // - is defined as CreateEx instead of overload Create to avoid weird Delphi
    // compilation issues, especially within packages
    constructor CreateEx(aFlags: TRawUtf8ListFlags);
    {$ifndef PUREMORMOT2}
    /// backward compatiliby overloaded constructor
    // - please rather use the overloaded CreateEx(TRawUtf8ListFlags)
    // - for instance, Create(true) is CreateEx([fObjectsOwned, fCaseSensitive]);
    constructor Create(aOwnObjects: boolean; aNoDuplicate: boolean = false;
      aCaseSensitive: boolean = true); reintroduce; overload;
    {$endif PUREMORMOT2}
    /// finalize the internal objects stored
    // - if instance was created with fOwnObjects flag
    destructor Destroy; override;
    /// get a stored Object item by its associated UTF-8 text
    // - returns nil and raise no exception if aText doesn't exist
    // - thread-safe method, unless returned TObject is deleted in the background
    function GetObjectFrom(const aText: RawUtf8): pointer;
    /// store a new RawUtf8 item
    // - without the fNoDuplicate flag, it will always add the supplied value
    // - if fNoDuplicate was set and aText already exists (using the internal
    // hash table), it will return -1 unless aRaiseExceptionIfExisting is forced
    // - thread-safe method
    function Add(const aText: RawUtf8;
      aRaiseExceptionIfExisting: boolean = false): PtrInt;
      {$ifdef HASINLINE}inline;{$endif}
    /// store a new RawUtf8 item, and its associated TObject
    // - without the fNoDuplicate flag, it will always add the supplied value
    // - if fNoDuplicate was set and aText already exists (using the internal hash
    // table), it will return -1 unless aRaiseExceptionIfExisting is forced;
    // optionally freeing the supplied aObject if aFreeAndReturnExistingObject
    // is set, in which pointer the existing Objects[] is copied (see
    // AddObjectUnique as a convenient wrapper around this behavior);
    // if aFreeAndReturnExistingObject is nil, and aReplaceExistingObject is
    // true, the existing object is freed and replaced by aObject
    // - thread-safe method
    function AddObject(const aText: RawUtf8; aObject: TObject;
      aRaiseExceptionIfExisting: boolean = false;
      aFreeAndReturnExistingObject: PPointer = nil;
      aReplaceExistingObject: boolean = false): PtrInt;
    /// try to store a new RawUtf8 item and its associated TObject
    // - fNoDuplicate should have been specified in the list flags
    // - if aText doesn't exist, will add the values
    // - if aText exist, will call aObjectToAddOrFree.Free and set the value
    // already stored in Objects[] into aObjectToAddOrFree - allowing dual
    // commit thread-safe update of the list, e.g. after a previous unsuccessful
    // call to GetObjectFrom(aText)
    // - thread-safe method, using an internal Hash Table to speedup IndexOf()
    // - in fact, this method is just a wrapper around
    // ! AddObject(aText,aObjectToAddOrFree^,false,@aObjectToAddOrFree);
    procedure AddObjectUnique(const aText: RawUtf8; aObjectToAddOrFree: PPointer);
      {$ifdef HASINLINE}inline;{$endif}
    /// force the storage of a RawUtf8 item, and its associated TObject
    // - without the fNoDuplicate flag, it will always add the supplied value
    // - if fNoDuplicate was set and aText already exists (using the internal hash
    // table), it will free any existing Objects[] and put aObject in its place
    // - thread-safe method, using an internal Hash Table to speedup IndexOf()
    function AddOrReplaceObject(const aText: RawUtf8; aObject: TObject): PtrInt;
      {$ifdef HASINLINE}inline;{$endif}
    /// append a specified list to the current content
    // - thread-safe method
    procedure AddRawUtf8List(List: TRawUtf8List);
    /// delete a stored RawUtf8 item, and its associated TObject
    // - raise no exception in case of out of range supplied index
    // - this method is not thread-safe: use Safe.Lock/UnLock if needed
    procedure Delete(Index: PtrInt); overload;
    /// delete a stored RawUtf8 item, and its associated TObject
    // - will search for the value using IndexOf(aText), and returns its index
    // - returns -1 if no entry was found and deleted
    // - thread-safe method, using the internal Hash Table if fNoDuplicate is set
    function Delete(const aText: RawUtf8): PtrInt; overload;
    /// delete a stored RawUtf8 item, and its associated TObject, from
    // a given Name when stored as 'Name=Value' pairs
    // - raise no exception in case of out of range supplied index
    // - thread-safe method, but not using the internal Hash Table
    // - consider using TSynNameValue if you expect efficient name/value process
    function DeleteFromName(const Name: RawUtf8): PtrInt; virtual;
    /// find the index of a given Name when stored as 'Name=Value' pairs
    // - search on Name is case-insensitive with 'Name=Value' pairs
    // - this method is not thread-safe, and won't use the internal Hash Table
    // - consider using TSynNameValue if you expect efficient name/value process
    function IndexOfName(const Name: RawUtf8): PtrInt;
    /// access to the Value of a given 'Name=Value' pair at a given position
    // - this method is not thread-safe
    // - consider using TSynNameValue if you expect efficient name/value process
    function GetValueAt(Index: PtrInt): RawUtf8;
    /// compare a Value with some RawUtf8 text
    // - this method is not thread-safe
    function EqualValueAt(Index: PtrInt; const aText: RawUtf8): boolean;
      {$ifdef HASINLINE}inline;{$endif}
    /// retrieve Value from an existing Name=Value, then optinally delete the entry
    // - if Name is found, will fill Value with the stored content and return true
    // - if Name is not found, Value is not modified, and false is returned
    // - thread-safe method, but not using the internal Hash Table
    // - consider using TSynNameValue if you expect efficient name/value process
    function UpdateValue(const Name: RawUtf8; var Value: RawUtf8;
      ThenDelete: boolean): boolean;
    /// retrieve and delete the first RawUtf8 item in the list
    // - could be used as a FIFO, calling Add() as a "push" method
    // - thread-safe method
    function PopFirst(out aText: RawUtf8; aObject: PObject = nil): boolean;
    /// retrieve and delete the last RawUtf8 item in the list
    // - could be used as a FILO, calling Add() as a "push" method
    // - thread-safe method
    function PopLast(out aText: RawUtf8; aObject: PObject = nil): boolean;
    /// erase all stored RawUtf8 items
    // - and corresponding objects (if aOwnObjects was true at constructor)
    // - thread-safe method, also clearing the internal Hash Table
    procedure Clear; virtual;
    /// find a RawUtf8 item in the stored Strings[] list
    // - this search is case sensitive if fCaseSensitive flag was set (which
    // is the default)
    // - this method is not thread-safe since the internal list may change
    // and the returned index may not be accurate any more
    // - see also Exists() and GetObjectFrom() method
    // - uses the internal Hash Table if fNoDuplicate was set
    function IndexOf(const aText: RawUtf8): PtrInt;
    /// find a RawUtf8 item in the stored Strings[] list
    // - search is case sensitive if fCaseSensitive flag was set (default)
    // - this method is thread-safe
    // - uses the internal Hash Table if fNoDuplicate was set
    function Exists(const aText: RawUtf8): boolean;
    /// find a TObject item index in the stored Objects[] list
    // - this method is not thread-safe since the internal list may change
    // and the returned index may not be accurate any more
    // - aObject lookup won't use the internal Hash Table
    function IndexOfObject(aObject: TObject): PtrInt;
    /// search for any RawUtf8 item containing some text
    // - uses PosEx() on the stored lines
    // - this method is not thread-safe since the internal list may change
    // and the returned index may not be accurate any more
    // - by design, aText lookup can't use the internal Hash Table
    function Contains(const aText: RawUtf8; aFirstIndex: integer = 0): PtrInt;
    /// retrieve the all lines, separated by the supplied delimiter
    // - this method is thread-safe
    function GetText(const Delimiter: RawUtf8 = #13#10): RawUtf8;
    /// the OnChange event will be raised only when EndUpdate will be called
    // - this method will also call Safe.Lock for thread-safety
    procedure BeginUpdate;
    /// call the OnChange event if changes occurred
    // - this method will also call Safe.UnLock for thread-safety
    procedure EndUpdate;
    /// set low-level text and objects from existing arrays
    procedure SetFrom(const aText: TRawUtf8DynArray; const aObject: TObjectDynArray);
    /// set all lines, separated by the supplied delimiter
    // - this method is thread-safe
    procedure SetText(const aText: RawUtf8; const Delimiter: RawUtf8 = #13#10);
    /// set all lines from a text file
    // - will assume text file with no BOM is already UTF-8 encoded
    // - this method is thread-safe
    procedure LoadFromFile(const FileName: TFileName);
    /// write all lines into the supplied stream
    // - this method is thread-safe
    procedure SaveToStream(Dest: TStream; const Delimiter: RawUtf8 = #13#10);
    /// write all lines into a new UTF-8 file
    // - this method is thread-safe
    procedure SaveToFile(const FileName: TFileName; const Delimiter: RawUtf8 = #13#10);
    /// return the count of stored RawUtf8
    // - reading this property is not thread-safe, since size may change
    property Count: PtrInt
      read GetCount;
    /// set or retrieve the current memory capacity of the RawUtf8 list
    // - reading this property is not thread-safe, since size may change
    property Capacity: PtrInt
      read GetCapacity write SetCapacity;
    /// set if IndexOf() shall be case sensitive or not
    // - default is TRUE
    // - matches fCaseSensitive in Flags
    property CaseSensitive: boolean
      read GetCaseSensitive write SetCaseSensitive;
    /// set if the list doesn't allow duplicated UTF-8 text
    // - if true, an internal hash table is maintained for faster IndexOf()
    // - matches fNoDuplicate in Flags
    property NoDuplicate: boolean
      read GetNoDuplicate;
    /// access to the low-level flags of this list
    property Flags: TRawUtf8ListFlags
      read fFlags write fFlags;
    /// get or set a RawUtf8 item
    // - returns '' and raise no exception in case of out of range supplied index
    // - if you want to use it with the UI, use Utf8ToString() function
    // - reading this property is not thread-safe, since content may change
    property Strings[Index: PtrInt]: RawUtf8
      read Get write Put; default;
    /// get or set an item as RTL string, ready to be used with the UI
    // - returns '' and raise no exception in case of out of range supplied index
    // - wrap Strings[] with Utf8ToString/StringToUtf8 functions
    // - reading this property is not thread-safe, since content may change
    property Str[Index: PtrInt]: string
      read GetS write PutS;
    /// get or set a Object item
    // - returns nil and raise no exception in case of out of range supplied index
    // - reading this property is not thread-safe, since content may change
    property Objects[Index: PtrInt]: pointer
      read GetObject write PutObject;
    /// retrieve the corresponding Name when stored as 'Name=Value' pairs
    // - reading this property is not thread-safe, since content may change
    // - consider TSynNameValue if you expect more efficient name/value process
    property Names[Index: PtrInt]: RawUtf8
      read GetName;
    /// access to the corresponding 'Name=Value' pairs
    // - search on Name is case-insensitive with 'Name=Value' pairs
    // - reading this property is thread-safe, but won't use the hash table
    // - consider TSynNameValue if you expect more efficient name/value process
    property Values[const Name: RawUtf8]: RawUtf8
      read GetValue write SetValue;
    /// the char separator between 'Name=Value' pairs
    // - equals '=' by default
    // - consider TSynNameValue if you expect more efficient name/value process
    property NameValueSep: AnsiChar
      read fNameValueSep write fNameValueSep;
    /// set or retrieve all items as text lines
    // - lines are separated by #13#10 (CRLF) by default; use GetText and
    // SetText methods if you want to use another line delimiter (even a comma)
    // - this property is thread-safe
    property Text: RawUtf8
      read GetTextCRLF write SetTextCRLF;
    /// Event triggered when an entry is modified
    property OnChange: TNotifyEvent
      read fOnChange write fOnChange;
    /// direct access to the memory of the TRawUtf8DynArray items
    // - reading this property is not thread-safe, since content may change
    property TextPtr: PPUtf8CharArray
      read GetTextPtr;
    /// direct access to the memory of the TObjectDynArray items
    // - reading this property is not thread-safe, since content may change
    property ObjectPtr: PPointerArray
      read GetObjectPtr;
    /// direct access to the TRawUtf8DynArray instance
    // - reading this property is not thread-safe, since content may change
    property ValuePtr: TRawUtf8DynArray
      read fValue;
    /// direct access to the TRawUtf8DynArray items dynamic array wrapper
    // - using this property is not thread-safe, since content may change
    property ValuesArray: TDynArrayHashed
      read fValues;
  end;

  PRawUtf8List = ^TRawUtf8List;

{$ifndef PUREMORMOT2}

  // some declarations used for backward compatibility only
  TRawUtf8ListLocked = class(TRawUtf8List)
    protected procedure SetDefaultFlags; override; end;
  TRawUtf8ListHashed = class(TRawUtf8List)
    protected procedure SetDefaultFlags; override; end;
  TRawUtf8ListHashedLocked = class(TRawUtf8ListHashed)
    protected procedure SetDefaultFlags; override; end;
  // deprecated TRawUtf8MethodList should be replaced by a TSynDictionary

{$endif PUREMORMOT2}

/// sort a dynamic array of PUtf8Char items, via an external array of indexes
// - you can use FastFindIndexedPUtf8Char() for fast O(log(n)) binary search
procedure QuickSortIndexedPUtf8Char(Values: PPUtf8CharArray; Count: integer;
  var SortedIndexes: TCardinalDynArray; CaseSensitive: boolean = false);

var
  /// low-level JSON unserialization function
  // - defined in this unit to avoid circular reference with mormot.core.json,
  // but to publish the TDynArray.LoadFromJson overloaded methods
  // - this unit will just set a wrapper raising an ERttiException
  // - link mormot.core.json.pas to have a working implementation
  // - rather call LoadJson() from mormot.core.json than this low-level function
  GetDataFromJson: procedure(Data: pointer; var Json: PUtf8Char;
    EndOfObject: PUtf8Char; Rtti: TRttiCustom;
    CustomVariantOptions: PDocVariantOptions; Tolerant: boolean;
    Interning: TRawUtf8InterningAbstract);


{ ************ Abstract Radix Tree Classes }

type
  TRadixTree = class;

  /// refine the TRadixTreeNode content
  // - rtfParam is <param> node, i.e. a TRadixTreeNodeParams with Names <> nil
  // - rtfParamInteger is for a rtfParam which value should be only an integer,
  // either from rtoIntegerParams global flag, or individually as <int:###>
  // - rtfParamPath is for a rtfParam which value should be the whole path,
  // until the end of the URI or the beginning of the parameters (i.e. at '?'),
  // set individually as <path:###> parameter - * being synonymous to <path:path>
  TRadixTreeNodeFlags = set of (
    rtfParam,
    rtfParamInteger,
    rtfParamPath);

  /// implement an abstract Radix Tree node
  TRadixTreeNode = class
  protected
    function ComputeDepth: integer;
    procedure SortChildren;
  public
    /// the main Tree holding this node
    Owner: TRadixTree;
    /// the characters to be compared at this level
    Chars: RawUtf8;
    /// how many branches are within this node - used to sort by priority
    Depth: integer;
    /// describe the content of this node
    Flags: TRadixTreeNodeFlags;
    /// the nested nodes
    Child: array of TRadixTreeNode;
    /// the whole text up to this level
    FullText: RawUtf8;
    /// initialize this node instance
    constructor Create(aOwner: TRadixTree); reintroduce;
    /// instantiate a new node with the same class and properties
    function Split(const Text: RawUtf8): TRadixTreeNode; virtual;
    /// finalize this Radix Tree node
    destructor Destroy; override;
    /// search for the node corresponding to a given text
    function Find(P: PUtf8Char): TRadixTreeNode;
    /// internal debugging/testing method
    procedure ToText(var Result: RawUtf8; Level: integer);
  end;

  /// our TRadixTree works on dynamic/custom types of node classes
  TRadixTreeNodeClass = class of TRadixTreeNode;

  /// allow to customize TRadixTree process
  // - e.g. if static text matching should be case-insensitive (but <params> are
  // always case-sensitive, because they are user-specific runtime variables)
  // - if <param> values should be only plain integers, never alphabetical text -
  // you may also specify int:xxx for a single parameter, e.g. as <int:id>
  TRadixTreeOptions = set of (
    rtoCaseInsensitiveUri,
    rtoIntegerParams);

  /// implement an abstract Radix Tree over UTF-8 case-insensitive text
  // - as such, this class is not very useful if you just need to lookup for
  // a text value: a TDynArrayHasher/TDictionary is faster and uses less RAM
  // - but, once extended e.g. as TUriTree, it can very efficiently parse
  // some text with variants parts (e.g. parameters)
  TRadixTree = class
  protected
    fRoot: TRadixTreeNode;
    fDefaultNodeClass: TRadixTreeNodeClass;
    fOptions: TRadixTreeOptions;
    fNormTable: PNormTable; // for efficient rtoCaseInsensitiveUri
  public
    /// initialize the Radix Tree
    constructor Create(aNodeClass: TRadixTreeNodeClass;
      aOptions: TRadixTreeOptions = []); reintroduce;
    /// finalize this Radix Tree
    destructor Destroy; override;
    /// define how TRadixTreeNode.Lookup() will process this node
    // - as set with this class constructor
    property Options: TRadixTreeOptions
      read fOptions;
    /// finalize this Radix Tree node
    procedure Clear;
    /// low-level insertion of a given Text entry as a given child
    // - may return an existing node instance, if Text was already inserted
    function Insert(Text: RawUtf8; Node: TRadixTreeNode = nil;
      NodeClass: TRadixTreeNodeClass = nil): TRadixTreeNode;
    /// to be called after Insert() to consolidate the internal tree state
    // - nodes will be sorted by search priority, i.e. the longest depths first
    // - as called e.g. by TUriTree.Setup()
    procedure AfterInsert;
    /// search for the node corresponding to a given text
    // - more than 6 million lookups per second, with 1000 items stored
    function Find(const Text: RawUtf8): TRadixTreeNode;
    /// internal debugging/testing method
    function ToText: RawUtf8;
    /// low-level access to the root node of the Radix Tree
    property Root: TRadixTreeNode
      read fRoot;
  end;

  /// implement an abstract Radix Tree static or <param> node
  TRadixTreeNodeParams = class(TRadixTreeNode)
  protected
    /// is called for each <param> as Pos/Len pair
    // - called eventually with Pos^='?' and Len=-1 for the inlined parameters
    // - should return true on success, false to abort
    function LookupParam(Ctxt: TObject; Pos: PUtf8Char; Len: integer): boolean;
      virtual; abstract;
  public
    /// all the <param1> <param2> names, in order, up to this parameter
    // - equals nil for static nodes
    // - is referenced as pointer into THttpServerRequestAbstract.fRouteName
    Names: TRawUtf8DynArray;
    /// overriden to support the additional Names fields
    function Split(const Text: RawUtf8): TRadixTreeNode; override;
    /// main search method, recognizing static or <param> patterns
    function Lookup(P: PUtf8Char; Ctxt: TObject): TRadixTreeNodeParams;
  end;

  /// implement an abstract Radix Tree with static or <param> nodes
  TRadixTreeParams = class(TRadixTree)
  public
    /// low-level registration of a new URI path, with <param> support
    // - returns the node matching the given URI
    // - called e.g. from TUriRouter.Rewrite/Run methods
    // - will recognize <param> alphanumerical and <int:id> integer parameters
    function Setup(const aFromUri: RawUtf8; out aNames: TRawUtf8DynArray): TRadixTreeNodeParams;
  end;

  ERadixTree = class(ESynException);


implementation


{ ************ RTL TPersistent / TInterfacedObject with Custom Constructor }

{ TPersistentWithCustomCreate }

constructor TPersistentWithCustomCreate.Create;
begin
  // nothing to do by default - overridden constructor may add custom code
end;


{ TInterfacedObjectWithCustomCreate }

constructor TInterfacedObjectWithCustomCreate.Create;
begin
  // nothing to do by default - overridden constructor may add custom code
end;

procedure TInterfacedObjectWithCustomCreate.RefCountUpdate(Release: boolean);
begin
  if Release then
    _Release
  else
    _AddRef;
end;


{ TInterfacedCollection }

constructor TInterfacedCollection.Create;
begin
  inherited Create(GetClass);
end;


{ TSynInterfacedObject }

constructor TSynInterfacedObject.Create;
begin
  // do-nothing virtual constructor
end;

function TSynInterfacedObject._AddRef: TIntCnt;
begin
  result := VirtualAddRef;
end;

function TSynInterfacedObject._Release: TIntCnt;
begin
  result := VirtualRelease;
end;

function TSynInterfacedObject.QueryInterface(
  {$ifdef FPC_HAS_CONSTREF}constref{$else}const{$endif} IID: TGuid;
  out Obj): TIntQry;
begin
  result := VirtualQueryInterface(@IID, Obj);
end;

function TSynInterfacedObject.VirtualQueryInterface(IID: PGuid; out Obj): TIntQry;
begin
  result := E_NOINTERFACE;
end;


{ TAutoFree }

constructor TAutoFree.Create(var localVariable; obj: TObject);
begin
  fObject := obj;
  TObject(localVariable) := obj;
end;

constructor TAutoFree.Create(const varObjPairs: array of pointer);
var
  n, i: PtrInt;
begin
  n := length(varObjPairs);
  if (n = 0) or
     (n and 1 = 1) then
    exit;
  n := n shr 1;
  if n = 0 then
    exit;
  if n = 1 then
  begin
    fObject := varObjPairs[1];
    PPointer(varObjPairs[0])^ := fObject;
    exit;
  end;
  SetLength(fObjectList, n);
  for i := 0 to n - 1 do
  begin
    fObjectList[i] := varObjPairs[i * 2 + 1];
    PPointer(varObjPairs[i * 2])^ := fObjectList[i];
  end;
end;

procedure TAutoFree.ForMethod;
begin
  // do-nothing method to circumvent the Delphi 10.4 IAutoFree early release
end;

class function TAutoFree.One(var localVariable; obj: TObject): IAutoFree;
begin
  result := Create(localVariable,obj);
  {$ifdef ISDELPHI104}
  result.ForMethod;
  {$endif ISDELPHI104}
end;

class function TAutoFree.Several(const varObjPairs: array of pointer): IAutoFree;
begin
  result := Create(varObjPairs);
  // inlining is not possible on Delphi -> Delphi 10.4 caller should run ForMethod :(
end;

procedure TAutoFree.Another(var localVariable; obj: TObject);
var
  n: PtrInt;
begin
  n := length(fObjectList);
  SetLength(fObjectList, n + 1);
  fObjectList[n] := obj;
  TObject(localVariable) := obj;
end;

destructor TAutoFree.Destroy;
var
  i: PtrInt;
begin
  if fObjectList <> nil then
    for i := length(fObjectList) - 1 downto 0 do // release FILO
      fObjectList[i].Free;
  fObject.Free;
  inherited;
end;


{ TAutoLocker }

constructor TAutoLocker.Create;
begin
  fSafe.Init;
end;

destructor TAutoLocker.Destroy;
begin
  fSafe.Done;
  inherited Destroy;
end;

function TAutoLocker.ProtectMethod: IUnknown;
begin
  result := TAutoLock.Create(@fSafe);
end;

procedure TAutoLocker.Enter;
begin
  fSafe.Lock;
end;

procedure TAutoLocker.Leave;
begin
  fSafe.UnLock;
end;

function TAutoLocker.Safe: PSynLocker;
begin
  result := @fSafe;
end;


{ ************ TSynPersistent* / TSyn*List / TSynLocker classes }

{ TSynPersistent }

procedure TSynPersistent.AssignError(Source: TSynPersistent);
begin
  raise EConvertError.CreateFmt('Cannot assign a %s to a %s',
    [ClassNameShort(Source)^, ClassNameShort(self)^]);
end;

procedure TSynPersistent.AssignTo(Dest: TSynPersistent);
begin
  Dest.AssignError(Self);
end;

procedure TSynPersistent.Assign(Source: TSynPersistent);
begin
  if Source <> nil then
    Source.AssignTo(Self)
  else
    AssignError(nil);
end;

{$ifdef HASITERATORS}

{ TPointerEnumerator }

procedure TPointerEnumerator.Init(Values: PPointerArray; Count: PtrUInt);
begin
  if Count = 0 then
  begin
    Curr := nil;
    After := nil;
  end
  else
  begin
    Curr := pointer(Values);
    After := @Values[Count];
    dec(Curr);
  end;
end;

function TPointerEnumerator.MoveNext: Boolean;
begin
  inc(Curr);
  result := PtrUInt(Curr) < PtrUInt(After);
end;

function TPointerEnumerator.GetCurrent: pointer;
begin
  result := Curr^;
end;

function TPointerEnumerator.GetEnumerator: TPointerEnumerator;
begin
  result := self;
end;

{$endif HASITERATORS}

{ TSynList }

constructor TSynList.Create;
begin
  // nothing to do
end;

function TSynList.Add(item: pointer): PtrInt;
begin
  // inlined result := ObjArrayAddCount(fList, item, fCount);
  result := fCount;
  if result = length(fList) then
    SetLength(fList, NextGrow(result));
  fList[result] := item;
  inc(fCount);
end;

function TSynList.Insert(item: pointer; index: PtrInt): PtrInt;
begin
  result := PtrArrayInsert(fList, item, index, fCount);
end;

procedure TSynList.Clear;
begin
  fList := nil;
  fCount := 0;
end;

procedure TSynList.Delete(index: integer; dontfree: boolean);
begin
  PtrArrayDelete(fList, index, @fCount);
  if (fCount > 64) and
     (length(fList) > fCount * 2) then
    SetLength(fList, fCount); // reduce capacity when half list is void
end;

function TSynList.Exists(item: pointer): boolean;
begin
  result := IndexOf(item) >= 0;
end;

function TSynList.Get(index: integer): pointer;
begin
  if cardinal(index) < cardinal(fCount) then
    result := fList[index]
  else
    result := nil;
end;

function TSynList.IndexOf(item: pointer): PtrInt;
begin
  result := PtrUIntScanIndex(pointer(fList), fCount, PtrUInt(item));
end;

function TSynList.Remove(item: pointer): PtrInt;
begin
  result := IndexOf(item);
  if result >= 0 then
    Delete(result);
end;

{$ifdef HASITERATORS}

function TSynList.GetEnumerator: TPointerEnumerator;
begin
  result.Init(pointer(fList), fCount);
end;

{$endif HASITERATORS}


{ TSynObjectList }

constructor TSynObjectList.Create(aOwnObjects: boolean; aItemClass: TClass);
begin
  fOwnObjects := aOwnObjects;
  fItemClass := aItemClass;
  inherited Create;
end;

procedure TSynObjectList.Delete(index: integer; dontfree: boolean);
begin
  if cardinal(index) >= cardinal(fCount) then
    exit;
  if fOwnObjects and
     not dontfree then
    TObject(fList[index]).Free;
  inherited Delete(index);
end;

procedure TSynObjectList.Clear;
begin
  if fOwnObjects then
    RawObjectsClear(pointer(fList), fCount);
  inherited Clear;
end;

procedure TSynObjectList.ClearFromLast;
var
  i: PtrInt;
begin
  if fOwnObjects then
    for i := fCount - 1 downto 0 do // call Free in reverse order
      FreeAndNilSafe(fList[i]);     // safer
  inherited Clear;
end;

destructor TSynObjectList.Destroy;
begin
  Clear;
  inherited Destroy;
end;

function TSynObjectList.NewItem: pointer;
begin
  result := nil;
  if fItemClass = nil then
    exit;
  result := Rtti.RegisterClass(fItemClass).ClassNewInstance;
  Add(result);
end;


{ TSynPersistentLock }

constructor TSynPersistentLock.Create;
begin
  inherited Create; // may have been overriden
  fSafe := NewSynLocker;
end;

destructor TSynPersistentLock.Destroy;
begin
  inherited Destroy;
  fSafe^.DoneAndFreeMem;
end;

procedure TSynPersistentLock.Lock;
begin
  if self <> nil then
    fSafe^.Lock;
end;

procedure TSynPersistentLock.Unlock;
begin
  if self <> nil then
    fSafe^.UnLock;
end;

class procedure TSynPersistentLock.RttiCustomSetParser(Rtti: TRttiCustom);
begin
  // let's call our overriden RttiBeforeWriteObject and RttiAfterWriteObject
  Rtti.Flags := Rtti.Flags + [rcfHookWrite];
end;

function TSynPersistentLock.RttiBeforeWriteObject(W: TTextWriter;
  var Options: TTextWriterWriteObjectOptions): boolean;
begin
  if woPersistentLock in Options then
    fSafe.Lock;
  result := false; // continue with default JSON serialization
end;

procedure TSynPersistentLock.RttiAfterWriteObject(W: TTextWriter;
  Options: TTextWriterWriteObjectOptions);
begin
  if woPersistentLock in Options then
    fSafe.UnLock;
end;

{ TInterfacedObjectLocked }

constructor TInterfacedObjectLocked.Create;
begin
  inherited Create;
  fSafe := NewSynLocker;
end;

destructor TInterfacedObjectLocked.Destroy;
begin
  inherited Destroy;
  fSafe^.DoneAndFreeMem;
end;


{ TSynObjectListLocked }

function TSynObjectListLocked.Add(item: pointer): PtrInt;
begin
  Safe.WriteLock;
  try
    result := inherited Add(item);
  finally
    Safe.WriteUnLock;
  end;
end;

function TSynObjectListLocked.Remove(item: pointer): PtrInt;
begin
  Safe.WriteLock;
  try
    result := inherited Remove(item);
  finally
    Safe.WriteUnLock;
  end;
end;

function TSynObjectListLocked.Exists(item: pointer): boolean;
begin
  Safe.ReadOnlyLock;
  try
    result := inherited Exists(item);
  finally
    Safe.ReadOnlyUnLock;
  end;
end;

procedure TSynObjectListLocked.Clear;
begin
  Safe.WriteLock;
  try
    inherited Clear;
  finally
    Safe.WriteUnLock;
  end;
end;

procedure TSynObjectListLocked.ClearFromLast;
begin
  Safe.WriteLock;
  try
    inherited ClearFromLast;
  finally
    Safe.WriteUnLock;
  end;
end;


{ TSynObjectListSorted }

constructor TSynObjectListSorted.Create(const aCompare: TOnObjectCompare;
  aOwnsObjects: boolean);
begin
  inherited Create(aOwnsObjects);
  fCompare := aCompare;
end;

function TSynObjectListSorted.Locate(item: pointer; out index: PtrInt): boolean;
var
  n, l, i: PtrInt;
  cmp: integer;
begin // see TDynArray.FastLocateSorted below
  result := false;
  n := fCount;
  if n = 0 then // a void array is always sorted
    index := 0
  else
  begin
    dec(n);
    cmp := fCompare(fList[n], item);
    if cmp <= 0 then
    begin
      // greater than last sorted item (may be a common case)
      if cmp = 0 then
        // returns true + index of existing item
        result := true
      else
        // returns false + insert after last position
        inc(n);
      index := n;
      exit;
    end;
    l := 0;
    repeat
      // O(log(n)) binary search of the sorted position
      i := (l + n) shr 1;
      cmp := fCompare(fList[i], item);
      if cmp = 0 then
      begin
        // returns true + index of existing item
        index := i;
        result := true;
        exit;
      end
      else if cmp < 0 then
        l := i + 1
      else
        n := i - 1;
    until l > n;
    // item not found: returns false + the index where to insert
    index := l;
  end;
end;

function TSynObjectListSorted.Add(item: pointer): PtrInt;
begin
  Safe.WriteLock;
  try
    if Locate(item, result) then // O(log(n)) binary search
      result := -(result + 1)
    else
      Insert(item, result);
  finally
    Safe.WriteUnLock;
  end;
end;

function TSynObjectListSorted.IndexOf(item: pointer): PtrInt;
begin
  if not Locate(item, result) then // O(log(n)) binary search
    result := -1;
end;

function TSynObjectListSorted.Find(item: TObject): TObject;
var
  i: PtrInt;
begin
  if Locate(item, i) then
    result := fList[i]
  else
    result := nil;
end;


{ ************ TSynPersistentStore with proper Binary Serialization }

{ TSynPersistentStore }

constructor TSynPersistentStore.Create(const aName: RawUtf8);
begin
  inherited Create; // may have been overriden
  fName := aName;
end;

constructor TSynPersistentStore.CreateFrom(const aBuffer: RawByteString;
  aLoad: TAlgoCompressLoad);
begin
  CreateFromBuffer(pointer(aBuffer), length(aBuffer), aLoad);
end;

constructor TSynPersistentStore.CreateFromBuffer(
  aBuffer: pointer; aBufferLen: integer; aLoad: TAlgoCompressLoad);
begin
  inherited Create; // may have been overriden
  LoadFrom(aBuffer, aBufferLen, aLoad);
end;

constructor TSynPersistentStore.CreateFromFile(const aFileName: TFileName;
  aLoad: TAlgoCompressLoad);
begin
  inherited Create; // may have been overriden
  LoadFromFile(aFileName, aLoad);
end;

procedure TSynPersistentStore.LoadFromReader;
begin
  fReader.VarUtf8(fName);
end;

procedure TSynPersistentStore.SaveToWriter(aWriter: TBufferWriter);
begin
  aWriter.Write(fName);
end;

procedure TSynPersistentStore.LoadFrom(const aBuffer: RawByteString;
  aLoad: TAlgoCompressLoad);
begin
  if aBuffer <> '' then
    LoadFrom(pointer(aBuffer), length(aBuffer), aLoad);
end;

procedure TSynPersistentStore.LoadFrom(aBuffer: pointer; aBufferLen: integer;
  aLoad: TAlgoCompressLoad);
var
  localtemp: RawByteString;
  p: pointer;
  temp: PRawByteString;
begin
  if (aBuffer = nil) or
     (aBufferLen <= 0) then
    exit; // nothing to load
  fLoadFromLastAlgo := TAlgoCompress.Algo(aBuffer, aBufferLen);
  if fLoadFromLastAlgo = nil then
    fReader.ErrorData('%.LoadFrom unknown TAlgoCompress AlgoID=%',
      [self, PByteArray(aBuffer)[4]]);
  temp := fReaderTemp;
  if temp = nil then
    temp := @localtemp;
  p := fLoadFromLastAlgo.Decompress(aBuffer, aBufferLen,
    fLoadFromLastUncompressed, temp^, aLoad);
  if p = nil then
    fReader.ErrorData('%.LoadFrom %.Decompress failed',
      [self, fLoadFromLastAlgo]);
  fReader.Init(p, fLoadFromLastUncompressed);
  LoadFromReader;
end;

function TSynPersistentStore.LoadFromFile(const aFileName: TFileName;
  aLoad: TAlgoCompressLoad): boolean;
var
  temp: RawByteString;
begin
  temp := StringFromFile(aFileName);
  result := temp <> '';
  if result then
    LoadFrom(temp, aLoad);
end;

procedure TSynPersistentStore.SaveTo(out aBuffer: RawByteString;
  nocompression: boolean; BufLen: integer; ForcedAlgo: TAlgoCompress;
  BufferOffset: integer);
var
  writer: TBufferWriter;
  temp: array[word] of byte;
begin
  if BufLen <= SizeOf(temp) then
    writer := TBufferWriter.Create(TRawByteStringStream, @temp, SizeOf(temp))
  else
    writer := TBufferWriter.Create(TRawByteStringStream, BufLen);
  try
    SaveToWriter(writer);
    fSaveToLastUncompressed := writer.TotalWritten;
    aBuffer := writer.FlushAndCompress(nocompression, ForcedAlgo, BufferOffset);
  finally
    writer.Free;
  end;
end;

function TSynPersistentStore.SaveTo(nocompression: boolean; BufLen: integer;
  ForcedAlgo: TAlgoCompress; BufferOffset: integer): RawByteString;
begin
  SaveTo(result, nocompression, BufLen, ForcedAlgo, BufferOffset);
end;

function TSynPersistentStore.SaveToFile(const aFileName: TFileName;
  nocompression: boolean; BufLen: integer; ForcedAlgo: TAlgoCompress): PtrUInt;
var
  temp: RawByteString;
begin
  SaveTo(temp, nocompression, BufLen, ForcedAlgo);
  if FileFromString(temp, aFileName) then
    result := length(temp)
  else
    result := 0;
end;




{ ************ INI Files and In-memory Access }

function IdemPChar2(table: PNormTable; p: PUtf8Char; up: PAnsiChar): boolean;
  {$ifdef HASINLINE}inline;{$endif}
var
  u: AnsiChar;
begin
  // here p and up are expected to be <> nil
  result := false;
  dec(PtrUInt(p), PtrUInt(up));
  repeat
    u := up^;
    if u = #0 then
      break;
    if table^[up[PtrUInt(p)]] <> u then
      exit;
    inc(up);
  until false;
  result := true;
end;

function FindSectionFirstLine(var source: PUtf8Char; search: PAnsiChar): boolean;
var
  table: PNormTable;
  charset: PTextCharSet;
begin
  result := false;
  if (source = nil) or
     (search = nil) then
    exit;
  table := @NormToUpperAnsi7;
  charset := @TEXT_CHARS;
  repeat
    if source^ = '[' then
    begin
      inc(source);
      result := IdemPChar2(table, source, search);
    end;
    while tcNot01013 in charset[source^] do
      inc(source);
    while tc1013 in charset[source^] do
      inc(source);
    if result then
      exit; // found
  until source^ = #0;
  source := nil;
end;

function FindSectionFirstLineW(var source: PWideChar; search: PUtf8Char): boolean;
begin
  result := false;
  if source = nil then
    exit;
  repeat
    if source^ = '[' then
    begin
      inc(source);
      result := IdemPCharW(source, search);
    end;
    while not (cardinal(source^) in [0, 10, 13]) do
      inc(source);
    while cardinal(source^) in [10, 13] do
      inc(source);
    if result then
      exit; // found
  until source^ = #0;
  source := nil;
end;

function FindIniNameValue(P: PUtf8Char; UpperName: PAnsiChar;
  const DefaultValue: RawUtf8): RawUtf8;
var
  u, PBeg: PUtf8Char;
  by4: cardinal;
  {$ifdef CPUX86NOTPIC}
  table: TNormTable absolute NormToUpperAnsi7;
  {$else}
  table: PNormTable;
  {$endif CPUX86NOTPIC}
begin
  // expect UpperName as 'NAME='
  if (P <> nil) and
     (P^ <> '[') and
     (UpperName <> nil) then
  begin
    {$ifndef CPUX86NOTPIC}
    table := @NormToUpperAnsi7;
    {$endif CPUX86NOTPIC}
    PBeg := nil;
    u := P;
    repeat
      while u^ = ' ' do
        inc(u); // trim left ' '
      if u^ = #0 then
        break;
      if table[u^] = UpperName[0] then
        PBeg := u;
      repeat
        by4 := PCardinal(u)^;
        if ToByte(by4) > 13 then
          if ToByte(by4 shr 8) > 13 then
            if ToByte(by4 shr 16) > 13 then
              if ToByte(by4 shr 24) > 13 then
              begin
                inc(u, 4);
                continue;
              end
              else
                inc(u, 3)
            else
              inc(u, 2)
          else
            inc(u);
        if u^ in [#0, #10, #13] then
          break;
        inc(u);
      until false;
      if PBeg <> nil then
      begin
        inc(PBeg);
        P := u;
        u := pointer(UpperName + 1);
        repeat
          if u^ <> #0 then
            if table[PBeg^] <> u^ then
              break
            else
            begin
              inc(u);
              inc(PBeg);
            end
          else
          begin
            FastSetString(result, PBeg, P - PBeg);
            exit;
          end;
        until false;
        PBeg := nil;
        u := P;
      end;
      if u^ = #13 then
        inc(u);
      if u^ = #10 then
        inc(u);
    until u^ in [#0, '['];
  end;
  result := DefaultValue;
end;

function ExistsIniName(P: PUtf8Char; UpperName: PAnsiChar): boolean;
var
  table: PNormTable;
begin
  result := false;
  if (P <> nil) and
     (P^ <> '[') then
  begin
    table := @NormToUpperAnsi7;
    repeat
      if P^ = ' ' then
      begin
        repeat
          inc(P)
        until P^ <> ' '; // trim left ' '
        if P^ = #0 then
          break;
      end;
      if IdemPChar2(table, P, UpperName) then
      begin
        result := true;
        exit;
      end;
      repeat
        if P[0] > #13 then
          if P[1] > #13 then
            if P[2] > #13 then
              if P[3] > #13 then
              begin
                inc(P, 4);
                continue;
              end
              else
                inc(P, 3)
            else
              inc(P, 2)
          else
            inc(P);
        case P^ of
          #0:
            exit;
          #10:
            begin
              inc(P);
              break;
            end;
          #13:
            begin
              if P[1] = #10 then
                inc(P, 2)
              else
                inc(P);
              break;
            end;
        else
          inc(P);
        end;
      until false;
    until P^ = '[';
  end;
end;

function ExistsIniNameValue(P: PUtf8Char; const UpperName: RawUtf8;
  UpperValues: PPAnsiChar): boolean;
var
  table: PNormTable;
begin
  if (UpperValues <> nil) and
     (UpperValues^ <> nil) and
     (UpperName <> '') then
  begin
    result := true;
    table := @NormToUpperAnsi7;
    while (P <> nil) and
          (P^ <> '[') do
    begin
      if P^ = ' ' then
        repeat
          inc(P)
        until P^ <> ' '; // trim left ' '
      if IdemPChar2(table, P, pointer(UpperName)) then
      begin
        inc(P, length(UpperName));
        if IdemPPChar(P, UpperValues) >= 0 then
          exit; // found one value
        break;
      end;
      P := GotoNextLine(P);
    end;
  end;
  result := false;
end;

function GetSectionContent(SectionFirstLine: PUtf8Char): RawUtf8;
var
  PBeg: PUtf8Char;
begin
  PBeg := SectionFirstLine;
  while (SectionFirstLine <> nil) and
        (SectionFirstLine^ <> '[') do
    SectionFirstLine := GotoNextLine(SectionFirstLine);
  if SectionFirstLine = nil then
    result := PBeg
  else
    FastSetString(result, PBeg, SectionFirstLine - PBeg);
end;

function GetSectionContent(const Content, SectionName: RawUtf8): RawUtf8;
var
  P: PUtf8Char;
  UpperSection: array[byte] of AnsiChar;
begin
  P := pointer(Content);
  PWord(UpperCopy255(UpperSection{%H-}, SectionName))^ := ord(']');
  if FindSectionFirstLine(P, UpperSection) then
    result := GetSectionContent(P)
  else
    result := '';
end;

function DeleteSection(var Content: RawUtf8; const SectionName: RawUtf8;
  EraseSectionHeader: boolean): boolean;
var
  P: PUtf8Char;
  UpperSection: array[byte] of AnsiChar;
begin
  result := false; // no modification
  P := pointer(Content);
  PWord(UpperCopy255(UpperSection{%H-}, SectionName))^ := ord(']');
  if FindSectionFirstLine(P, UpperSection) then
    result := DeleteSection(P, Content, EraseSectionHeader);
end;

function DeleteSection(SectionFirstLine: PUtf8Char; var Content: RawUtf8;
  EraseSectionHeader: boolean): boolean;
var
  PEnd: PUtf8Char;
  IndexBegin: PtrInt;
begin
  result := false;
  PEnd := SectionFirstLine;
  if EraseSectionHeader then // erase [Section] header line
    while (PtrUInt(SectionFirstLine) > PtrUInt(Content)) and
          (SectionFirstLine^ <> '[') do
      dec(SectionFirstLine);
  while (PEnd <> nil) and
        (PEnd^ <> '[') do
    PEnd := GotoNextLine(PEnd);
  IndexBegin := SectionFirstLine - pointer(Content);
  if IndexBegin = 0 then
    exit; // no modification
  if PEnd = nil then
    SetLength(Content, IndexBegin)
  else
    delete(Content, IndexBegin + 1, PEnd - SectionFirstLine);
  result := true; // Content was modified
end;

procedure ReplaceSection(SectionFirstLine: PUtf8Char; var Content: RawUtf8;
  const NewSectionContent: RawUtf8);
var
  PEnd: PUtf8Char;
  IndexBegin: PtrInt;
begin
  if SectionFirstLine = nil then
    exit;
  // delete existing [Section] content
  PEnd := SectionFirstLine;
  while (PEnd <> nil) and
        (PEnd^ <> '[') do
    PEnd := GotoNextLine(PEnd);
  IndexBegin := SectionFirstLine - pointer(Content);
  if PEnd = nil then
    SetLength(Content, IndexBegin)
  else
    delete(Content, IndexBegin + 1, PEnd - SectionFirstLine);
  // insert section content
  insert(NewSectionContent, Content, IndexBegin + 1);
end;

procedure ReplaceSection(var Content: RawUtf8; const SectionName, NewSectionContent: RawUtf8);
var
  UpperSection: array[byte] of AnsiChar;
  P: PUtf8Char;
begin
  P := pointer(Content);
  PWord(UpperCopy255(UpperSection{%H-}, SectionName))^ := ord(']');
  if FindSectionFirstLine(P, UpperSection) then
    ReplaceSection(P, Content, NewSectionContent)
  else
    Content := Content + '[' + SectionName + ']'#13#10 + NewSectionContent;
end;

function FindIniNameValueInteger(P: PUtf8Char; const UpperName: RawUtf8): PtrInt;
var
  table: PNormTable;
begin
  result := 0;
  if (P = nil) or
     (UpperName = '') then
    exit;
  table := @NormToUpperAnsi7;
  repeat
    if IdemPChar2(table, P, pointer(UpperName)) then
      break;
    P := GotoNextLine(P);
    if P = nil then
      exit;
  until false;
  result := GetInteger(P + length(UpperName));
end;

function FindIniEntry(const Content, Section, Name, DefaultValue: RawUtf8): RawUtf8;
var
  P: PUtf8Char;
  UpperSection, UpperName: array[byte] of AnsiChar;
begin
  result := DefaultValue;
  P := pointer(Content);
  if P = nil then
    exit;
  // fast UpperName := UpperCase(Name)+'='
  PWord(UpperCopy255(UpperName{%H-}, Name))^ := ord('=');
  if Section = '' then
    // find the Name= entry before any [Section]
    result := FindIniNameValue(P, UpperName, DefaultValue)
  else
  begin
    // find the Name= entry in the specified [Section]
    PWord(UpperCopy255(UpperSection{%H-}, Section))^ := ord(']');
    if FindSectionFirstLine(P, UpperSection) then
      result := FindIniNameValue(P, UpperName, DefaultValue);
  end;
end;

function FindWinAnsiIniEntry(const Content, Section, Name: RawUtf8): RawUtf8;
begin
  result := WinAnsiToUtf8(WinAnsiString(FindIniEntry(Content, Section, Name)));
end;

function FindIniEntryInteger(const Content, Section, Name: RawUtf8): integer;
begin
  result := GetInteger(pointer(FindIniEntry(Content, Section, Name)));
end;

function FindIniEntryFile(const FileName: TFileName;
  const Section, Name, DefaultValue: RawUtf8): RawUtf8;
var
  Content: RawUtf8;
begin
  Content := StringFromFile(FileName);
  if Content = '' then
    result := DefaultValue
  else
    result := FindIniEntry(Content, Section, Name, DefaultValue);
end;

function UpdateIniNameValueInternal(var Content: RawUtf8;
  const NewValue, NewValueCRLF: RawUtf8;
  var P: PUtf8Char; UpperName: PAnsiChar; UpperNameLength: integer): boolean;
var
  PBeg: PUtf8Char;
  i: integer;
begin
  if UpperName <> nil then
    while (P <> nil) and
          (P^ <> '[') do
    begin
      while P^ = ' ' do
        inc(P);   // trim left ' '
      PBeg := P;
      P := GotoNextLine(P);
      if IdemPChar2(@NormToUpperAnsi7, PBeg, UpperName) then
      begin
       // update Name=Value entry
        result := true;
        inc(PBeg, UpperNameLength);
        i := (PBeg - pointer(Content)) + 1;
        if (i = length(NewValue)) and
           CompareMem(PBeg, pointer(NewValue), i) then
          exit; // new Value is identical to the old one -> no change
        if P = nil then // avoid last line (P-PBeg) calculation error
          SetLength(Content, i - 1)
        else
          delete(Content, i, P - PBeg); // delete old Value
        insert(NewValueCRLF, Content, i); // set new value
        exit;
      end;
    end;
  result := false;
end;

function UpdateIniNameValue(var Content: RawUtf8;
  const Name, UpperName, NewValue: RawUtf8): boolean;
var
  P: PUtf8Char;
begin
  if UpperName = '' then
    result := false
  else
  begin
    P := pointer(Content);
    result := UpdateIniNameValueInternal(Content, NewValue, NewValue + #13#10,
      P, pointer(UpperName), length(UpperName));
    if result or
       (Name = '') then
      exit;
    AppendLine(Content, [Name, NewValue]);
    result := true;
  end;
end;

procedure UpdateIniEntry(var Content: RawUtf8;
  const Section, Name, Value: RawUtf8);
const
  CRLF = #13#10;
var
  P: PUtf8Char;
  SectionFound: boolean;
  i, UpperNameLength: PtrInt;
  V: RawUtf8;
  UpperSection, UpperName: array[byte] of AnsiChar;
begin
  UpperNameLength := length(Name);
  PWord(UpperCopy255Buf(
    UpperName{%H-}, pointer(Name), UpperNameLength))^ := ord('=');
  inc(UpperNameLength);
  V := Value + CRLF;
  P := pointer(Content);
  // 1. find Section, and try update within it
  if Section = '' then
    SectionFound := true // find the Name= entry before any [Section]
  else
  begin
    PWord(UpperCopy255(UpperSection{%H-}, Section))^ := ord(']');
    SectionFound := FindSectionFirstLine(P, UpperSection);
  end;
  if SectionFound and
     UpdateIniNameValueInternal(
       Content, Value, V, P, @UpperName, UpperNameLength) then
      exit;
  // 2. section or Name= entry not found: add Name=Value
  V := Name + '=' + V;
  if not SectionFound then
    // create not existing [Section]
    V := '[' + Section + (']' + CRLF) + V;
  // insert Name=Value at P^ (end of file or end of [Section])
  if P = nil then
    // insert at end of file
    Content := Content + V
  else
  begin
    // insert at end of [Section]
    i := (P - pointer(Content)) + 1;
    insert(V, Content, i);
  end;
end;

procedure UpdateIniEntryFile(const FileName: TFileName;
  const Section, Name, Value: RawUtf8);
var
  Content: RawUtf8;
begin
  Content := StringFromFile(FileName);
  UpdateIniEntry(Content, Section, Name, Value);
  FileFromString(Content, FileName);
end;

function IsHtmlContentTypeTextual(Headers: PUtf8Char): boolean;
begin
  result := ExistsIniNameValue(Headers, HEADER_CONTENT_TYPE_UPPER, @CONTENT_TYPE_TEXTUAL);
end;

const
  WS_UPGRADE: array[0..2] of PAnsiChar = (
    'UPGRADE',
    'KEEP-ALIVE, UPGRADE',
    nil);

function IsWebSocketUpgrade(headers: PUtf8Char): boolean;
begin
  result := ExistsIniNameValue(pointer(headers), 'CONNECTION: ', @WS_UPGRADE);
end;

function IniToObject(const Ini: RawUtf8; Instance: TObject;
  const SectionName: RawUtf8; DocVariantOptions: PDocVariantOptions;
  Level: integer): boolean;
var
  r: TRttiCustom;
  i: integer;
  p: PRttiCustomProp;
  section, nested, json: PUtf8Char;
  name: PAnsiChar;
  n, v: RawUtf8;
  up: array[byte] of AnsiChar;
begin
  result := false; // true when at least one property has been read
  if (Ini = '') or
     (Instance = nil) then
    exit;
  PWord(UpperCopy255(up{%H-}, SectionName))^ := ord(']');
  section := pointer(Ini);
  if not FindSectionFirstLine(section, @up) then
    exit; // section not found
  r := Rtti.RegisterClass(Instance);
  p := pointer(r.Props.List);
  for i := 1 to r.Props.Count do
  begin
    if p^.Prop <> nil then
      if p^.Value.Kind = rkClass then
      begin
        // recursive load from another per-property section
        if Level = 0 then
          n := p^.Name
        else
          n := SectionName + '.' + p^.Name;
        if IniToObject(Ini, p^.Prop^.GetObjProp(Instance), n,
              DocVariantOptions, Level + 1) then
          result := true;
      end
      else
      begin
        PWord(UpperCopy255(up{%H-}, p^.Name))^ := ord('=');
        v := FindIniNameValue(section, @up, #0);
        if p^.Value.Parser in ptMultiLineStringTypes then
        begin
          if v = #0 then // may be stored in a multi-line section body
          begin
            name := @up;
            if Level <> 0 then
            begin
              name := UpperCopy255(name, SectionName);
              name^ := '.';
              inc(name);
            end;
            PWord(UpperCopy255(name, p^.Name))^ := ord(']');
            nested := pointer(Ini);
            if FindSectionFirstLine(nested, @up) then
            begin
              // multi-line text value has been stored in its own section
              v := GetSectionContent(nested);
              if p^.Prop^.SetValueText(Instance, v) then
                result := true;
            end;
          end
          else if p^.Prop^.SetValueText(Instance, v) then // single line text
            result := true;
        end
        else if v <> #0 then
          if (p^.OffsetSet <= 0) or // has a setter?
             (rcfBoolean in p^.Value.Cache.Flags) or // simple value?
             (p^.Value.Kind in (rkGetIntegerPropTypes + [rkEnumeration, rkFloat])) then
          begin
            if p^.Prop^.SetValueText(Instance, v) then // RTTI conversion
              result := true;
          end
          else // e.g. rkVariant, rkSet, rkDynArray
          begin
            json := pointer(v); // convert complex values from JSON
            GetDataFromJson(@PByteArray(Instance)[p^.OffsetSet], json,
              nil, p^.Value, DocVariantOptions, true, nil);
            if json <> nil then
              result := true;
          end;
      end;
    inc(p);
  end;
end;

function TrimAndIsMultiLine(var U: RawUtf8): boolean;
var
  L: PtrInt;
  P: PUtf8Char absolute U;
begin
  result := false;
  L := length(U);
  if L = 0 then
    exit;
  while P[L - 1] in [#13, #10] do
  begin
    dec(L);
    if L = 0 then
    begin
      U := ''; // no meaningful text
      exit;
    end;
  end;
  if L <> length(U) then
    SetLength(U, L); // trim right
  if BufferLineLength(P, P + L) = L then // may use x86_64 SSE2 asm
    exit; // no line feed
  result := true; // there are line feeds within this text
  U := TrimChar(U, [#13]); // normalize #13#10 into #10 as ObjectToIni()
end;

function ObjectToIni(const Instance: TObject; const SectionName: RawUtf8;
  Options: TTextWriterWriteObjectOptions; Level: integer): RawUtf8;
var
  W: TTextWriter;
  tmp: TTextWriterStackBuffer;
  nested: TRawUtf8DynArray;
  i, nestedcount: integer;
  r: TRttiCustom;
  p: PRttiCustomProp;
  n, s: RawUtf8;
begin
  result := '';
  if Instance = nil then
    exit;
  nestedcount := 0;
  W := DefaultJsonWriter.CreateOwnedStream(tmp);
  try
    W.CustomOptions := W.CustomOptions + [twoTrimLeftEnumSets];
    W.Add('[%]'#10, [SectionName]);
    r := Rtti.RegisterClass(Instance);
    p := pointer(r.Props.List);
    for i := 1 to r.Props.Count do
    begin
      if p^.Prop <> nil then
        if p^.Value.Kind = rkClass then
        begin
          if Level = 0 then
            n := p^.Name
          else
            n := SectionName + '.' + p^.Name;
          s := ObjectToIni(p^.Prop^.GetObjProp(Instance), n, Options, Level + 1);
          if s <> '' then
            AddRawUtf8(nested, nestedcount, s);
        end
        else if p^.Value.Kind = rkEnumeration then
        begin
          if woHumanReadableEnumSetAsComment in Options then
          begin
            p^.Value.Cache.EnumInfo^.GetEnumNameAll(
              s, '; values=', {quoted=}false, #10, {uncamelcase=}true);
            W.AddString(s);
          end;
          // AddValueJson() would have written "quotes"
          W.AddString(p^.Name);
          W.Add('=');
          W.AddTrimLeftLowerCase(p^.Value.Cache.EnumInfo^.GetEnumNameOrd(
            p^.Prop^.GetOrdProp(Instance)));
          W.Add(#10);
        end
        else if p^.Value.Parser in ptMultiLineStringTypes then
        begin
          p^.Prop^.GetAsString(Instance, s);
          if TrimAndIsMultiLine(s) then
          begin
            // store multi-line text values in their own section
            if Level = 0 then
              FormatUtf8('[%]'#10'%'#10#10, [p^.Name, s], n)
            else
              FormatUtf8('[%.%]'#10'%'#10#10, [SectionName, p^.Name, s], n);
            AddRawUtf8(nested, nestedcount, n);
          end
          else
          begin
            W.AddString(p^.Name);
            W.Add('=');
            W.AddString(s); // single line text
            W.Add(#10);
          end;
        end
        else
        begin
          W.AddString(p^.Name);
          W.Add('=');
          p^.AddValueJson(W, Instance, // simple and complex types
            Options - [woHumanReadableEnumSetAsComment], twOnSameLine);
          W.Add(#10);
        end;
      inc(p);
    end;
    W.Add(#10);
    for i := 0 to nestedcount - 1 do
      W.AddString(nested[i]);
    W.SetText(result);
  finally
    W.Free;
  end;
end;


{ ************ RawUtf8 String Values Interning and TRawUtf8List }

var // filled at startup with a 32-bit random value to avoid hash flooding
  HashSeed: cardinal; // defined locally in this unit to avoid symbol export

{ TRawUtf8Hashed }

procedure TRawUtf8Hashed.Init;
begin
  Values.InitSpecific(TypeInfo(TRawUtf8DynArray), Value, ptRawUtf8,
    @Count, false, InterningHasher);
end;


{ TRawUtf8InterningSlot }

procedure TRawUtf8InterningSlot.Init;
begin
  fHash.Init;
end;

procedure TRawUtf8InterningSlot.Unique(var aResult: RawUtf8;
  const aText: RawUtf8; aTextHash: cardinal);
var
  i: PtrInt;
  added: boolean;
begin
  fSafe.ReadLock; // a TRWLightLock is faster here than an upgradable TRWLock
  i := fHash.Values.Hasher.FindOrNewComp(aTextHash, @aText);
  if i >= 0 then
  begin
    aResult := fHash.Value[i]; // return unified string instance
    fSafe.ReadUnLock;
    exit;
  end;
  fSafe.ReadUnLock;
  fSafe.WriteLock; // need to be added within the write lock
  i := fHash.Values.FindHashedForAdding(aText, added, aTextHash);
  if added then
  begin
    fHash.Value[i] := aText; // copy new value to the pool
    aResult := aText;
  end
  else
    aResult := fHash.Value[i]; // was added in a background thread
  fSafe.WriteUnLock;
end;

procedure TRawUtf8InterningSlot.UniqueFromBuffer(var aResult: RawUtf8;
  aText: PUtf8Char; aTextLen: PtrInt; aTextHash: cardinal);
var
  c: AnsiChar;
  added: boolean;
  i: PtrInt;
  bak: TDynArraySortCompare;
begin
  if not fSafe.TryReadLock then
  begin
    FastSetString(aResult, aText, aTextLen); // avoid waiting on contention
    exit;
  end;
  c := aText[aTextLen];
  aText[aTextLen] := #0; // input buffer may not be #0 terminated
  i := fHash.Values.Hasher.FindOrNewComp(aTextHash, @aText, @SortDynArrayPUtf8Char);
  if i >= 0 then
  begin
    aResult := fHash.Value[i]; // return unified string instance
    fSafe.ReadUnLock;
    aText[aTextLen] := c;
    exit;
  end;
  fSafe.ReadUnLock;
  fSafe.WriteLock; // need to be added in exclusive mode
  bak := fHash.Values.Hasher.Compare; // (RawUtf8,RawUtf8) -> (RawUtf8,PUtf8Char)
  PDynArrayHasher(@fHash.Values.Hasher)^.fCompare := @SortDynArrayPUtf8Char;
  i := fHash.Values.FindHashedForAdding(aText, added, aTextHash);
  PDynArrayHasher(@fHash.Values.Hasher)^.fCompare := bak;
  if added then
    FastSetString(fHash.Value[i], aText, aTextLen); // new value to the pool
  aResult := fHash.Value[i];
  fSafe.WriteUnLock;
  aText[aTextLen] := c;
end;

procedure TRawUtf8InterningSlot.UniqueFromBuffer(var aResult: RawUtf8;
  aText: PUtf8Char; aTextLen: PtrInt);
begin
  UniqueFromBuffer(aResult, aText, aTextLen,
    InterningHasher(HashSeed, pointer(aText), aTextLen));
end;

procedure TRawUtf8InterningSlot.UniqueText(var aText: RawUtf8; aTextHash: cardinal);
var
  i: PtrInt;
  added: boolean;
begin
  fSafe.ReadLock;
  i := fHash.Values.Hasher.FindOrNewComp(aTextHash, @aText);
  if i >= 0 then
  begin
    aText := fHash.Value[i]; // return unified string instance
    fSafe.ReadUnLock;
    exit;
  end;
  fSafe.ReadUnLock;
  fSafe.WriteLock; // need to be added
  i := fHash.Values.FindHashedForAdding(aText, added, aTextHash);
  if added then
    fHash.Value[i] := aText  // copy new value to the pool
  else
    aText := fHash.Value[i]; // was added in a background thread
  fSafe.WriteUnLock;
end;

function TRawUtf8InterningSlot.Existing(const aText: RawUtf8; aTextHash: cardinal): pointer;
var
  i: PtrInt;
begin
  result := nil;
  fSafe.ReadLock;
  i := fHash.Values.Hasher.FindOrNewComp(aTextHash, @aText);
  if i >= 0 then
    result := pointer(fHash.Value[i]); // return a pointer to unified string instance
  fSafe.ReadUnLock;
end;

procedure TRawUtf8InterningSlot.Clear;
begin
  fSafe.WriteLock;
  try
    fHash.Values.SetCount(0); // Values.Clear
    fHash.Values.Hasher.ForceReHash;
  finally
    fSafe.WriteUnLock;
  end;
end;

function TRawUtf8InterningSlot.Clean(aMaxRefCount: TStrCnt): integer;
var
  i: integer;
  s, d: PPtrUInt; // points to RawUtf8 values
begin
  result := 0;
  if fHash.Count = 0 then
    exit;
  fSafe.WriteLock;
  try
    if fHash.Count = 0 then
      exit;
    s := pointer(fHash.Value);
    d := s;
    for i := 1 to fHash.Count do
    begin
      if PStrCnt(PAnsiChar(s^) - _STRCNT)^ <= aMaxRefCount then
      begin
        {$ifdef FPC}
        FastAssignNew(PRawUtf8(s)^);
        {$else}
        PRawUtf8(s)^ := '';
        {$endif FPC}
        inc(result);
      end
      else
      begin
        if s <> d then
        begin
          d^ := s^; // bypass COW assignments
          s^ := 0;  // avoid GPF
        end;
        inc(d);
      end;
      inc(s);
    end;
    if result > 0 then
    begin
      fHash.Values.SetCount((PtrUInt(d) - PtrUInt(fHash.Value)) div SizeOf(d^));
      fHash.Values.ForceReHash;
    end;
  finally
    fSafe.WriteUnLock;
  end;
end;


{ TRawUtf8Interning }

constructor TRawUtf8Interning.Create(aHashTables: integer);
var
  p: integer;
  i: PtrInt;
begin
  inherited Create; // may have been overriden
  for p := 0 to 9 do
    if aHashTables = 1 shl p then
    begin
      SetLength(fPool, aHashTables);
      fPoolLast := aHashTables - 1;
      for i := 0 to fPoolLast do
        fPool[i].Init;
      exit;
    end;
  ESynException.RaiseUtf8('%.Create(%) not allowed: ' +
    'should be a power of 2 <= 512', [self, aHashTables]);
end;

procedure TRawUtf8Interning.Clear;
var
  i: PtrInt;
begin
  if self <> nil then
    for i := 0 to fPoolLast do
      fPool[i].Clear;
end;

function TRawUtf8Interning.Clean(aMaxRefCount: TStrCnt): integer;
var
  i: PtrInt;
begin
  result := 0;
  if self <> nil then
    for i := 0 to fPoolLast do
      inc(result, fPool[i].Clean(aMaxRefCount));
end;

function TRawUtf8Interning.Count: integer;
var
  i: PtrInt;
begin
  result := 0;
  if self <> nil then
    for i := 0 to fPoolLast do
      inc(result, fPool[i].Count);
end;

procedure TRawUtf8Interning.Unique(var aResult: RawUtf8; const aText: RawUtf8);
var
  hash: cardinal;
begin
  if aText = '' then
    aResult := ''
  else if self = nil then
    aResult := aText
  else
  begin
    // inlined fPool[].Values.HashElement
    hash := InterningHasher(HashSeed, pointer(aText), length(aText));
    fPool[hash and fPoolLast].Unique(aResult, aText, hash);
  end;
end;

procedure TRawUtf8Interning.UniqueText(var aText: RawUtf8);
var
  hash: cardinal;
begin
  if (self <> nil) and
     (aText <> '') then
  begin
    // inlined fPool[].Values.HashElement
    hash := InterningHasher(HashSeed, pointer(aText), length(aText));
    fPool[hash and fPoolLast].UniqueText(aText, hash);
  end;
end;

function TRawUtf8Interning.Unique(const aText: RawUtf8): RawUtf8;
var
  hash: cardinal;
begin
  if aText = '' then
    FastAssignNew(result)
  else if self = nil then
    result := aText
  else
  begin
    // inlined fPool[].Values.HashElement
    hash := InterningHasher(HashSeed, pointer(aText), length(aText));
    fPool[hash and fPoolLast].Unique(result, aText, hash);
  end;
end;

function TRawUtf8Interning.Existing(const aText: RawUtf8): pointer;
var
  hash: cardinal;
begin
  result := nil;
  if self = nil then
    exit;
  hash := InterningHasher(HashSeed, pointer(aText), length(aText));
  result := fPool[hash and fPoolLast].Existing(aText, hash);
end;

function TRawUtf8Interning.Unique(aText: PUtf8Char; aTextLen: PtrInt): RawUtf8;
begin
  Unique(result, aText, aTextLen);
end;

procedure TRawUtf8Interning.Unique(var aResult: RawUtf8;
  aText: PUtf8Char; aTextLen: PtrInt);
var
  hash: cardinal;
begin
  if (aText = nil) or
     (aTextLen <= 0) then
    FastAssignNew(aResult)
  else if self = nil then
    FastSetString(aResult, aText, aTextLen)
  else
  begin
    // inlined fPool[].Values.HashElement
    hash := InterningHasher(HashSeed, pointer(aText), aTextLen);
    fPool[hash and fPoolLast].UniqueFromBuffer(aResult, aText, aTextLen, hash);
  end;
end;

procedure TRawUtf8Interning.UniqueVariant(
  var aResult: variant; const aText: RawUtf8);
begin
  ClearVariantForString(aResult);
  Unique(RawUtf8(TVarData(aResult).VAny), aText);
end;

procedure TRawUtf8Interning.UniqueVariantString(var aResult: variant;
  const aText: string);
var
  tmp: RawUtf8;
begin
  StringToUtf8(aText, tmp);
  UniqueVariant(aResult, tmp);
end;

procedure TRawUtf8Interning.UniqueVariant(var aResult: variant);
var
  vd: TVarData absolute aResult;
  vt: cardinal;
begin
  vt := vd.VType;
  if vt = varString then
    UniqueText(RawUtf8(vd.VString))
  else if vt = varVariantByRef then
    UniqueVariant(PVariant(vd.VPointer)^)
  else if vt = varStringByRef then
    UniqueText(PRawUtf8(vd.VPointer)^);
end;


{ TRawUtf8List }

{$ifdef PUREMORMOT2}
constructor TRawUtf8List.Create;
begin
  CreateEx([fCaseSensitive]);
end;
{$else}
constructor TRawUtf8List.Create;
begin
  SetDefaultFlags;
  CreateEx(fFlags + [fCaseSensitive]);
end;

constructor TRawUtf8List.Create(aOwnObjects, aNoDuplicate, aCaseSensitive: boolean);
begin
  SetDefaultFlags;
  if aOwnObjects then
    include(fFlags, fObjectsOwned);
  if aNoDuplicate then
    include(fFlags, fNoDuplicate);
  if aCaseSensitive then
    include(fFlags, fCaseSensitive);
  CreateEx(fFlags);
end;

procedure TRawUtf8List.SetDefaultFlags;
begin
end;
procedure TRawUtf8ListLocked.SetDefaultFlags;
begin
  fFlags := [fThreadSafe];
end;
procedure TRawUtf8ListHashed.SetDefaultFlags;
begin
  fFlags := [fNoDuplicate];
end;
procedure TRawUtf8ListHashedLocked.SetDefaultFlags;
begin
  fFlags := [fNoDuplicate, fThreadSafe];
end;
{$endif PUREMORMOT2}

constructor TRawUtf8List.CreateEx(aFlags: TRawUtf8ListFlags);
begin
  inherited Create; // may have been overriden
  fNameValueSep := '=';
  fFlags := aFlags;
  fValues.InitSpecific(TypeInfo(TRawUtf8DynArray), fValue, ptRawUtf8, @fCount,
    not (fCaseSensitive in aFlags));
end;

destructor TRawUtf8List.Destroy;
begin
  SetCapacity(0);
  inherited Destroy;
end;

procedure TRawUtf8List.SetCaseSensitive(Value: boolean);
begin
  if (self = nil) or
     (fCaseSensitive in fFlags = Value) then
    exit;
  if fThreadSafe in fFlags then
    fSafe.WriteLock;
  try
    if Value then
      include(fFlags, fCaseSensitive)
    else
      exclude(fFlags, fCaseSensitive);
    fValues.Hasher.InitSpecific(@fValues, ptRawUtf8, not Value, nil);
    Changed;
  finally
    if fThreadSafe in fFlags then
      fSafe.WriteUnLock;
  end;
end;

procedure TRawUtf8List.SetCapacity(const capa: PtrInt);
begin
  if self <> nil then
  begin
    if fThreadSafe in fFlags then
      fSafe.WriteLock;
    try
      if capa <= 0 then
      begin
        // clear
        if fObjects <> nil then
        begin
          if fObjectsOwned in fFlags then
            RawObjectsClear(pointer(fObjects), fCount);
          fObjects := nil;
        end;
        fValues.Clear;
        if fNoDuplicate in fFlags then
          fValues.ForceReHash;
        Changed;
      end
      else
      begin
        // resize
        if capa < fCount then
        begin
          // resize down
          if fObjects <> nil then
          begin
            if fObjectsOwned in fFlags then
              RawObjectsClear(@fObjects[capa], fCount - capa - 1);
            SetLength(fObjects, capa);
          end;
          fValues.Count := capa;
          if fNoDuplicate in fFlags then
            fValues.ForceReHash;
          Changed;
        end;
        if capa > length(fValue) then
        begin
          // resize up
          SetLength(fValue, capa);
          if fObjects <> nil then
            SetLength(fObjects, capa);
        end;
      end;
    finally
      if fThreadSafe in fFlags then
        fSafe.WriteUnLock;
    end;
  end;
end;

function TRawUtf8List.Add(const aText: RawUtf8; aRaiseExceptionIfExisting: boolean): PtrInt;
begin
  result := AddObject(aText, nil, aRaiseExceptionIfExisting);
end;

function TRawUtf8List.AddObject(const aText: RawUtf8; aObject: TObject;
  aRaiseExceptionIfExisting: boolean; aFreeAndReturnExistingObject: PPointer;
  aReplaceExistingObject: boolean): PtrInt;
var
  added: boolean;
  obj: TObject;
begin
  result := -1;
  if self = nil then
    exit;
  if fThreadSafe in fFlags then
    fSafe.WriteLock;
  try
    if fNoDuplicate in fFlags then
    begin
      result := fValues.FindHashedForAdding(aText, added, {noadd=}true);
      if not added then
      begin
        obj := GetObject(result);
        if (obj = aObject) and
           (obj <> nil) then
          exit; // found identical aText/aObject -> behave as if added
        if aFreeAndReturnExistingObject <> nil then
        begin
          aObject.Free;
          aFreeAndReturnExistingObject^ := obj;
        end;
        if aRaiseExceptionIfExisting then
          ESynException.RaiseUtf8('%.Add duplicate [%]', [self, aText]);
        if aReplaceExistingObject then
        begin
          if obj = nil then
            ESynException.RaiseUtf8(
              '%.AddOrReplaceObject with no object at [%]', [self, aText]);
          if fObjectsOwned in fFlags then
            FreeAndNil(fObjects[result]);
          fObjects[result] := aObject;
        end
        else
          result := -1;
        exit;
      end;
    end;
    result := fValues.Add(aText);
    if (fObjects <> nil) or
       (aObject <> nil) then
    begin
      if result >= length(fObjects) then
        SetLength(fObjects, length(fValue)); // same capacity
      if aObject <> nil then
        fObjects[result] := aObject;
    end;
    if Assigned(fOnChange) then
      Changed;
  finally
    if fThreadSafe in fFlags then
      fSafe.WriteUnLock;
  end;
end;

function TRawUtf8List.AddOrReplaceObject(const aText: RawUtf8; aObject: TObject): PtrInt;
begin
  result := AddObject(aText, aObject, {raiseexisting=}false, nil, {replace=}true);
end;

procedure TRawUtf8List.AddObjectUnique(const aText: RawUtf8;
  aObjectToAddOrFree: PPointer);
begin
  if fNoDuplicate in fFlags then
    AddObject(aText, aObjectToAddOrFree^, {raiseexc=}false,
      {freeandreturnexisting=}aObjectToAddOrFree);
end;

procedure TRawUtf8List.AddRawUtf8List(List: TRawUtf8List);
var
  i: PtrInt;
begin
  if List <> nil then
  begin
    BeginUpdate; // includes Safe.Lock
    try
      for i := 0 to List.fCount - 1 do
        AddObject(List.fValue[i], List.GetObject(i));
    finally
      EndUpdate;
    end;
  end;
end;

procedure TRawUtf8List.BeginUpdate;
begin
  if InterLockedIncrement(fOnChangeLevel) > 1 then
    exit;
  if fThreadSafe in fFlags then
    fSafe.WriteLock;
  fOnChangeBackupForBeginUpdate := fOnChange;
  fOnChange := OnChangeHidden;
  exclude(fFlags, fOnChangeTrigerred);
end;

procedure TRawUtf8List.EndUpdate;
begin
  if (fOnChangeLevel <= 0) or
     (InterLockedDecrement(fOnChangeLevel) > 0) then
    exit; // allows nested BeginUpdate..EndUpdate calls
  fOnChange := fOnChangeBackupForBeginUpdate;
  if (fOnChangeTrigerred in fFlags) and
     Assigned(fOnChange) then
    Changed;
  exclude(fFlags, fOnChangeTrigerred);
  if fThreadSafe in fFlags then
    fSafe.WriteUnLock;
end;

procedure TRawUtf8List.Changed;
begin
  if Assigned(fOnChange) then
  try
    fOnChange(self);
  except // ignore any exception in user code (may not trigger fSafe.UnLock)
  end;
end;

procedure TRawUtf8List.Clear;
begin
  SetCapacity(0); // will also call Changed
end;

procedure TRawUtf8List.InternalDelete(Index: PtrInt);
begin
  // caller ensured Index is correct
  fValues.Delete(Index); // includes dec(fCount)
  if PtrUInt(Index) < PtrUInt(length(fObjects)) then
  begin
    if fObjectsOwned in fFlags then
      fObjects[Index].Free;
    if fCount > Index then
      MoveFast(fObjects[Index + 1], fObjects[Index],
        (fCount - Index) * SizeOf(pointer));
    fObjects[fCount] := nil;
  end;
  if Assigned(fOnChange) then
    Changed;
end;

procedure TRawUtf8List.Delete(Index: PtrInt);
begin
  if (self <> nil) and
     (PtrUInt(Index) < PtrUInt(fCount)) then
    if fNoDuplicate in fFlags then // force update the hash table
      Delete(fValue[Index])
    else
      InternalDelete(Index);
end;

function TRawUtf8List.Delete(const aText: RawUtf8): PtrInt;
begin
  if fThreadSafe in fFlags then
    fSafe.WriteLock;
  try
    if fNoDuplicate in fFlags then
      result := fValues.FindHashedAndDelete(aText, nil, {nodelete=}true)
    else
      result := FindRawUtf8(pointer(fValue), aText, fCount, fCaseSensitive in fFlags);
    if result >= 0 then
      InternalDelete(result);
  finally
    if fThreadSafe in fFlags then
      fSafe.WriteUnLock;
  end;
end;

function TRawUtf8List.DeleteFromName(const Name: RawUtf8): PtrInt;
begin
  result := -1;
  if fThreadSafe in fFlags then
    fSafe.ReadWriteLock;
  try
    result := IndexOfName(Name);
    if result >= 0 then
    begin
      if fThreadSafe in fFlags then
        fSafe.WriteLock;
      Delete(result);
    end;
  finally
    if fThreadSafe in fFlags then
    begin
      if result >= 0 then
        fSafe.WriteUnlock;
      fSafe.ReadWriteUnLock;
    end;
  end;
end;

function TRawUtf8List.Exists(const aText: RawUtf8): boolean;
begin
  if self <> nil then
    if fThreadSafe in fFlags then
    begin
      fSafe.ReadOnlyLock;
      try
        result := IndexOf(aText) >= 0;
      finally
        fSafe.ReadOnlyUnLock;
      end;
    end
    else
      result := IndexOf(aText) >= 0
  else
    result := false;
end;

function TRawUtf8List.IndexOf(const aText: RawUtf8): PtrInt;
begin
  if self <> nil then
  begin
    if fNoDuplicate in fFlags then
      result := fValues.FindHashed(aText)
    else
      result := FindRawUtf8(
        pointer(fValue), aText, fCount, fCaseSensitive in fFlags);
  end
  else
    result := -1;
end;

function TRawUtf8List.Get(Index: PtrInt): RawUtf8;
begin
  if (self = nil) or
     (PtrUInt(Index) >= PtrUInt(fCount)) then
    result := ''
  else
    result := fValue[Index];
end;

function TRawUtf8List.GetS(Index: PtrInt): string;
begin
  if (self = nil) or
     (PtrUInt(Index) >= PtrUInt(fCount)) then
    result := ''
  else
    Utf8ToStringVar(fValue[Index], result);
end;

function TRawUtf8List.GetCapacity: PtrInt;
begin
  if self = nil then
    result := 0
  else
    result := length(fValue);
end;

function TRawUtf8List.GetCount: PtrInt;
begin
  if self = nil then
    result := 0
  else
    result := fCount;
end;

function TRawUtf8List.GetTextPtr: PPUtf8CharArray;
begin
  result := pointer(self);
  if self <> nil then
    result := pointer(fValue);
end;

function TRawUtf8List.GetObjectPtr: PPointerArray;
begin
  result := pointer(self);
  if self <> nil then
    result := pointer(fObjects);
end;

function TRawUtf8List.GetName(Index: PtrInt): RawUtf8;
begin
  result := Get(Index);
  if result = '' then
    exit;
  Index := PosExChar(NameValueSep, result);
  if Index = 0 then
    result := ''
  else
    SetLength(result, Index - 1);
end;

function TRawUtf8List.GetObject(Index: PtrInt): pointer;
begin
  if (self <> nil) and
     (fObjects <> nil) and
     (PtrUInt(Index) < PtrUInt(fCount)) then
    result := fObjects[Index]
  else
    result := nil;
end;

function TRawUtf8List.GetObjectFrom(const aText: RawUtf8): pointer;
var
  ndx: PtrUInt;
begin
  result := nil;
  if (self <> nil) and
     (fObjects <> nil) then
  begin
    if fThreadSafe in fFlags then
      fSafe.ReadOnlyLock;
    try
      ndx := IndexOf(aText);
      if ndx < PtrUInt(fCount) then
        result := fObjects[ndx];
    finally
      if fThreadSafe in fFlags then
        fSafe.ReadOnlyUnLock;
    end;
  end;
end;

function TRawUtf8List.GetText(const Delimiter: RawUtf8): RawUtf8;
var
  DelimLen, i, Len: PtrInt;
  P: PUtf8Char;
begin
  result := '';
  if (self = nil) or
     (fCount = 0) then
    exit;
  if fThreadSafe in fFlags then
    fSafe.ReadOnlyLock;
  try
    DelimLen := length(Delimiter);
    Len := DelimLen * (fCount - 1);
    for i := 0 to fCount - 1 do
      inc(Len, length(fValue[i]));
    FastSetString(result, Len);
    P := pointer(result);
    i := 0;
    repeat
      Len := length(fValue[i]);
      if Len > 0 then
      begin
        MoveFast(pointer(fValue[i])^, P^, Len);
        inc(P, Len);
      end;
      inc(i);
      if i >= fCount then
        Break;
      if DelimLen > 0 then
      begin
        MoveByOne(pointer(Delimiter), P, DelimLen);
        inc(P, DelimLen);
      end;
    until false;
  finally
    if fThreadSafe in fFlags then
      fSafe.ReadOnlyUnLock;
  end;
end;

procedure TRawUtf8List.SaveToStream(Dest: TStream; const Delimiter: RawUtf8);
var
  W: TTextWriter;
  i: PtrInt;
  temp: TTextWriterStackBuffer;
begin
  if (self = nil) or
     (fCount = 0) then
    exit;
  if fThreadSafe in fFlags then
    fSafe.ReadOnlyLock;
  try
    W := TTextWriter.Create(Dest, @temp, SizeOf(temp));
    try
      i := 0;
      repeat
        W.AddString(fValue[i]);
        inc(i);
        if i >= fCount then
          Break;
        W.AddString(Delimiter);
      until false;
      W.FlushFinal;
    finally
      W.Free;
    end;
  finally
    if fThreadSafe in fFlags then
      fSafe.ReadOnlyUnLock;
  end;
end;

procedure TRawUtf8List.SaveToFile(
  const FileName: TFileName; const Delimiter: RawUtf8);
var
  FS: TStream;
begin
  FS := TFileStreamEx.Create(FileName, fmCreate);
  try
    SaveToStream(FS, Delimiter);
  finally
    FS.Free;
  end;
end;

function TRawUtf8List.GetTextCRLF: RawUtf8;
begin
  result := GetText;
end;

function TRawUtf8List.GetValue(const Name: RawUtf8): RawUtf8;
begin
  if fThreadSafe in fFlags then
    fSafe.ReadOnlyLock;
  try
    result := GetValueAt(IndexOfName(Name));
  finally
    if fThreadSafe in fFlags then
      fSafe.ReadOnlyUnLock;
  end;
end;

function TRawUtf8List.GetValueAt(Index: PtrInt): RawUtf8;
begin
  result := Get(Index);
  if result = '' then
    exit;
  Index := PosExChar(NameValueSep, result);
  if Index = 0 then
    result := ''
  else
    TrimChars(result, Index, 0);
end;

function TRawUtf8List.EqualValueAt(Index: PtrInt; const aText: RawUtf8): boolean;
begin
  result := (self <>nil) and
            (PtrUInt(Index) < PtrUInt(fCount)) and
            (fValue[Index] = aText);
end;

function TRawUtf8List.IndexOfName(const Name: RawUtf8): PtrInt;
var
  UpperName: array[byte] of AnsiChar;
  table: PNormTable;
begin
  if self <> nil then
  begin
    PWord(UpperCopy255(UpperName{%H-}, Name))^ := ord(NameValueSep);
    table := @NormToUpperAnsi7;
    for result := 0 to fCount - 1 do
      if IdemPChar(pointer(fValue[result]), UpperName, table) then
        exit;
  end;
  result := -1;
end;

function TRawUtf8List.IndexOfObject(aObject: TObject): PtrInt;
begin
  if (self <> nil) and
     (fObjects <> nil) then
  begin
    if fThreadSafe in fFlags then
      fSafe.ReadOnlyLock;
    try
      result := PtrUIntScanIndex(pointer(fObjects), fCount, PtrUInt(aObject));
    finally
      if fThreadSafe in fFlags then
        fSafe.ReadOnlyUnLock;
    end
  end
  else
    result := -1;
end;

function TRawUtf8List.Contains(const aText: RawUtf8; aFirstIndex: integer): PtrInt;
var
  i: PtrInt; // use a temp variable to make oldest Delphi happy :(
begin
  result := -1;
  if self = nil then
    exit;
  if fThreadSafe in fFlags then
    fSafe.ReadOnlyLock;
  try
    for i := aFirstIndex to fCount - 1 do
      if PosEx(aText, fValue[i]) > 0 then
      begin
        result := i;
        exit;
      end;
  finally
    if fThreadSafe in fFlags then
      fSafe.ReadOnlyUnLock;
  end;
end;

procedure TRawUtf8List.OnChangeHidden(Sender: TObject);
begin
  if self <> nil then
    include(fFlags, fOnChangeTrigerred);
end;

procedure TRawUtf8List.Put(Index: PtrInt; const Value: RawUtf8);
begin
  if (self <> nil) and
     (PtrUInt(Index) < PtrUInt(fCount)) then
  begin
    fValue[Index] := Value;
    if Assigned(fOnChange) then
      Changed;
  end;
end;

procedure TRawUtf8List.PutS(Index: PtrInt; const Value: string);
begin
  Put(Index, StringToUtf8(Value));
end;

procedure TRawUtf8List.PutObject(Index: PtrInt; Value: pointer);
begin
  if (self <> nil) and
     (PtrUInt(Index) < PtrUInt(fCount)) then
  begin
    if fObjects = nil then
      SetLength(fObjects, Length(fValue));
    fObjects[Index] := Value;
    if Assigned(fOnChange) then
      Changed;
  end;
end;

procedure TRawUtf8List.SetText(const aText: RawUtf8; const Delimiter: RawUtf8);
begin
  SetTextPtr(pointer(aText), PUtf8Char(pointer(aText)) + length(aText), Delimiter);
end;

procedure TRawUtf8List.LoadFromFile(const FileName: TFileName);
begin
  SetText(RawUtf8FromFile(FileName), #13#10); // RawUtf8FromFile() detects BOM
end;

procedure TRawUtf8List.SetTextPtr(P, PEnd: PUtf8Char; const Delimiter: RawUtf8);
var
  DelimLen: PtrInt;
  DelimFirst: AnsiChar;
  PBeg, DelimNext: PUtf8Char;
  Line: RawUtf8;
begin
  DelimLen := length(Delimiter);
  BeginUpdate; // also makes fSafe.Lock
  try
    Clear;
    if (P <> nil) and
       (DelimLen > 0) and
       (P < PEnd) then
    begin
      DelimFirst := Delimiter[1];
      DelimNext := PUtf8Char(pointer(Delimiter)) + 1;
      repeat
        PBeg := P;
        while P < PEnd do
        begin
          if (P^ = DelimFirst) and
             CompareMemSmall(P + 1, DelimNext, DelimLen - 1) then
            break;
          inc(P);
        end;
        FastSetString(Line, PBeg, P - PBeg);
        AddObject(Line, nil);
        if P >= PEnd then
          break;
        inc(P, DelimLen);
      until P >= PEnd;
    end;
  finally
    EndUpdate;
  end;
end;

procedure TRawUtf8List.SetTextCRLF(const Value: RawUtf8);
begin
  SetText(Value, #13#10);
end;

procedure TRawUtf8List.SetFrom(const aText: TRawUtf8DynArray;
  const aObject: TObjectDynArray);
var
  n: integer;
begin
  BeginUpdate; // also makes fSafe.Lock
  try
    Clear;
    n := length(aText);
    if n = 0 then
      exit;
    SetCapacity(n);
    fCount := n;
    fValue := aText;
    fObjects := aObject;
    if fNoDuplicate in fFlags then
      fValues.ForceReHash;
  finally
    EndUpdate;
  end;
end;

procedure TRawUtf8List.SetValue(const Name, Value: RawUtf8);
var
  i: PtrInt;
  txt: RawUtf8;
begin
  txt := Name + RawUtf8(NameValueSep) + Value;
  if fThreadSafe in fFlags then
    fSafe.WriteLock;
  try
    i := IndexOfName(Name);
    if i < 0 then
      AddObject(txt, nil)
    else if fValue[i] <> txt then
    begin
      fValue[i] := txt;
      if fNoDuplicate in fFlags then
        fValues.Hasher.ForceReHash; // invalidate internal hash table
      Changed;
    end;
  finally
    if fThreadSafe in fFlags then
      fSafe.WriteUnLock;
  end;
end;

function TRawUtf8List.GetCaseSensitive: boolean;
begin
  result := (self <> nil) and
            (fCaseSensitive in fFlags);
end;

function TRawUtf8List.GetNoDuplicate: boolean;
begin
  result := (self <> nil) and
            (fNoDuplicate in fFlags);
end;

function TRawUtf8List.UpdateValue(const Name: RawUtf8; var Value: RawUtf8;
  ThenDelete: boolean): boolean;
var
  i: PtrInt;
begin
  result := false;
  if fThreadSafe in fFlags then
    fSafe.WriteLock;
  try
    i := IndexOfName(Name);
    if i >= 0 then
    begin
      Value := GetValueAt(i); // copy value
      if ThenDelete then
        Delete(i); // optionally delete
      result := true;
    end;
  finally
    if fThreadSafe in fFlags then
      fSafe.WriteUnLock;
  end;
end;

function TRawUtf8List.PopFirst(out aText: RawUtf8; aObject: PObject): boolean;
begin
  result := false;
  if fCount = 0 then
    exit;
  if fThreadSafe in fFlags then
    fSafe.WriteLock;
  try
    if fCount > 0 then
    begin
      aText := fValue[0];
      if aObject <> nil then
        if fObjects <> nil then
          aObject^ := fObjects[0]
        else
          aObject^ := nil;
      Delete(0);
      result := true;
    end;
  finally
    if fThreadSafe in fFlags then
      fSafe.WriteUnLock;
  end;
end;

function TRawUtf8List.PopLast(out aText: RawUtf8; aObject: PObject): boolean;
var
  last: PtrInt;
begin
  result := false;
  if fCount = 0 then
    exit;
  if fThreadSafe in fFlags then
    fSafe.WriteLock;
  try
    last := fCount - 1;
    if last >= 0 then
    begin
      aText := fValue[last];
      if aObject <> nil then
        if fObjects <> nil then
          aObject^ := fObjects[last]
        else
          aObject^ := nil;
      Delete(last);
      result := true;
    end;
  finally
    if fThreadSafe in fFlags then
      fSafe.WriteUnLock;
  end;
end;


{ ********** Efficient RTTI Values Binary Serialization and Comparison }

// per-type efficient binary serialization

function _BS_Ord(Data: pointer; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
begin
  result := ORDTYPE_SIZE[Info^.RttiOrd];
  Dest.Write(Data, result);
end;

function _BL_Ord(Data: pointer; var Source: TFastReader; Info: PRttiInfo): PtrInt;
begin
  result := ORDTYPE_SIZE[Info^.RttiOrd];
  Source.Copy(Data, result);
end;

function _BS_Float(Data: pointer; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
begin
  result := FLOATTYPE_SIZE[Info^.RttiFloat];
  Dest.Write(Data, result);
end;

function _BL_Float(Data: pointer; var Source: TFastReader; Info: PRttiInfo): PtrInt;
begin
  result := FLOATTYPE_SIZE[Info^.RttiFloat];
  Source.Copy(Data, result);
end;

function _BS_64(Data: PInt64; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
begin
  {$ifdef CPU32}
  Dest.Write8(Data);
  {$else}
  Dest.WriteI64(Data^);
  {$endif CPU32}
  result := 8;
end;

function _BL_64(Data: PQWord; var Source: TFastReader; Info: PRttiInfo): PtrInt;
begin
  Data^ := Source.Next8;
  result := 8;
end;

function _BS_String(Data: PRawByteString; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
begin
  Dest.WriteVar(pointer(Data^), length(Data^));
  result := SizeOf(pointer);
end;

function _BL_LString(Data: PRawByteString; var Source: TFastReader; Info: PRttiInfo): PtrInt;
begin
  with Source.VarBlob do
    {$ifdef HASCODEPAGE}
    FastSetStringCP(Data^, Ptr, Len, Info^.AnsiStringCodePageStored);
    {$else}
    SetString(Data^, Ptr, Len);
    {$endif HASCODEPAGE}
  result := SizeOf(pointer);
end;

{$ifdef HASVARUSTRING}

function _BS_UString(Data: PUnicodeString; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
begin
  Dest.WriteVar(pointer(Data^), length(Data^) * 2);
  result := SizeOf(pointer);
end;

function _BL_UString(Data: PUnicodeString; var Source: TFastReader; Info: PRttiInfo): PtrInt;
begin
  with Source.VarBlob do
    SetString(Data^, PWideChar(Ptr), Len shr 1); // length in bytes was stored
  result := SizeOf(pointer);
end;

{$endif HASVARUSTRING}

function _BS_WString(Data: PWideString; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
begin
  Dest.WriteVar(pointer(Data^), length(Data^) * 2);
  result := SizeOf(pointer);
end;

function _BL_WString(Data: PWideString; var Source: TFastReader; Info: PRttiInfo): PtrInt;
begin
  with Source.VarBlob do
    SetString(Data^, PWideChar(Ptr), Len shr 1); // length in bytes was stored
  result := SizeOf(pointer);
end;

// efficient branchless comparison of every TRttiOrd/TRttiFloat raw value

function _BC_SByte(A, B: PShortInt; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := 1;
end;

function _BC_UByte(A, B: PByte; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := 1;
end;

function _BC_SWord(A, B: PSmallInt; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := 2;
end;

function _BC_UWord(A, B: PWord; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := 2;
end;

function _BC_SLong(A, B: PInteger; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := 4;
end;

function _BC_ULong(A, B: PCardinal; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := 4;
end;

function _BC_SQWord(A, B: PInt64; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := 8;
end;

function _BC_UQWord(A, B: PQWord; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := 8;
end;

function _BC_Ord(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  result := RTTI_ORD_COMPARE[Info^.RttiOrd](A, B, Info, Compared);
end;

function _BC_Single(A, B: PSingle; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := SizeOf(single);
end;

function _BC_Double(A, B: PDouble; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := SizeOf(double);
end;

function _BC_Extended(A, B: PExtended; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ord(A^ > B^) - ord(A^ < B^);
  result := SizeOf(extended);
end;

function _BC_Float(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  result := RTTI_FLOAT_COMPARE[Info^.RttiFloat](A, B, Info, Compared);
end;

function _BC_64(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  if Info^.IsQWord then
    Compared := ord(PQWord(A)^ > PQWord(B)^) - ord(PQWord(A)^ < PQWord(B)^)
  else
    Compared := ord(PInt64(A)^ > PInt64(B)^) - ord(PInt64(A)^ < PInt64(B)^);
  result := 8;
end;

function _BC_LString(A, B: PRawByteString; Info: PRttiInfo;
  out Compared: integer): PtrInt;
begin
  // StrComp() would fail for RawByteString
  {$ifdef CPUINTEL}
  compared := SortDynArrayAnsiString(A^, B^); // optimized asm using length()
  {$else}
  compared := SortDynArrayRawByteString(A^, B^);
  {$endif CPUINTEL}
  result := SizeOf(pointer);
end;

function _BC_WString(A, B: PPWideChar; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  compared := StrCompW(A^, B^);
  result := SizeOf(pointer);
end;

function _BCI_LString(A, B: PPUtf8Char; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  compared := StrIComp(A^, B^);
  result := SizeOf(pointer);
end;

function _BCI_WString(A, B: PPWideChar; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  compared := AnsiICompW(A^, B^);
  result := SizeOf(pointer);
end;

function DelphiType(Info: PRttiInfo): integer;
  {$ifdef HASINLINE} inline; {$endif}
begin
  // compatible with legacy TDynArray.SaveTo() format
  if Info = nil then
    result := 0
  else
    {$ifdef FPC}
    result := ord(FPCTODELPHI[Info^.Kind]);
    {$else}
    result := ord(Info^.Kind);
    {$endif FPC}
end;

procedure DynArraySave(Data: PAnsiChar; ExternalCount: PInteger;
  Dest: TBufferWriter; Info: PRttiInfo);
var
  n, itemsize: PtrInt;
  sav: TRttiBinarySave;
label
  raw;
begin
  Info := Info^.DynArrayItemType(itemsize); // nil if unmanaged
  Dest.Write1(0); // warning: store itemsize=0 (mORMot 1 ignores it anyway)
  Dest.Write1(DelphiType(Info));
  Data := PPointer(Data)^; // de-reference pointer to array data
  if Data = nil then
    Dest.Write1(0) // store dynamic array count of 0
  else
  begin
    if ExternalCount <> nil then
      n := ExternalCount^ // e.g. from TDynArray with external count
    else
      n := PDALen(Data - _DALEN)^ + _DAOFF;
    Dest.WriteVarUInt32(n);
    Dest.Write4(0); // warning: we don't store any Hash32 checksum any more
    if Info = nil then
raw:  Dest.Write(Data, itemsize * n)
    else
    begin
      sav := RTTI_BINARYSAVE[Info^.Kind];
      if Assigned(sav) then // paranoid check
        repeat
          inc(Data, sav(Data, Dest, Info));
          dec(n);
        until n = 0
      else
        goto raw;
    end;
  end;
end;

function _BS_DynArray(Data: PAnsiChar; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
begin
  DynArraySave(Data, nil, Dest, Info);
  result := SizeOf(pointer);
end;

function DynArrayLoad(var Value; Source: PAnsiChar; TypeInfo: PRttiInfo;
  TryCustomVariants: PDocVariantOptions; SourceMax: PAnsiChar): PAnsiChar;
begin
  {$ifndef PUREMORMOT2}
  if SourceMax = nil then
    // mORMot 1 unsafe backward compatible: assume fake 100MB Source input
    SourceMax := Source + 100 shl 20;
  {$endif PUREMORMOT2}
  result := BinaryLoad(
    @Value, source, TypeInfo, nil, SourceMax, [rkDynArray], TryCustomVariants);
end;

function DynArraySave(var Value; TypeInfo: PRttiInfo): RawByteString;
begin
  result := BinarySave(@Value, TypeInfo, [rkDynArray]);
end;

function DynArrayLoadHeader(var Source: TFastReader;
  ArrayInfo, ItemInfo: PRttiInfo): integer;
begin
  Source.VarNextInt; // ignore stored itemsize (0 stored now)
  if Source.NextByte <> DelphiType(ItemInfo) then
    Source.ErrorData('RTTI_BINARYLOAD[rkDynArray] failed for %', [ArrayInfo.RawName]);
  result := Source.VarUInt32;
  if result <> 0 then
    Source.Next4; // ignore deprecated Hash32 checksum (0 stored now)
end;

function _BL_DynArray(Data: PAnsiChar; var Source: TFastReader; Info: PRttiInfo): PtrInt;
var
  n, siz: PtrInt;
  nfo: PRttiInfo;
  load: TRttiBinaryLoad;
label
  raw;
begin
  nfo := Info^.DynArrayItemType(siz); // nil for unmanaged items
  n := DynArrayLoadHeader(Source, Info, nfo);
  if PPointer(Data)^ <> nil then
    FastDynArrayClear(pointer(Data), nfo);
  if n > 0 then
  begin
    DynArrayNew(pointer(Data), n, siz); // allocate zeroed  memory
    Data := PPointer(Data)^; // point to first item
    if nfo = nil then
raw:  Source.Copy(Data, siz * n)
    else
    begin
      load := RTTI_BINARYLOAD[nfo^.Kind];
      if Assigned(load) then
        repeat
          inc(Data, load(Data, Source, nfo));
          dec(n);
        until n = 0
      else
        goto raw;
    end;
  end;
  result := SizeOf(pointer);
end;

function DynArrayCompare(A, B: PAnsiChar; ExternalCountA, ExternalCountB: PInteger;
  Info: PRttiInfo; CaseInSensitive: boolean): integer;
var
  n1, n2, n: PtrInt;
begin
  A := PPointer(A)^;
  B := PPointer(B)^;
  if A = B then
  begin
    result := 0;
    exit;
  end
  else if A = nil then
  begin
    result := -1;
    exit;
  end
  else if B = nil then
  begin
    result := 1;
    exit;
  end;
  if ExternalCountA <> nil then
    n1 := ExternalCountA^ // e.g. from TDynArray with external count
  else
    n1 := PDALen(A - _DALEN)^ + _DAOFF;
  if ExternalCountB <> nil then
    n2 := ExternalCountB^
  else
    n2 := PDALen(B - _DALEN)^ + _DAOFF;
  n := n1;
  if n > n2 then
    n := n2;
  if Info = TypeInfo(TObjectDynArray) then
    result := ObjectCompare(PObject(A), PObject(B), n, CaseInSensitive)
  else
    result := BinaryCompare(A, B, Info^.DynArrayItemType, n, CaseInSensitive);
  if result = 0 then
    result := n1 - n2;
end;

function DynArrayAdd(TypeInfo: PRttiInfo; var DynArray; const Item): integer;
var
  da: TDynArray;
begin
  da.Init(TypeInfo, DynArray);
  result := da.Add(Item);
end;

function DynArrayDelete(TypeInfo: PRttiInfo; var DynArray; Index: PtrInt): boolean;
var
  da: TDynArray;
begin
  da.Init(TypeInfo, DynArray);
  result := da.Delete(Index);
end;

function DynArrayEquals(TypeInfo: PRttiInfo; var Array1, Array2;
  Array1Count, Array2Count: PInteger; CaseInsensitive: boolean): boolean;
begin
  result := DynArrayCompare(@Array1, @Array2, Array1Count, Array2Count,
    TypeInfo, CaseInsensitive) = 0;
end;

{$ifdef FPCGENERICS}

function DynArrayAdd<TArray>(var DynArray: TArray; const Item): integer;
begin
  result := DynArrayAdd(TypeInfo(TArray), DynArray, Item);
end;

function DynArrayDelete<TArray>(var DynArray: TArray; Index: PtrInt): boolean;
begin
  result := DynArrayDelete(TypeInfo(TArray), DynArray, Index);
end;

function DynArrayCompare<TArray>(var Array1, Array2: TArray;
  CaseInSensitive: boolean): integer;
begin
  result := DynArrayCompare(
    @Array1, @Array2, nil, nil, TypeInfo(TArray), CaseInSensitive);
end;

{$endif FPCGENERICS}

function _BC_DynArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := DynArrayCompare(A, B, nil, nil, Info, {caseinsens=}false);
  result := SizeOf(pointer);
end;

function _BCI_DynArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := DynArrayCompare(A, B, nil, nil, Info, {caseinsens=}true);
  result := SizeOf(pointer);
end;

function _BC_ObjArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := DynArrayCompare(
    A, B, nil, nil, TypeInfo(TObjectDynArray), {caseinsens=}false);
  result := SizeOf(pointer);
end;

function _BCI_ObjArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := DynArrayCompare(
    A, B, nil, nil, TypeInfo(TObjectDynArray), {caseinsens=}true);
  result := SizeOf(pointer);
end;

function _BS_Record(Data: PAnsiChar; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
var
  fields: TRttiRecordManagedFields; // Size/Count/Fields
  offset: PtrUInt;
  f: PRttiRecordField;
begin
  Info^.RecordManagedFields(fields);
  f := fields.Fields;
  fields.Fields := @RTTI_BINARYSAVE; // reuse pointer slot on stack
  offset := 0;
  while fields.Count <> 0 do
  begin
    dec(fields.Count);
    Info := f^.{$ifdef HASDIRECTTYPEINFO}TypeInfo{$else}TypeInfoRef^{$endif};
    {$ifdef FPC_OLDRTTI}
    if Info^.Kind in rkManagedTypes then
    {$endif FPC_OLDRTTI}
    begin
      offset := f^.Offset - offset;
      if offset <> 0 then
      begin
        Dest.Write(Data, offset);
        inc(Data, offset);
      end;
      offset := PRttiBinarySaves(fields.Fields)[Info^.Kind](Data, Dest, Info);
      inc(Data, offset);
      inc(offset, f^.Offset);
    end;
    inc(f);
  end;
  offset := PtrUInt(fields.Size) - offset;
  if offset <> 0 then
    Dest.Write(Data, offset);
  result := fields.Size;
end;

function _BL_Record(Data: PAnsiChar; var Source: TFastReader; Info: PRttiInfo): PtrInt;
var
  fields: TRttiRecordManagedFields; // Size/Count/Fields
  offset: PtrUInt;
  f: PRttiRecordField;
begin
  Info^.RecordManagedFields(fields);
  f := fields.Fields;
  fields.Fields := @RTTI_BINARYLOAD; // reuse pointer slot on stack
  offset := 0;
  while fields.Count <> 0 do
  begin
    dec(fields.Count);
    Info := f^.{$ifdef HASDIRECTTYPEINFO}TypeInfo{$else}TypeInfoRef^{$endif};
    {$ifdef FPC_OLDRTTI}
    if Info^.Kind in rkManagedTypes then
    {$endif FPC_OLDRTTI}
    begin
      offset := f^.Offset - offset;
      if offset <> 0 then
      begin
        Source.Copy(Data, offset);
        inc(Data, offset);
      end;
      offset := PRttiBinaryLoads(fields.Fields)[Info^.Kind](Data, Source, Info);
      inc(Data, offset);
      inc(offset, f^.Offset);
    end;
    inc(f);
  end;
  offset := PtrUInt(fields.Size) - offset;
  if offset <> 0 then
    Source.Copy(Data, offset);
  result := fields.Size;
end;

function _RecordCompare(A, B: PUtf8Char; Info: PRttiInfo;
 CaseInSensitive: boolean): integer;
var
  fields: TRttiRecordManagedFields; // Size/Count/Fields
  offset: PtrUInt;
  f: PRttiRecordField;
begin
  Info^.RecordManagedFields(fields);
  f := fields.Fields;
  fields.Fields := @RTTI_COMPARE[CaseInSensitive]; // reuse pointer slot on stack
  offset := 0;
  if fields.Count <> 0 then
    repeat
      dec(fields.Count);
      Info := f^.{$ifdef HASDIRECTTYPEINFO}TypeInfo{$else}TypeInfoRef^{$endif};
      {$ifdef FPC_OLDRTTI}
      if Info^.Kind in rkManagedTypes then
      {$endif FPC_OLDRTTI}
      begin
        offset := f^.Offset - offset;
        if offset <> 0 then
        begin
          result := MemCmp(pointer(A), pointer(B), offset); // binary comparison
          if result <> 0 then
            exit;
          inc(A, offset);
          inc(B, offset);
        end;
        offset := PRttiCompares(fields.Fields)[Info^.Kind](A, B, Info, result);
        inc(A, offset);
        inc(B, offset);
        if result <> 0 then
          exit;
        inc(offset, f^.Offset);
      end;
      inc(f);
    until fields.Count = 0
  else
    result := 0;
  offset := PtrUInt(fields.Size) - offset;
  if offset <> 0 then
    result := MemCmp(pointer(A), pointer(B), offset); // trailing binary
end;

function _BC_Record(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  if A = B then
    Compared := 0
  else
    Compared := _RecordCompare(A, B, Info, {caseinsens=}false);
  result := Info^.RecordSize;
end;

function _BCI_Record(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  if A = B then
    Compared := 0
  else
    Compared := _RecordCompare(A, B, Info, {caseinsens=}true);
  result := Info^.RecordSize;
end;

function _BS_Array(Data: PAnsiChar; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
var
  n: PtrInt;
  sav: TRttiBinarySave;
label
  raw;
begin
  Info := Info^.ArrayItemType(n, result);
  if Info = nil then // unmanaged
raw:Dest.Write(Data, result)
  else
  begin
    sav := RTTI_BINARYSAVE[Info^.Kind];
    if Assigned(sav) then // paranoid check
      repeat
        inc(Data, sav(Data, Dest, Info));
        dec(n);
      until n = 0
    else
      goto raw;
  end;
end;

function _BL_Array(Data: PAnsiChar; var Source: TFastReader; Info: PRttiInfo): PtrInt;
var
  n: PtrInt;
  load: TRttiBinaryLoad;
label
  raw;
begin
  Info := Info^.ArrayItemType(n, result);
  if Info = nil then // unmanaged
raw:Source.Copy(Data, result)
  else
  begin
    load := RTTI_BINARYLOAD[Info^.Kind];
    if Assigned(load) then // paranoid check
      repeat
        inc(Data, load(Data, Source, Info));
        dec(n);
      until n = 0
    else
      goto raw;
  end;
end;

function _BC_Array(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
var
  n: PtrInt;
begin
  Info := Info^.ArrayItemType(n, result); // nil if unmanaged
  Compared := BinaryCompare(A, B, Info, n, {CaseInSensitive=}false);
end;

function _BCI_Array(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
var
  n: PtrInt;
begin
  Info := Info^.ArrayItemType(n, result); // nil if unmanaged
  Compared := BinaryCompare(A, B, Info, n, {CaseInSensitive=}true);
end;

procedure _BS_VariantComplex(Data: PVariant; Dest: TBufferWriter);
var
  temp: RawUtf8;
begin
  // not very fast, but creates valid JSON
  _VariantSaveJson(Data^, twJsonEscape, temp);
  Dest.Write(temp);
end;

procedure _BL_VariantComplex(Data: PVariant; var Source: TFastReader);
var
  temp: TSynTempBuffer;
begin
  Source.VarBlob(temp); // load into a private copy for in-place JSON parsing
  try
    BinaryVariantLoadAsJson(Data^, temp.buf, Source.CustomVariants);
  finally
    temp.Done;
  end;
end;

const
  // 0 for unserialized VType, 255 for valOleStr
  VARIANT_SIZE: array[varEmpty .. varWord64] of byte = (
    0, 0, 2, 4, 4, 8, 8, 8, 255, 0, 0, 2, 0, 0, 0, 0, 1, 1, 2, 4, 8, 8);

function _BS_Variant(Data: PVarData; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
var
  vt: cardinal;
begin
  Data := VarDataFromVariant(PVariant(Data)^); // handle varByRef
  vt := Data^.VType;
  Dest.Write2(vt);
  if vt <= high(VARIANT_SIZE) then
  begin
    vt := VARIANT_SIZE[vt];
    if vt <> 0 then
      if vt = 255 then // valOleStr
        Dest.WriteVar(Data^.vAny, length(WideString(Data^.vAny)) * 2)
      else
        Dest.Write(@Data^.VInt64, vt); // simple types are stored as binary
  end
  else if (vt = varString) and  // expect only RawUtf8
          (Data^.vAny <> nil) then
    Dest.WriteVar(Data^.vAny, PStrLen(PAnsiChar(Data^.VAny) - _STRLEN)^)
  {$ifdef HASVARUSTRING}
  else if vt = varUString then
    Dest.WriteVar(Data^.vAny, length(UnicodeString(Data^.vAny)) * 2)
  {$endif HASVARUSTRING}
  else
    _BS_VariantComplex(pointer(Data), Dest);
  result := SizeOf(Data^);
end;

function _BL_Variant(Data: PVarData; var Source: TFastReader; Info: PRttiInfo): PtrInt;
var
  vt: cardinal;
begin
  VarClear(PVariant(Data)^);
  Source.Copy(@Data^.VType, 2);
  Data^.VAny := nil; // to avoid GPF below
  vt := Data^.VType;
  if vt <= high(VARIANT_SIZE) then
  begin
    vt := VARIANT_SIZE[vt];
    if vt <> 0 then
      if vt = 255 then
        with Source.VarBlob do // valOleStr
          SetString(WideString(Data^.vAny), PWideChar(Ptr), Len shr 1)
      else
        Source.Copy(@Data^.VInt64, vt); // simple types
  end
  else if vt = varString then
    with Source.VarBlob do
      FastSetString(RawUtf8(Data^.vAny), Ptr, Len) // expect only RawUtf8
  {$ifdef HASVARUSTRING}
  else if vt = varUString then
    with Source.VarBlob do
      SetString(UnicodeString(Data^.vAny), PWideChar(Ptr), Len shr 1)
  {$endif HASVARUSTRING}
  else if Assigned(BinaryVariantLoadAsJson) then
    _BL_VariantComplex(pointer(Data), Source)
  else
    Source.ErrorData('RTTI_BINARYLOAD[tkVariant] missing mormot.core.json.pas', []);
  result := SizeOf(Data^);
end;

function _BC_Variant(A, B: PVarData; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  if A = B then
    Compared := 0
  else
    Compared := SortDynArrayVariantComp(A^, B^, {caseinsens=}false);
  result := SizeOf(variant);
end;

function _BCI_Variant(A, B: PVarData; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  if A = B then
    Compared := 0
  else
    Compared := SortDynArrayVariantComp(A^, B^, {caseinsens=}true);
  result := SizeOf(variant);
end;

function ObjectCompare(A, B: TObject; CaseInSensitive: boolean): integer;
var
  rA, rB: TRttiCustom;
  pA, pB: PRttiCustomProp;
  i: integer;
begin
  if (A = nil) or
     (B = nil) or
     (A = B) then
  begin
    result := ComparePointer(A, B);
    exit;
  end;
  result := 0;
  rA := Rtti.RegisterClass(A); // faster than RegisterType(Info)
  pA := pointer(rA.Props.List);
  if PClass(B)^.InheritsFrom(PClass(A)^) then
    // same (or similar/inherited) class -> compare per exact properties
    for i := 1 to rA.Props.Count do
    begin
      result := pA^.CompareValue(A, B, pA^, CaseInSensitive);
      if result <> 0 then
        exit;
      inc(pA);
    end
  else
  begin
    // compare properties by name
    rB := Rtti.RegisterClass(B);
    for i := 1 to rA.Props.Count do
    begin
      if pA^.Name <> '' then
      begin
        pB := rB.Props.Find(pA^.Name);
        if pB <> nil then // just ignore missing properties
        begin
          result := pA^.CompareValue(A, B, pB^, CaseInSensitive);
          if result <> 0 then
            exit;
        end;
      end;
      inc(pA);
    end;
  end;
end;

function _BC_Object(A, B: PObject; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ObjectCompare(A^, B^, {caseinsens=}false);
  result := SizeOf(pointer);
end;

function _BCI_Object(A, B: PObject; Info: PRttiInfo; out Compared: integer): PtrInt;
begin
  Compared := ObjectCompare(A^, B^, {caseinsens=}true);
  result := SizeOf(pointer);
end;

function ObjectEquals(A, B: TObject): boolean;
begin
  result := ObjectCompare(A, B, {caseinsensitive=}false) = 0;
end;

function ObjectEqualsI(A, B: TObject): boolean;
begin
  result := ObjectCompare(A, B, {caseinsensitive=}true) = 0;
end;

function ObjectCompare(A, B: PObject; Count: PtrInt;
  CaseInsensitive: boolean): integer;
begin
  if Count > 0 then
    repeat
      result := ObjectCompare(A^, B^, CaseInsensitive);
      if result <> 0 then
        exit;
      inc(A);
      inc(B);
      dec(Count);
    until Count = 0;
  result := 0;
end;

function BinaryEquals(A, B: pointer; Info: PRttiInfo; PSize: PInteger;
  Kinds: TRttiKinds; CaseInSensitive: boolean): boolean;
var
  size, comp: integer;
  cmp: TRttiCompare;
begin
  cmp := RTTI_COMPARE[CaseInSensitive, Info^.Kind];
  if Assigned(cmp) and
     (Info^.Kind in Kinds) then
  begin
    size := cmp(A, B, Info, comp);
    if PSize <> nil then
      PSize^ := size;
    result := comp = 0;
  end
  else
    result := false; // no fair comparison possible
end;

function BinaryCompare(A, B: pointer; Info: PRttiInfo;
  CaseInSensitive: boolean): integer;
var
  cmp: TRttiCompare;
begin
  if A <> B then
    if Info <> nil then
    begin
      cmp := RTTI_COMPARE[CaseInSensitive, Info^.Kind];
      if Assigned(cmp) then
        cmp(A, B, Info, result)
      else
        result := MemCmp(A, B, Info^.RttiSize);
    end
    else
      result := ComparePointer(A, B)
  else
    result := 0;
end;

function BinaryCompare(A, B: pointer; Info: PRttiInfo; Count: PtrInt;
  CaseInSensitive: boolean): integer;
var
  cmp: TRttiCompare;
  siz: PtrInt;
begin
  if (A <> B) and
     (Count > 0) then
    if Info <> nil then
    begin
      cmp := RTTI_COMPARE[CaseInSensitive, Info^.Kind];
      if Assigned(cmp) then
        repeat
          siz := cmp(A, B, Info, result);
          inc(PAnsiChar(A), siz);
          inc(PAnsiChar(B), siz);
          if result <> 0 then
            exit;
          dec(Count);
        until Count = 0
      else
        result := MemCmp(A, B, Count * Info^.RttiSize);
    end
    else
      result := ComparePointer(A, B)
  else
    result := 0;
end;

{$ifndef PUREMORMOT2}

function BinarySaveLength(Data: pointer; Info: PRttiInfo; Len: PInteger;
  Kinds: TRttiKinds): integer;
var
  size: integer;
  W: TBufferWriter; // not very fast, but good enough (RecordSave don't use it)
  temp: array[byte] of byte; // will use mostly TFakeWriterStream.Write()
  save: TRttiBinarySave;
begin
  save := RTTI_BINARYSAVE[Info^.Kind];
  if Assigned(save) and
     (Info^.Kind in Kinds) then
  begin
    W := TBufferWriter.Create(TFakeWriterStream, @temp, SizeOf(temp));
    try
      size := save(Data, W, Info);
      if Len <> nil then
        Len^ := size;
      result := W.TotalWritten;
    finally
      W.Free;
    end;
  end
  else
    result := 0;
end;

function BinarySave(Data: pointer; Dest: PAnsiChar; Info: PRttiInfo;
  out Len: integer; Kinds: TRttiKinds): PAnsiChar;
var
  W: TBufferWriter;
  save: TRttiBinarySave;
begin
  save := RTTI_BINARYSAVE[Info^.Kind];
  if Assigned(save) and
     (Info^.Kind in Kinds) then
  begin
    W := TBufferWriter.Create(TFakeWriterStream, Dest, 1 shl 30);
    try
      Len := save(Data, W, Info);
      result := Dest + W.BufferPosition; // Dest as a 1GB temporary buffer :)
    finally
      W.Free;
    end;
  end
  else
    result := nil;
end;

{$endif PUREMORMOT2}

procedure BinarySave(Data: pointer; Info: PRttiInfo; Dest: TBufferWriter);
var
  save: TRttiBinarySave;
begin
  save := RTTI_BINARYSAVE[Info^.Kind];
  if Assigned(save) then
    save(Data, Dest, Info);
end;

function BinarySave(Data: pointer; Info: PRttiInfo;
  Kinds: TRttiKinds; WithCrc: boolean): RawByteString;
var
  W: TBufferWriter;
  temp: TTextWriterStackBuffer; // 8KB
  save: TRttiBinarySave;
begin
  save := RTTI_BINARYSAVE[Info^.Kind];
  if Assigned(save) and
     (Info^.Kind in Kinds) then
  begin
    W := TBufferWriter.Create(temp{%H-});
    try
      if WithCrc then
        W.Write4(0);
      save(Data, W, Info);
      result := W.FlushTo;
      if WithCrc then
        PCardinal(result)^ :=
          crc32c(0, @PCardinalArray(result)[1], length(result) - 4);
    finally
      W.Free;
    end;
  end
  else
    result := '';
end;

function BinarySaveBytes(Data: pointer; Info: PRttiInfo;
  Kinds: TRttiKinds): TBytes;
var
  W: TBufferWriter;
  temp: TTextWriterStackBuffer; // 8KB
  save: TRttiBinarySave;
begin
  save := RTTI_BINARYSAVE[Info^.Kind];
  if Assigned(save) and
     (Info^.Kind in Kinds) then
  begin
    W := TBufferWriter.Create(temp{%H-});
    try
      save(Data, W, Info);
      result := W.FlushToBytes;
    finally
      W.Free;
    end;
  end
  else
    result := nil;
end;

procedure BinarySave(Data: pointer; var Dest: TSynTempBuffer; Info: PRttiInfo;
  Kinds: TRttiKinds; WithCrc: boolean);
var
  W: TBufferWriter;
  save: TRttiBinarySave;
begin
  save := RTTI_BINARYSAVE[Info^.Kind];
  if Assigned(save) and
     (Info^.Kind in Kinds) then
  begin
    W := TBufferWriter.Create(TRawByteStringStream, @Dest.tmp,
      SizeOf(Dest.tmp) - 16); // Dest.Init() reserves 16 additional bytes
    try
      if WithCrc then
        W.Write4(0);
      save(Data, W, Info);
      if W.Stream.Position = 0 then
        // only Dest.tmp buffer was used -> just set the proper size
        Dest.Init(W.TotalWritten)
      else
        // more than 4KB -> temporary allocation through the temp RawByteString
        Dest.Init(W.FlushTo);
      if WithCrc then
        PCardinal(Dest.buf)^ :=
          crc32c(0, @PCardinalArray(Dest.buf)[1], Dest.len  - 4);
    finally
      W.Free;
    end;
  end
  else
    Dest.Init(0);
end;

function BinarySaveBase64(Data: pointer; Info: PRttiInfo; UriCompatible: boolean;
  Kinds: TRttiKinds; WithCrc: boolean): RawUtf8;
var
  W: TBufferWriter;
  temp: TTextWriterStackBuffer; // 8KB
  tmp: RawByteString;
  P: PAnsiChar;
  len: integer;
  save: TRttiBinarySave;
begin
  save := RTTI_BINARYSAVE[Info^.Kind];
  if Assigned(save) and
     (Info^.Kind in Kinds) then
  begin
    W := TBufferWriter.Create(temp{%H-});
    try
      if WithCrc then
        // placeholder for the trailing crc32c
        W.Write4(0);
      save(Data, W, Info);
      len := W.TotalWritten;
      if W.Stream.Position = 0 then
        // only temp buffer was used
        P := pointer(@temp)
      else
      begin
        // more than 8KB -> temporary allocation
        tmp := W.FlushTo;
        P := pointer(tmp);
      end;
      if WithCrc then
        // as mORMot 1.18 RecordSaveBase64()
        PCardinal(P)^ := crc32c(0, P + 4, len - 4);
      if UriCompatible then
        result := BinToBase64uri(P, len)
      else
        result := BinToBase64(P, len);
    finally
      W.Free;
    end;
  end
  else
    result := '';
end;

function BinaryLoad(Data: pointer; Source: PAnsiChar; Info: PRttiInfo;
  Len: PInteger; SourceMax: PAnsiChar; Kinds: TRttiKinds;
  TryCustomVariants: PDocVariantOptions): PAnsiChar;
var
  size: integer;
  read: TFastReader;
  load: TRttiBinaryLoad;
begin
  load := RTTI_BINARYLOAD[Info^.Kind];
  if Assigned(load) and
     (Info^.Kind in Kinds) and
     (SourceMax <> nil) then
  begin
    {%H-}read.Init(Source, SourceMax - Source);
    read.CustomVariants := TryCustomVariants;
    size := load(Data, read, Info);
    if Len <> nil then
      Len^ := size;
    result := read.P;
  end
  else
    result := nil;
end;

function BinaryLoad(Data: pointer; const Source: RawByteString; Info: PRttiInfo;
  Kinds: TRttiKinds; TryCustomVariants: PDocVariantOptions): boolean;
var
  P: PAnsiChar;
begin
  if Info^.Kind in Kinds then
  begin
    P := pointer(Source);
    P := BinaryLoad(Data, P, Info, nil, P + length(Source), Kinds, TryCustomVariants);
    result := (P <> nil) and
              (P - pointer(Source) = length(Source));
  end
  else
    result := false;
end;

function BinaryLoadBase64(Source: PAnsiChar; Len: PtrInt; Data: pointer;
  Info: PRttiInfo; UriCompatible: boolean; Kinds: TRttiKinds;
  WithCrc: boolean; TryCustomVariants: PDocVariantOptions): boolean;
var
  temp: TSynTempBuffer;
  tempend: pointer;
begin
  if (Len > 6) and
     (Info^.Kind in Kinds) then
  begin
    if UriCompatible then
      result := Base64uriToBin(Source, Len, temp)
    else
      result := Base64ToBin(Source, Len, temp);
    tempend := PAnsiChar(temp.buf) + temp.len;
    if result then
      if WithCrc then
        result := (temp.len >= 4) and
          (crc32c(0, PAnsiChar(temp.buf) + 4, temp.len - 4) = PCardinal(temp.buf)^) and
          (BinaryLoad(Data, PAnsiChar(temp.buf) + 4, Info, nil, tempend,
            Kinds, TryCustomVariants) = tempend)
      else
        result := (BinaryLoad(Data, temp.buf, Info, nil, tempend,
            Kinds, TryCustomVariants) = tempend);
    temp.Done;
  end
  else
    result := false;
end;


function RecordEquals(const RecA, RecB; TypeInfo: PRttiInfo; PRecSize: PInteger;
  CaseInSensitive: boolean): boolean;
begin
  result := BinaryEquals(@RecA, @RecB, TypeInfo, PRecSize,
    rkRecordTypes, CaseInSensitive);
end;

{$ifndef PUREMORMOT2}

function RecordSaveLength(const Rec; TypeInfo: PRttiInfo; Len: PInteger): integer;
begin
 result := {%H-}BinarySaveLength(@Rec, TypeInfo, Len, rkRecordTypes);
end;

function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: PRttiInfo;
  out Len: integer): PAnsiChar;
begin
  result := {%H-}BinarySave(@Rec, Dest, TypeInfo, Len, rkRecordTypes);
end;

function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: PRttiInfo): PAnsiChar;
var
  dummylen: integer;
begin
  result := {%H-}BinarySave(@Rec, Dest, TypeInfo, dummylen, rkRecordTypes);
end;

{$endif PUREMORMOT2}

function RecordSave(const Rec; TypeInfo: PRttiInfo): RawByteString;
begin
  result := BinarySave(@Rec, TypeInfo, rkRecordTypes);
end;

function RecordSaveBytes(const Rec; TypeInfo: PRttiInfo): TBytes;
begin
 result := BinarySaveBytes(@Rec, TypeInfo, rkRecordTypes);
end;

procedure RecordSave(const Rec; var Dest: TSynTempBuffer; TypeInfo: PRttiInfo);
begin
  BinarySave(@Rec, Dest, TypeInfo, rkRecordTypes);
end;

function RecordSaveBase64(const Rec; TypeInfo: PRttiInfo; UriCompatible: boolean): RawUtf8;
begin
  result := BinarySaveBase64(@Rec, TypeInfo, UriCompatible, rkRecordTypes);
end;

function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: PRttiInfo;
  Len: PInteger; SourceMax: PAnsiChar; TryCustomVariants: PDocVariantOptions): PAnsiChar;
begin
  {$ifndef PUREMORMOT2}
  if SourceMax = nil then
    // mORMot 1 unsafe backward compatible: assume fake 100MB Source input
    SourceMax := Source + 100 shl 20;
  {$endif PUREMORMOT2}
  result := BinaryLoad(@Rec, Source, TypeInfo, Len, SourceMax,
    rkRecordTypes, TryCustomVariants);
end;

function RecordLoad(var Rec; const Source: RawByteString; TypeInfo: PRttiInfo;
  TryCustomVariants: PDocVariantOptions): boolean;
begin
  result := BinaryLoad(@Rec, Source, TypeInfo, rkRecordTypes, TryCustomVariants);
end;

function RecordLoadBase64(Source: PAnsiChar; Len: PtrInt; var Rec;
  TypeInfo: PRttiInfo; UriCompatible: boolean; TryCustomVariants: PDocVariantOptions): boolean;
begin
  result := BinaryLoadBase64(Source, Len, @Rec, TypeInfo, UriCompatible,
    rkRecordTypes, {withcrc=}true, TryCustomVariants);
end;





{ ************ TDynArray and TDynArrayHashed Wrappers }

const
  // helper arrays to get the standard comparison/hash functions
  PT_SORT: array[{caseins=}boolean, TRttiParserType] of TDynArraySortCompare = (
    // case sensitive comparison/sort functions:
    (nil,                       //  ptNone
     nil,                       //  ptArray
     SortDynArrayBoolean,       //  ptBoolean
     SortDynArrayByte,          //  ptByte
     SortDynArrayCardinal,      //  ptCardinal
     SortDynArrayInt64,         //  ptCurrency
     SortDynArrayDouble,        //  ptDouble
     SortDynArrayExtended,      //  ptExtended
     SortDynArrayInt64,         //  ptInt64
     SortDynArrayInteger,       //  ptInteger
     SortDynArrayQWord,         //  ptQWord
     {$ifdef CPUINTEL}SortDynArrayAnsiString
     {$else}SortDynArrayRawByteString{$endif}, //  ptRawByteString
     SortDynArrayAnsiString,    //  ptRawJson
     SortDynArrayAnsiString,    //  ptRawUtf8
     nil,                       //  ptRecord
     SortDynArraySingle,        //  ptSingle
     {$ifdef UNICODE}SortDynArrayString
     {$else}SortDynArrayAnsiString{$endif}, //  ptString
     SortDynArrayUnicodeString, //  ptSynUnicode
     SortDynArrayDouble,        //  ptDateTime
     SortDynArrayDouble,        //  ptDateTimeMS
     SortDynArray128,           //  ptGuid
     SortDynArray128,           //  ptHash128
     SortDynArray256,           //  ptHash256
     SortDynArray512,           //  ptHash512
     SortDynArrayInt64,         //  ptOrm
     SortDynArrayInt64,         //  ptTimeLog
     SortDynArrayUnicodeString, //  ptUnicodeString
     SortDynArrayInt64,         //  ptUnixTime
     SortDynArrayInt64,         //  ptUnixMSTime
     SortDynArrayVariant,       //  ptVariant
     SortDynArrayUnicodeString, //  ptWideString
     SortDynArrayAnsiString,    //  ptWinAnsi
     SortDynArrayWord,          //  ptWord
     nil,                       //  ptEnumeration
     nil,                       //  ptSet
     SortDynArrayPointer,       //  ptClass
     nil,                       //  ptDynArray
     SortDynArrayPointer,       //  ptInterface
     SortDynArrayPUtf8Char,     //  ptPUtf8Char
     nil),                      //  ptCustom
    // case insensitive comparison/sort functions:
    (nil,                        //  ptNone
     nil,                        //  ptArray
     SortDynArrayBoolean,        //  ptBoolean
     SortDynArrayByte,           //  ptByte
     SortDynArrayCardinal,       //  ptCardinal
     SortDynArrayInt64,          //  ptCurrency
     SortDynArrayDouble,         //  ptDouble
     SortDynArrayExtended,       //  ptExtended
     SortDynArrayInt64,          //  ptInt64
     SortDynArrayInteger,        //  ptInteger
     SortDynArrayQWord,          //  ptQWord
     {$ifdef CPUINTEL}SortDynArrayAnsiString
     {$else}SortDynArrayRawByteString{$endif}, //  ptRawByteString
     SortDynArrayAnsiStringI,    //  ptRawJson
     SortDynArrayAnsiStringI,    //  ptRawUtf8
     nil,                        //  ptRecord
     SortDynArraySingle,         //  ptSingle
     SortDynArrayStringI,        //  ptString
     SortDynArrayUnicodeStringI, //  ptSynUnicode
     SortDynArrayDouble,         //  ptDateTime
     SortDynArrayDouble,         //  ptDateTimeMS
     SortDynArray128,            //  ptGuid
     SortDynArray128,            //  ptHash128
     SortDynArray256,            //  ptHash256
     SortDynArray512,            //  ptHash512
     SortDynArrayInt64,          //  ptOrm
     SortDynArrayInt64,          //  ptTimeLog
     SortDynArrayUnicodeStringI, //  ptUnicodeString
     SortDynArrayInt64,          //  ptUnixTime
     SortDynArrayInt64,          //  ptUnixMSTime
     SortDynArrayVariantI,       //  ptVariant
     SortDynArrayUnicodeStringI, //  ptWideString
     SortDynArrayAnsiStringI,    //  ptWinAnsi
     SortDynArrayWord,           //  ptWord
     nil,                        //  ptEnumeration
     nil,                        //  ptSet
     SortDynArrayPointer,        //  ptClass
     nil,                        //  ptDynArray
     SortDynArrayPointer,        //  ptInterface
     SortDynArrayPUtf8CharI,     //  ptPUtf8Char
     nil));                      //  ptCustom

function DynArraySortOne(Kind: TRttiParserType;
  CaseInsensitive: boolean): TDynArraySortCompare;
begin
  result := PT_SORT[CaseInsensitive, Kind];
end;

procedure ObjArraySort(var aValue; Compare: TDynArraySortCompare;
  CountPointer: PInteger);
begin
  DynArray(TypeInfo(TObjectDynArray), aValue, CountPointer).Sort(Compare);
end;


{ TDynArray }

procedure TDynArray.InitRtti(aInfo: TRttiCustom; var aValue;
  aCountPointer: PInteger);
begin
  fInfo := aInfo;
  fValue := @aValue;
  fCountP := aCountPointer;
  if fCountP <> nil then
    fCountP^ := 0;
  fCompare := nil;
  fSorted := false;
  fNoFinalize := false;
end;

procedure TDynArray.InitRtti(aInfo: TRttiCustom; var aValue);
begin
  fInfo := aInfo;
  fValue := @aValue;
  fCountP := nil;
  fCompare := nil;
  fSorted := false;
  fNoFinalize := false;
end;

procedure TDynArray.Init(aTypeInfo: PRttiInfo; var aValue;
  aCountPointer: PInteger);
begin
  if aTypeInfo^.Kind <> rkDynArray then
    EDynArray.RaiseUtf8('TDynArray.Init: % is %, expected rkDynArray',
      [aTypeInfo.RawName, ToText(aTypeInfo.Kind)^]);
  InitRtti(Rtti.RegisterType(aTypeInfo), aValue, aCountPointer);
end;

function TDynArray.InitSpecific(aTypeInfo: PRttiInfo; var aValue;
  aKind: TRttiParserType; aCountPointer: PInteger; aCaseInsensitive: boolean): TRttiParserType;
begin
  if aTypeInfo^.Kind <> rkDynArray then
    EDynArray.RaiseUtf8('TDynArray.InitSpecific: % is %, expected rkDynArray',
      [aTypeInfo.RawName, ToText(aTypeInfo.Kind)^]);
  InitRtti(Rtti.RegisterType(aTypeInfo), aValue, aCountPointer);
  result := SetParserType(aKind, aCaseInsensitive);
end;

function TDynArray.SetParserType(aKind: TRttiParserType;
  aCaseInsensitive: boolean): TRttiParserType;
begin
  case aKind of
    ptNone:
      if Assigned(fInfo.ArrayRtti) then
        result := fInfo.ArrayRtti.Parser
      else
        result := fInfo.ArrayFirstField;
  else
    result := aKind;
  end;
  fCompare := PT_SORT[aCaseInsensitive, result];
  if not Assigned(fCompare) then
    if aKind <> ptNone then
      EDynArray.RaiseUtf8('TDynArray.SetParserType(%,%) unsupported %',
        [ToText(aKind)^, fInfo.Name, ToText(result)^]);
end;

function TDynArray.ItemSize: PtrUInt;
begin
  result := fInfo.Cache.ItemSize;
end;

function TDynArray.GetCount: PtrInt;
begin // use result as a single temporary variable for better FPC asm generation
  result := PtrUInt(fCountP);
  if result <> 0 then
    result := PInteger(result)^ // count is external
  else
  begin
    result := PtrUInt(fValue);
    if result <> 0 then
    begin
      result := PPtrInt(result)^;
      if result <> 0 then
      begin
        result := PDALen(result - _DALEN)^; // count = length()
        {$ifdef FPC} inc(result, _DAOFF); {$endif}
      end;
    end;
  end;
end;

function TDynArray.GetCapacity: PtrInt;
begin
  result := PtrInt(fValue);
  if result <> 0 then
  begin
    result := PPtrInt(result)^;
    if result <> 0 then
    begin
      result := PDALen(result - _DALEN)^; // capacity = length()
      {$ifdef FPC} inc(result, _DAOFF); {$endif}
    end;
  end;
end;

procedure TDynArray.ItemCopy(Source, Dest: pointer);
var
  nfo: TRttiCustom;
begin
  nfo := fInfo.ArrayRtti;
  if (nfo <> nil) and // inlined nfo.ValueCopy() to avoid MoveFast() twice
     Assigned(nfo.Copy) then
    nfo.Copy(Dest, Source, nfo.Info) // also for T*ObjArray
  else
    MoveFast(Source^, Dest^, fInfo.Cache.ItemSize);
end;

procedure TDynArray.ItemClear(Item: pointer);
begin
  if Item = nil then
    exit;
  if (fInfo.ArrayRtti <> nil) and
     not fNoFinalize then
    fInfo.ArrayRtti.ValueFinalize(Item); // also for T*ObjArray
  FillCharFast(Item^, fInfo.Cache.ItemSize, 0); // always
end;

procedure TDynArray.ItemRandom(Item: pointer);
begin
  if Item <> nil then
    if fInfo.ArrayRtti <> nil then
      fInfo.ArrayRtti.ValueRandom(Item)
    else
      SharedRandom.Fill(Item, fInfo.Cache.ItemSize);
end;

function TDynArray.ItemEquals(A, B: pointer; CaseInSensitive: boolean): boolean;
begin
  result := ItemCompare(A, B, CaseInSensitive) = 0;
end;

function TDynArray.ItemCompare(A, B: pointer; CaseInSensitive: boolean): integer;
var
  comp: TRttiCompare;
  rtti: PRttiInfo;
label
  raw;
begin
  if Assigned(fCompare) then
    result := fCompare(A^, B^)
  else if not(rcfArrayItemManaged in fInfo.Flags) then
     // fast binary comparison with length
raw: result := MemCmp(A, B, fInfo.Cache.ItemSize)
  else
  begin
    rtti := fInfo.Cache.ItemInfoRaw;
    comp := RTTI_COMPARE[CaseInsensitive, rtti.Kind];
    if Assigned(comp) then
      comp(A, B, rtti, result)
    else
      goto raw;
  end;
end;

function TDynArray.Add(const Item): PtrInt;
begin
  result := GetCount;
  if fValue = nil then
    exit; // avoid GPF if void
  SetCount(result + 1);
  ItemCopy(@Item, PAnsiChar(fValue^) + result * fInfo.Cache.ItemSize);
end;

function TDynArray.New: PtrInt;
begin
  result := GetCount;
  SetCount(result + 1);
end;

function TDynArray.NewPtr: pointer;
var
  index: PtrInt;
begin
  index := GetCount; // in two explicit steps to ensure no problem at inlining
  SetCount(index + 1);
  result := PAnsiChar(fValue^) + index * fInfo.Cache.ItemSize;
end;

function TDynArray.Peek(var Dest): boolean;
var
  index: PtrInt;
begin
  index := GetCount - 1;
  result := index >= 0;
  if result then
    ItemCopy(PAnsiChar(fValue^) + index * fInfo.Cache.ItemSize, @Dest);
end;

function TDynArray.Pop(var Dest): boolean;
var
  index: PtrInt;
begin
  index := GetCount - 1;
  result := index >= 0;
  if result then
  begin
    ItemMoveTo(index, @Dest);
    SetCount(index);
  end;
end;

function TDynArray.PeekHead(var Dest): boolean;
begin
  result := GetCount <> 0;
  if result then
    ItemCopy(fValue^, @Dest);
end;

function TDynArray.PopHead(var Dest): boolean;
begin
  result := GetCount <> 0;
  if result then
  begin
    ItemMoveTo(0, @Dest);
    Delete(0);
  end;
end;

procedure TDynArray.Insert(Index: PtrInt; const Item);
var
  n: PtrInt;
  s: PtrUInt;
  P: PAnsiChar;
begin
  if fValue = nil then
    exit; // avoid GPF if void
  n := GetCount;
  SetCount(n + 1);
  s := fInfo.Cache.ItemSize;
  if PtrUInt(Index) < PtrUInt(n) then
  begin
    // reserve space for the new item
    P := PAnsiChar(fValue^) + PtrUInt(Index) * s;
    MoveFast(P[0], P[s], PtrUInt(n - Index) * s);
    if rcfArrayItemManaged in fInfo.Flags then // avoid GPF in ItemCopy() below
      FillCharFast(P^, s, 0);
  end
  else
    // Index>=Count -> add at the end
    P := PAnsiChar(fValue^) + PtrUInt(n) * s;
  ItemCopy(@Item, P);
end;

procedure TDynArray.Clear;
begin
  SetCount(0);
end;

function TDynArray.ClearSafe: boolean;
begin
  try
    SetCount(0);
    result := true;
  except // weak code, but may be a good idea in a destructor
    result := false;
  end;
end;

function TDynArray.Delete(aIndex: PtrInt): boolean;
var
  n: PtrInt;
  s, len: PtrUInt;
  P: PAnsiChar;
  wassorted: boolean;
begin
  result := false;
  if fValue = nil then
    exit; // avoid GPF if void
  n := GetCount;
  if PtrUInt(aIndex) >= PtrUInt(n) then
    exit; // out of range
  if PDACnt(PAnsiChar(fValue^) - _DACNT)^ > 1 then
    InternalSetLength(n, n); // unique
  dec(n);
  s := fInfo.Cache.ItemSize;
  P := PAnsiChar(fValue^) + PtrUInt(aIndex) * s;
  if (fInfo.ArrayRtti <> nil) and
     not fNoFinalize then
    fInfo.ArrayRtti.ValueFinalize(P); // also for T*ObjArray
  len := n - aIndex;
  if len <> 0 then
  begin
    len := len * s;
    MoveFast(P[s], P[0], len);
    inc(P, len);
  end;
  FillCharFast(P^, s, 0);
  wassorted := fSorted;
  SetCount(n); // won't reallocate
  fSorted := wassorted; // deletion won't change the order
  result := true;
end;

{$ifdef FPC} // very efficient inlined code on FPC
function TDynArray.ItemPtr(index: PtrInt): pointer;
label
  ok, ko; // labels make the code shorter and more efficient
var
  c: PtrUInt;
begin
  result := pointer(fValue);
  if result = nil then
    exit;
  result := PPointer(result)^;
  if result = nil then
    exit;
  c := PtrUInt(fCountP);
  if c <> 0 then
  begin
    if PtrUInt(index) < PCardinal(c)^ then
ok:   inc(PByte(result), index * fInfo.Cache.ItemSize) // branchless ext count
    else
      goto ko;
  end
  else // FPC stores high() in TDALen=PtrInt
    if PtrUInt(index) <= PPtrUInt(PAnsiChar(result) - _DALEN)^ then
      goto ok
    else
ko:   result := nil;
end;
{$else} // latest Delphi compilers have troubles with inlining + labels
function TDynArray.ItemPtr(index: PtrInt): pointer;
var
  c: PtrUInt;
begin
  result := pointer(fValue);
  if result = nil then
    exit;
  result := PPointer(result)^;
  if result = nil then
    exit;
  c := PtrUInt(fCountP);
  if c <> 0 then
    if PtrUInt(index) < PCardinal(c)^ then
      inc(PByte(result), index * fInfo.Cache.ItemSize) // branchless ext count
    else
      result := nil
  else // Delphi stores length() in TDALen=NativeInt
    if PtrUInt(index) < PPtrUInt(PtrUInt(result) - _DALEN)^ then
      inc(PByte(result), index * fInfo.Cache.ItemSize)
    else
      result := nil;
end;
{$endif FPC}

function TDynArray.ItemCopyAt(index: PtrInt; Dest: pointer): boolean;
var
  p: pointer;
begin
  p := ItemPtr(index);
  if p <> nil then
  begin
    ItemCopy(p, Dest);
    result := true;
  end
  else
    result := false;
end;

function TDynArray.ItemMoveTo(index: PtrInt; Dest: pointer): boolean;
var
  p: pointer;
begin
  p := ItemPtr(index);
  if (p = nil) or
     (Dest = nil) then
  begin
    result := false;
    exit;
  end;
  if (fInfo.ArrayRtti <> nil) and
     not fNoFinalize then
    fInfo.ArrayRtti.ValueFinalize(Dest); // also handle T*ObjArray
  MoveFast(p^, Dest^, fInfo.Cache.ItemSize);
  FillCharFast(p^, fInfo.Cache.ItemSize, 0);
  result := true;
end;

procedure TDynArray.ItemCopyFrom(Source: pointer; index: PtrInt;
  ClearBeforeCopy: boolean);
var
  p: pointer;
begin
  p := ItemPtr(index);
  if p <> nil then
  begin
    if ClearBeforeCopy then // safer if Source is a copy of p^
      ItemClear(p);
    ItemCopy(Source, p);
  end;
end;

{$ifdef CPU64}
procedure Exchg16(P1, P2: PPtrIntArray); inline;
var
  c: PtrInt;
begin
  c := P1[0];
  P1[0] := P2[0];
  P2[0] := c;
  c := P1[1];
  P1[1] := P2[1];
  P2[1] := c;
end;
{$endif CPU64}

procedure TDynArray.Reverse;
var
  n, siz: PtrInt;
  P1, P2: PAnsiChar;
  c: AnsiChar;
  i32: integer;
  i64: Int64;
begin
  n := GetCount - 1;
  if n > 0 then
  begin
    siz := fInfo.Cache.ItemSize;
    P1 := fValue^;
    case siz of
      1:
        begin
          // optimized version for TByteDynArray and such
          P2 := P1 + n;
          while P1 < P2 do
          begin
            c := P1^;
            P1^ := P2^;
            P2^ := c;
            inc(P1);
            dec(P2);
          end;
        end;
      4:
        begin
          // optimized version for TIntegerDynArray and such
          P2 := P1 + n * SizeOf(integer);
          while P1 < P2 do
          begin
            i32 := PInteger(P1)^;
            PInteger(P1)^ := PInteger(P2)^;
            PInteger(P2)^ := i32;
            inc(P1, 4);
            dec(P2, 4);
          end;
        end;
      8:
        begin
          // optimized version for TInt64DynArray + TDoubleDynArray and such
          P2 := P1 + n * SizeOf(Int64);
          while P1 < P2 do
          begin
            i64 := PInt64(P1)^;
            PInt64(P1)^ := PInt64(P2)^;
            PInt64(P2)^ := i64;
            inc(P1, 8);
            dec(P2, 8);
          end;
        end;
      16:
        begin
          // optimized version for 32-bit TVariantDynArray and such
          P2 := P1 + n * 16;
          while P1 < P2 do
          begin
            {$ifdef CPU64}Exchg16{$else}ExchgVariant{$endif}(pointer(P1), pointer(P2));
            inc(P1, 16);
            dec(P2, 16);
          end;
        end;
    {$ifdef CPU64}
      24:
        begin
          // optimized version for 64-bit TVariantDynArray and such
          P2 := P1 + n * 24;
          while P1 < P2 do
          begin
            ExchgVariant(pointer(P1), pointer(P2));
            inc(P1, 24);
            dec(P2, 24);
          end;
        end;
    {$endif CPU64}
    else
      begin
        // generic version
        P2 := P1 + n * siz;
        while P1 < P2 do
        begin
          Exchg(P1, P2, siz);
          inc(P1, siz);
          dec(P2, siz);
        end;
      end;
    end;
  end;
end;

procedure TDynArray.FillZero;
var
  n: integer;
begin
  n := GetCount;
  if n <> 0 then
    if not (rcfArrayItemManaged in fInfo.Flags) then
      FillCharFast(fValue^^, n * fInfo.Cache.ItemSize, 0) // e.g. THash256
    else
      FillZeroRtti(fInfo.Cache.ItemInfoRaw, fValue^^);
end;

procedure TDynArray.SaveTo(W: TBufferWriter);
begin
  DynArraySave(pointer(fValue), fCountP, W, Info.Info);
end;

procedure TDynArray.SaveToStream(Stream: TStream);
var
  W: TBufferWriter;
  tmp: TTextWriterStackBuffer; // 8KB buffer
begin
  if (fValue = nil) or
     (Stream = nil) then
    exit; // avoid GPF if void
  W := TBufferWriter.Create(Stream, @tmp, SizeOf(tmp));
  try
    SaveTo(W);
    W.Flush;
  finally
    W.Free;
  end;
end;

function TDynArray.SaveTo: RawByteString;
var
  W: TRawByteStringStream;
begin
  W := TRawByteStringStream.Create;
  try
    SaveToStream(W);
    result := W.DataString;
  finally
    W.Free;
  end;
end;

function TDynArray.LoadFrom(Source, SourceMax: PAnsiChar): PAnsiChar;
var
  read: TFastReader;
begin
  {$ifndef PUREMORMOT2}
  if SourceMax = nil then
    // mORMot 1 unsafe backward compatible: assume fake 100MB Source input
    SourceMax := Source + 100 shl 20;
  {$endif PUREMORMOT2}
  {%H-}read.Init(Source, SourceMax - Source);
  LoadFromReader(read);
  if read.P <> Source then
    result := read.P
  else
    result := nil;
end;

function TDynArray.LoadFromBinary(const Buffer: RawByteString): boolean;
var
  read: TFastReader;
begin
  read.Init(Buffer);
  LoadFromReader(read);
  result := read.P = read.Last;
end;

procedure TDynArray.LoadFromReader(var Read: TFastReader);
begin
  if fValue <> nil then
  begin
    _BL_DynArray(pointer(fValue), Read, Info.Info);
    if fCountP <> nil then // _BL_DynArray() set length -> reflect on Count
      if fValue^ = nil then
        fCountP^ := 0
      else
        fCountP^ := PDALen(PAnsiChar(fValue^) - _DALEN)^ + _DAOFF;
  end;
end;

procedure TDynArray.LoadFromStream(Stream: TCustomMemoryStream);
var
  S, P: PAnsiChar;
begin
  S := PAnsiChar(Stream.Memory);
  P := LoadFrom(S + Stream.Position, S + Stream.Size);
  Stream.Seek(Int64(PtrUInt(P) - PtrUInt(S)), soBeginning);
end;

function TDynArray.SaveToJson(EnumSetsAsText: boolean; reformat: TTextWriterJsonFormat): RawUtf8;
begin
  SaveToJson(result, EnumSetsAsText, reformat);
end;

procedure TDynArray.SaveToJson(out result: RawUtf8; EnumSetsAsText: boolean;
  reformat: TTextWriterJsonFormat);
begin
  SaveToJson(result, TEXTWRITEROPTIONS_ENUMASTEXT[EnumSetsAsText],
    TEXTWRITEROBJECTOPTIONS_ENUMASTEXT[EnumSetsAsText], reformat);
end;

procedure TDynArray.SaveToJson(out result: RawUtf8; Options: TTextWriterOptions;
  ObjectOptions: TTextWriterWriteObjectOptions; reformat: TTextWriterJsonFormat);
var
  W: TTextWriter;
  temp: TTextWriterStackBuffer;
begin
  if GetCount = 0 then
    result := '[]'
  else
  begin
    W := DefaultJsonWriter.CreateOwnedStream(temp);
    try
      W.CustomOptions := W.CustomOptions + Options;
      SaveToJson(W, ObjectOptions);
      W.SetText(result, reformat);
    finally
      W.Free;
    end;
  end;
end;

procedure TDynArray.SaveToJson(W: TTextWriter;
  ObjectOptions: TTextWriterWriteObjectOptions);
var
  len, backup: PtrInt;
  hacklen: PDALen;
begin
  len := GetCount;
  if len = 0 then
    W.Add('[', ']')
  else
  begin
    hacklen := PDALen(PAnsiChar(fValue^) - _DALEN);
    backup := hacklen^;
    try
      hacklen^ := len - _DAOFF; // may use ExternalCount
      W.AddTypedJson(fValue, Info.Info, ObjectOptions); // from mormot.core.json
    finally
      hacklen^ := backup;
    end;
  end;
end;

procedure _GetDataFromJson(Data: pointer; var Json: PUtf8Char;
  EndOfObject: PUtf8Char; Rtti: TRttiCustom;
  CustomVariantOptions: PDocVariantOptions; Tolerant: boolean;
  Interning: TRawUtf8InterningAbstract);
begin
  raise ERttiException.Create('GetDataFromJson() not implemented - ' +
    'please include mormot.core.json in your uses clause');
end;

function TDynArray.LoadFromJson(P: PUtf8Char; EndOfObject: PUtf8Char;
  CustomVariantOptions: PDocVariantOptions; Tolerant: boolean;
  Interning: TRawUtf8InterningAbstract): PUtf8Char;
begin
  SetCount(0); // faster to use our own routine now
  GetDataFromJson(fValue, P,
    EndOfObject, Info, CustomVariantOptions, Tolerant, Interning);
  if fCountP <> nil then
    // GetDataFromJson() set the array length (capacity), not the external count
    if fValue^ = nil then
      fCountP^ := 0
    else
      fCountP^ := PDALen(PAnsiChar(fValue^) - _DALEN)^ + _DAOFF;
  result := P;
end;

function TDynArray.LoadFromJson(const Json: RawUtf8;
  CustomVariantOptions: PDocVariantOptions; Tolerant: boolean;
  Interning: TRawUtf8InterningAbstract): boolean;
var
  tmp: TSynTempBuffer;
begin
  tmp.Init(Json);
  try
    result := LoadFromJson(tmp.buf, nil,
      CustomVariantOptions, Tolerant, Interning) <> nil;
  finally
    tmp.Done;
  end;
end;

function TDynArray.ItemCopyFirstField(Source, Dest: pointer): boolean;
var
  rtti: PRttiInfo;
begin
  result := false;
  if fInfo.ArrayFirstField in ptUnmanagedTypes then
    MoveFast(Source^, Dest^, PT_SIZE[fInfo.ArrayFirstField])
  else
    begin
      rtti := PT_INFO[fInfo.ArrayFirstField];
      if rtti = nil then
        exit; // ptNone, ptInterface, ptCustom
      rtti^.Copy(Dest, Source);
    end;
  result := true;
end;

function BruteFind(P, V: PAnsiChar; cmp: TDynArraySortCompare; n, s: PtrInt): PtrInt;
begin // array is very small, or not sorted -> O(n) iterative search
  result := 0;
  repeat
    if cmp(P^, V^) = 0 then
      exit;
    inc(result);
    inc(P, s);
  until result = n;
  result := -1;
end;

function TDynArray.Find(const Item; const aIndex: TIntegerDynArray;
  aCompare: TDynArraySortCompare): PtrInt;
var
  n, L: PtrInt;
  cmp: integer;
  P: PAnsiChar;
begin
  n := GetCount;
  if Assigned(aCompare) and
     (n > 0) then
  begin
    P := fValue^;
    if length(aIndex) >= n then
    begin // fast O(log(n)) binary search over aIndex[]
      dec(n);
      L := 0;
      repeat
        result := (L + n) shr 1;
        cmp := aCompare(P[aIndex[result] * fInfo.Cache.ItemSize], Item);
        if cmp = 0 then
        begin
          result := aIndex[result]; // returns index in TDynArray
          exit;
        end;
        if cmp < 0 then
          L := result + 1
        else
          n := result - 1;
      until L > n;
    end
    else
    begin // fallback to O(n) linear search on void aIndex[]
      result := BruteFind(P, @Item, aCompare, n, fInfo.Cache.ItemSize);
      exit;
    end;
  end;
  result := -1;
end;

function SortFind(P, V: PAnsiChar; cmp: TDynArraySortCompare; R, s: PtrInt): PtrInt;
var
  m, L: PtrInt;
  res: integer;
begin // array is sorted -> use fast O(log(n)) binary search
  L := 0;
  dec(R);
  repeat
    result := (L + R) shr 1;
    res := cmp(P[result * s], V^);
    if res = 0 then
      exit;
    m := result - 1;
    inc(result);
    if res > 0 then // compile as cmovnle/cmovle opcodes on FPC x86_64
      R := m
    else
      L := result;
  until L > R;
  result := -1;
end;

function TDynArray.Find(const Item; aCompare: TDynArraySortCompare): PtrInt;
var
  n: PtrInt;
  fnd: function(P, V: PAnsiChar; cmp: TDynArraySortCompare; n, s: PtrInt): PtrInt;
begin
  n := GetCount;
  if not Assigned(aCompare) then
    aCompare := fCompare;
  if n > 0 then
    if Assigned(aCompare) then
    begin
      fnd := @BruteFind;
      if n > 10 then
        if fSorted and
           (@aCompare = @fCompare) then
          fnd := @SortFind
        else if not(rcfArrayItemManaged in fInfo.Flags) and
                (fInfo.ArrayRtti <> nil) and
                (@aCompare = @PT_SORT[false, fInfo.ArrayRtti.Parser]) then
        begin // optimized brute force search with potential SSE2 asm
          result := AnyScanIndex(fValue^, @Item, n, fInfo.Cache.ItemSize);
          exit;
        end;
      result := fnd(fValue^, @Item, aCompare, n, fInfo.Cache.ItemSize);
    end
    else
      result := IndexOf(Item, {caseinsens=}false) // no fCompare -> default
  else
    result := -1;
end;

function TDynArray.FindIndex(const Item; aIndex: PIntegerDynArray;
  aCompare: TDynArraySortCompare): PtrInt;
begin
  if aIndex <> nil then
    result := Find(Item, aIndex^, aCompare)
  else
    result := Find(Item, aCompare);
end;

function TDynArray.FindAndFill(var Item; aIndex: PIntegerDynArray;
  aCompare: TDynArraySortCompare): integer;
begin
  result := FindIndex(Item, aIndex, aCompare);
  if result >= 0 then
    // if found, fill Item with the matching item
    ItemCopy(PAnsiChar(fValue^) + (result * fInfo.Cache.ItemSize), @Item);
end;

function TDynArray.FindAndDelete(const Item; aIndex: PIntegerDynArray;
  aCompare: TDynArraySortCompare): integer;
begin
  result := FindIndex(Item, aIndex, aCompare);
  if result >= 0 then
    // if found, delete the item from the array
    Delete(result);
end;

function TDynArray.FindAndUpdate(const Item; aIndex: PIntegerDynArray;
  aCompare: TDynArraySortCompare): integer;
begin
  result := FindIndex(Item, aIndex, aCompare);
  if result >= 0 then
    // if found, fill Item with the matching item
    ItemCopy(@Item, PAnsiChar(fValue^) + (result * fInfo.Cache.ItemSize));
end;

function TDynArray.FindAndAddIfNotExisting(const Item; aIndex: PIntegerDynArray;
  aCompare: TDynArraySortCompare): integer;
begin
  result := FindIndex(Item, aIndex, aCompare);
  if result < 0 then
    // -1 will mark success
    Add(Item);
end;

function TDynArray.FindAllSorted(const Item;
  out FirstIndex, LastIndex: integer): boolean;
var
  found, last: integer; // FastLocateSorted() requires an integer
  siz: PtrInt;
  P, val: PAnsiChar;
begin
  result := FastLocateSorted(Item, found);
  if not result then
    exit;
  FirstIndex := found;
  P := fValue^;
  siz := fInfo.Cache.ItemSize;
  inc(P, found * siz);
  val := P; // faster than Item after RawUtf8 interning
  while FirstIndex > 0 do
  begin
    dec(P, siz);
    if fCompare(P^, val^) <> 0 then
      break;
    dec(FirstIndex);
  end;
  last := GetCount - 1;
  LastIndex := found;
  P := val;
  while LastIndex < last do
  begin
    inc(P, siz);
    if fCompare(P^, val^) <> 0 then
      break;
    inc(LastIndex);
  end;
end;

function TDynArray.FindAllSorted(const Item; out FindCount: integer): pointer;
var
  found: integer; // FastLocateSorted() requires an integer
  siz: PtrInt;
  P, fnd, limit: PAnsiChar;
begin
  FindCount := 0;
  result := nil;
  if not FastLocateSorted(Item, found) then // O(log(n)) locate a matching item
    exit;
  P := fValue^;
  limit := P;
  siz := fInfo.Cache.ItemSize;
  inc(P, found * siz);
  fnd := P; // faster than Item after RawUtf8 interning
  repeat
    result := P;
    inc(FindCount);
    dec(P, siz);
  until (P < limit) or
        (fCompare(P^, fnd^) <> 0); // search all matches before located item
  inc(limit, GetCount * siz);
  P := fnd;
  repeat
    inc(P, siz);
    if (P >= limit) or
       (fCompare(P^, fnd^) <> 0) then // search after
      break;
    inc(FindCount);
  until false;
end;

function TDynArray.FastLocateSorted(const Item; out Index: integer): boolean;
var
  n, i: PtrInt;
  cmp: integer;
  P: PAnsiChar;
begin
  result := false;
  n := GetCount;
  if Assigned(fCompare) then
    if n = 0 then // a void array is always sorted
      Index := 0
    else if fSorted then
    begin
      P := fValue^;
      // first compare with the last sorted item (common case, e.g. with IDs)
      dec(n);
      cmp := fCompare(Item, P[n * fInfo.Cache.ItemSize]);
      if cmp >= 0 then
      begin
        Index := n;
        if cmp = 0 then
          // was just added: returns true + index of last item
          result := true
        else
          // bigger than last item: returns false + insert after last position
          inc(Index);
        exit;
      end;
      // O(log(n)) binary search of the sorted position
      Index := 0; // more efficient code if we use Index and not a local var
      repeat
        i := (Index + n) shr 1;
        cmp := fCompare(Item, P[i * fInfo.Cache.ItemSize]);
        if cmp = 0 then
        begin
          // returns true + index of (first found) existing Item
          Index := i;
          result := true;
          exit;
        end
        else if cmp > 0 then
          Index := i + 1
        else
          n := i - 1;
      until Index > n;
      // Item not found: returns false + the index where to insert
    end
    else
      Index := -1 // not Sorted
  else
    Index := -1; // no fCompare()
end;

procedure TDynArray.FastAddSorted(Index: PtrInt; const Item);
begin
  Insert(Index, Item);
  fSorted := true; // Insert -> SetCount -> fSorted := false
end;

procedure TDynArray.FastDeleteSorted(Index: PtrInt);
begin
  Delete(Index);
  fSorted := true; // Delete -> SetCount -> fSorted := false
end;

function TDynArray.FastLocateOrAddSorted(const Item; wasAdded: PBoolean): integer;
var
  added: boolean;
begin
  added := not FastLocateSorted(Item, result) and
           (result >= 0);
  if added then
  begin
    Insert(result, Item);
    fSorted := true; // Insert -> SetCount -> fSorted := false
  end;
  if wasAdded <> nil then
    wasAdded^ := added;
end;

type
  // internal structure used to make QuickSort faster & with less stack usage
  {$ifdef USERECORDWITHMETHODS}
  TDynArrayQuickSort = record
  {$else}
  TDynArrayQuickSort = object
  {$endif USERECORDWITHMETHODS}
  public
    Compare: TDynArraySortCompare;
    CompareEvent: TOnDynArraySortCompare;
    Pivot: pointer;
    index: PCardinalArray;
    ElemSize: cardinal;
    p: PtrInt;
    Value: PAnsiChar;
    IP, JP: PAnsiChar;
    procedure QuickSort(L, R: PtrInt);
    procedure QuickSortIndexed(L, R: PtrInt);
    procedure QuickSortEvent(L, R: PtrInt);
    procedure QuickSortEventReverse(L, R: PtrInt);
  end;

procedure QuickSortIndexedPUtf8Char(Values: PPUtf8CharArray; Count: integer;
  var SortedIndexes: TCardinalDynArray; CaseSensitive: boolean);
var
  QS: TDynArrayQuickSort;
begin
  if CaseSensitive then
    QS.Compare := SortDynArrayPUtf8Char
  else
    QS.Compare := SortDynArrayPUtf8CharI;
  QS.Value := pointer(Values);
  QS.ElemSize := SizeOf(PUtf8Char);
  SetLength(SortedIndexes, Count);
  FillIncreasing(pointer(SortedIndexes), 0, Count);
  QS.Index := pointer(SortedIndexes);
  QS.QuickSortIndexed(0, Count - 1);
end;

procedure DynArraySortIndexed(Values: pointer; ItemSize, Count: integer;
  out Indexes: TSynTempBuffer; Compare: TDynArraySortCompare);
begin
  DynArraySortIndexed(Values, ItemSize, Count,
    pointer(Indexes.InitIncreasing(Count)), Compare);
end;

procedure DynArraySortIndexed(Values: pointer; ItemSize, Count: integer;
  Indexes: PCardinalArray; Compare: TDynArraySortCompare);
var
  QS: TDynArrayQuickSort;
begin
  QS.Compare := Compare;
  QS.Value := Values;
  QS.ElemSize := ItemSize;
  QS.Index := Indexes;
  QS.QuickSortIndexed(0, Count - 1);
end;

procedure TDynArrayQuickSort.QuickSort(L, R: PtrInt);
var
  I, J: PtrInt;
begin
  if L < R then
    repeat
      I := L;
      J := R;
      p := (L + R) shr 1;
      repeat
        Pivot := Value + PtrUInt(p) * ElemSize;
        IP := Value + PtrUInt(I) * ElemSize;
        JP := Value + PtrUInt(J) * ElemSize;
        while Compare(IP^, Pivot^) < 0 do
        begin
          inc(I);
          inc(IP, ElemSize);
        end;
        while Compare(JP^, Pivot^) > 0 do
        begin
          dec(J);
          dec(JP, ElemSize);
        end;
        if I <= J then
        begin
          if I <> J then
            Exchg(IP, JP, ElemSize);
          if p = I then
            p := J
          else if p = J then
            p := I;
          inc(I);
          dec(J);
        end;
      until I > J;
      if J - L < R - I then
      begin
        // use recursion only for smaller range
        if L < J then
          QuickSort(L, J);
        L := I;
      end
      else
      begin
        if I < R then
          QuickSort(I, R);
        R := J;
      end;
    until L >= R;
end;

procedure TDynArrayQuickSort.QuickSortEvent(L, R: PtrInt);
var
  I, J: PtrInt;
begin
  if L < R then
    repeat
      I := L;
      J := R;
      p := (L + R) shr 1;
      repeat
        Pivot := Value + PtrUInt(p) * ElemSize;
        IP := Value + PtrUInt(I) * ElemSize;
        JP := Value + PtrUInt(J) * ElemSize;
        while CompareEvent(IP^, Pivot^) < 0 do
        begin
          inc(I);
          inc(IP, ElemSize);
        end;
        while CompareEvent(JP^, Pivot^) > 0 do
        begin
          dec(J);
          dec(JP, ElemSize);
        end;
        if I <= J then
        begin
          if I <> J then
            Exchg(IP, JP, ElemSize);
          if p = I then
            p := J
          else if p = J then
            p := I;
          inc(I);
          dec(J);
        end;
      until I > J;
      if J - L < R - I then
      begin
        // use recursion only for smaller range
        if L < J then
          QuickSortEvent(L, J);
        L := I;
      end
      else
      begin
        if I < R then
          QuickSortEvent(I, R);
        R := J;
      end;
    until L >= R;
end;

procedure TDynArrayQuickSort.QuickSortEventReverse(L, R: PtrInt);
var
  I, J: PtrInt;
begin
  if L < R then
    repeat
      I := L;
      J := R;
      p := (L + R) shr 1;
      repeat
        Pivot := Value + PtrUInt(p) * ElemSize;
        IP := Value + PtrUInt(I) * ElemSize;
        JP := Value + PtrUInt(J) * ElemSize;
        while CompareEvent(IP^, Pivot^) > 0 do
        begin
          inc(I);
          inc(IP, ElemSize);
        end;
        while CompareEvent(JP^, Pivot^) < 0 do
        begin
          dec(J);
          dec(JP, ElemSize);
        end;
        if I <= J then
        begin
          if I <> J then
            Exchg(IP, JP, ElemSize);
          if p = I then
            p := J
          else if p = J then
            p := I;
          inc(I);
          dec(J);
        end;
      until I > J;
      if J - L < R - I then
      begin
        // use recursion only for smaller range
        if L < J then
          QuickSortEventReverse(L, J);
        L := I;
      end
      else
      begin
        if I < R then
          QuickSortEventReverse(I, R);
        R := J;
      end;
    until L >= R;
end;

procedure TDynArrayQuickSort.QuickSortIndexed(L, R: PtrInt);
var
  I, J: PtrInt;
  tmp: integer;
begin
  if L < R then
    repeat
      I := L;
      J := R;
      p := (L + R) shr 1;
      repeat
        Pivot := Value + index[p] * ElemSize;
        while Compare(Value[index[I] * ElemSize], Pivot^) < 0 do
          inc(I);
        while Compare(Value[index[J] * ElemSize], Pivot^) > 0 do
          dec(J);
        if I <= J then
        begin
          if I <> J then
          begin
            tmp := index[I];
            index[I] := index[J];
            index[J] := tmp;
          end;
          if p = I then
            p := J
          else if p = J then
            p := I;
          inc(I);
          dec(J);
        end;
      until I > J;
      if J - L < R - I then
      begin
        // use recursion only for smaller range
        if L < J then
          QuickSortIndexed(L, J);
        L := I;
      end
      else
      begin
        if I < R then
          QuickSortIndexed(I, R);
        R := J;
      end;
    until L >= R;
end;

procedure TDynArray.Sort(aCompare: TDynArraySortCompare);
begin
  SortRange(0, Count - 1, aCompare);
  fSorted := true;
end;

procedure QuickSortPtr(L, R: PtrInt; Compare: TDynArraySortCompare; V: PPointerArray);
var
  I, J, P: PtrInt;
  tmp: pointer;
begin
  if L < R then
    repeat
      I := L;
      J := R;
      P := (L + R) shr 1;
      repeat
        while Compare(V[I], V[P]) < 0 do
          inc(I);
        while Compare(V[J], V[P]) > 0 do
          dec(J);
        if I <= J then
        begin
          tmp := V[I];
          V[I] := V[J];
          V[J] := tmp;
          if P = I then
            P := J
          else if P = J then
            P := I;
          inc(I);
          dec(J);
        end;
      until I > J;
      if J - L < R - I then
      begin
        // use recursion only for smaller range
        if L < J then
          QuickSortPtr(L, J, Compare, V);
        L := I;
      end
      else
      begin
        if I < R then
          QuickSortPtr(I, R, Compare, V);
        R := J;
      end;
    until L >= R;
end;

procedure TDynArray.SortRange(aStart, aStop: integer;
  aCompare: TDynArraySortCompare);
var
  QuickSort: TDynArrayQuickSort;
begin
  if aStop <= aStart then
    exit; // nothing to sort
  if Assigned(aCompare) then
    QuickSort.Compare := aCompare
  else
    QuickSort.Compare := @fCompare;
  if Assigned(QuickSort.Compare) and
     (fValue <> nil) and
     (fValue^ <> nil) then
  begin
    if fInfo.ArrayRtti <> nil then
      case fInfo.ArrayRtti.Parser of
        // call optimized sorting functions for most simple types
        ptWord:
          if @QuickSort.Compare = @SortDynArrayWord then
          begin
            QuickSortWord(fValue^, aStart, aStop);
            exit;
          end;
        ptInteger:
          if @QuickSort.Compare = @SortDynArrayInteger then
          begin
            QuickSortInteger(fValue^, aStart, aStop);
            exit;
          end;
        ptInt64:
          if @QuickSort.Compare = @SortDynArrayInt64 then
          begin
            QuickSortInt64(fValue^, aStart, aStop);
            exit;
          end;
        ptQWord:
          if @QuickSort.Compare = @SortDynArrayQWord then
          begin
            QuickSortQWord(fValue^, aStart, aStop);
            exit;
          end;
        ptDouble:
          if @QuickSort.Compare = @SortDynArrayDouble then
          begin
            QuickSortDouble(fValue^, aStart, aStop);
            exit;
          end;
      end;
    if fInfo.Cache.ItemSize = SizeOf(pointer) then
      // dedicated function for pointers - e.g. strings or T*ObjArray
      QuickSortPtr(aStart, aStop, QuickSort.Compare, fValue^)
    else
    begin
      // generic process for any kind of array items
      QuickSort.Value := fValue^;
      QuickSort.ElemSize := fInfo.Cache.ItemSize;
      QuickSort.QuickSort(aStart, aStop);
    end;
  end;
end;

function TDynArray.IsSorted(aCompare: TDynArraySortCompare): boolean;
var
  n: integer;
  siz: PtrInt;
  p, prev: PAnsiChar;
begin
  result := false;
  n := GetCount;
  if not Assigned(aCompare) then
    aCompare := fCompare;
  if (not Assigned(aCompare)) or
     (n = 0) then
    exit; // nothing to sort
  siz := fInfo.Cache.ItemSize;
  p := fValue^;
  prev := p;
  inc(p, siz);
  dec(n);
  if n <> 0 then
    repeat
      if aCompare(p^, prev^) < 0 then
        exit;
      prev := p;
      inc(p, siz);
      dec(n);
    until n = 0;
  result := true; // all items are sorted
end;

procedure TDynArray.EnsureSorted(aCompare: TDynArraySortCompare);
begin
  if IsSorted(aCompare) then
    fSorted := true
  else
    Sort(aCompare);
end;

procedure TDynArray.Sort(const aCompare: TOnDynArraySortCompare; aReverse: boolean);
var
  QuickSort: TDynArrayQuickSort;
  R: PtrInt;
begin
  if (not Assigned(aCompare)) or
     (fValue = nil) or
     (fValue^ = nil) then
    exit; // nothing to sort
  QuickSort.CompareEvent := aCompare;
  QuickSort.Value := fValue^;
  QuickSort.ElemSize := fInfo.Cache.ItemSize;
  R := Count - 1;
  if aReverse then
    QuickSort.QuickSortEventReverse(0, R)
  else
    QuickSort.QuickSortEvent(0, R);
end;

procedure TDynArray.CreateOrderedIndex(var aIndex: TIntegerDynArray;
  aCompare: TDynArraySortCompare);
var
  QuickSort: TDynArrayQuickSort;
  n: integer;
begin
  if Assigned(aCompare) then
    QuickSort.Compare := aCompare
  else
    QuickSort.Compare := @fCompare;
  if Assigned(QuickSort.Compare) and
     (fValue <> nil) and
     (fValue^ <> nil) then
  begin
    n := GetCount;
    if length(aIndex) < n then
    begin
      SetLength(aIndex, n);
      FillIncreasing(pointer(aIndex), 0, n);
    end;
    QuickSort.Value := fValue^;
    QuickSort.ElemSize := fInfo.Cache.ItemSize;
    QuickSort.Index := pointer(aIndex);
    QuickSort.QuickSortIndexed(0, n - 1);
  end;
end;

procedure TDynArray.CreateOrderedIndex(out aIndex: TSynTempBuffer;
  aCompare: TDynArraySortCompare);
var
  QuickSort: TDynArrayQuickSort;
  n: integer;
begin
  if Assigned(aCompare) then
    QuickSort.Compare := aCompare
  else
    QuickSort.Compare := @fCompare;
  if Assigned(QuickSort.Compare) and
     (fValue <> nil) and
     (fValue^ <> nil) then
  begin
    n := GetCount;
    QuickSort.Value := fValue^;
    QuickSort.ElemSize := fInfo.Cache.ItemSize;
    QuickSort.Index := PCardinalArray(aIndex.InitIncreasing(n));
    QuickSort.QuickSortIndexed(0, n - 1);
  end
  else
    aIndex.buf := nil; // avoid GPF in aIndex.Done
end;

procedure TDynArray.CreateOrderedIndexAfterAdd(var aIndex: TIntegerDynArray;
  aCompare: TDynArraySortCompare);
var
  ndx: integer;
begin
  ndx := GetCount - 1;
  if ndx < 0 then
    exit;
  if aIndex <> nil then
  begin
    // whole FillIncreasing(aIndex[]) for first time
    if ndx >= length(aIndex) then
      SetLength(aIndex, NextGrow(ndx)); // grow aIndex[] if needed
    aIndex[ndx] := ndx;
  end;
  CreateOrderedIndex(aIndex, aCompare);
end;

procedure TDynArray.InitFrom(aAnother: PDynArray; var aValue);
begin
  self := aAnother^; // raw RTTI fields copy
  fValue := @aValue; // points to the new value
  fCountP := nil;
end;

procedure TDynArray.AddDynArray(aSource: PDynArray;
  aStartIndex: integer; aCount: integer);
var
  SourceCount: integer;
begin
  if (aSource <> nil) and
     (aSource^.fValue <> nil) and
     (fInfo.Cache.ItemInfoRaw = aSource^.Info.Cache.ItemInfoRaw) then
  begin
    // check supplied aCount paramter with (external) Source.Count
    SourceCount := aSource^.Count;
    if (aCount < 0) or
       (aCount > SourceCount) then
      aCount := SourceCount;
    // actually add the items
    AddArray(aSource.fValue^, aStartIndex, aCount);
  end;
end;

function TDynArray.Equals(B: PDynArray; IgnoreCompare, CaseSensitive: boolean): boolean;
begin
  result := Compares(B, IgnoreCompare, CaseSensitive) = 0;
end;

function TDynArray.Compares(B: PDynArray; IgnoreCompare, CaseSensitive: boolean): integer;
var
  i, n: integer;
  s: PtrUInt;
  P1, P2: PAnsiChar;
begin
  n := GetCount;
  result := n - B.Count;
  if (result = 0) and
     (n <> 0) then
    if fInfo.Cache.ItemInfoRaw <> B.Info.Cache.ItemInfoRaw then
      result := ComparePointer(fValue^, B.fValue^)
    else if Assigned(fCompare) and
       not ignorecompare then
    begin
      // use specified fCompare() function
      P1 := fValue^;
      P2 := B.fValue^;
      s := fInfo.Cache.ItemSize;
      for i := 1 to n do
      begin
        result := fCompare(P1^, P2^);
        if result <> 0 then
          exit;
        inc(P1, s);
        inc(P2, s);
      end;
    end
    else if rcfObjArray in fInfo.Flags then
      // T*ObjArray comparison of published properties
      result := ObjectCompare(fValue^, B.fValue^, n, not CaseSensitive)
    else if not(rcfArrayItemManaged in fInfo.Flags) then
      // binary comparison with length (always CaseSensitive)
      result := MemCmp(fValue^, B.fValue^, n * fInfo.Cache.ItemSize)
    else
      result := BinaryCompare(fValue^, B.fValue^, fInfo.Cache.ItemInfoRaw, n,
        not CaseSensitive);
end;

procedure TDynArray.Copy(Source: PDynArray; ObjArrayByRef: boolean);
begin
  if (fValue = nil) or
     (fInfo.Cache.ItemInfoRaw <> Source.Info.Cache.ItemInfoRaw) then
    exit;
  if not ObjArrayByRef and
     (rcfObjArray in fInfo.Flags) then
    LoadFromJson(pointer(Source.SaveToJson))
  else
  begin
    DynArrayCopy(fValue, Source.fValue, fInfo.Info, Source.fCountP);
    if fCountP <> nil then
      fCountP^ := GetCapacity;
  end;
end;

procedure TDynArray.CopyFrom(const Source; MaxItem: integer; ObjArrayByRef: boolean);
var
  SourceDynArray: TDynArray;
begin
  SourceDynArray.InitRtti(fInfo, pointer(@Source)^);
  SourceDynArray.fCountP := @MaxItem; // would set Count=0 at Init()
  Copy(@SourceDynArray, ObjArrayByRef);
end;

procedure TDynArray.CopyTo(out Dest; ObjArrayByRef: boolean);
var
  DestDynArray: TDynArray;
begin
  DestDynArray.InitRtti(fInfo, Dest);
  DestDynArray.Copy(@self, ObjArrayByRef);
end;

function IndexFind(P, V: PAnsiChar; cmp: TRttiCompare; rtti: PRttiInfo; n: integer): PtrInt;
var
  comp: integer;
begin
  result := 0;
  repeat
    inc(P, cmp(P, V, rtti, comp));
    if comp = 0 then
      exit;
    inc(result);
    dec(n);
  until n = 0;
  result := -1;
end;

function TDynArray.IndexOf(const Item; CaseInSensitive: boolean): PtrInt;
var
  rtti: PRttiInfo;
  cmp: TRttiCompare;
  n: PtrInt;
label
  bin;
begin
  n := GetCount;
  if (n <> 0) and
     (@Item <> nil) then
    if not(rcfArrayItemManaged in fInfo.Flags) then
bin:  result := AnyScanIndex(fValue^, @Item, n, fInfo.Cache.ItemSize)
    else
    begin
      rtti := fInfo.Cache.ItemInfoManaged;
      if rtti = nil then
        goto bin; // unmanaged items
      cmp := RTTI_COMPARE[CaseInSensitive, rtti.Kind];
      if Assigned(cmp) then
        result := IndexFind(fValue^, @Item, cmp, rtti, n)
      else
        goto bin;
    end
  else
    result := -1;
end;

procedure TDynArray.UseExternalCount(aCountPointer: PInteger);
begin
  fCountP := aCountPointer;
end;

procedure TDynArray.Void;
begin
  fValue := nil;
end;

function TDynArray.IsVoid: boolean;
begin
  result := fValue = nil;
end;

procedure TDynArray.InternalSetLength(OldLength, NewLength: PtrUInt);
var
  p: PDynArrayRec;
  NeededSize, minLength: PtrUInt;
begin
  // this method is faster than default System.DynArraySetLength() function
  p := fValue^;
  // check that new array length is not just a finalize in disguise
  if NewLength = 0 then
  begin
    if p <> nil then
    begin
      // FastDynArrayClear() with ObjArray support
      dec(p);
      if (p^.refCnt > 0) and
         DACntDecFree(p^.refCnt) then
      begin
        if (OldLength <> 0) and
           not fNoFinalize then
          if rcfArrayItemManaged in fInfo.Flags then
            FastFinalizeArray(fValue^, fInfo.Cache.ItemInfoManaged, OldLength)
          else if rcfObjArray in fInfo.Flags then
            RawObjectsClear(fValue^, OldLength);
        FreeMem(p);
      end;
      fValue^ := nil;
    end;
    exit;
  end;
  // calculate the needed size of the resulting memory structure on heap
  NeededSize := NewLength * PtrUInt(fInfo.Cache.ItemSize) + SizeOf(TDynArrayRec);
  {$ifdef CPU32}
  if NeededSize > 1 shl 30 then
    // in practice, consider that max workable memory block is 1 GB on 32-bit
    EDynArray.RaiseUtf8('TDynArray.InternalSetLength(%,%) size concern',
      [fInfo.Name, NewLength]);
  {$endif CPU32}
  // if not shared (refCnt=1), resize; if shared, create copy (not thread safe)
  if p = nil then
  begin
    p := AllocMem(NeededSize); // RTL/OS will return zeroed memory
    OldLength := NewLength;    // no FillcharFast() below
  end
  else
  begin
    dec(p); // p^ = start of heap object
    if p^.refCnt = 1 then
    begin
      // we own the dynamic array instance -> direct reallocation
      if (NewLength < OldLength) and
         not fNoFinalize then
        // reduce array in-place
        if rcfArrayItemManaged in fInfo.Flags then // in trailing items
          FastFinalizeArray(pointer(PAnsiChar(p) + NeededSize),
            fInfo.Cache.ItemInfoManaged, OldLength - NewLength)
        else if rcfObjArray in fInfo.Flags then // FreeAndNil() of resized objects
          RawObjectsClear(pointer(PAnsiChar(p) + NeededSize), OldLength - NewLength);
      ReallocMem(p, NeededSize);
    end
    else
    begin
      // dynamic array already referenced elsewhere -> create our own copy
      minLength := OldLength;
      if minLength > NewLength then
        minLength := NewLength;
      if fInfo.Cache.ItemInfoManaged = nil then // unmanaged items
      begin
        GetMem(p, NeededSize);
        MoveFast(fValue^^, PByteArray(p)[SizeOf(TDynArrayRec)],
          minLength * PtrUInt(fInfo.Cache.ItemSize));
      end
      else
      begin
        p := AllocMem(NeededSize);
        OldLength := NewLength;    // no FillcharFast() below
        CopySeveral(@PByteArray(p)[SizeOf(TDynArrayRec)], fValue^,
          minLength, fInfo.Cache.ItemInfoManaged, fInfo.Cache.ItemSize);
      end;
      // for thread safety, adjust the refcount after data copy
      if fNoFinalize then
        FastDynArrayClear(fValue, nil)
      else // note: rcfObjArray should never appear with refcnt>1
        FastDynArrayClear(fValue, fInfo.Cache.ItemInfoManaged);
    end;
  end;
  // set refCnt=1 and new length to the heap header
  with p^ do
  begin
    refCnt := 1;
    length := NewLength;
  end;
  inc(p); // start of dynamic aray items
  fValue^ := p;
  // reset new allocated items content to zero
  if NewLength > OldLength then
  begin
    minLength := fInfo.Cache.ItemSize;
    OldLength := OldLength * minLength;
    FillCharFast(PAnsiChar(p)[OldLength], NewLength * minLength - OldLength, 0);
  end;
end;

procedure TDynArray.SetCount(aCount: PtrInt);
const
  MINIMUM_SIZE = 64;
var
  oldlen, extcount, arrayptr, capa, delta: PtrInt;
begin
  arrayptr := PtrInt(fValue);
  extcount := PtrInt(fCountP);
  fSorted := false;
  if arrayptr = 0 then
    exit; // avoid GPF if void
  arrayptr := PPtrInt(arrayptr)^;
  if extcount <> 0 then
  begin
    // fCountP^ as external capacity
    oldlen := PInteger(extcount)^;
    delta := aCount - oldlen;
    if delta = 0 then
      exit;
    PInteger(extcount)^ := aCount; // store new length
    if arrayptr <> 0 then
    begin
      // non void array: check new count against existing capacity
      capa := PDALen(arrayptr - _DALEN)^ + _DAOFF;
      if delta > 0 then
      begin
        // size-up - Add() - is handled branchless
        if capa >= aCount then
          exit; // no need to grow
        capa := NextGrow(capa);
        if capa > aCount then
          aCount := capa; // grow by chunks
      end
      else
      // size-down - Delete()
      if (aCount > 0) and
         ((capa <= MINIMUM_SIZE) or
          (capa - aCount < capa shr 3)) then
        // reallocate memory only if worth it (for faster Delete)
        exit;
    end
    else
    begin
      // void array
      if (delta > 0) and
         (aCount < MINIMUM_SIZE) then
        // reserve some minimal (64) items for Add()
        aCount := MINIMUM_SIZE;
    end;
  end
  else
    // no external capacity: use length()
    if arrayptr = 0 then
      oldlen := arrayptr
    else
    begin
      oldlen := PDALen(arrayptr - _DALEN)^ + _DAOFF;
      if oldlen = aCount then
        exit; // InternalSetLength(samecount) would have made a private copy
    end;
  // no external Count, array size-down or array up-grow -> realloc
  InternalSetLength(oldlen, aCount);
end;

procedure TDynArray.SetCapacity(aCapacity: PtrInt);
var
  oldlen, capa: PtrInt;
begin
  if fValue = nil then
    exit;
  capa := GetCapacity;
  if fCountP <> nil then
  begin
    oldlen := fCountP^;
    if oldlen > aCapacity then
      fCountP^ := aCapacity;
  end
  else
    oldlen := capa;
  if capa <> aCapacity then
    InternalSetLength(oldlen, aCapacity);
end;

procedure TDynArray.SetCompare(const aCompare: TDynArraySortCompare);
begin
  if @aCompare <> @fCompare then
  begin
    @fCompare := @aCompare;
    fSorted := false;
  end;
end;

procedure TDynArray.Slice(var Dest; Limit, Offset: cardinal);
var
  n: cardinal;
  dst: TDynArray;
begin
  if fValue = nil then
    exit; // avoid GPF if void
  n := GetCount;
  if Offset >= n then
    Limit := 0
  else
  begin
    dec(n, Offset);
    if Limit > n  then
      Limit := n;
  end;
  dst.InitRtti(fInfo, Dest);
  dst.SetCapacity(Limit);
  CopySeveral(pointer(Dest),
    @(PByteArray(fValue^)[Offset * cardinal(fInfo.Cache.ItemSize)]),
    Limit, fInfo.Cache.ItemInfoManaged, fInfo.Cache.ItemSize);
end;

procedure TDynArray.SliceAsDynArray(Dest: PPointer; Offset, Limit: integer);
var
  p: PDynArrayRec;
  n: integer;
begin
  if dest^ <> nil then // reset Dest variable slot
    FastDynArrayClear(dest, fInfo.Cache.ItemInfoManaged);
  n := GetCount;
  if Offset < 0 then
  begin
    // ! SliceAsDynArray(DA, -10);  // last Count-10..Count-1 items
    inc(Offset, n);
    if Offset < 0 then
      Offset := 0;
  end;
  if Offset >= n then // also handles n = 0
    exit;
  if (Offset = 0) and
     ((Limit = 0) or
      (Limit >= n)) then
  begin
    // we can return the current dynamic array with proper Copy-On-Write
    p := fValue^;
    if p = nil then
      exit;
    dec(p);
    inc(p^.refCnt); // COW reuse of the existing dynamic array instance
    p^.Length := n; // no memory realloc/copy, just force Capacity=Length=Count
    inc(p);
    dest^ := p;     // assign to Dest variable
  end
  else
  begin
    // ! SliceAsDynArray(DA, 0, 10);  // first 0..9 items
    // ! SliceAsDynArray(DA, 10, 20); // items 10..29 - truncated if Count < 30
    if Limit = 0 then
      // ! SliceAsDynArray(DA);       // all items
      // ! SliceAsDynArray(DA, 10);   // all items excluding the first 0..9
      Limit := n;
    Slice(Dest^, Limit, Offset);
  end;
end;

function TDynArray.AddArray(const DynArrayVar; aStartIndex, aCount: integer): integer;
var
  c, s: PtrInt;
  n: integer;
  PS, PD: pointer;
begin
  result := 0;
  if fValue = nil then
    exit; // avoid GPF if void
  c := PtrInt(DynArrayVar);
  if c <> 0 then
    c := PDALen(c - _DALEN)^ + _DAOFF;
  if aStartIndex >= c then
    exit; // nothing to copy
  if (aCount < 0) or
     (cardinal(aStartIndex + aCount) > cardinal(c)) then
    aCount := c - aStartIndex;
  if aCount <= 0 then
    exit;
  result := aCount;
  n := GetCount;
  SetCount(n + aCount);
  s := fInfo.Cache.ItemSize;
  PS := PAnsiChar(DynArrayVar) + aStartIndex * s;
  PD := PAnsiChar(fValue^) + n * s;
  CopySeveral(PD, PS, aCount, fInfo.Cache.ItemInfoManaged, s);
end;

function TDynArray.ItemLoadMem(Source, SourceMax: PAnsiChar): RawByteString;
begin
  if (Source <> nil) and
     (fInfo.Cache.ItemInfoManaged = nil) then // unmanaged items
    FastSetRawByteString(result, Source, fInfo.Cache.ItemSize)
  else
  begin
    FastSetRawByteString(result, nil, fInfo.Cache.ItemSize);
    FillCharFast(pointer(result)^, fInfo.Cache.ItemSize, 0);
    ItemLoad(Source, pointer(result), SourceMax);
  end;
end;

procedure TDynArray.ItemLoad(Source, SourceMax: PAnsiChar; Item: pointer);
begin
  if Source <> nil then // avoid GPF
    if fInfo.Cache.ItemInfoManaged = nil then
    begin
      if {$ifndef PUREMORMOT2} (SourceMax = nil) or {$endif}
         (Source + fInfo.Cache.ItemSize <= SourceMax) then
        MoveFast(Source^, Item^, fInfo.Cache.ItemSize);
    end
    else
      BinaryLoad(Item, Source, fInfo.Cache.ItemInfoManaged, nil, SourceMax, rkAllTypes);
end;

procedure TDynArray.ItemLoadMemClear(var ItemTemp: RawByteString);
begin
  ItemClear(pointer(ItemTemp));
  ItemTemp := '';
end;

function TDynArray.ItemSave(Item: pointer): RawByteString;
begin
  if fInfo.Cache.ItemInfoManaged = nil then
    FastSetRawByteString(result, Item, fInfo.Cache.ItemSize)
  else
    result := BinarySave(Item, fInfo.Cache.ItemInfoManaged, rkAllTypes);
end;

function TDynArray.ItemLoadFind(Source, SourceMax: PAnsiChar): integer;
var
  tmp: array[0..2047] of byte;
  data: pointer;
begin
  result := -1;
  if (Source = nil) or
     (fInfo.Cache.ItemSize > SizeOf(tmp)) then
    exit;
  if fInfo.Cache.ItemInfoManaged = nil then // nil for unmanaged items
    data := Source
  else
  begin
    FillCharFast(tmp, fInfo.Cache.ItemSize, 0);
    BinaryLoad(@tmp, Source, fInfo.Cache.ItemInfoManaged, nil, SourceMax, rkAllTypes);
    if Source = nil then
      exit;
    data := @tmp;
  end;
  try
    if Assigned(fCompare) then
      result := Find(data^) // use specific comparer
    else
      result := IndexOf(data^); // use RTTI
  finally
    if data = @tmp then
      fInfo.ArrayRtti.ValueFinalize(data);
  end;
end;


{ ************ TDynArrayHasher }

function HashAnsiString(Item: PAnsiChar; Hasher: THasher): cardinal;
var
  l: PtrInt;
begin
  Item := PPointer(Item)^; // passed as non-nil PAnsiString reference
  if Item <> nil then
  begin
    l := PStrLen(Item - _STRLEN)^;
    if l > 256 then // no need to hash too big a content
    begin
      Item := @Item[l - 256]; // hash ending of string (more likely to vary)
      l := 256;
    end;
    result := Hasher(HashSeed, Item, l);
  end
  else
    result := 0;
end;

function HashAnsiStringI(Item: PUtf8Char; Hasher: THasher): cardinal;
var
  tmp: array[byte] of AnsiChar; // avoid any slow heap allocation
begin
  Item := PPointer(Item)^; // passed as non-nil PAnsiString reference
  if Item <> nil then
    result := Hasher(HashSeed, tmp{%H-},
      UpperCopy255Buf(tmp{%H-}, Item, PStrLen(Item - _STRLEN)^) - {%H-}tmp)
  else
    result := 0;
end;

function HashSynUnicode(Item: PSynUnicode; Hasher: THasher): cardinal;
var
  l: PtrInt;
begin
  if PtrUInt(Item^) <> 0 then
  begin
    l := Length(Item^) * 2;
    if l > 255 then // no need to hash too big a content
    begin
      Item := @PAnsiChar(Item)[l - 256]; // hash ending of string
      l := 256;
    end;
    result := Hasher(HashSeed, pointer(Item^), l)
  end
  else
    result := 0;
end;

function HashSynUnicodeI(Item: PSynUnicode; Hasher: THasher): cardinal;
var
  tmp: array[byte] of AnsiChar; // avoid slow heap allocation
begin
  if PtrUInt(Item^) <> 0 then
    result := Hasher(HashSeed, tmp{%H-}, UpperCopy255W(tmp{%H-}, Item^) - {%H-}tmp)
  else
    result := 0;
end;

function HashWideString(Item: PWideString; Hasher: THasher): cardinal;
begin
  // WideString internal size is in bytes, not WideChar
  if PtrUInt(Item^) <> 0 then
    result := Hasher(HashSeed, pointer(Item^), Length(Item^) * 2)
  else
    result := 0;
end;

function HashWideStringI(Item: PWideString; Hasher: THasher): cardinal;
var
  tmp: array[byte] of AnsiChar; // avoid slow heap allocation
begin
  if PtrUInt(Item^) <> 0 then
    result := Hasher(HashSeed, tmp{%H-},
      UpperCopy255W(tmp{%H-}, pointer(Item^), Length(Item^)) - {%H-}tmp)
  else
    result := 0;
end;

function HashPUtf8Char(Item: PAnsiChar; Hasher: THasher): cardinal;
begin
  Item := PPointer(Item)^; // passed as non-nil PPAnsiChar reference
  if Item <> nil then
    result := Hasher(HashSeed, Item, StrLen(Item))
  else
    result := 0;
end;

function HashPUtf8CharI(Item: PUtf8Char; Hasher: THasher): cardinal;
var
  tmp: array[byte] of AnsiChar; // avoid slow heap allocation
begin
  Item := PPointer(Item)^; // passed as non-nil PPUtf8Char reference
  if Item <> nil then
    result := Hasher(HashSeed, tmp{%H-},
      UpperCopy255Buf(tmp{%H-}, Item, StrLen(Item)) - {%H-}tmp)
  else
    result := 0;
end;

function HashByte(Item: pointer; Hasher: THasher): cardinal;
begin
  result := Hasher(HashSeed, Item, SizeOf(byte));
end;

function HashWord(Item: pointer; Hasher: THasher): cardinal;
begin
  result := Hasher(HashSeed, Item, SizeOf(word));
end;

function HashInteger(Item: pointer; Hasher: THasher): cardinal;
begin
  result := Hasher(HashSeed, Item, SizeOf(integer));
end;

function HashInt64(Item: pointer; Hasher: THasher): cardinal;
begin
  result := Hasher(HashSeed, Item, SizeOf(Int64));
end;

function HashExtended(Item: pointer; Hasher: THasher): cardinal;
begin
  result := Hasher(HashSeed, Item, SizeOf(TSynExtended));
end;

function Hash128(Item: pointer; Hasher: THasher): cardinal;
begin
  result := Hasher(HashSeed, Item, SizeOf(THash128));
end;

function Hash256(Item: pointer; Hasher: THasher): cardinal;
begin
  result := Hasher(HashSeed, Item, SizeOf(THash256));
end;

function Hash512(Item: pointer; Hasher: THasher): cardinal;
begin
  result := Hasher(HashSeed, Item, SizeOf(THash512));
end;

function VariantHash(const value: variant; CaseInsensitive: boolean;
  Hasher: THasher): cardinal;
var
  tmp: array[byte] of AnsiChar; // avoid heap allocation
  vt: cardinal;
  S: TStream;
  W: TTextWriter;
  P: pointer;
  len: integer;
begin
  if not Assigned(Hasher) then
    Hasher := DefaultHasher;
  with TVarData(value) do
  begin
    vt := VType;
    P := @VByte; // same address than VWord/VInteger/VInt64...
    case vt of
      varNull, varEmpty:
        len := 0; // good enough for void values
      varShortInt, varByte:
        len := 1;
      varSmallint, varWord, varboolean:
        len := 2;
      varLongWord, varInteger, varSingle:
        len := 4;
      varInt64, varDouble, varDate, varCurrency, varWord64:
        len := 8;
      varString:
        begin
          len := length(RawUtf8(VAny));
          P := VAny;
        end;
      varOleStr:
        begin
          len := length(WideString(VAny));
          P := VAny;
        end;
      {$ifdef HASVARUSTRING}
      varUString:
        begin
          len := length(UnicodeString(VAny));
          P := VAny;
        end;
      {$endif HASVARUSTRING}
      else
      begin
        S := TFakeWriterStream.Create;
        W := DefaultJsonWriter.Create(S, @tmp, SizeOf(tmp));
        try
          W.AddVariant(value, twJsonEscape);
          len := W.WrittenBytes;
          if len > 255 then
            len := 255;
          P := @tmp; // big JSON won't be hasheable anyway -> use only buffer
        finally
          W.Free;
          S.Free;
        end;
      end;
    end;
    if CaseInsensitive and
       (P <> @VByte) then
    begin
      len := UpperCopy255Buf(tmp, P, len) - tmp;
      P := @tmp;
    end;
    result := Hasher(vt xor HashSeed, P, len)
  end;
end;

function HashVariant(Item: PVariant; Hasher: THasher): cardinal;
begin
  result := VariantHash(Item^, false, Hasher);
end;

function HashVariantI(Item: PVariant; Hasher: THasher): cardinal;
begin
  result := VariantHash(Item^, true, Hasher);
end;

const
  // helper arrays to get the standard hash functions
  PT_HASH: array[{caseinsensitive=}boolean, TRttiParserType] of pointer = (
   // case sensitive hash functions:
   (nil,                     //  ptNone
    nil,                     //  ptArray
    @HashByte,               //  ptBoolean
    @HashByte,               //  ptByte
    @HashInteger,            //  ptCardinal
    @HashInt64,              //  ptCurrency
    @HashInt64,              //  ptDouble
    @HashExtended,           //  ptExtended
    @HashInt64,              //  ptInt64
    @HashInteger,            //  ptInteger
    @HashInt64,              //  ptQWord
    @HashAnsiString,         //  ptRawByteString
    @HashAnsiString,         //  ptRawJson
    @HashAnsiString,         //  ptRawUtf8
    nil,                     //  ptRecord
    @HashInteger,            //  ptSingle
    {$ifdef UNICODE} @HashSynUnicode {$else} @HashAnsiString {$endif}, //  ptString
    @HashSynUnicode,         //  ptSynUnicode
    @HashInt64,              //  ptDateTime
    @HashInt64,              //  ptDateTimeMS
    @Hash128,                //  ptGuid
    @Hash128,                //  ptHash128
    @Hash256,                //  ptHash256
    @Hash512,                //  ptHash512
    @HashInt64,              //  ptOrm
    @HashInt64,              //  ptTimeLog
    @HashSynUnicode,         //  ptUnicodeString
    @HashInt64,              //  ptUnixTime
    @HashInt64,              //  ptUnixMSTime
    @HashVariant,            //  ptVariant
    @HashWideString,         //  ptWideString
    @HashAnsiString,         //  ptWinAnsi
    @HashWord,               //  ptWord
    nil,                     //  ptEnumeration
    nil,                     //  ptSet
    {$ifdef CPU32} @HashInteger {$else} @HashInt64 {$endif}, // ptClass
    nil,                     //  ptDynArray
    {$ifdef CPU32} @HashInteger {$else} @HashInt64 {$endif}, // ptInterface
    @HashPUtf8Char,          //  ptPUtf8Char
    nil),                    //  ptCustom
   // case insensitive hash functions:
   (nil,                     //  ptNone
    nil,                     //  ptArray
    @HashByte,               //  ptBoolean
    @HashByte,               //  ptByte
    @HashInteger,            //  ptCardinal
    @HashInt64,              //  ptCurrency
    @HashInt64,              //  ptDouble
    @HashExtended,           //  ptExtended
    @HashInt64,              //  ptInt64
    @HashInteger,            //  ptInteger
    @HashInt64,              //  ptQWord
    @HashAnsiString,         //  ptRawByteString
    @HashAnsiStringI,        //  ptRawJson
    @HashAnsiStringI,        //  ptRawUtf8
    nil,                     //  ptRecord
    @HashInteger,            //  ptSingle
    {$ifdef UNICODE} @HashSynUnicodeI {$else} @HashAnsiStringI {$endif}, //  ptString
    @HashSynUnicodeI,        //  ptSynUnicode
    @HashInt64,              //  ptDateTime
    @HashInt64,              //  ptDateTimeMS
    @Hash128,                //  ptGuid
    @Hash128,                //  ptHash128
    @Hash256,                //  ptHash256
    @Hash512,                //  ptHash512
    @HashInt64,              //  ptOrm
    @HashInt64,              //  ptTimeLog
    @HashSynUnicodeI,        //  ptUnicodeString
    @HashInt64,              //  ptUnixTime
    @HashInt64,              //  ptUnixMSTime
    @HashVariantI,           //  ptVariant
    @HashWideStringI,        //  ptWideString
    @HashAnsiStringI,        //  ptWinAnsi
    @HashWord,               //  ptWord
    nil,                     //  ptEnumeration
    nil,                     //  ptSet
    {$ifdef CPU32} @HashInteger {$else} @HashInt64 {$endif}, // ptClass
    nil,                     //  ptDynArray
    {$ifdef CPU32} @HashInteger {$else} @HashInt64 {$endif}, // ptInterface
    @HashPUtf8CharI,         //  ptPUtf8Char
    nil));                   //  ptCustom

function DynArrayHashOne(Kind: TRttiParserType;
  CaseInsensitive: boolean): TDynArrayHashOne;
begin
  result := PT_HASH[CaseInsensitive, Kind];
end;

procedure TDynArrayHasher.Init(aDynArray: PDynArray; aHashItem: TDynArrayHashOne;
  const aEventHash: TOnDynArrayHashOne; aHasher: THasher;
  aCompare: TDynArraySortCompare; const aEventCompare: TOnDynArraySortCompare;
  aCaseInsensitive: boolean);
begin
  fDynArray := aDynArray;
  if not (Assigned(aHashItem) or
          Assigned(aEventHash)) then
    aHashItem := PT_HASH[aCaseInsensitive, aDynArray^.Info.ArrayFirstFieldSort];
  fHashItem := aHashItem;
  fEventHash := aEventHash;
  if not (Assigned(aCompare) or
          Assigned(aEventCompare)) then
    aCompare := PT_SORT[aCaseInsensitive, aDynArray^.Info.ArrayFirstFieldSort];
  fCompare := aCompare;
  fEventCompare := aEventCompare;
  HashTableInit(aHasher);
end;

procedure TDynArrayHasher.InitSpecific(aDynArray: PDynArray;
  aKind: TRttiParserType; aCaseInsensitive: boolean; aHasher: THasher);
begin
  fDynArray := aDynArray;
  if aKind in [ptNone, ptEnumeration, ptSet] then
    aKind := fDynArray^.Info.ArrayFirstFieldSort; // use RTTI if not enough
  fHashItem := PT_HASH[aCaseInsensitive, aKind];
  fEventHash := nil;
  fCompare := PT_SORT[aCaseInsensitive, aKind];
  fEventCompare := nil;
  HashTableInit(aHasher);
end;

procedure TDynArrayHasher.HashTableInit(aHasher: THasher);
begin
  if not Assigned(aHasher) then
    aHasher := DefaultHasher;
  fHasher := aHasher;
  fHashTableStore := nil;
  if (Assigned(fHashItem) or
      Assigned(fEventHash)) and
     (Assigned(fCompare) or
      Assigned(fEventCompare)) then
  begin
    // same logic than ReHash(true) with no data - default to 256 buckets
    fHashTableSize := 256;
    {$ifdef DYNARRAYHASH_16BIT}
    SetLength(fHashTableStore, 129);
    fState := [hasHasher, hash16bit];
    {$else}
    SetLength(fHashTableStore, 257);
    byte(State) := 1 shl ord(hasHasher)
    {$endif DYNARRAYHASH_16BIT}
  end
  else
    byte(fState) := 0;
  {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
  CountCollisions := 0;
  CountCollisionsCurrent := 0;
  {$endif DYNARRAYHASHCOLLISIONCOUNT}
end;

procedure TDynArrayHasher.SetEventCompare(const Value: TOnDynArraySortCompare);
begin
  if fDynArray^.GetCount <> 0 then
    raise EDynArray.Create('TDynArrayHasher: unexpected SetEventCompare');
  fEventCompare := Value;
  HashTableInit(fHasher);
end;

procedure TDynArrayHasher.SetEventHash(const Value: TOnDynArrayHashOne);
begin
  if fDynArray^.GetCount <> 0 then
    raise EDynArray.Create('TDynArrayHasher: unexpected SetEventHash');
  fEventHash := Value;
  HashTableInit(fHasher);
end;

function TDynArrayHasher.HashOne(Item: pointer): cardinal;
begin
  if Assigned(fEventHash) then
    result := fEventHash(Item^)
  else if not Assigned(fHashItem) then
    result := 0 // will be ignored afterwards for sure
  else
    result := fHashItem(Item^, fHasher);
end;

function TDynArrayHasher.Equals(Item: pointer; ndx: PtrInt): boolean;
begin
  ndx := ndx * fDynArray^.fInfo.Cache.ItemSize;
  inc(ndx, PPtrInt(fDynArray^.Value)^);
  if Assigned(fEventCompare) then
    result := fEventCompare(pointer(ndx)^, Item^) = 0
  else
    result := fCompare(pointer(ndx)^, Item^) = 0;
end;

const
  // reduces memory consumption and enhances distribution at hash table growing
  _PRIMES: array[0..38 {$ifndef DYNARRAYHASH_PO2} + 13 {$endif}] of integer = (
    {$ifndef DYNARRAYHASH_PO2}
    251, 499, 797, 1259, 2011, 3203, 5087, 8089, 12853, 20399, 81649, 129607, 205759,
    {$endif DYNARRAYHASH_PO2}
    // start after HASH_PO2=2^18=262144 for DYNARRAYHASH_PO2 (poor 64-bit mul)
    326617, 411527, 518509, 653267, 823117, 1037059, 1306601, 1646237,
    2074129, 2613229, 3292489, 4148279, 5226491, 6584983, 8296553, 10453007,
    13169977, 16593127, 20906033, 26339969, 33186281, 41812097, 52679969,
    66372617, 83624237, 105359939, 132745199, 167248483, 210719881, 265490441,
    334496971, 421439783, 530980861, 668993977, 842879579, 1061961721,
    1337987929, 1685759167, 2123923447);

// as used internally by TDynArrayHasher.ForceReHash()
function NextPrime(v: integer): integer; {$ifdef HASINLINE}inline;{$endif}
var
  i: PtrInt;
  P: PIntegerArray;
begin
  P := @_PRIMES;
  for i := 0 to high(_PRIMES) do
  begin
    result := P^[i];
    if result > v then
      exit;
  end;
end;

// see TTestCoreBase._TSynDictionary for some numbers, and why
//  DYNARRAYHASH_LEMIRE + DYNARRAYHASH_PO2 are defined by default
function TDynArrayHasher.HashTableIndex(aHashCode: PtrUInt): PtrUInt;
begin
  result := fHashTableSize;
  {$ifdef DYNARRAYHASH_PO2}
  // Delphi Win32 e.g. is not efficient with Lemire 64-bit multiplication
  if result <= HASH_PO2 then
    // efficient AND for power of two division
    result := aHashCode and (result - 1)
  else
  {$endif DYNARRAYHASH_PO2}
  {$ifdef DYNARRAYHASH_LEMIRE}
    // FPC or dcc64 compile next line as very optimized asm
    result := (QWord(aHashCode) * result) shr 32;
    // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction
  {$else}
    // regular 32-bit modulo over a Prime: slower but best from our tests
    result := aHashCode mod result;
  {$endif DYNARRAYHASH_LEMIRE}
end;

function TDynArrayHasher.HashTableIndexToIndex(aHashTableIndex: PtrInt): PtrInt;
begin
  result := PtrUInt(fHashTableStore);
  {$ifdef DYNARRAYHASH_16BIT}
  if hash16bit in fState then
    result := PWordArray(result)[aHashTableIndex]
  else
  {$endif DYNARRAYHASH_16BIT}
    result := PIntegerArray(result)[aHashTableIndex];
end;

function TDynArrayHasher.Find(aHashCode: cardinal; aForAdd: boolean): PtrInt;
var
  first, last, ndx, siz: PtrInt;
  P: PAnsiChar;
begin
  if hasHasher in fState then
  begin
    result := HashTableIndex(aHashCode);
    first := result;
    last := fHashTableSize;
    P := fDynArray^.Value^;
    siz := fDynArray^.Info.Cache.ItemSize;
    repeat
      ndx := HashTableIndexToIndex(result) - 1; // index+1 was stored
      if ndx < 0 then
      begin
        // found void entry
        result := -(result + 1);
        exit;
      end
      else if not aForAdd and
              (HashOne(P + ndx * siz) = aHashCode) then
      begin
        result := ndx;
        exit;
      end;
      inc(result); // try next entry on hash collision
      if result = last then
        // reached the end -> search once from HashTable[0] to HashTable[first-1]
        if result = first then
          break
        else
        begin
          result := 0;
          last := first;
        end;
    until false;
  end;
  result := RaiseFatalCollision('Find', aHashCode);
end;

function TDynArrayHasher.FindOrNew(aHashCode: cardinal; Item: pointer;
  aHashTableIndex: PPtrInt): PtrInt;
var
  first, last, ndx: PtrInt;
  {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
  collisions: integer;
  {$endif DYNARRAYHASHCOLLISIONCOUNT}
  P: PAnsiChar;
begin
  if hasHasher in fState then // we need comparison and hash functions
  begin
    {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
    collisions := 0;
    {$endif DYNARRAYHASHCOLLISIONCOUNT}
    result := HashTableIndex(aHashCode);
    first := result;
    last := fHashTableSize;
    repeat
      ndx := HashTableIndexToIndex(result) - 1; // index+1 was stored
      if ndx < 0 then
      begin
        // not found: returns void index in HashTable[] as negative value
        result := - (result + 1);
        {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
        inc(CountCollisions, collisions);
        inc(CountCollisionsCurrent, collisions);
        {$endif DYNARRAYHASHCOLLISIONCOUNT}
        exit;
      end;
      // comparison with item is faster than hash e.g. for huge strings
      with fDynArray^ do
        P := PAnsiChar(Value^) + ndx * fInfo.Cache.ItemSize;
      if ((not Assigned(fEventCompare)) and
          (fCompare(P^, Item^) = 0)) or
         (Assigned(fEventCompare) and
          (fEventCompare(P^, Item^) = 0)) then
      begin
        // found: returns the matching index
        if aHashTableIndex <> nil then
          aHashTableIndex^ := result;
        result := ndx;
        exit;
      end;
      // hash or slot collision -> search next item
      {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
      inc(collisions);
      {$endif DYNARRAYHASHCOLLISIONCOUNT}
      inc(result);
      if result = last then
        // reached the end -> search once from HashTable[0] to HashTable[first-1]
        if result = first then
          break
        else
        begin
          result := 0;
          last := first;
        end;
    until false;
  end;
  result := RaiseFatalCollision('FindOrNew', aHashCode);
end;

function TDynArrayHasher.FindOrNewComp(aHashCode: cardinal; Item: pointer;
  Comp: TDynArraySortCompare): PtrInt;
var
  first, last, ndx: PtrInt;
begin // cut-down version of FindOrNew()
  if hasHasher in fState then
  begin
    if not Assigned(Comp) then
      Comp := fCompare;
    ndx := HashTableIndex(aHashCode);
    first := ndx;
    last := fHashTableSize;
    repeat
      result := HashTableIndexToIndex(ndx) - 1; // Index+1 was stored
      if (result < 0) or // void slot = not found, or return matching index
         (Comp((PAnsiChar(fDynArray^.Value^) +
           result * fDynArray^.fInfo.Cache.ItemSize)^, Item^) = 0) then
        exit;
      inc(ndx); // hash or slot collision -> search next item
      if ndx = last then
        if ndx = first then
          break
        else
        begin
          ndx := 0;
          last := first;
        end;
    until false;
  end;
  result := RaiseFatalCollision('FindOrNewComp', aHashCode);
end;

procedure TDynArrayHasher.HashAdd(aHashCode: cardinal; var result: PtrInt);
var
  n, ndx: PtrInt;
begin
  // on input: HashTable[result] slot is already computed
  n := fDynArray^.Count;
  ndx := result;
  result := n;
  if fHashTableSize < n then
    RaiseFatalCollision('HashAdd HashTableSize', aHashCode);
  if fHashTableSize - n < n shr 2 then
  begin
    // grow hash table when 25% void (192/256,384/512,768/1024,1536/2048...)
    ForceReHash;
    ndx := Find(aHashCode, {foradd=}true); // recompute position
    if ndx >= 0 then
      RaiseFatalCollision('HashAdd', aHashCode);
  end;
  ndx := -ndx - 1; // store Index+1 (0 means void slot)
  inc(n);
  {$ifdef DYNARRAYHASH_16BIT}
  if hash16bit in fState then
    PWordArray(fHashTableStore)[ndx] := n
  else
  {$endif DYNARRAYHASH_16BIT}
    fHashTableStore[ndx] := n;
end; // on output: result holds the position in fValue[]

procedure TDynArrayHasher.HashDelete(aArrayIndex, aHashTableIndex: PtrInt;
  aHashCode: cardinal);
var
  first, next, last, n, s, ndx, i: PtrInt;
  P: PAnsiChar;
  indexes: array[0..511] of integer; // to be rehashed  (seen always < 32)
begin
  // retrieve hash table entries to be recomputed
  first := aHashTableIndex;
  last := fHashTableSize;
  next := first;
  n := 0;
  repeat
    {$ifdef DYNARRAYHASH_16BIT}
    if hash16bit in fState then
      PWordArray(fHashTableStore)[next] := 0
    else
    {$endif DYNARRAYHASH_16BIT}
      fHashTableStore[next] := 0; // Clear slots
    inc(next);
    if next = last then
      if next = first then
        RaiseFatalCollision('HashDelete down', aHashCode)
      else
      begin
        next := 0;
        last := first;
      end;
    ndx := HashTableIndexToIndex(next) - 1; // index+1 was stored
    if ndx < 0 then
      break; // stop at void entry
    if n = high(indexes) then // paranoid (typical 0..23 range)
      RaiseFatalCollision('HashDelete indexes[] overflow', aHashCode);
    indexes[n] := ndx;
    inc(n);
  until false;
  // ReHash collided entries - note: item is not yet deleted in Value^[]
  s := fDynArray^.Info.Cache.ItemSize;
  for i := 0 to n - 1 do
  begin
    P := PAnsiChar(fDynArray^.Value^) + {%H-}indexes[i] * s;
    ndx := FindOrNew(HashOne(P), P, nil);
    if ndx < 0 then // ignore ndx>=0 dups (like ReHash)
    begin
      ndx := -ndx - 1;     // compute the new slot position
      n := indexes[i] + 1; // store index+1
      {$ifdef DYNARRAYHASH_16BIT}
      if hash16bit in fState then
        PWordArray(fHashTableStore)[ndx] := n
      else
      {$endif DYNARRAYHASH_16BIT}
        fHashTableStore[ndx] := n;
    end;
  end;
  // adjust all stored indexes (using SSE2/AVX2 on x86_64)
  {$ifdef DYNARRAYHASH_16BIT}
  if hash16bit in fState then
    DynArrayHashTableAdjust16(pointer(fHashTableStore), aArrayIndex, fHashTableSize)
  else
  {$endif DYNARRAYHASH_16BIT}
    DynArrayHashTableAdjust(pointer(fHashTableStore), aArrayIndex, fHashTableSize);
end;

function TDynArrayHasher.FindBeforeAdd(Item: pointer; out wasAdded: boolean;
  aHashCode: cardinal): PtrInt;
begin
  wasAdded := false;
  if hasHasher in fState then
  begin
    result := FindOrNew(aHashCode, Item, nil);
    if result >= 0 then
      exit;
    // found no matching item
    wasAdded := true;
    HashAdd(aHashCode, result);
  end
  else
    result := -1
end;

function TDynArrayHasher.FindBeforeDelete(Item: pointer): PtrInt;
var
  h: cardinal;
  ndx: PtrInt;
begin
  if hasHasher in fState then
  begin
    h := HashOne(Item);
    result := FindOrNew(h, Item, @ndx);
    if result < 0 then
      result := -1
    else
      HashDelete(result, ndx, h);
  end
  else
    result := -1;
end;

function TDynArrayHasher.RaiseFatalCollision(const caller: shortstring;
  aHashCode: cardinal): integer;
begin   // a dedicated sub-procedure reduces code size
  result := 0; // make compiler happy
  EDynArray.RaiseUtf8('TDynArrayHasher.% fatal collision: ' +
    'aHashCode=% HashTableSize=% Count=% Capacity=% Array=% Parser=%',
    [caller, CardinalToHexShort(aHashCode), fHashTableSize, fDynArray^.Count,
     fDynArray^.Capacity, fDynArray^.Info.Name, ToText(fDynArray^.Info.Parser)^]);
end;

function TDynArrayHasher.GetHashFromIndex(aIndex: PtrInt): cardinal;
var
  P: pointer;
begin
  P := fDynArray^.ItemPtr(aIndex);
  if P <> nil then
    result := HashOne(P)
  else
    result := 0;
end;

function TDynArrayHasher.Scan(Item: pointer): PtrInt;
var
  P: PAnsiChar;
  i, max, siz: PtrInt;
begin
  result := -1;
  max := fDynArray^.Count - 1;
  P := fDynArray^.Value^;
  siz := fDynArray^.Info.Cache.ItemSize;
  if Assigned(fEventCompare) then // custom comparison
    for i := 0 to max do
      if fEventCompare(P^, Item^) = 0 then
      begin
        result := i;
        break;
      end
      else
        inc(P, siz)
  else if Assigned(fCompare) then
    for i := 0 to max do
      if fCompare(P^, Item^) = 0 then
      begin
        result := i;
        break;
      end
      else
        inc(P, siz)
  else
    exit;
end;

function TDynArrayHasher.Find(Item: pointer): PtrInt;
begin
  result := Find(Item, HashOne(Item));
end;

function TDynArrayHasher.Find(Item: pointer; aHashCode: cardinal): PtrInt;
begin
  result := FindOrNew(aHashCode, Item, nil); // fallback to Scan() if needed
  if result < 0 then
    result := -1; // for coherency with most search methods
end;

type // dedicated TFastReHash engine for better register allocation
  {$ifdef USERECORDWITHMETHODS}
  TFastReHash = record
  {$else}
  TFastReHash = object
  {$endif USERECORDWITHMETHODS}
  public
    hc: cardinal;
    {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
    collisions: integer;
    {$endif DYNARRAYHASHCOLLISIONCOUNT}
    ht: integer;
    values, first, last, siz: PtrInt;
    duplicates: PInteger;
    P: PAnsiChar;
    // fill fHashTableStore[] from all stored items
    procedure Process(Hasher: PDynArrayHasher; count: PtrInt);
  end;

procedure TFastReHash.Process(Hasher: PDynArrayHasher; count: PtrInt);
var
  fnd, ndx: PtrInt;
label
  s;
begin
  // should match FindOrNew() logic
  {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
  collisions := 0;
  {$endif DYNARRAYHASHCOLLISIONCOUNT}
  P := Hasher^.fDynArray^.Value^;
  values := PtrUInt(P);
  siz := Hasher^.fDynArray^.Info.Cache.ItemSize;
  ht := 1; // store index + 1
  repeat
s:  if Assigned(Hasher^.fEventHash) then // inlined HashOne()
      hc := Hasher^.fEventHash(P^)
    else
      hc := Hasher^.fHashItem(P^, Hasher^.fHasher);
    ndx := Hasher^.HashTableIndex(hc);
    first := ndx;
    last := Hasher^.fHashTableSize;
    repeat
      {$ifdef DYNARRAYHASH_16BIT} // inlined HashTableIndexToIndex()
      if hash16bit in Hasher^.fState then
      begin
        if PWordArray(Hasher^.fHashTableStore)[ndx] = 0 then // store index + 1
        begin
          // we can use this void entry (most common case)
          PWordArray(Hasher^.fHashTableStore)[ndx] := ht;
          inc(P, siz); // next item
          inc(ht);
          dec(count);
          if count <> 0 then
            goto s;
          exit;
        end;
      end
      else
      {$endif DYNARRAYHASH_16BIT}
      if Hasher^.fHashTableStore[ndx] = 0 then // void entry
      begin
        Hasher^.fHashTableStore[ndx] := ht;
        inc(P, siz); // next item
        inc(ht);
        dec(count);
        if count <> 0 then
          goto s;
        exit;
      end;
      {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
      inc(collisions);
      {$endif DYNARRAYHASHCOLLISIONCOUNT}
      if duplicates <> nil then
      begin
        // check for duplicated values only if necessary (slow down process)
        if hash16bit in Hasher^.fState then
          fnd := PWordArray(Hasher^.fHashTableStore)[ndx]
        else
          fnd := Hasher^.fHashTableStore[ndx];
        fnd := values + (fnd - 1) * siz; // stored index + 1
        if ((not Assigned(Hasher^.fEventCompare)) and
            (Hasher^.fCompare(pointer(fnd)^, P^) = 0)) or
           (Assigned(Hasher^.fEventCompare) and
            (Hasher^.fEventCompare(pointer(fnd)^, P^) = 0)) then
        begin
          inc(duplicates^); // report but ignore duplicates
          break;
        end;
      end;
      inc(ndx);
      if ndx <> last then
        continue;
      // reached the end -> search from HashTable[0] to HashTable[first-1]
      if ndx = first then
        Hasher.RaiseFatalCollision('ReHash', hc);
      ndx := 0;
      last := first;
    until false;
    inc(P, siz); // next item
    inc(ht);
    dec(count);
  until count = 0;
end;

procedure TDynArrayHasher.ForceReHash(duplicates: PInteger);
var
  n, cap, siz: PtrInt;
  fastrehash: TFastReHash;
begin
  if duplicates <> nil then
    duplicates^ := 0;
  if not (hasHasher in fState) then
    exit;
  // Capacity better than Count or HashTableSize, * 2 to reserve some void slots
  cap := fDynArray^.Capacity * 2;
  {$ifdef DYNARRAYHASH_PO2}
  if cap <= HASH_PO2 then
  begin
    siz := 256; // find nearest power of two for fast bitwise division
    while siz < cap do
      siz := siz shl 1;
  end
  else
  {$endif DYNARRAYHASH_PO2}
    siz := NextPrime(cap);
//QueryPerformanceMicroSeconds(t1); write('rehash count=',n,' old=',HashTableSize,
//' new=', siz, ' oldcol=',CountCollisionsCurrent);
  fHashTableStore := nil;
  fHashTableSize := siz;
  {$ifdef DYNARRAYHASH_16BIT}
  if siz <= 1 shl 16 then
  begin
    include(fState, hash16bit); // we can store indexes as 16-bit word values
    siz := siz shr 1; // 32-bit count
  end
  else
    exclude(fState, hash16bit);
  {$endif DYNARRAYHASH_16BIT}
  SetLength(fHashTableStore, siz + 1); // fill with 0 (void slot)
  {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
  CountCollisionsCurrent := 0; // count collision for this HashTable[] only
  {$endif DYNARRAYHASHCOLLISIONCOUNT}
  // fill fHashTableStore[]=index+1 from all existing items
  n := fDynArray^.Count;
  if n <> 0 then
  begin
    fastrehash.duplicates := duplicates;
    fastrehash.Process(@self, n);
    {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
    inc(CountCollisions, fastrehash.collisions);
    inc(CountCollisionsCurrent, fastrehash.collisions);
    {$endif DYNARRAYHASHCOLLISIONCOUNT}
  end;
//QueryPerformanceMicroSeconds(t2); writeln(' newcol=',CountCollisionsCurrent,' ',
//(CountCollisionsCurrent * 100) div cardinal(n), '%  ',MicroSecToString(t2-t1));
end;

{$ifndef PUREMORMOT2}
function TDynArrayHasher.ReHash(forced: boolean): integer;
begin
  ForceRehash(@result); // always forced for true thread-safety
end;
{$endif PUREMORMOT2}


{ ************ TDynArrayHashed }

{ TDynArrayHashed }

{$ifdef UNDIRECTDYNARRAY} // some Delphi 2009+ wrapper definitions

function TDynArrayHashed.GetCount: PtrInt;
begin
  result := InternalDynArray.GetCount;
end;

procedure TDynArrayHashed.SetCount(aCount: PtrInt);
begin
  InternalDynArray.SetCount(aCount);
end;

function TDynArrayHashed.GetCapacity: PtrInt;
begin
  result := InternalDynArray.GetCapacity;
end;

procedure TDynArrayHashed.SetCapacity(aCapacity: PtrInt);
begin
  InternalDynArray.SetCapacity(aCapacity);
end;

function TDynArrayHashed.Value: PPointer;
begin
  result := InternalDynArray.fValue;
end;

function TDynArrayHashed.Info: TRttiCustom;
begin
  result := InternalDynArray.fInfo;
end;

function TDynArrayHashed.ItemSize: PtrUInt;
begin
  result := InternalDynArray.fInfo.Cache.ItemSize;
end;

procedure TDynArrayHashed.ItemCopy(Source, Dest: pointer);
begin
  InternalDynArray.ItemCopy(Source, Dest);
end;

function TDynArrayHashed.ItemPtr(index: PtrInt): pointer;
begin
  result := InternalDynArray.ItemPtr(index);
end;

function TDynArrayHashed.ItemCopyAt(index: PtrInt; Dest: pointer): boolean;
begin
  result := InternalDynArray.ItemCopyAt(index, Dest);
end;

procedure TDynArrayHashed.Clear;
begin
  InternalDynArray.SetCount(0);
end;

function TDynArrayHashed.Add(const Item): PtrInt;
begin
  result := InternalDynArray.Add(Item);
end;

procedure TDynArrayHashed.Delete(aIndex: PtrInt);
begin
  InternalDynArray.Delete(aIndex);
end;

function TDynArrayHashed.SaveTo: RawByteString;
begin
  result := InternalDynArray.SaveTo;
end;

function TDynArrayHashed.LoadFrom(Source, SourceMax: PAnsiChar): PAnsiChar;
begin
  result := InternalDynArray.LoadFrom(Source, SourceMax);
end;

function TDynArrayHashed.LoadFromBinary(const Buffer: RawByteString): boolean;
begin
  result := InternalDynArray.LoadFromBinary(Buffer);
end;

procedure TDynArrayHashed.SaveTo(W: TBufferWriter);
begin
  InternalDynArray.SaveTo(W);
end;

procedure TDynArrayHashed.Sort(aCompare: TDynArraySortCompare);
begin
  InternalDynArray.Sort(aCompare);
end;

procedure TDynArrayHashed.CreateOrderedIndex(var aIndex: TIntegerDynArray;
  aCompare: TDynArraySortCompare);
begin
  InternalDynArray.CreateOrderedIndex(aIndex, aCompare);
end;

function TDynArrayHashed.SaveToJson(EnumSetsAsText: boolean;
  reformat: TTextWriterJsonFormat): RawUtf8;
begin
  result := InternalDynArray.SaveToJson(EnumSetsAsText, reformat);
end;

procedure TDynArrayHashed.SaveToJson(out result: RawUtf8; EnumSetsAsText: boolean;
  reformat: TTextWriterJsonFormat);
begin
  InternalDynArray.SaveToJson(result, EnumSetsAsText, reformat);
end;

procedure TDynArrayHashed.SaveToJson(W: TTextWriter);
begin
  InternalDynArray.SaveToJson(W);
end;

function TDynArrayHashed.LoadFromJson(P: PUtf8Char; aEndOfObject: PUtf8Char;
  CustomVariantOptions: PDocVariantOptions): PUtf8Char;
begin
  result := InternalDynArray.LoadFromJson(P, aEndOfObject, CustomVariantOptions);
end;

{$endif UNDIRECTDYNARRAY}

procedure TDynArrayHashed.Init(aTypeInfo: PRttiInfo; var aValue;
  aHashItem: TDynArrayHashOne; aCompare: TDynArraySortCompare;
  aHasher: THasher; aCountPointer: PInteger; aCaseInsensitive: boolean);
begin
  InitRtti(Rtti.RegisterType(aTypeInfo), aValue, aHashItem, aCompare,
    aHasher, aCountPointer, aCaseInsensitive);
end;

procedure TDynArrayHashed.InitRtti(aRtti: TRttiCustom; var aValue;
  aHashItem: TDynArrayHashOne; aCompare: TDynArraySortCompare;
  aHasher: THasher; aCountPointer: PInteger; aCaseInsensitive: boolean);
begin
  {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$else}inherited{$endif}
    InitRtti(aRtti, aValue, aCountPointer);
  fHash.Init(@self, aHashItem, nil, aHasher, aCompare, nil, aCaseInsensitive);
  {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare := fHash.fCompare;
end;

procedure TDynArrayHashed.InitSpecific(aTypeInfo: PRttiInfo; var aValue;
  aKind: TRttiParserType; aCountPointer: PInteger; aCaseInsensitive: boolean;
  aHasher: THasher);
begin
  {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$else}inherited{$endif}
    Init(aTypeInfo, aValue, aCountPointer);
  fHash.InitSpecific(@self, aKind, aCaseInsensitive, aHasher);
  {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare := fHash.fCompare;
end;

function TDynArrayHashed.Scan(const Item): PtrInt;
begin
  result := fHash.Scan(@Item);
end;

function TDynArrayHashed.FindHashed(const Item): PtrInt;
begin
  result := fHash.FindOrNew(fHash.HashOne(@Item), @Item, nil);
  if result < 0 then
    result := -1; // for coherency with most methods
end;

function TDynArrayHashed.FindFromHash(const Item; aHashCode: cardinal): PtrInt;
begin
  // overload FindHashed() trigger F2084 Internal Error: C2130 on Delphi XE3
  result := fHash.FindOrNew(aHashCode, @Item, nil); // fallback to Scan() if needed
  if result < 0 then
    result := -1; // for coherency with most methods
end;

function TDynArrayHashed.FindHashedForAdding(const Item; out wasAdded: boolean;
  noAddEntry: boolean): PtrInt;
begin
  result := FindHashedForAdding(Item, wasAdded, fHash.HashOne(@Item), noAddEntry);
end;

function TDynArrayHashed.FindHashedForAdding(const Item; out wasAdded: boolean;
  aHashCode: cardinal; noAddEntry: boolean): PtrInt;
begin
  result := fHash.FindBeforeAdd(@Item, wasAdded, aHashCode);
  if wasAdded and
     not noAddEntry then
    SetCount(result + 1); // reserve space for a void element in array
end;

function TDynArrayHashed.AddAndMakeUniqueName(aName: RawUtf8): pointer;
var
  ndx: PtrInt;
  j: PtrUInt;
  added: boolean;
  aName_: RawUtf8;
begin
  if aName = '' then
    aName := '_';
  ndx := FindHashedForAdding(aName, added);
  if not added then
  begin
    // force unique column name
    aName_ := aName + '_';
    j := 1;
    repeat
      if j > high(SmallUInt32Utf8) then // should never happen - 999 is enough
        raise EDynArray.Create('TDynArrayHashed.AddAndMakeUniqueName overflow');
      aName := aName_ + SmallUInt32Utf8[j];
      ndx := FindHashedForAdding(aName, added);
      inc(j);
    until added;
  end;
  result := PAnsiChar(Value^) + ndx * Info.Cache.ItemSize;
  PRawUtf8(result)^ := aName; // store unique name at 1st position
end;

function TDynArrayHashed.AddUniqueName(const aName: RawUtf8;
  aNewIndex: PPtrInt): pointer;
begin
  result := AddUniqueName(aName, '', [], aNewIndex);
end;

function TDynArrayHashed.AddUniqueName(const aName: RawUtf8; const ExceptionMsg: RawUtf8;
  const ExceptionArgs: array of const; aNewIndex: PPtrInt): pointer;
var
  ndx: PtrInt;
  added: boolean;
begin
  ndx := FindHashedForAdding(aName, added);
  if not added then
    if ExceptionMsg = '' then
      EDynArray.RaiseUtf8('TDynArrayHashed: Duplicated [%] name', [aName])
    else
      EDynArray.RaiseUtf8(ExceptionMsg, ExceptionArgs);
  if aNewIndex <> nil then
    aNewIndex^ := ndx;
  result := PAnsiChar(Value^) + ndx * Info.Cache.ItemSize;
  PRawUtf8(result)^ := aName; // store unique name at 1st position
end;

function TDynArrayHashed.FindHashedAndFill(var ItemToFill): PtrInt;
begin
  result := fHash.FindOrNew(fHash.HashOne(@ItemToFill), @ItemToFill, nil);
  if result < 0 then
    result := -1
  else
    ItemCopy(PAnsiChar(Value^) + result * Info.Cache.ItemSize, @ItemToFill);
end;

function TDynArrayHashed.FindHashedAndUpdate(const Item;
  AddIfNotExisting: boolean): PtrInt;
var
  hc: cardinal;
begin
  if hasHasher in fHash.fState then
  begin
    hc := fHash.HashOne(@Item);
    result := fHash.FindOrNew(hc, @Item, nil);
    if (result < 0) and
       AddIfNotExisting then
    begin
      fHash.HashAdd(hc, result); // ReHash only if necessary
      SetCount(result + 1); // add new item
    end;
  end
  else
    result := -1;
  if result >= 0 then // update
    ItemCopy(@Item, PAnsiChar(Value^) + result * Info.Cache.ItemSize);
end;

function TDynArrayHashed.FindHashedAndDelete(const Item; FillDeleted: pointer;
  noDeleteEntry: boolean): PtrInt;
begin
  result := fHash.FindBeforeDelete(@Item);
  if result >= 0 then
  begin
    if FillDeleted <> nil then
      ItemCopyAt(result, FillDeleted);
    if not noDeleteEntry then
      Delete(result);
  end;
end;

function TDynArrayHashed.GetHashFromIndex(aIndex: PtrInt): cardinal;
begin
  result := fHash.GetHashFromIndex(aIndex);
end;

procedure TDynArrayHashed.ForceReHash;
begin
  fHash.ForceReHash;
end;

{$ifndef PUREMORMOT2}
function TDynArrayHashed.ReHash(forced: boolean): integer;
begin
  fHash.ForceReHash(@result); // always forced
end;
{$endif PUREMORMOT2}

procedure TDynArrayHashed.SetEventCompare(const cmp: TOnDynArraySortCompare);
begin
  fHash.SetEventCompare(cmp);
end;

procedure TDynArrayHashed.SetEventHash(const hsh: TOnDynArrayHashOne);
begin
  fHash.SetEventHash(hsh);
end;


function DynArray(aTypeInfo: PRttiInfo; var aValue; aCountPointer: PInteger): TDynArray;
begin
  result.Init(aTypeInfo, aValue, aCountPointer);
end;



{ *************** Integer Arrays Extended Process }

procedure Exchg32(var A, B: integer); {$ifdef HASINLINE}inline;{$endif}
var
  tmp: integer;
begin
  tmp := A;
  A := B;
  B := tmp;
end;

function MedianQuickSelectInteger(Values: PIntegerArray; n: integer): integer;
var
  low, high, median, middle, ll, hh: PtrInt;
begin
  if n = 0 then
  begin
    result := 0;
    exit;
  end;
  if n = 1 then
  begin
    result := Values[0];
    exit;
  end;
  low := 0;
  high := n - 1;
  median := high shr 1;
  repeat
    if high <= low then
    begin
      // one item left
      result := Values[median];
      exit;
    end;
    if high = low + 1 then
    begin
      // two items -> return the smallest (not average)
      if Values[low] > Values[high] then
        Exchg32(Values[low], Values[high]);
      result := Values[median];
      exit;
    end;
    // find median of low, middle and high items; swap into position low
    middle := (low + high) shr 1;
    if Values[middle] > Values[high] then
      Exchg32(Values[middle], Values[high]);
    if Values[low] > Values[high] then
      Exchg32(Values[low], Values[high]);
    if Values[middle] > Values[low] then
      Exchg32(Values[middle], Values[low]);
    // swap low item (now in position middle) into position (low+1)
    Exchg32(Values[middle], Values[low + 1]);
    // nibble from each end towards middle, swapping items when stuck
    ll := low + 1;
    hh := high;
    repeat
      repeat
        inc(ll);
      until not (Values[low] > Values[ll]);
      repeat
        dec(hh);
      until not (Values[hh] > Values[low]);
      if hh < ll then
        break;
      Exchg32(Values[ll], Values[hh]);
    until false;
    // swap middle item (in position low) back into correct position
    Exchg32(Values[low], Values[hh]);
    // next active partition
    if hh <= median then
      low := ll;
    if hh >= median then
      high := hh - 1;
  until false;
end;

function MedianQuickSelect(const OnCompare: TOnValueGreater; n: integer;
  var TempBuffer: TSynTempBuffer): integer;
var
  low, high, middle, median, ll, hh: PtrInt;
  tmp: integer;
  ndx: PIntegerArray;
begin
  if n <= 1 then
  begin
    TempBuffer.buf := nil; // avoid GPF in TempBuffer.Done
    result := 0;
    exit;
  end;
  low := 0;
  high := n - 1;
  ndx := TempBuffer.InitIncreasing(n * 4); // no heap alloacation until n>1024
  median := high shr 1;
  repeat
    if high <= low then
    begin
      // one item left
      result := ndx[median];
      {%H-}TempBuffer.Done;
      exit;
    end;
    if high = low + 1 then
    begin
      // two items -> return the smallest (not average)
      if OnCompare(ndx[low], ndx[high]) then
        Exchg32(ndx[low], ndx[high]);
      result := ndx[median];
      {%H-}TempBuffer.Done;
      exit;
    end;
    // find median of low, middle and high items; swap into position low
    middle := (low + high) shr 1;
    if OnCompare(ndx[middle], ndx[high]) then
      Exchg32(ndx[middle], ndx[high]);
    if OnCompare(ndx[low], ndx[high]) then
      Exchg32(ndx[low], ndx[high]);
    if OnCompare(ndx[middle], ndx[low]) then
      Exchg32(ndx[middle], ndx[low]);
    // swap low item (now in position middle) into position (low+1)
    Exchg32(ndx[middle], ndx[low + 1]);
    // nibble from each end towards middle, swapping items when stuck
    ll := low + 1;
    hh := high;
    repeat
      tmp := ndx[low];
      repeat
        inc(ll);
      until not OnCompare(tmp, ndx[ll]);
      repeat
        dec(hh);
      until not OnCompare(ndx[hh], tmp);
      if hh < ll then
        break;
      tmp := ndx[ll];
      ndx[ll] := ndx[hh];
      ndx[hh] := tmp; // Exchg32(ndx[ll],ndx[hh]);
    until false;
    // swap middle item (in position low) back into correct position
    Exchg32(ndx[low], ndx[hh]);
    // next active partition
    if hh <= median then
      low := ll;
    if hh >= median then
      high := hh - 1;
  until false;
end;

procedure NotifySortedIntegerChanges(old, new: PIntegerArray; oldn, newn: PtrInt;
  const added, deleted: TOnNotifySortedIntegerChange; const sender);
var
  o, n: PtrInt;
begin
  o := 0;
  n := 0;
  repeat
    while (n < newn) and
          (o < oldn) and
          (old[o] = new[n]) do
    begin
      inc(o);
      inc(n);
    end;
    while (o < oldn) and
          ((n >= newn) or
           (old[o] < new[n])) do
    begin
      if Assigned(deleted) then
        deleted(sender, old[o]);
      inc(o);
    end;
    while (n < newn) and
          ((o >= oldn) or
           (new[n] < old[o])) do
    begin
      if Assigned(added) then
        added(sender, new[n]);
      inc(n);
    end;
  until (o >= oldn) and
        (n >= newn);
end;

procedure CopyAndSortInteger(Values: PIntegerArray; ValuesCount: integer;
  var Dest: TIntegerDynArray);
begin
  if ValuesCount > Length(Dest) then
    SetLength(Dest, ValuesCount);
  MoveFast(Values^[0], Dest[0], ValuesCount * SizeOf(integer));
  QuickSortInteger(pointer(Dest), 0, ValuesCount - 1);
end;

procedure CopyAndSortInt64(Values: PInt64Array; ValuesCount: integer;
  var Dest: TInt64DynArray);
begin
  if ValuesCount > Length(Dest) then
    SetLength(Dest, ValuesCount);
  MoveFast(Values^[0], Dest[0], ValuesCount * SizeOf(Int64));
  QuickSortInt64(pointer(Dest), 0, ValuesCount - 1);
end;

procedure ExcludeInteger(var Values, Excluded: TIntegerDynArray; ExcludedSortSize: integer);
var
  i, v, x, n: PtrInt;
begin
  if (Values = nil) or
     (Excluded = nil) then
    exit; // nothing to exclude
  EnsureUnique(Values);
  EnsureUnique(Excluded);
  v := Length(Values);
  n := 0;
  x := Length(Excluded);
  if (x > ExcludedSortSize) or
     (v > ExcludedSortSize) then
  begin
    // sort if worth it
    dec(x);
    QuickSortInteger(pointer(Excluded), 0, x);
    for i := 0 to v - 1 do
      if FastFindIntegerSorted(pointer(Excluded), x, Values[i]) < 0 then
      begin
        if n <> i then
          Values[n] := Values[i];
        inc(n);
      end;
  end
  else
    for i := 0 to v - 1 do
      if not IntegerScanExists(pointer(Excluded), x, Values[i]) then
      begin
        if n <> i then
          Values[n] := Values[i];
        inc(n);
      end;
  if n <> v then
    SetLength(Values, n);
end;

procedure IncludeInteger(var Values, Included: TIntegerDynArray; IncludedSortSize: integer);
var
  i, v, x, n: PtrInt;
begin
  if (Values = nil) or
     (Included = nil) then
  begin
    Values := nil;
    exit;
  end;
  EnsureUnique(Values);
  EnsureUnique(Included);
  v := Length(Values);
  n := 0;
  x := Length(Included);
  if (x > IncludedSortSize) or
     (v > IncludedSortSize) then
  begin
    // sort if worth it
    dec(x);
    QuickSortInteger(pointer(Included), 0, x);
    for i := 0 to v - 1 do
      if FastFindIntegerSorted(pointer(Included), x, Values[i]) >= 0 then
      begin
        if n <> i then
          Values[n] := Values[i];
        inc(n);
      end;
  end
  else
    for i := 0 to v - 1 do
      if IntegerScanExists(pointer(Included), x, Values[i]) then
      begin
        if n <> i then
          Values[n] := Values[i];
        inc(n);
      end;
  if n <> v then
    SetLength(Values, n);
end;

procedure ExcludeInt64(var Values, Excluded: TInt64DynArray; ExcludedSortSize: integer);
var
  i, v, x, n: PtrInt;
begin
  if (Values = nil) or
     (Excluded = nil) then
    exit; // nothing to exclude
  v := Length(Values);
  n := 0;
  x := Length(Excluded);
  if (x > ExcludedSortSize) or
     (v > ExcludedSortSize) then
  begin
    // sort if worth it
    dec(x);
    QuickSortInt64(pointer(Excluded), 0, x);
    for i := 0 to v - 1 do
      if FastFindInt64Sorted(pointer(Excluded), x, Values[i]) < 0 then
      begin
        if n <> i then
          Values[n] := Values[i];
        inc(n);
      end;
  end
  else
    for i := 0 to v - 1 do
      if not Int64ScanExists(pointer(Excluded), x, Values[i]) then
      begin
        if n <> i then
          Values[n] := Values[i];
        inc(n);
      end;
  if n <> v then
    SetLength(Values, n);
end;

procedure IncludeInt64(var Values, Included: TInt64DynArray; IncludedSortSize: integer);
var
  i, v, x, n: PtrInt;
begin
  if (Values = nil) or
     (Included = nil) then
  begin
    Values := nil;
    exit;
  end;
  v := Length(Values);
  n := 0;
  x := Length(Included);
  if (x > IncludedSortSize) or
     (v > IncludedSortSize) then
  begin
    // sort if worth it
    dec(x);
    QuickSortInt64(pointer(Included), 0, x);
    for i := 0 to v - 1 do
      if FastFindInt64Sorted(pointer(Included), x, Values[i]) >= 0 then
      begin
        if n <> i then
          Values[n] := Values[i];
        inc(n);
      end;
  end
  else
    for i := 0 to v - 1 do
      if Int64ScanExists(pointer(Included), x, Values[i]) then
      begin
        if n <> i then
          Values[n] := Values[i];
        inc(n);
      end;
  if n <> v then
    SetLength(Values, n);
end;

procedure DeduplicateInteger(var Values: TIntegerDynArray);
begin
  DeduplicateInteger(Values, Length(Values));
end;

function DeduplicateIntegerSorted(val: PIntegerArray; last: PtrInt): PtrInt;
var
  i: PtrInt;
begin
  // sub-function for better code generation
  i := 0;
  repeat // here last>0 so i<last
    if val[i] = val[i + 1] then
      break;
    inc(i);
    if i <> last then
      continue;
    result := i;
    exit;
  until false;
  result := i;
  inc(i);
  if i <> last then
  begin
    repeat
      if val[i] <> val[i + 1] then
      begin
        val[result] := val[i];
        inc(result);
      end;
      inc(i);
    until i = last;
    val[result] := val[i];
  end;
end;

function DeduplicateInteger(var Values: TIntegerDynArray; Count: PtrInt): PtrInt;
begin
  result := Count;
  dec(Count);
  if Count > 0 then
  begin
    QuickSortInteger(pointer(Values), 0, Count);
    result := DeduplicateIntegerSorted(pointer(Values), Count) + 1;
  end;
  if result <> Length(Values) then
    SetLength(Values, result);
end;

procedure DeduplicateInt64(var Values: TInt64DynArray);
begin
  DeduplicateInt64(Values, Length(Values));
end;

function DeduplicateInt64Sorted(val: PInt64Array; last: PtrInt): PtrInt;
var
  i: PtrInt;
begin
  // sub-function for better code generation
  i := 0;
  repeat // here last>0 so i<last
    if val[i] = val[i + 1] then
      break;
    inc(i);
    if i <> last then
      continue;
    result := i;
    exit;
  until false;
  result := i;
  inc(i);
  if i <> last then
  begin
    repeat
      if val[i] <> val[i + 1] then
      begin
        val[result] := val[i];
        inc(result);
      end;
      inc(i);
    until i = last;
    val[result] := val[i];
  end;
end;

function DeduplicateInt64(var Values: TInt64DynArray; Count: PtrInt): PtrInt;
begin
  result := Count;
  dec(Count);
  if Count > 0 then
  begin
    QuickSortInt64(pointer(Values), 0, Count);
    result := DeduplicateInt64Sorted(pointer(Values), Count) + 1;
  end;
  if result <> Length(Values) then
    SetLength(Values, result);
end;

procedure CopyInteger(const Source: TIntegerDynArray; out Dest: TIntegerDynArray);
var
  n: integer;
begin
  n := Length(Source);
  SetLength(Dest, n);
  MoveFast(Source[0], Dest[0], n * SizeOf(integer));
end;

procedure CopyInt64(const Source: TInt64DynArray; out Dest: TInt64DynArray);
var
  n: integer;
begin
  n := Length(Source);
  SetLength(Dest, n);
  MoveFast(Source[0], Dest[0], n * SizeOf(Int64));
end;

function MaxInteger(const Values: TIntegerDynArray; ValuesCount: PtrInt; MaxStart: integer): integer;
var
  p: PInteger;
  v: integer;
begin
  result := MaxStart;
  if ValuesCount > 0 then
  begin
    p := pointer(Values);
    repeat
      v := p^;
      if v > result then
        result := v; // movca branchless opcode on FPC
      inc(p);
      dec(ValuesCount);
    until ValuesCount = 0;
  end;
end;

function MaxInt64(const Values: TInt64DynArray; ValuesCount: PtrInt; MaxStart: Int64): Int64;
var
  p: PInt64;
  v: Int64;
begin
  result := MaxStart;
  if ValuesCount > 0 then
  begin
    p := pointer(Values);
    repeat
      v := p^;
      if v > result then
        result := v; // movca branchless opcode on FPC
      inc(p);
      dec(ValuesCount);
    until ValuesCount = 0;
  end;
end;

function SumInteger(const Values: TIntegerDynArray; ValuesCount: PtrInt): integer;
var
  i: PtrInt;
begin
  result := 0;
  for i := 0 to ValuesCount - 1 do
    inc(result, Values[i]);
end;

procedure Reverse(const Values: TIntegerDynArray; ValuesCount: PtrInt; Reversed: PIntegerArray);
var
  i: PtrInt;
begin
  i := 0;
  if ValuesCount >= 4 then
  begin
    dec(ValuesCount, 4);
    while i < ValuesCount do
    begin
      // faster pipelined version
      Reversed[Values[i]] := i;
      Reversed[Values[i + 1]] := i + 1;
      Reversed[Values[i + 2]] := i + 2;
      Reversed[Values[i + 3]] := i + 3;
      inc(i, 4);
    end;
    inc(ValuesCount, 4);
  end;
  while i < ValuesCount do
  begin
    Reversed[Values[i]] := i;
    inc(i);
  end;
  //for i := 0 to Count-1 do Assert(Reverse[Orig[i]]=i);
end;

procedure Int64ToUInt32(Values64: PInt64Array; Values32: PCardinalArray; Count: PtrInt);
var
  i: PtrInt;
begin
  for i := 0 to Count - 1 do
    Values32[i] := Values64[i];
end;

function AnyScanIndex(P, V: pointer; Count, VSize: PtrInt): PtrInt;
begin
  case VSize of
    // optimized versions for arrays of most simple types
    1:
      result := ByteScanIndex(P, Count, PByte(V)^); // SSE2 asm on Intel/AMD
    2:
      result := WordScanIndex(P, Count, PWord(V)^); // may use SSE2 asm
    4:
      result := IntegerScanIndex(P, Count, PInteger(V)^); // may use SSE2 asm
    8:
      result := Int64ScanIndex(P, Count, PInt64(V)^);
    SizeOf(THash128):
      result := Hash128Index(P, Count, V);
    SizeOf(THash256):
      result := Hash256Index(P, Count, V);
    // small VSize version (<SizeOf(PtrInt))
    3, 5..7:
      begin
        for result := 0 to Count - 1 do
          if CompareMemSmall(P, V, VSize) then
            exit
          else
            inc(PByte(P), VSize);
        result := -1;
      end;
  else
    begin
      // generic binary comparison (fast with inlined CompareMemSmall)
      for result := 0 to Count - 1 do
        if (PInt64(P)^ = PInt64(V)^) and // not better using a local Int64 var
           CompareMemSmall(PAnsiChar(P) + 8, PAnsiChar(V) + 8, VSize - 8) then
          exit
        else
          inc(PByte(P), VSize);
      result := -1;
    end;
  end;
end;

function AnyScanExists(P, V: pointer; Count, VSize: PtrInt): boolean;
begin
  result := AnyScanIndex(P, V, Count, VSize) >= 0;
end;


{ ************ Abstract Radix Tree Classes }

{ TRadixTreeNode }

function TRadixTreeNode.ComputeDepth: integer;
var
  i: PtrInt;
begin
  result := 1;
  for i := 0 to high(Child) do
    inc(result, Child[i].ComputeDepth); // recursive calculation
  Depth := result;
end;

function RadixTreeNodeCompare(const A, B): integer;
begin // sort static first, then deeper, by path:, by longest path, by text
  result := ord(TRadixTreeNode(B).Chars[1] <> '<') -
            ord(TRadixTreeNode(A).Chars[1] <> '<');
  if result = 0 then
    result := ord(IdemPChar(pointer(TRadixTreeNode(A).Chars), '<PATH:')) -
              ord(IdemPChar(pointer(TRadixTreeNode(B).Chars), '<PATH:'));
  if result = 0 then
    result := CompareInteger(TRadixTreeNode(B).Depth, TRadixTreeNode(A).Depth);
  if result = 0 then
    result := CompareInteger(length(TRadixTreeNode(B).FullText),
                             length(TRadixTreeNode(A).FullText));
  if result = 0 then
    result := StrComp(pointer(TRadixTreeNode(A).FullText),
                      pointer(TRadixTreeNode(B).FullText));
end;

procedure TRadixTreeNode.SortChildren;
var
  i: PtrInt;
begin
  for i := 0 to high(Child) do
    Child[i].SortChildren; // recursive sorting
  ObjArraySort(Child, RadixTreeNodeCompare);
end;

constructor TRadixTreeNode.Create(aOwner: TRadixTree);
begin
  inherited Create;
  Owner := aOwner;
end;

function TRadixTreeNode.Split(const Text: RawUtf8): TRadixTreeNode;
begin
  result := TRadixTreeNodeClass(PPointer(self)^).Create(Owner);
  result.Chars := Text;
  result.FullText := FullText;
  result.Child := Child;
  result.Flags := Flags;
  Chars := '';
  FullText := '';
  Child := nil;
  Flags := [];
  ObjArrayAdd(Child, result);
end;

destructor TRadixTreeNode.Destroy;
begin
  inherited Destroy;
  ObjArrayClear(Child);
end;

function TRadixTreeNode.Find(P: PUtf8Char): TRadixTreeNode;
var
  c: PUtf8Char;
  n: TDALen;
  t: PNormTable;
  ch: ^TRadixTreeNode;
begin
  result := nil; // no match
  t := Owner.fNormTable;
  c := pointer(Chars);
  repeat
    if (t[P^] <> c^) or // may do LowerCaseSelf(Chars) at Insert()
       (P^ = #0) then
      break;
    inc(P);
    inc(c);
  until false;
  if c^ <> #0 then
    exit; // not enough matched chars
  // if we reached here, the text do match up to now
  if P^ = #0 then
    result := self // exact match found for this entry
  else
  begin
    ch := pointer(Child);
    if ch = nil then
      exit;
    n := PDALen(PAnsiChar(ch) - _DALEN)^ + _DAOFF;
    repeat
      if ch^.Chars[1] = t[P^] then
      begin
        result := ch^.Find(P);
        if result <> nil then
          exit; // match found in children
      end;
      inc(ch);
      dec(n);
    until n = 0;
  end;
end;

procedure TRadixTreeNode.ToText(var Result: RawUtf8; Level: integer);
var
  i: PtrInt;
begin
  Append(Result, [RawUtf8OfChar(' ', Level), Chars, #10]);
  for i := 0 to high(Child) do
    Child[i].ToText(Result, Level + length(Chars));
end;


{ TRadixTree }

constructor TRadixTree.Create(aNodeClass: TRadixTreeNodeClass;
  aOptions: TRadixTreeOptions);
begin
  if aNodeClass = nil then
    ERadixTree.RaiseUtf8('%.Create with aNodeClass=nil', [self]);
  fDefaultNodeClass := aNodeClass;
  fOptions := aOptions;
  if rtoCaseInsensitiveUri in aOptions then
    fNormTable := @NormToLower
  else
    fNormTable := @NormToNorm;
  fRoot := fDefaultNodeClass.Create(self); // with no text
end;

destructor TRadixTree.Destroy;
begin
  inherited Destroy;
  fRoot.Free; // will recursively free all nested children
end;

procedure TRadixTree.Clear;
begin
  if self = nil then
    exit;
  fRoot.Free;
  fRoot := fDefaultNodeClass.Create(self);
end;

function TRadixTree.Insert(Text: RawUtf8; Node: TRadixTreeNode;
  NodeClass: TRadixTreeNodeClass): TRadixTreeNode;
var
  match, textlen, nodelen, i: PtrInt;
  chars: RawUtf8;
begin
  result := nil;
  if Text = '' then
    exit;
  if Node = nil then
    Node := fRoot;
  if rtoCaseInsensitiveUri in Options then
    LowerCaseSelf(Text);
  textlen := length(Text);
  nodelen := length(Node.Chars);
  // check how many chars of Text are within Node.Chars
  match := 0;
  while (match < textlen) and
        (match < nodelen) and
        (Text[match + 1] = Node.Chars[match + 1]) do
    inc(match);
  // insert the node where fits
  chars := copy(Text, match + 1, maxInt);
  if (match = 0) or
     (Node = fRoot) or
     ((match < textlen) and
      (match >= nodelen)) then
  begin
    // we can just insert a new leaf node
    if chars <> '' then
      for i := 0 to high(Node.Child) do
        if Node.Child[i].Chars[1] = chars[1] then
        begin
          result := Insert(chars, Node.Child[i]); // recursive insertion
          result.FullText := Text;
          exit;
        end;
  end
  else if match <> nodelen then
  begin
    // need to split the existing node
    Node.Split(copy(Node.Chars, match + 1, maxInt)); // split children
    Node.Chars := copy(Text, 1, match); // new shared root
    if chars = '' then
    begin
      result := Node; // don't need a sub child - use shared root
      result.FullText := Text;
      exit;
    end;
  end
  else
  begin
    // match an existing node
    result := Node;
    exit;
  end;
  // create new leaf
  if NodeClass = nil then
    NodeClass := fDefaultNodeClass;
  result := NodeClass.Create(self);
  result.Chars := chars;
  result.FullText := Text;
  ObjArrayAdd(Node.Child, result);
end;

procedure TRadixTree.AfterInsert;
begin
  fRoot.ComputeDepth;
  fRoot.SortChildren;
end;

function TRadixTree.Find(const Text: RawUtf8): TRadixTreeNode;
var
  n: TDALen;
  c: AnsiChar;
  ch: ^TRadixTreeNode;
begin
  result := nil;
  if (self = nil) or
     (Text = '') then
    exit;
  ch := pointer(fRoot.Child);
  if ch = nil then
    exit;
  n := PDALen(PAnsiChar(ch) - _DALEN)^ + _DAOFF;
  c := fNormTable[Text[1]];
  repeat
    if ch^.Chars[1] = c then // recursive call if may match
    begin
      result := ch^.Find(pointer(Text));
      if result <> nil then
        exit;
    end;
    inc(ch);
    dec(n);
  until n = 0;
end;

function TRadixTree.ToText: RawUtf8;
begin
  result := '';
  if self <> nil then
    fRoot.ToText(result, 0);
end;


{ TRadixTreeNodeParams }

function TRadixTreeNodeParams.Split(const Text: RawUtf8): TRadixTreeNode;
begin
  result := inherited Split(Text);
  TRadixTreeNodeParams(result).Names := Names;
  Names := nil;
end;

function TRadixTreeNodeParams.Lookup(P: PUtf8Char; Ctxt: TObject): TRadixTreeNodeParams;
var
  n: TDALen;
  c: PUtf8Char;
  t: PNormTable;
  f: TRadixTreeNodeFlags;
  ch: ^TRadixTreeNodeParams;
begin
  result := nil; // no match
  t := Owner.fNormTable;
  if Names = nil then
  begin
    // static text
    c := pointer(Chars);
    if c <> nil then
    begin
      repeat
        if (t^[P^] <> c^) or // may do LowerCaseSelf(Chars) at Insert()
           (P^ = #0) then
          break;
        inc(P);
        inc(c);
      until false;
      if c^ <> #0 then
        exit; // not enough matched chars
    end;
  end
  else
  begin
    // <named> parameter
    c := P;
    f := Flags;
    if rtfParamInteger in f then // <int:name> or rtoIntegerParams
    begin
      if (P^ < '0') or (P^ > '9') then
        exit; // void <integer> is not allowed
      repeat
        inc(P)
      until (P^ < '0') or (P^ > '9');
      if (P^ <> #0) and (P^ <> '?') and (P^ <> '/') then
        exit; // not an integer
    end
    else if rtfParamPath in f then // <path:filename> or * as <path:path>
      while (P^ <> #0) and (P^ <> '?') do
        inc(P)
    else // regular <param>
      while (P^ <> #0) and (P^ <> '?') and (P^ <> '/') do
        inc(P);
    if (Ctxt <> nil) and not LookupParam(Ctxt, c, P - c) then
      exit; // the parameter is not in the expected format for Ctxt
  end;
  // if we reached here, the URI do match up to now
  if (P^ = #0) or (P^ = '?') then
  begin
    if (P^ = '?') and (Ctxt <> nil) then
      LookupParam(Ctxt, P, -1); // store the inlined parameters position in Ctxt
    result := self; // exact match found for this entry (excluding URI params)
    exit;
  end;
  ch := pointer(Child);
  if ch = nil then
    exit;
  n := PDALen(PAnsiChar(ch) - _DALEN)^ + _DAOFF;
  repeat
    if (ch^.Names <> nil) or
       (ch^.Chars[1] = t^[P^]) then // recursive call only if worth it
    begin
      result := ch^.Lookup(P, Ctxt);
      if result <> nil then
        exit; // match found in children
    end;
    inc(ch);
    dec(n);
  until n = 0;
end;


{ TRadixTreeParams }

function TRadixTreeParams.Setup(const aFromUri: RawUtf8;
  out aNames: TRawUtf8DynArray): TRadixTreeNodeParams;
var
  u: PUtf8Char;
  item, full: RawUtf8;
  flags: TRadixTreeNodeFlags;
begin
  u := pointer(TrimU(aFromUri));
  if PosExChar('<', aFromUri) = 0 then
    // a simple static route
    result := Insert(aFromUri) as TRadixTreeNodeParams
  else
    // parse static..<param1>..static..<param2>..static into static/param nodes
    repeat
      GetNextItem(u, '<', item);
      full := full + item;
      result := Insert(full) as TRadixTreeNodeParams; // static (Names = nil)
      if u = nil then
        break;
      GetNextItem(u, '>', item);
      if item = '' then
        ERadixTree.RaiseUtf8('Void <> in %.Setup(''%'')', [self, aFromUri]);
      flags := [rtfParam];
      if IdemPChar(pointer(item), 'INT:') then
      begin
        delete(item, 1, 4);
        include(flags, rtfParamInteger);
      end
      else if rtoIntegerParams in Options then
        include(flags, rtfParamInteger);
      if IdemPChar(pointer(item), 'PATH:') then
      begin
        delete(item, 1, 5);
        include(flags, rtfParamPath);
      end;
      if FindRawUtf8(aNames{%H-}, item) >= 0 then
        ERadixTree.RaiseUtf8('Duplicated <%> in %.Setup(''%'')',
          [item, self, aFromUri]);
      AddRawUtf8(aNames, item);
      full := full + '<' + item + '>'; // avoid name collision with static
      result := Insert(full) as TRadixTreeNodeParams; // param (Names <> nil)
      result.Names := copy(aNames); // each node has its own Names copy
      result.Flags := flags;
      if (u = nil) or
         (u^ = #0) then
        // TODO: detect wildchar incompatibilities with nested searches?
        break;
      if u^ <> '/' then
        ERadixTree.RaiseUtf8('Unexpected <%>% in %.Setup(''%'')',
          [item, u^, self, aFromUri]);
    until false;
  AfterInsert; // compute Depth and sort by priority
end;



procedure InitializeUnit;
var
  k: TRttiKind;
begin
  HashSeed := Random32; // to avoid hash flooding
  // initialize RTTI low-level comparison functions
  RTTI_ORD_COMPARE[roSByte]  := @_BC_SByte;
  RTTI_ORD_COMPARE[roUByte]  := @_BC_UByte;
  RTTI_ORD_COMPARE[roSWord]  := @_BC_SWord;
  RTTI_ORD_COMPARE[roUWord]  := @_BC_UWord;
  RTTI_ORD_COMPARE[roSLong]  := @_BC_SLong;
  RTTI_ORD_COMPARE[roULong]  := @_BC_ULong;
  {$ifdef FPC_NEWRTTI}
  RTTI_ORD_COMPARE[roSQWord] := @_BC_SQWord;
  RTTI_ORD_COMPARE[roUQWord] := @_BC_UQWord;
  {$endif FPC_NEWRTTI}
  RTTI_FLOAT_COMPARE[rfSingle]   := @_BC_Single;
  RTTI_FLOAT_COMPARE[rfDouble]   := @_BC_Double;
  RTTI_FLOAT_COMPARE[rfExtended] := @_BC_Extended;
  RTTI_FLOAT_COMPARE[rfComp]     := @_BC_SQWord; // PInt64 is the best
  RTTI_FLOAT_COMPARE[rfCurr]     := @_BC_SQWord;
  // initialize RTTI binary persistence and high-level comparison functions
  for k := succ(low(k)) to high(k) do
    case k of
      rkInteger,
      rkEnumeration,
      rkSet,
      rkChar,
      rkWChar
      {$ifdef FPC}, rkBool{$endif}:
        begin
          RTTI_BINARYSAVE[k] := @_BS_Ord;
          RTTI_BINARYLOAD[k] := @_BL_Ord;
          RTTI_COMPARE[false, k] := @_BC_Ord;
          RTTI_COMPARE[true,  k] := @_BC_Ord;
        end;
      {$ifdef FPC} rkQWord, {$endif}
      rkInt64:
        begin
          RTTI_BINARYSAVE[k] := @_BS_64;
          RTTI_BINARYLOAD[k] := @_BL_64;
          RTTI_COMPARE[false, k] := @_BC_64;
          RTTI_COMPARE[true,  k] := @_BC_64;
        end;
      rkFloat:
        begin
          RTTI_BINARYSAVE[k] := @_BS_Float;
          RTTI_BINARYLOAD[k] := @_BS_Float;
          RTTI_COMPARE[false, k] := @_BC_Float;
          RTTI_COMPARE[true,  k] := @_BC_Float;
        end;
      rkLString:
        begin
          RTTI_BINARYSAVE[k] := @_BS_String;
          RTTI_BINARYLOAD[k] := @_BL_LString;
          RTTI_COMPARE[false, k] := @_BC_LString;
          RTTI_COMPARE[true,  k] := @_BCI_LString;
        end;
      {$ifdef HASVARUSTRING}
      rkUString:
        begin
          RTTI_BINARYSAVE[k] := @_BS_UString;
          RTTI_BINARYLOAD[k] := @_BL_UString;
          RTTI_COMPARE[false, k] := @_BC_WString;
          RTTI_COMPARE[true,  k] := @_BCI_WString;
        end;
      {$endif HASVARUSTRING}
      rkWString:
        begin
          RTTI_BINARYSAVE[k] := @_BS_WString;
          RTTI_BINARYLOAD[k] := @_BL_WString;
          RTTI_COMPARE[false, k] := @_BC_WString;
          RTTI_COMPARE[true,  k] := @_BCI_WString;
        end;
      {$ifdef FPC}rkObject,{$else}{$ifdef UNICODE}rkMRecord,{$endif}{$endif}
      rkRecord:
        begin
          RTTI_BINARYSAVE[k] := @_BS_Record;
          RTTI_BINARYLOAD[k] := @_BL_Record;
          RTTI_COMPARE[false, k] := @_BC_Record;
          RTTI_COMPARE[true,  k] := @_BCI_Record;
        end;
      rkDynArray:
        begin
          RTTI_BINARYSAVE[k] := @_BS_DynArray;
          RTTI_BINARYLOAD[k] := @_BL_DynArray;
          RTTI_COMPARE[false, k] := @_BC_DynArray;
          RTTI_COMPARE[true,  k] := @_BCI_DynArray;
        end;
      rkArray:
        begin
          RTTI_BINARYSAVE[k] := @_BS_Array;
          RTTI_BINARYLOAD[k] := @_BL_Array;
          RTTI_COMPARE[false, k] := @_BC_Array;
          RTTI_COMPARE[true,  k] := @_BCI_Array;
        end;
      rkVariant:
        begin
          RTTI_BINARYSAVE[k] := @_BS_Variant;
          RTTI_BINARYLOAD[k] := @_BL_Variant;
          RTTI_COMPARE[false, k] := @_BC_Variant;
          RTTI_COMPARE[true,  k] := @_BCI_Variant;
        end;
      rkClass:
        begin
          RTTI_COMPARE[false, k] := @_BC_Object;
          RTTI_COMPARE[true,  k] := @_BCI_Object;
        end;
        // unsupported types will contain nil
    end;
  // setup internal function wrappers
  GetDataFromJson := _GetDataFromJson;
end;


initialization
  InitializeUnit;

end.

