unit ueverettrandom;

{ Random integer generation via beam-splitter quantum event generator

  Code copyright (C)2019 minesadorada@charcodelvalle.com

  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version with the following modification:

  As a special exception, the copyright holders of this library give you
  permission to link this library with independent modules to produce an
  executable, regardless of the license terms of these independent modules,and
  to copy and distribute the resulting executable under terms of your choice,
  provided that you also meet, for each linked independent module, the terms
  and conditions of the license of that module. An independent module is a
  module which is not derived from or based on this library. If you modify
  this library, you may extend this exception to your version of the library,
  but you are not obligated to do so. If you do not wish to do so, delete this
  exception statement from your version.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; if not, write to the Free Software Foundation,
  Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.

================================================================================
Description and purpose
=======================
The Everett interpretation of quantum mechanics ("Many Worlds") is that when
an interaction is made with an elementary wave function (such as an electron or
photon etc) the universe bifurcates.
ref: https://en.wikipedia.org/wiki/Many-worlds_interpretation

This happens naturally of course (just via radioactive decays in atoms of your
body there are about 5000 bifucations per second) but this component brings into
the mix "Free Will".  By requesting a random number from the online source, which
is a beam-splitter based in Austrailia you are bifurcating the Universe deliberately
- that is, based on your Free Will.
You may or may not find that interesting, but nevertheless this component gives
you this ability (to "play God" with the Universe)

The random numbers returned are truly random (i.e. not pseudorandom via algorithm)
Details of the online resource below:

================================================================================
webpage: https://qrng.anu.edu.au/

To get a set of numbers generated online by a quantum number generator:
Post to: https://qrng.anu.edu.au/API/jsonI.php?length=[array length]&type=[data type]&size=[block size]
If the request is successful, the random numbers are returned in a JSON encoded array named 'data'
(Note: block size parameter is only needed for data type=hex16)
The random numbers are generated in real-time in our lab by measuring the quantum fluctuations of the vacuum

Example to get 10 numbers of range 0-255 is
https://qrng.anu.edu.au/API/jsonI.php?length=10&type=uint8
JSON returned:
{"type":"uint8","length":10,"data":[241,83,235,48,81,154,222,4,77,120],"success":true}

Example to get 10 numbers of range  0–65535 is
https://qrng.anu.edu.au/API/jsonI.php?length=10&type=uint16
JSON returned:
{"type":"uint16","length":10,"data":[50546,25450,24289,44825,10457,49509,48848,30970,33829,47807],"success":true}

Example to get 10 hexadecimal numbers of range  00–FF is
https://qrng.anu.edu.au/API/jsonI.php?length=10&type=hex16
JSON returned:
{"type":"string","length":10,"size":1,"data":["5d","f9","aa","bf","5e","02","3c","55","6e","9e"],"success":true}

Example to get 10 hexadecimal numbers of range  0000–FFFF (blocksize=2) is
https://qrng.anu.edu.au/API/jsonI.php?length=10&type=hex16&size=2
JSON returned:
{"type":"string","length":10,"size":2,"data":["2138","592e","0643","8cdf","b955","e42f","eda6","c62a","2c66","f009"],"success":true}

Example to get 10 hexadecimal numbers of range  000000–FFFFFF (blocksize=3) is
https://qrng.anu.edu.au/API/jsonI.php?length=10&type=hex16&size=3
JSON returned:
{"type":"string","length":10,"size":3,"data":["add825","ac3530","79b708","ee8d42","683647","b6bb25","a92571","a8ae6a","963131","f62ec2"],"success":true}


Javascript:
var json = eval('('+ ajaxobject.responseText +')'); /* JSON is here*/
  document.getElementById('json_success').innerHTML = json.success;
  document.getElementById('dataHere').innerHTML = ajaxobject.responseText;
================================================================================
Version History:
V0.1.2.0 - initial commit
V0.1.3.0 - cleanup
}

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Dialogs, Controls, Forms, StdCtrls, Variants,
  everett_httpclient, open_ssl, fpjson, fpjsonrtti;

const
  C_QUANTUMSERVERLIMIT = 1024;
  C_URL = 'https://qrng.anu.edu.au/API/jsonI.php?length=%d&type=%s&size=%d';

resourcestring
  rsSSLLibraries = 'SSL libraries unavailable and/or unable to be downloaded '
    + 'on this system. Please fix.';
  rsFailedTooMan = 'Failed - Too many requests to the Quantum server%s%s';
  rsFailedQuantu = 'Failed - Quantum server refused with code %d';
  rsQuantumServe = 'Quantum server did not deliver a valid array';
  rsFailedQuantu2 = 'Failed - Quantum server refused with code %s';
  rsPleaseWaitCo = 'Please wait. Contacting Quantum Server';

