program CircularBufferIntU;
{$MODE DELPHI}

uses
  SysUtils;

type
  TIntegerCircularBuffer = class(TObject)
  private
    FBuffer: PIntegerArray;
    FFullSize: integer;
    FWritePos: integer;
    FReadPos: integer;
    FSampleCount: integer;
    function GetIntAtIndex(AIndex: word): integer;
    function GetReadAvail: integer;
    function GetWriteAvail: integer;
  public

    constructor Create(ASize: Integer); virtual;
    destructor Destroy; override;

    function AddSample(const AValue: integer): boolean;
    procedure Delete(ACount: integer);
    procedure SetFillPoint(AFillPoint: integer);

    property FullSize: integer read FFullSize;
    property ReadAvail: integer read GetReadAvail;
    property WriteAvail: integer read GetWriteAvail;
    property SampleCount: integer read FSampleCount;
    property Samples[Index: word]: integer read GetIntAtIndex; default;
  end;

constructor TIntegerCircularBuffer.Create(ASize: Integer);
begin
  inherited Create;

  FFullSize := ASize;
  FSampleCount := 0;
  FReadPos := 0;
  FWritePos := 0;
  FBuffer := AllocMem(ASize * sizeof(integer));
end;

destructor TIntegerCircularBuffer.Destroy;
begin
  FreeMem(FBuffer);

  inherited Destroy;
end;

function TIntegerCircularBuffer.AddSample(const AValue: integer): boolean;
begin
  result := true;
  if (GetWriteAvail < 1) then begin
    result := false;
    Exit;
  end;

  FBuffer^[FWritePos] := AValue;

  if (FWritePos = FFullSize -1) then FWritePos := 0 else inc(FWritePos);

  inc(FSampleCount);
end;

procedure TIntegerCircularBuffer.Delete(ACount: integer);
begin
  if (ACount <= 0 ) then begin
    Exit;
  end;

  if (ACount >= FSampleCount) then begin
    FSampleCount := 0;
    FReadPos := FWritePos;
    Exit;
  end;

  inc(FReadPos, ACount);
  FReadPos := FReadPos mod FFullSize;

  dec(FSampleCount, ACount);
end;

function TIntegerCircularBuffer.GetIntAtIndex(AIndex: word): integer;
var
  ii: integer;
begin
  ii := FReadPos + AIndex;
  if (ii > FFullSize -1) then
    ii := ii - FFullSize;

  result := FBuffer^[ii];
end;

function TIntegerCircularBuffer.GetReadAvail: integer;
begin
  result := FSampleCount;
end;

function TIntegerCircularBuffer.GetWriteAvail: integer;
begin
  result := FFullSize - FSampleCount;
end;

procedure TIntegerCircularBuffer.SetFillPoint(AFillPoint: integer);
begin
  FReadPos := 0;
  FWritePos := AFillPoint;

  FillChar(FBuffer^[0], SizeOf(Integer)* FFullSize, 0);
end;

procedure ReadValues(icb: TIntegerCircularBuffer; NextExpectedRead: Integer; const msg2: string);
var
  j: Integer;
  ReadVal: Integer;
  msg3: string;
begin
  Assert(icb.WriteAvail = 0, msg2);
  for j := 0 to Pred(icb.FullSize) do begin
    msg3 := Format('%s; j = %d', [msg2, j]);
    ReadVal := icb[j];
    Assert(ReadVal = NextExpectedRead, msg3);
    Inc(NextExpectedRead);
  end;
end;

procedure TestUsingBuffer(Size: Integer);
var
  icb: TIntegerCircularBuffer;
  i: Integer;
  NextAdd: Integer;
  NextExpectedRead: Integer;
  msg1, msg2: string;
begin
  icb := TIntegerCircularBuffer.Create(Size);
  try
    msg1 := Format('size = %d', [Size]);
    Assert(icb.FullSize = Size, msg1);
    Assert(icb.ReadAvail = 0, msg1);
    Assert(icb.WriteAvail = Size, msg1);
    Assert(icb.SampleCount = 0, msg1);

    NextAdd := 1000;
    NextExpectedRead := 1000;

    for i := 1 to 1000 do begin
      msg2 := Format('size = %d; i = %d', [Size, i]);
      while icb.WriteAvail > 0 do begin
        icb.AddSample(NextAdd);
        Inc(NextAdd);
      end;

      ReadValues(icb, NextExpectedRead, msg2);

      icb.Delete(5);
      Inc(NextExpectedRead, 5);
      Assert(icb.ReadAvail = Size - 5, msg2);
      Assert(icb.WriteAvail = 5, msg2);
      Assert(icb.SampleCount = Size - 5, msg2);
    end;
  finally
    icb.Free;
  end;
end;

begin
  TestUsingBuffer(17);
  TestUsingBuffer(29);
  TestUsingBuffer(501);
  Writeln('Done');
end.