unit dstreamrewinder;

//
//  Stream wrapper. Saves beginning of a stream in a memory until StopBuffering
//  called and allows fast rewind to stream beginning.
//

{$MODE FPC}
{$MODESWITCH AUTODEREF}
{$MODESWITCH DEFAULTPARAMETERS}
{$MODESWITCH DUPLICATELOCALS}
{$MODESWITCH RESULT}

interface

uses
  dexception,
  dtypes,
  dmath,
  dstream;

type
PStreamRewinder = ^TStreamRewinder;
TStreamRewinder = object(TStream)
private
  FStream: PStream;
  FAutoFree: Boolean;
  FPosition: Int64;
  FBuffer: array of Byte;
  FBufferSize: Int64;
  FBuffering: Boolean;
  function Bufferize(Size: LongInt): LongInt;
public
  constructor Init(Stream: PStream; AutoFree: Boolean = True);
  destructor Done; virtual;
  function Create(Stream: PStream; AutoFree: Boolean = True): PStreamRewinder; static;
  procedure StopBuffering;
protected
  function DoRead(P: Pointer; Size: LongInt): LongInt; virtual;
  function DoWrite(P: Pointer; Size: LongInt): LongInt; virtual;
  function DoSeek(Offset: TInt64; Origin: TSeekOrigin): TInt64; virtual;
  function DoGetPosition: Int64; virtual;
  function DoEof: Boolean; virtual;
  function DoGetSize: Int64; virtual;
end;

implementation

function TStreamRewinder.Bufferize(Size: LongInt): LongInt;
var
  NewLength: LongInt;
begin
  NewLength := Align(FBufferSize + Size, 64);
  if Length(FBuffer) <> NewLength then
    SetLength(FBuffer, NewLength);
  Result := FStream.Read(@FBuffer[FBufferSize], Size);
  FBufferSize := FBufferSize + Result;
end;

constructor TStreamRewinder.Init(Stream: PStream; AutoFree: Boolean = True);
begin
  inherited Init;
  SetReadable;
  FStream := Stream;
  FAutoFree := AutoFree;
  FPosition := 0;
  SetLength(FBuffer, 64);
  FBufferSize := 0;
  FBuffering := True;

  FBufRequiredSize := 0;
  FBufSize := 0;
  FBufOwner := False;
  FBufAllocedSize := 0;
end;

destructor TStreamRewinder.Done;
begin
  SetLength(FBuffer, 0);
  if FAutoFree then
    FStream.Free;
  inherited Done;
end;

function TStreamRewinder.Create(Stream: PStream; AutoFree: Boolean = True): PStreamRewinder;
begin
  Result := New(PStreamRewinder, Init(Stream, AutoFree));
end;

procedure TStreamRewinder.StopBuffering;
begin
  FBuffering := False;
end;

function TStreamRewinder.DoRead(P: Pointer; Size: LongInt): LongInt;
var
  S: LongInt;
begin
  S := Max(Min(Size, FBufferSize - FPosition), 0);
  if S < Size then begin
    if FBuffering then begin
      S := S + Bufferize(Size - S);
    end else begin
      if P <> nil then begin
        Move(FBuffer[FPosition], P^, S);
        Result := S + FStream.Read(@PArrayOfByte(P)^[S], Size - S);
      end else
        Result := S + FStream.Read(nil, Size - S);
      Inc(FPosition, Result);
      Exit;
    end;
  end;

  S := Max(Min(Size, FBufferSize - FPosition), 0);
  if P <> nil then
    Move(FBuffer[FPosition], P^, S);
  Inc(FPosition, S);
  Result := S;
end;

function TStreamRewinder.DoWrite(P: Pointer; Size: LongInt): LongInt;
begin
  Result := 0;
end;

function TStreamRewinder.DoSeek(Offset: TInt64; Origin: TSeekOrigin): TInt64;
begin
  case Origin of
    SO_BEGINNING:
      begin
        if Offset = 0 then begin
          FPosition := 0;
          Result := 0;
        end else
          RaiseException('TStreamRewinder.Seek is in unsupported mode');
      end;
    else
      RaiseException('TStreamRewinder.Seek is in unsupported mode');
  end;
  Result := 0;
end;

function TStreamRewinder.DoGetPosition: Int64;
begin
  if FBuffering then begin
    Result := FPosition;
  end else
    Result := FStream.GetPosition;
end;

function TStreamRewinder.DoEof: Boolean;
begin
  Result := FStream.Eof;
end;

function TStreamRewinder.DoGetSize: Int64;
begin
  Result := FStream.GetSize;
end;

end.
