unit URangeEncoder;

{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}

interface

uses Classes,ULZMACommon;

const kNumBitPriceShiftBits = 6;
      kTopMask = not ((1 shl 24) - 1);
      kNumBitModelTotalBits = 11;
      kBitModelTotal = (1 shl kNumBitModelTotalBits);
      kNumMoveBits = 5;
      kNumMoveReducingBits = 2;

type TRangeEncoder=class
       private
         ProbPrices: array [0..kBitModelTotal shr kNumMoveReducingBits-1] of integer;
       public
         Stream:TStream;
         Low,Position:int64;
         Range,cacheSize,cache:integer;
         procedure SetStream(const stream:TStream);
         procedure ReleaseStream;
         procedure Init;
         procedure FlushData;
         procedure FlushStream;
         procedure ShiftLow;
         procedure EncodeDirectBits(const v,numTotalBits:integer);
         function GetProcessedSizeAdd:int64;
         procedure Encode(var probs: array of smallint;const index,symbol:integer);
         constructor Create;
         function GetPrice(const Prob,symbol:integer):integer;
         function GetPrice0(const Prob:integer):integer;
         function GetPrice1(const Prob:integer):integer;
       end;

var RangeEncoder:TRangeEncoder;

procedure InitBitModels(var probs:array of smallint);

implementation

procedure TRangeEncoder.SetStream(const stream:TStream);
begin
self.Stream:=Stream;
end;

procedure TRangeEncoder.ReleaseStream;
begin
stream:=nil;
end;

procedure TRangeEncoder.Init;
begin
position := 0;
Low := 0;
Range := -1;
cacheSize := 1;
cache := 0;
end;

procedure TRangeEncoder.FlushData;
var i:integer;
begin
for i:=0 to 4 do
    ShiftLow();
end;

procedure TRangeEncoder.FlushStream;
begin
//stream.flush;
end;

procedure TRangeEncoder.ShiftLow;
var LowHi:integer;
    temp:integer;
begin
LowHi := (Low shr 32);
if (LowHi <> 0) or (Low < int64($FF000000)) then begin
   position := position + cacheSize;
   temp := cache;
   repeat
     WriteByte(stream,byte(temp + LowHi));
     temp := $FF;
     dec(cacheSize);
     until(cacheSize = 0);
   cache := (Low shr 24);
   end;
inc(cacheSize);
Low := (Low and integer($FFFFFF)) shl 8;
end;

procedure TRangeEncoder.EncodeDirectBits(const v,numTotalBits:integer);
var i:integer;
begin
for i := numTotalBits - 1 downto 0 do begin
    Range := Range shr 1;
    if (((v shr i) and 1) = 1) then
       Low := Low + Range;
    if ((Range and kTopMask) = 0) then begin
       Range := range shl 8;
       ShiftLow;
       end;
    end;
end;

function TRangeEncoder.GetProcessedSizeAdd:int64;
begin
result:=cacheSize + position + 4;
end;

procedure InitBitModels(var probs:array of smallint);
var i:integer;
begin
for i := 0 to length(probs) -1 do
    probs[i] := kBitModelTotal shr 1;
end;

procedure TRangeEncoder.Encode(var probs: array of smallint;const index,symbol:integer);
var prob,newbound:integer;
begin
prob := probs[index];
newBound := integer((Range shr kNumBitModelTotalBits) * prob);
if (symbol = 0) then begin
   Range := newBound;
   probs[index] := (prob + ((kBitModelTotal - prob) shr kNumMoveBits));
   end else begin
       Low := Low + (newBound and int64($FFFFFFFF));
       Range := integer(Range - newBound);
       probs[index] := (prob - ((prob) shr kNumMoveBits));
       end;
if ((Range and kTopMask) = 0) then begin
   Range := Range shl 8;
   ShiftLow;
   end;
end;

constructor TRangeEncoder.Create;
var kNumBits:integer;
    i,j,start,_end:integer;
begin
kNumBits := (kNumBitModelTotalBits - kNumMoveReducingBits);
for i := kNumBits - 1 downto 0 do begin
    start := 1 shl (kNumBits - i - 1);
    _end := 1 shl (kNumBits - i);
    for j := start to _end -1 do
        ProbPrices[j] := (i shl kNumBitPriceShiftBits) +
            (((_end - j) shl kNumBitPriceShiftBits) shr (kNumBits - i - 1));
    end;
end;

function TRangeEncoder.GetPrice(const Prob,symbol:integer):integer;
begin
result:=ProbPrices[(((Prob - symbol) xor ((-symbol))) and (kBitModelTotal - 1)) shr kNumMoveReducingBits];
end;

function TRangeEncoder.GetPrice0(const Prob:integer):integer;
begin
result:= ProbPrices[Prob shr kNumMoveReducingBits];
end;

function TRangeEncoder.GetPrice1(const Prob:integer):integer;
begin
result:= ProbPrices[(kBitModelTotal - Prob) shr kNumMoveReducingBits];
end;

initialization
RangeEncoder:=TRangeEncoder.Create;
finalization
RangeEncoder.Free;
end.
