; ES = Object table segment
; CX = Object count, must be divisible by 8
TABLE_InitializeBitmap:
    MOV WORD [ES:0], CX ; Store the object count
    SHR CX, 3 ; Divide CX by 8
    MOV AL, 0 ; Fill with zeroes
    XOR DI, DI ; The bitmap is the first section of the table save for the obj count
    ADD DI, 2 ; First WORD is the object count
    REP STOSB ; TIMES CX Fill ES:DI with AL. 
              ; We can't assume the bitmap will be divisible by a WORD or DWORD,
              ; but thankfully this only really gets called once during the entire lifetime of the program.
    RET

; ES = Object table segment
; AX = Object handle
; RET: ES:DI = Object addr
TABLE_GetObjectAddr:
    MOV WORD BX, [ES:0] ; Get object count
    MOV DI, BX     ; Set to object count
    SHR DI, 3      ; Divide by 8
    ADD DI, 2      ; And skip the object count
    ADD DI, BX     ; Then add object lengths
    SHL AX, 4      ; Multiply the object handle by 16
    ADD DI, AX     ; And point to the proper paragraph
    ADD DI, 15     ; Make sure we round up if not aligned
    AND DI, 0xFFF0 ; Clear the lower 4 bits to align to 16 bytes
    RET
; AX = Object handle
; ES = Object table segment
TABLE_ObjFree:
    PUSH DI         ; Store DI
    MOV DI, [ES:0]  ; Store object count
    SHR DI, 3       ; Divide by 8 to skip the bitmap
    ADD DI, 2       ; Skip the object count WORD
    ADD DI, AX      ; Ptr to the object length
    MOV BYTE CL, [ES:DI]    ; Get the object length
    CALL TABLE_INTR_ObjFree ; And free the specified bits
    POP DI                  ; Restore DI
    RET

; ES = Object table segment
; AX = Object handle
; CX = Number of paragraphs to free
TABLE_INTR_ObjFree:
    PUSH AX ; Store the object handle
    PUSH CX ; And store the object length
    .loop:
        BTR [ES:2], AX ; Object handle is simply the object position, we can use it verbatim
        INC AX         ; Increase the bit position
        DEC CX         ; And decrease the counter
        JNZ .loop      ; If counter is nonzero, loop
    POP CX  ; Restore object length
    POP AX  ; Restore object handle
    ; Technically those two are now invalid, but I'm not sure how the final code will look like.
    RET

; ES = Object segment
; DX = Object offset
; CX = Object length
TABLE_INTR_SetObjLength:
    PUSH DI              ; Store DI
    MOV WORD DI, [ES:0]  ; Store the object count
    SHR DI, 3            ; Divide by 8 to skip the bitmap
    ADD DI, 2            ; Skip the object count
    ADD DI, DX           ; Ptr to the object size
    MOV BYTE [ES:DI], CL ; Store the object size
    POP DI               ; And restore DI
    RET

; ES = Object table segment
; CX = Number of paragraphs to allocate
; RET AX = Object handle (bit index)
TABLE_ObjAlloc:
    PUSH CX
    MOV BX, [ES:0]         ; Object count
    SUB BX, CX             ; Max starting bit index = total - needed
    XOR DX, DX             ; DX = current bit index (our cursor)

.next_candidate:
    PUSH DX                ; Save starting bit index
    PUSH CX                ; Save number of bits to test
    MOV SI, DX             ; SI = test index
    MOV DI, CX             ; DI = countdown

.test_bits:
    BT [ES:2], SI
    JC .bit_used           ; If bit set, abort this sequence
    INC SI
    DEC DI
    JNZ .test_bits

    ; If we got here, we found CX contiguous 0s starting at DX
    POP CX                 ; Restore CX for writing
    POP DX                 ; DX = starting index
    MOV SI, DX

.set_bits:
    BTS [ES:2], SI
    INC SI
    LOOP .set_bits

    MOV AX, DX             ; Return object handle = starting bit
    POP CX                 ; Clean up
    CALL TABLE_INTR_SetObjLength
    RET

.bit_used:
    POP CX
    POP DX
    INC DX
    CMP DX, BX
    JBE .next_candidate

.fail:
    MOV AX, 0xFFFF         ; Error: no space
    STC
    RET