type
  TQuantumNumberType = (uint8, uint16, hex16);
  TQuantumNumberDataObject = class; // Forward declaration

  // This is a persistent class with an owner
  { TEverett }
  TEverett = class(TComponent)
  private
    fHttpClient: TFPHTTPClient;
    fQuantumNumberType: TQuantumNumberType;
    fQuantumNumberDataObject: TQuantumNumberDataObject;
    fShowWaitDialog: boolean;
    fWaitDialogCaption: string;
    fArraySize,fHexSize:Integer;
    procedure SetArraySize(AValue:Integer);
  protected
    // Main worker function
    function FetchQuantumRandomNumbers(AQuantumNumberType: TQuantumNumberType;
      Alength: integer; ABlocksize: integer = 1): boolean; virtual;
    // Object that contains array results
    property QuantumNumberDataObject: TQuantumNumberDataObject
      read fQuantumNumberDataObject;
  public
    // (Dynamic) Array results
    IntegerArray: array of integer;
    HexArray: array of string;

    // TEverett should have an owner so that cleanup is easy
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    // Fetch a single random number
    function GetSingle8Bit: integer;
    function GetSingle16Bit: integer;
    function GetSingleHex: String;

    // Array functions will put results into:
    // (uint8, uint16) IntegerArray[0..Pred(ArraySize)]
    // (hex16) HexArray[0..Pred(ArraySize)]
    function GetInteger8BitArray:Boolean;
    function GetInteger16BitArray:Boolean;
    function GetHexArray:Boolean;
  published
    property NumberType: TQuantumNumberType read fQuantumNumberType
      write fQuantumNumberType default uint8;
    property ShowWaitDialog: boolean
      read fShowWaitDialog write fShowWaitDialog default True;
    property WaitDialogCaption: string read fWaitDialogCaption write fWaitDialogCaption;
    property ArraySize:Integer read fArraySize write SetArraySize default 1;
    property HexSize:Integer read fHexSize write fHexSize default 1;
  end;

  // DeStreamer.JSONToObject populates all the properties
  // Do not change any of the properties
  TQuantumNumberDataObject = class(TObject)
  private
    fNumberType: string;
    fNumberLength: integer;
    fNumbersize: integer;
    fNumberData: variant;
    fNumberSuccess: string;
  public
  published
    // Prefix property name with & to avoid using a reserved pascal word
    // Note: This bugs out the JEDI code formatter
    property &type: string read fNumberType write fNumberType;
    property length: integer read fNumberLength write fNumberLength;
    property size: integer read fNumbersize write fNumbersize;
    // Note: property "data" must be lowercase.  JEDI changes it to "Data"
    property data: variant read fNumberData write fNumberData;
    property success: string read fNumberSuccess write fNumberSuccess;
  end;

implementation

procedure TEverett.SetArraySize(AValue: Integer);
// Property setter
begin
  if Avalue <=C_QUANTUMSERVERLIMIT then
    fArraySize:=AValue
  else
    fArraySize:=1;
end;

// This is the core function.
// If successful, it populates either IntegerArray or HexArray
// Parameters:
// AQuantumNumberType can be uint8, uint16 or hex16
// ALength is the size of the returned array
// ABlocksize is only relavent if AQuantumNumberType=hex16
// it is the size of the hex number in HexArray (1=FF, 2=FFFF, 3=FFFFFF etc)
function TEverett.FetchQuantumRandomNumbers(AQuantumNumberType: TQuantumNumberType;
  Alength: integer; ABlocksize: integer): boolean;
var
  szURL: string;
  JSON: TJSONStringType;
  DeStreamer: TJSONDeStreamer;
  ct: integer;
  frmWaitDlg: TForm;
  lbl_WaitDialog: TLabel;
