; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: Dynamic bit streams library.
;
;  Target OS: Any
;
;  Dependencies:
;
;  Notes: Based on works of Mateusz Tymek.
;_________________________________________________________________________________________


module "BitStream"

;struct TBitStream2
;  .dwLength   dd ?       ; length of the stream in dwords
;  .dwCurrent  dd ?       ; current bit dword.
;
;  .bitLength  db ?       ; remainder of length in bits [0..31]
;  .bitCurrent db ?       ; current bit number [0..31]
;              rb 2       ; align
;
;  .capacity   dd ?       ; capacity in dwords
;  label .data dword
;ends


struct TBitStream
  .bitLength   dd ?     ; length of the stream data in bits.
  .capacity    dd ?     ; in dword
  .biCurrent   dd ?     ; current bit index...
  .lparam      dd ?     ; user defined value.
  label .data dword
ends


; creates bits stream and returns pointer
; free it with FreeMem
proc BitsCreate, .length
begin
        push    ecx

        mov     ecx, [.length]
        add     ecx, 3
        and     ecx, $fffffffc
        add     ecx, sizeof.TBitStream

        stdcall GetMem, ecx
        jc      .finish

        sub     ecx, sizeof.TBitStream
        shr     ecx, 2  ; length in dwords.
        mov     [eax+TBitStream.capacity], ecx

        clc

.finish:
        pop     ecx
        return
endp



; puts given number of bits into stream
; returns in EAX the address of the
; stream. It can be changed if the stream was resized.

proc BitsPut, .pStream, .bits, .bit_count
begin
        pushad

        and     [.bit_count], $1f       ; max of 32 bits at once.

        mov     ebx, [.pStream]

; provide some room first:

        mov     ecx, [ebx+TBitStream.biCurrent]
        shr     ecx, 5          ; dword index
        add     ecx, 2          ; at least two dword reserve.
        cmp     ecx, [ebx+TBitStream.capacity]
        jbe     .sizeok

        call    [ResizeIt]
        mov     edx, ecx

        shl     ecx, 2  ; in bytes
        add     ecx, sizeof.TBitStream

        stdcall ResizeMem, ebx, ecx
        jc      .error                  ; can't be resized; the stream buffer is full

        mov     ebx, eax
        mov     [ebx+TBitStream.capacity], edx

.sizeok:
        ; generate mask in edi:esi
        xor     eax, eax
        dec     eax
        mov     esi, eax
        mov     edi, eax

        mov     ecx, [.bit_count]

        shl     esi, cl
        mov     ecx, [ebx+TBitStream.biCurrent]
        and     ecx, $1f

        shld    edi, esi, cl
        shld    esi, eax, cl

        ; load new bits into edx:ecx; at this point ecx == [ebx+TBitStream.bitCurrent]
        xor     edx, edx
        mov     eax, [.bits]
        shld    edx, eax, cl
        shl     eax, cl
        mov     ecx, eax

; finally, store new bits using
        mov     eax, [ebx+TBitStream.biCurrent]
        shr     eax, 5

        and     [ebx+TBitStream.data+4*eax], esi
        or      [ebx+TBitStream.data+4*eax], ecx

        inc     eax

        and     [ebx+TBitStream.data+4*eax], edi
        or      [ebx+TBitStream.data+4*eax], edx

; and fix the pointers
.done:
        mov     eax, [.bit_count]
        add     eax, [ebx+TBitStream.biCurrent]

        cmp     eax, [ebx+TBitStream.bitLength]
        jbe     @f
        mov     [ebx+TBitStream.bitLength], eax
@@:
        mov     [ebx+TBitStream.biCurrent], eax

.finish:
        mov     [esp+4*regEAX], ebx
        clc
        popad
        return

.error:
        popad
        stc
        return
endp



