(*
  paper: Mersenne Twister: A 623-dimensionallyequidistributed uniformpseudorandom number generator
  post by 2002

  reference material
  https://baike.baidu.com/item/%E6%A2%85%E6%A3%AE%E7%B4%A0%E6%95%B0
  https://baike.baidu.com/item/%E6%A2%85%E6%A3%AE%E6%97%8B%E8%BD%AC%E7%AE%97%E6%B3%95
  https://www.cnblogs.com/lfri/p/11461695.html
  https://en.wikipedia.org/wiki/Mersenne_twister
*)

{ ****************************************************************************** }
{ * https://zpascal.net                                                        * }
{ * https://github.com/PassByYou888/zAI                                        * }
{ * https://github.com/PassByYou888/ZServer4D                                  * }
{ * https://github.com/PassByYou888/PascalString                               * }
{ * https://github.com/PassByYou888/zRasterization                             * }
{ * https://github.com/PassByYou888/CoreCipher                                 * }
{ * https://github.com/PassByYou888/zSound                                     * }
{ * https://github.com/PassByYou888/zChinese                                   * }
{ * https://github.com/PassByYou888/zExpression                                * }
{ * https://github.com/PassByYou888/zGameWare                                  * }
{ * https://github.com/PassByYou888/zAnalysis                                  * }
{ * https://github.com/PassByYou888/FFMPEG-Header                              * }
{ * https://github.com/PassByYou888/zTranslate                                 * }
{ * https://github.com/PassByYou888/InfiniteIoT                                * }
{ * https://github.com/PassByYou888/FastMD5                                    * }
{ ****************************************************************************** }
const
  MT19937N = 624;
  MT19937M = 397;
  Mag01: array [0 .. 1] of Integer = (0, Integer($9908B0DF));
  MT19937UPPER_MASK = Integer($80000000); // most significant r/w bits
  MT19937LOWER_MASK = Integer($7FFFFFFF); // least significant r bits
  TEMPERING_MASK_B = Integer($9D2C5680);
  TEMPERING_MASK_C = Integer($EFC60000);

type
  TMTVector = array [0 .. MT19937N - 1] of Integer;

type
  TMT19937Core = record
    MT: TMTVector; // the array for the state vector
    MTI: Integer;
    InternalRndSeed, InternalOldRndSeed: Cardinal;
    Thread: TCoreClassThread;
    LastActivtedTime: TTimeTick;
    Busy: Boolean;
    Instance: Integer;
    procedure BuildMT(Seed_: Integer);
    function GenRand_MT19937(): Integer;
    procedure Init(Thread_: TCoreClassThread; LastActivtedTime_: TTimeTick);
    procedure Serialize(stream: TCoreClassStream);
    procedure Unserialize(stream: TCoreClassStream);
  end;

  PMD19937Core = ^TMT19937Core;

  TMT19937List_Decl = {$IFDEF FPC}specialize {$ENDIF FPC} TGenericsList<PMD19937Core>;

  TMT19937List = class(TMT19937List_Decl)
  end;

  { Initializing the array with a seed }
procedure TMT19937Core.BuildMT(Seed_: Integer);
var
  i: Integer;
begin
  MT[0] := Integer(Seed_);
  for i := 1 to MT19937N - 1 do
    begin
      MT[i] := 1812433253 * (MT[i - 1] xor (MT[i - 1] shr 30)) + i;
      { See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. }
      { In the previous versions, MSBs of the seed affect }
      { only MSBs of the array mt[]. }
      { 2002/01/09 modified by Makoto Matsumoto }
    end;
  MTI := MT19937N;
end;

function TMT19937Core.GenRand_MT19937(): Integer;
var
  Y, K: Integer;
begin
  if InternalRndSeed <> InternalOldRndSeed then
      MTI := MT19937N + 1;

  { generate MT19937N longints at one time }
  if (MTI >= MT19937N) then
    begin
      { if BuildMT() has not been called }
      if MTI = (MT19937N + 1) then
        begin
          { default initial seed is used }
          BuildMT(Integer(InternalRndSeed));

          { hack: InternalRndSeed is not used more than once in this algorithm. Most }
          { user changes are re-initialising reandseed with the value it had }
          { at the start -> with the "not", we will detect this change. }
          { Detecting other changes is not useful, since the generated }
          { numbers will be different anyway. }
          InternalRndSeed := not(InternalRndSeed);
          InternalOldRndSeed := InternalRndSeed;
        end;

      for K := 0 to MT19937N - MT19937M - 1 do
        begin
          Y := (MT[K] and MT19937UPPER_MASK) or (MT[K + 1] and MT19937LOWER_MASK);
          MT[K] := MT[K + MT19937M] xor (Y shr 1) xor Mag01[Y and $00000001];
        end;

      for K := MT19937N - MT19937M to MT19937N - 2 do
        begin
          Y := (MT[K] and MT19937UPPER_MASK) or (MT[K + 1] and MT19937LOWER_MASK);
          MT[K] := MT[K + (MT19937M - MT19937N)] xor (Y shr 1) xor Mag01[Y and $00000001];
        end;

      Y := (MT[MT19937N - 1] and MT19937UPPER_MASK) or (MT[0] and MT19937LOWER_MASK);
      MT[MT19937N - 1] := MT[MT19937M - 1] xor (Y shr 1) xor Mag01[Y and $00000001];
      MTI := 0;
    end;

  Y := MT[MTI];
  inc(MTI);
  Y := Y xor (Y shr 11);
  Y := Y xor (Y shl 7) and TEMPERING_MASK_B;
  Y := Y xor (Y shl 15) and TEMPERING_MASK_C;
  Y := Y xor (Y shr 18);
  Result := Y;
end;

procedure TMT19937Core.Init(Thread_: TCoreClassThread; LastActivtedTime_: TTimeTick);
begin
  InternalRndSeed := 0;
  InternalOldRndSeed := 0;
  BuildMT(0);
  Thread := Thread_;
  LastActivtedTime := LastActivtedTime_;
  Busy := False;
  Instance := 0;
end;

procedure TMT19937Core.Serialize(stream: TCoreClassStream);
begin
  stream.WriteBuffer(MT[0], SizeOf(TMTVector));
  stream.WriteBuffer(MTI, 4);
  stream.WriteBuffer(InternalRndSeed, 4);
  stream.WriteBuffer(InternalOldRndSeed, 4);
end;

procedure TMT19937Core.Unserialize(stream: TCoreClassStream);
begin
  stream.ReadBuffer(MT[0], SizeOf(TMTVector));
  stream.ReadBuffer(MTI, 4);
  stream.ReadBuffer(InternalRndSeed, 4);
  stream.ReadBuffer(InternalOldRndSeed, 4);
end;

var
  MT19937InternalCritical: TCritical;
  MT19937POOL: TMT19937List;
  MT19937CoreToDelphi_: Boolean;

function InternalMT19937__(): PMD19937Core;
var
  th: TCoreClassThread;
  i: Integer;
  p: PMD19937Core;
begin
  th := TCoreClassThread.CurrentThread;

  if (th is TCompute) and (TCompute(th).FRndInstance <> nil) then
    begin
      p := TCompute(th).FRndInstance;
      p^.LastActivtedTime := GetTimeTick;
      Result := p;
      exit;
    end;

  Result := nil;

  MT19937InternalCritical.Acquire;

  i := 0;
  while i < MT19937POOL.Count do
    begin
      p := MT19937POOL[i];
      if p^.Thread = th then
        begin
          if i > 0 then
              MT19937POOL.Exchange(0, i);
          p^.LastActivtedTime := GetTimeTick;
          Result := p;
          inc(i);
        end
      else if (not p^.Busy) and (p^.Instance <= 0) and (GetTimeTick - p^.LastActivtedTime > MT19937LifeTime) then
        begin
          dispose(p);
          MT19937POOL.Delete(i);
        end
      else
          inc(i);
    end;

  if Result = nil then
    begin
      New(p);
      p^.Init(th, GetTimeTick);
      MT19937POOL.Add(p);
      Result := p;
    end;

  MT19937InternalCritical.Release;
end;

procedure RemoveMT19937Thread(th: TCoreClassThread);
var
  i: Integer;
  p: PMD19937Core;
begin
  MT19937InternalCritical.Acquire;
  i := 0;
  while i < MT19937POOL.Count do
    begin
      p := MT19937POOL[i];
      if (p^.Thread = th) or
        ((not p^.Busy) and (p^.Instance <= 0) and (GetTimeTick - p^.LastActivtedTime > MT19937LifeTime)) then
        begin
          dispose(p);
          MT19937POOL.Delete(i);
        end
      else
          inc(i);
    end;
  MT19937InternalCritical.Release;
end;

{$IFDEF DELPHI}

{$IFDEF InstallMT19937CoreToDelphi}


function DelphiRandom32Proc: UInt32;
begin
  Result := UInt32(InternalMT19937__()^.GenRand_MT19937());
end;

procedure DelphiRandomizeProc(NewSeed: UInt64);
begin
  InternalMT19937__()^.InternalRndSeed := Cardinal(NewSeed);
end;

procedure MT19937Install();
begin
  Random32Proc := DelphiRandom32Proc;
  RandomizeProc := DelphiRandomizeProc;
  MT19937CoreToDelphi_ := True;
end;
{$ENDIF InstallMT19937CoreToDelphi}

{$ENDIF DELPHI}


procedure InitMT19937Rand;
begin
  MT19937InternalCritical := TCritical.Create;
  MT19937POOL := TMT19937List.Create;
  MT19937CoreToDelphi_ := False;

{$IFDEF DELPHI}
{$IFDEF InstallMT19937CoreToDelphi}
  MT19937Install();
{$ENDIF InstallMT19937CoreToDelphi}
{$ENDIF DELPHI}
  MT19937LifeTime := 10 * 1000;
end;

procedure FreeMT19937Rand;
var
  i: Integer;
begin
  for i := 0 to MT19937POOL.Count - 1 do
      dispose(MT19937POOL[i]);
  DisposeObject(MT19937POOL);
  MT19937POOL := nil;
  MT19937InternalCritical.Free;
  MT19937InternalCritical := nil;
end;

function MT19937CoreToDelphi: Boolean;
begin
  Result := MT19937CoreToDelphi_;
end;

function MT19937InstanceNum(): Integer;
begin
  MT19937InternalCritical.Acquire;
  Result := MT19937POOL.Count;
  MT19937InternalCritical.Release;
end;

procedure SetMT19937Seed(seed: Integer);
begin
  with InternalMT19937__()^ do
    begin
      MT19937InternalCritical.Acquire;
      InternalRndSeed := seed;
      InternalOldRndSeed := seed;
      BuildMT(seed);
      Thread := TCoreClassThread.CurrentThread;
      LastActivtedTime := GetTimeTick();
      MT19937InternalCritical.Release;
    end;
end;

function GetMT19937Seed(): Integer;
begin
  Result := InternalMT19937__()^.InternalRndSeed;
end;

procedure MT19937Randomize();
begin
  SetMT19937Seed(Integer(GetTimeTick()));
end;

function MT19937Rand32(L: Integer): Integer;
begin
  { otherwise we can return values = L (JM) }
  if (L < 0) then
      inc(L);
  Result := Integer((Int64(Cardinal(InternalMT19937__()^.GenRand_MT19937())) * L) shr 32);
end;

procedure MT19937Rand32(L: Integer; dest: PInteger; num: NativeInt);
begin
  { otherwise we can return values = L (JM) }
  if (L < 0) then
      inc(L);

  with InternalMT19937__()^ do
    begin
      Busy := True;
      try
        while num > 0 do
          begin
            dest^ := Integer((Int64(Cardinal(GenRand_MT19937())) * L) shr 32);
            dec(num);
            inc(dest);
          end;
      finally
        LastActivtedTime := GetTimeTick;
        Busy := False;
      end;
    end;
end;

function MT19937Rand64(L: Int64): Int64;
begin
  { always call random, so the random generator cycles (TP-compatible) (JM) }
  with InternalMT19937__()^ do
      Result := Int64((UInt64(Cardinal(GenRand_MT19937())) or ((UInt64(Cardinal(GenRand_MT19937())) shl 32))) and $7FFFFFFFFFFFFFFF);
  if (L <> 0) then
      Result := Result mod L
  else
      Result := 0;
end;

procedure MT19937Rand64(L: Int64; dest: PInt64; num: NativeInt);
begin
  with InternalMT19937__()^ do
    begin
      Busy := True;
      try
        while num > 0 do
          begin
            dest^ := Int64((UInt64(Cardinal(GenRand_MT19937())) or ((UInt64(Cardinal(GenRand_MT19937())) shl 32))) and $7FFFFFFFFFFFFFFF);
            if (dest^ <> 0) then
                dest^ := dest^ mod L
            else
                dest^ := 0;
            dec(num);
            inc(dest);
          end;
      finally
        LastActivtedTime := GetTimeTick;
        Busy := False;
      end;
    end;
end;

function MT19937RandE: Extended;
const
  f = Extended(1.0) / (Int64(1) shl 32);
begin
  Result := f * Cardinal(InternalMT19937__()^.GenRand_MT19937());
end;

procedure MT19937RandE(dest: PExtended; num: NativeInt);
const
  f = Extended(1.0) / (Int64(1) shl 32);
begin
  with InternalMT19937__()^ do
    begin
      Busy := True;
      try
        while num > 0 do
          begin
            dest^ := f * Cardinal(GenRand_MT19937());
            dec(num);
            inc(dest);
          end;
      finally
        LastActivtedTime := GetTimeTick;
        Busy := False;
      end;
    end;
end;

function MT19937RandF: Single;
const
  f = Single(1.0) / (Int64(1) shl 32);
begin
  Result := f * Cardinal(InternalMT19937__()^.GenRand_MT19937());
end;

procedure MT19937RandF(dest: PSingle; num: NativeInt);
const
  f = Single(1.0) / (Int64(1) shl 32);
begin
  with InternalMT19937__()^ do
    begin
      Busy := True;
      try
        while num > 0 do
          begin
            dest^ := f * Cardinal(GenRand_MT19937());
            dec(num);
            inc(dest);
          end;
      finally
        LastActivtedTime := GetTimeTick;
        Busy := False;
      end;
    end;
end;

function MT19937RandD: Double;
const
  f = Double(1.0) / (Int64(1) shl 32);
begin
  Result := f * Cardinal(InternalMT19937__()^.GenRand_MT19937());
end;

procedure MT19937RandD(dest: PDouble; num: NativeInt);
const
  f = Double(1.0) / (Int64(1) shl 32);
begin
  with InternalMT19937__()^ do
    begin
      Busy := True;
      try
        while num > 0 do
          begin
            dest^ := f * Cardinal(GenRand_MT19937());
            dec(num);
            inc(dest);
          end;
      finally
        LastActivtedTime := GetTimeTick;
        Busy := False;
      end;
    end;
end;

procedure MT19937SaveToStream(stream: TCoreClassStream);
begin
  InternalMT19937__()^.Serialize(stream);
end;

procedure MT19937LoadFromStream(stream: TCoreClassStream);
begin
  InternalMT19937__()^.Unserialize(stream);
end;

{ ****************************************************************************** }
{ * TMT19937 classes                                                           * }
{ ****************************************************************************** }
function TMT19937Random.GetSeed: Integer;
begin
  with PMD19937Core(FRndInstance)^ do
      Result := InternalRndSeed;
end;

procedure TMT19937Random.SetSeed(const Value: Integer);
begin
  with PMD19937Core(FRndInstance)^ do
    begin
      InternalRndSeed := Value;
      InternalOldRndSeed := Value;
      BuildMT(Value);
    end;
end;

constructor TMT19937Random.Create;
begin
  inherited Create;
  FInternalCritical := TCritical.Create;
  FRndInstance := InternalMT19937__();
  AtomInc(PMD19937Core(FRndInstance)^.Instance);
end;

destructor TMT19937Random.Destroy;
begin
  FInternalCritical.Free;
  AtomDec(PMD19937Core(FRndInstance)^.Instance);
  inherited Destroy;
end;

procedure TMT19937Random.Rndmize;
begin
  FInternalCritical.Acquire;
  with PMD19937Core(FRndInstance)^ do
      InternalRndSeed := GetTimeTick;
  FInternalCritical.Release;
end;

function TMT19937Random.Rand32(L: Integer): Integer;
begin
  FInternalCritical.Acquire;
  { otherwise we can return values = L (JM) }
  if (L < 0) then
      inc(L);
  with PMD19937Core(FRndInstance)^ do
      Result := Integer((Int64(Cardinal(GenRand_MT19937())) * L) shr 32);
  FInternalCritical.Release;
end;

procedure TMT19937Random.Rand32(L: Integer; dest: PInteger; num: NativeInt);
begin
  FInternalCritical.Acquire;
  { otherwise we can return values = L (JM) }
  if (L < 0) then
      inc(L);

  with PMD19937Core(FRndInstance)^ do
    begin
      while num > 0 do
        begin
          dest^ := Integer((Int64(Cardinal(GenRand_MT19937())) * L) shr 32);
          dec(num);
          inc(dest);
        end;
    end;
  FInternalCritical.Release;
end;

function TMT19937Random.Rand64(L: Int64): Int64;
begin
  FInternalCritical.Acquire;
  { always call random, so the random generator cycles (TP-compatible) (JM) }
  with PMD19937Core(FRndInstance)^ do
      Result := Int64((UInt64(Cardinal(GenRand_MT19937())) or ((UInt64(Cardinal(GenRand_MT19937())) shl 32))) and $7FFFFFFFFFFFFFFF);
  if (L <> 0) then
      Result := Result mod L
  else
      Result := 0;
  FInternalCritical.Release;
end;

procedure TMT19937Random.Rand64(L: Int64; dest: PInt64; num: NativeInt);
begin
  FInternalCritical.Acquire;
  with PMD19937Core(FRndInstance)^ do
    begin
      while num > 0 do
        begin
          dest^ := Int64((UInt64(Cardinal(GenRand_MT19937())) or ((UInt64(Cardinal(GenRand_MT19937())) shl 32))) and $7FFFFFFFFFFFFFFF);
          if (dest^ <> 0) then
              dest^ := dest^ mod L
          else
              dest^ := 0;
          dec(num);
          inc(dest);
        end;
    end;
  FInternalCritical.Release;
end;

function TMT19937Random.RandE: Extended;
const
  f = Extended(1.0) / (Int64(1) shl 32);
begin
  FInternalCritical.Acquire;
  with PMD19937Core(FRndInstance)^ do
      Result := f * Cardinal(GenRand_MT19937());
  FInternalCritical.Release;
end;

procedure TMT19937Random.RandE(dest: PExtended; num: NativeInt);
const
  f = Extended(1.0) / (Int64(1) shl 32);
begin
  FInternalCritical.Acquire;
  with PMD19937Core(FRndInstance)^ do
    begin
      while num > 0 do
        begin
          dest^ := f * Cardinal(GenRand_MT19937());
          dec(num);
          inc(dest);
        end;
    end;
  FInternalCritical.Release;
end;

function TMT19937Random.RandF: Single;
const
  f = Single(1.0) / (Int64(1) shl 32);
begin
  FInternalCritical.Acquire;
  with PMD19937Core(FRndInstance)^ do
      Result := f * Cardinal(GenRand_MT19937());
  FInternalCritical.Release;
end;

procedure TMT19937Random.RandF(dest: PSingle; num: NativeInt);
const
  f = Single(1.0) / (Int64(1) shl 32);
begin
  FInternalCritical.Acquire;
  with PMD19937Core(FRndInstance)^ do
    begin
      while num > 0 do
        begin
          dest^ := f * Cardinal(GenRand_MT19937());
          dec(num);
          inc(dest);
        end;
    end;
  FInternalCritical.Release;
end;

function TMT19937Random.RandD: Double;
const
  f = Double(1.0) / (Int64(1) shl 32);
begin
  FInternalCritical.Acquire;
  with PMD19937Core(FRndInstance)^ do
      Result := f * Cardinal(GenRand_MT19937());
  FInternalCritical.Release;
end;

procedure TMT19937Random.RandD(dest: PDouble; num: NativeInt);
const
  f = Double(1.0) / (Int64(1) shl 32);
begin
  FInternalCritical.Acquire;
  with PMD19937Core(FRndInstance)^ do
    begin
      while num > 0 do
        begin
          dest^ := f * Cardinal(GenRand_MT19937());
          dec(num);
          inc(dest);
        end;
    end;
  FInternalCritical.Release;
end;

function TMT19937Random.RandBool: Boolean;
begin
  Result := ODD(Rand32(MaxInt));
end;