begin
  Result := False; // assume failure
  // Reset arrays
  SetLength(IntegerArray, 0);
  SetLength(HexArray, 0);
  // Parameter checks
  if Alength > C_QUANTUMSERVERLIMIT then
    Exit;
  if ABlocksize > C_QUANTUMSERVERLIMIT then
    Exit;

  // Is SSL installed? If not, download it.
  // If this fails then just early return FALSE;
  if not CheckForOpenSSL then
  begin
    ShowMessage(rsSSLLibraries);
    exit;
  end;

  // Make up the Quantum Server URL query
  case AQuantumNumberType of
    uint8:
      szURL := Format(C_URL, [Alength, 'uint8', ABlocksize]);
    uint16:
      szURL := Format(C_URL, [Alength, 'uint16', ABlocksize]);
    hex16:
      szURL := Format(C_URL, [Alength, 'hex16', ABlocksize]);
    else
      exit;
  end;
  try
    // Create the Wait Dialog
    frmWaitDlg := TForm.CreateNew(nil);
    with frmWaitDlg do
    begin
      // Set Dialog properties
      Height := 100;
      Width := 200;
      position := poOwnerFormCenter;
      borderstyle := bsNone;
      Caption := '';
      formstyle := fsSystemStayOnTop;
      lbl_WaitDialog := TLabel.Create(frmWaitDlg);
      with lbl_WaitDialog do
      begin
        align := alClient;
        alignment := tacenter;
        Caption := fWaitDialogCaption;
        ParentFont := True;
        Cursor := crHourGlass;
        parent := frmWaitDlg;
      end;
      Autosize := True;
      // Show it or not
      if fShowWaitDialog then
        Show;
      Application.ProcessMessages;
    end;
    with fhttpclient do
    begin
      // Set up the JSON destramer
      DeStreamer := TJSONDeStreamer.Create(nil);
      DeStreamer.Options := [jdoIgnorePropertyErrors];
      // Set up the http client
      ResponseHeaders.NameValueSeparator := ':';
      AddHeader('Accept', 'application/json;charset=UTF-8');
      //DEBUG:ShowMessage(szURL);

      // Go get the data!
      JSON := Get(szURL);
      // DEBUG: ShowMessageFmt('Response code = %d',[ResponseStatusCode]);

      // Any response other than 200 is bad news
      if (ResponseStatusCode <> 200) then
        case ResponseStatusCode of
          429:
          begin
            ShowMessageFmt(rsFailedTooMan,
              [LineEnding, JSON]);
            Exit(False);
          end;
          else
          begin
            ShowMessageFmt(rsFailedQuantu,
              [ResponseStatusCode]);
            Exit(False);
          end;
        end;
      try
        // Stream it to the object list
        DeStreamer.JSONToObject(JSON, fQuantumNumberDataObject);
        // Populate IntegerArray/Hexarray
        if VarIsArray(QuantumNumberDataObject.Data) then
        begin
          case AQuantumNumberType of
            uint8, uint16:
            begin
              SetLength(IntegerArray,
                fQuantumNumberDataObject.fNumberLength);
              for ct := 0 to Pred(fQuantumNumberDataObject.fNumberLength) do
                IntegerArray[ct] :=
                  StrToInt(fQuantumNumberDataObject.Data[ct]);
            end;
            hex16:
            begin
              SetLength(HexArray,
                fQuantumNumberDataObject.fNumberLength);
              for ct := 0 to Pred(fQuantumNumberDataObject.fNumberLength) do
                HexArray[ct] :=
                  fQuantumNumberDataObject.Data[ct];
            end;
          end;
        end
        else
        begin
          ShowMessage(rsQuantumServe);
          Exit;
        end;
      except
        On E: Exception do
          showmessagefmt(rsFailedQuantu2, [E.Message]);
        On E: Exception do
          Result := False;
      end;
    end;
  finally
    // No matter what - free memory
    DeStreamer.Free;
    frmWaitDlg.Free;
  end;
  Result := True; //SUCCESS!
  // DEBUG ShowMessage(fQuantumNumberDataObject.fNumberSuccess);
end;

constructor TEverett.Create(AOwner: TComponent);
begin
  inherited;
  fQuantumNumberType := uint8; // default is 8-bit (byte)
  fShowWaitDialog := True; // Show dialog whilst fetching data online
  fWaitDialogCaption := rsPleaseWaitCo;
  fHttpClient := TFPHTTPClient.Create(Self);
  fQuantumNumberDataObject := TQuantumNumberDataObject.Create;
  fArraySize:=1; // default
  fHexSize:=1; // default
  SetLength(IntegerArray, 0);
  SetLength(HexArray, 0);
end;

destructor TEverett.Destroy;
begin
  FreeAndNil(fQuantumNumberDataObject);
  FreeAndNil(fHttpClient);
  inherited;
end;

function TEverett.GetSingle8Bit: integer;
begin
  Result := 0;
  if FetchQuantumRandomNumbers(uint8, 1, 1) then
    Result := IntegerArray[0];
end;

function TEverett.GetSingle16Bit: integer;
begin
  Result := 0;
  if FetchQuantumRandomNumbers(uint16, 1, 1) then
    Result := IntegerArray[0];
end;

function TEverett.GetSingleHex: String;
begin
  Result:='00';
  if FetchQuantumRandomNumbers(hex16, 1, 1) then
    Result := HexArray[0];
end;

function TEverett.GetInteger8BitArray: Boolean;
// Populates IntegerArray
begin
  Result:=FetchQuantumRandomNumbers(uint8, fArraySize, 1);
end;

function TEverett.GetInteger16BitArray: Boolean;
// Populates IntegerArray
begin
  Result:=FetchQuantumRandomNumbers(uint16, fArraySize, 1);
end;

function TEverett.GetHexArray: Boolean;
// Populates HexArray
begin
  Result:=FetchQuantumRandomNumbers(hex16, fArraySize, fHexSize);
end;

end.