; reads given number of bits form the stream
proc BitsGet, .pStream, .bit_count
begin
        pushad

        and     [.bit_count], $1f

        ; generate mask (in esi)
        xor     esi, esi
        dec     esi
        mov     ecx, [.bit_count]
        shl     esi, cl
        not     esi

        mov     ebx, [.pStream]

        mov     eax, [ebx+TBitStream.biCurrent]
        mov     edx, [.bit_count]
        add     edx, eax
        cmp     edx, [ebx+TBitStream.bitLength]
        ja      .error

        mov     ecx, eax
        shr     eax, 5
        and     ecx, $1f

        mov     edx, [ebx+TBitStream.data+4*eax+4]
        mov     eax, [ebx+TBitStream.data+4*eax]

        shrd    eax, edx, cl
        and     eax, esi

; update the current position
        mov     edx, [.bit_count]
        add     [ebx+TBitStream.biCurrent], edx

        mov     [esp+4*regEAX], eax
        popad
        clc
        return

.error:
        popad
        stc
        return
endp


proc BitsSkipToByte, .pStream
begin
        mov     eax, [.pStream]
        add     [eax+TBitStream.biCurrent], 7
        and     [eax+TBitStream.biCurrent], $fffffff8
        mov     eax, [eax+TBitStream.biCurrent]
        shr     eax, 3
        return
endp

; returns the pointer at the begining of the requested block in EAX
; returns the stream pointer in EDX (it can be changed on reallocation)
proc BitsGetRoom, .pStream, .room
begin
        push    ebx ecx

        mov     edx, [.pStream]

        mov     ebx, [edx+TBitStream.biCurrent]
        add     ebx, 7
        and     ebx, $fffffff8
        shr     ebx, 3  ; index in bytes.

        mov     ecx, [edx+TBitStream.capacity]
        shl     ecx, 2  ; capacity in bytes.

        mov     eax, [.room]
        add     eax, ebx        ; the total size needed

        cmp     eax, ecx
        jbe     .sizeok

        mov     ecx, eax
        call    [ResizeIt]

; align to dword
        add     ecx, 3
        and     ecx, $fffffffc
        lea     eax, [ecx+sizeof.TBitStream]

        stdcall ResizeMem, edx, eax
        jc      .error ;.finish    ; CF=1

        shr     ecx, 2
        mov     edx, eax
        mov     [eax+TBitStream.capacity], ecx

        mov     eax, [.room]

.sizeok:
        mov     eax, [.room]
        add     eax, ebx
        lea     eax, [8*eax]
        mov     [edx+TBitStream.biCurrent], eax
        cmp     eax, [edx+TBitStream.bitLength]
        jbe     @f
        mov     [edx+TBitStream.bitLength], eax
@@:
        lea     eax, [edx+ebx+sizeof.TBitStream]
        clc

.finish:
        pop     ecx ebx
        return

.error:
        int3
        jmp     .finish

endp



; returns the current size (in bytes) of the given stream

proc BitsGetSize, .pStream
begin
        mov     eax, [.pStream]
        mov     eax, [eax+TBitStream.bitLength]
        add     eax, $7
        shr     eax, 3
        return
endp



proc BitsLoadFromFile, .ptrFileName
begin
        push    esi edi ebx

        stdcall FileOpen, [.ptrFileName]
        jc      .error

        mov     edi, eax

        stdcall FileSize, edi
        jc      .errorclose

        mov     ebx, eax
        lea     ecx, [eax+3]
        and     ecx, $fffffffc

        lea     ecx, [eax+8+sizeof.TBitStream]    ; It is for the case that the file is
                                                  ; text and we want to use it as null terminated
                                                  ; string.

        stdcall GetMem, ecx
        jc      .errorclose
        mov     esi, eax

        mov     eax, ebx
        mov     ecx, ebx
        shl     eax, 3          ; bit length
        lea     ecx, [eax+3]
        shr     ecx, 2          ; capacity in dword;

        mov     [esi + TBitStream.bitLength], eax
        mov     [esi + TBitStream.capacity], ecx

        lea     eax, [esi+TBitStream.data]
        stdcall FileRead, edi, eax, ebx
        jc      .errorclosemem

        mov     ecx, eax

        stdcall FileClose, edi

        mov     eax, esi
        clc
        pop     ebx edi esi
        return

.errorclosemem:
        stdcall FreeMem, esi

.errorclose:
        push    eax
        stdcall FileClose, edi
        pop     eax

.error:
        xor     ecx, ecx
        stc
        pop     ebx edi esi
        return
endp





endmodule