; ES:DI = String ptr
; RET: DI = End of string ptr
BASE_TTYWriteDOSString_ES:
    .loop:
        MOV AL, [ES:DI]
        CMP AL, 0x24
        JE .end ; The mess of branches is here to avoid printing the $ char
        CALL BIOS_WriteTTYChar
        INC DI
        JMP .loop
    .end:
        RET


; ES:DI = String ptr
; RET: DI = End of string ptr
BASE_TTYWriteASCIIZString_ES:
    .loop:
        MOV AL, [ES:DI]
        CMP AL, 0x00
        JE .end ; The mess of branches is here to avoid printing the NUL char
        CALL BIOS_WriteTTYChar
        INC DI
        JMP .loop
    .end:
        RET

; DS:DI = String ptr
; RET: DX = End of string ptr
BASE_TTYWriteASCIIZString_DS:
    .loop:
        MOV AL, [DS:DI]
        CMP AL, 0x00
        JE .end ; The mess of branches is here to avoid printing the NUL char
        CALL BIOS_WriteTTYChar
        INC DI
        JMP .loop
    .end:
        RET
; DS:DX = ASCIIZ Filename ptr
; OUT: ES = Loaded file, remember to free it with DOS_FreeMemory
BASE_AllocLoadFile:
    ; 
    CALL DOS_OpenReadonly ; We only need DX set, which is set by the caller
    JC ERROR_GeneralDOSError
    PUSH AX        ; Store the file handle on the stack
    MOV BX, 4096   ; Allocate a full segment, we'll modify that in a second
    CALL DOS_AllocateMemory
    JC ERROR_GeneralDOSError
    MOV ES, AX     ; Segment now in ES
    POP BX         ; File handle now in BX
    PUSH BX
    MOV CX, 0xFFFF ; Read to end
    XOR DX, DX     ; Offset in ES:DX
    CALL DOS_ReadFileIntoES
    JC ERROR_GeneralDOSError
    ; AX = Actual number of bytes
    SHR AX, 4      ; Divide by 16 to get the para number
    INC AX         ; Failsafe if not divisible by 16
    MOV BX, AX     ; Input to INT 21H AH = 4AH requested size in paras
    ; ES Still has our file segment
    MOV AH, 4AH    ; INT 21H AH = 4AH MODIFY MEMORY BLOCK
    INT 21H
    JC ERROR_GeneralDOSError
    ; BX still has our file handle
    POP BX ; No it didn't. Debugging moment
    CALL DOS_CloseFile ; Free it and done
    JC ERROR_GeneralDOSError
    RET


; ES:SI = Bitmap ptr, header and everything
; CX = X
; DX = Y
BASE_DrawBitmapFromFile:
    CMP BYTE [ES:SI], 'B'  ; Check
    JNE .badmagic          ; The
    INC SI                 ; Magic
    CMP BYTE [ES:SI], 'M'  ; Number
    JNE .badmagic          ; Or
    INC SI                 ; Fail
    CMP BYTE [ES:SI], 'A'  ; If
    JNE .badmagic          ; Invalid
    
    INC SI
    CMP BYTE [ES:SI], 0x00 ; Bitmap version, 0 is the only valid one
    JNE .nosupport         ; If other, throw an error

    INC SI
    CMP BYTE [ES:SI], 0x00 ; Bitmap kind, this fx only supports unindexed bitmaps
    JNE .nosupport         ; If other, throw an error
    INC SI                 ; Next field
    CMP BYTE [ES:SI], 0x01 ; Color range, 0 is 4bpp, 1 is 8bpp, 0 is not supported at this time
    JNE .nosupport         ; Say so
    INC SI                 ; Next field
    MOV WORD AX, [ES:SI]   ; Stride, save it in AX
    ADD SI, 2              ; Bump up by a WORD
    MOV WORD BX, [ES:SI]   ; Lines, save in BX
    ADD SI, 2              ; And bump up, SI is now our bitmap data

    PUSH CX
    ;PUSH DX
    PUSH DS                ; Since the bitmap routine wants the data ptr in DS, we save it
    MOV CX, ES             ; Then mess with intermediates
    MOV DS, CX             ; And set the DS
    POP ES                 ; Set ES to our old DS
    ;POP DX                 ; Get the Y coord
    POP CX                 ; And the X coord
    PUSH ES                ; Save ES (Holding DS) again
    CALL BASE_DrawBitmap   ; And finally draw our bitmap
    POP DS                 ; And set DS to its former value
    RET

.badmagic:
    CALL BIOS_SetVideoMode03H
    MOV DX, STR_ErrInvalidMagic
    CALL DOS_PrintString
    JMP Exit
.nosupport:
    CALL BIOS_SetVideoMode03H
    MOV DX, STR_ErrNotSupported
    CALL DOS_PrintString
    JMP Exit
    RET


; AL = Color
BASE_ClearScreen:
    SHL EAX, 16     ; Store AX in the upper 16b of EAX
    MOV AX, [RAM_Framebuffer] ; Segment A000h (VGA framebuffer)
    MOV ES, AX      ; Store in ES
    XOR DI, DI      ; Clear DI, we want to start at the, well, start
    SHR EAX, 16     ; Restore AX from the upper 16b of EAX
    ; We need to fill the entirety of EAX with 4 bytes as in [AL:AL:AL:AL]
    ; There's probably an instruction for that which I'm not aware of...
    MOV AH, AL      ; Now, copy lower 8b of AX to upper 8b of AX
    MOV CX, AX      ; Then cache AX, we set CX later so it's not important here
    SHL EAX, 16     ; Shift the bits
    MOV AX, CX      ; And fill the lower 16b of EAX with its upper 16b
    MOV CX, 16000   ; Fill 320 * 200 / 4 DWORDs
    REP STOSD       ; Write EAX to ES:DI, inc DI
    AND EAX, 0xFFFF ; Remove the upper 16 bits of EAX so that they're not just randomly there
    RET

; DS:SI = Input
; AX = Stride (MUST be 4 or greater)
; BX = Lines
; CX = X
; DX = Y
; NOTE: EBP is used as general-purpose storage, BP is preserved, but the upper 16 bits are not.
; NOTE: All upper 16 bits of 32-bit registers will be lost
BASE_DrawBitmap:
    ; Let's think about it
    ; DS:SI = Input
    ; AX = Stride
    ; BX = Lines
    ; CX = X
    ; DX = Y
    ; And so
    ; Wait, we need to save AX, we can't use the SHL/SHR trick because CalculateBitmapOffset uses MUL and ADD...
    ; But DrawLine destroys DI, and it's unused in CalculateBitmapOffset!
    MOV DI, AX  ; So save AX first
    SHL EDI, 16 ; Shift it to upper 16b
    MOV DI, BX  ; And save BX in the lower 16b
    CALL BASE_CalculateBitmapOffset ; X = CX Y = DX; RET AX, But it messes with BX
    MOV BX, DI  ; Then first BX -> DI -> BX
    SHR EDI, 16 ; Shift
    MOV CX, DI  ; And AX -> DI -> CX
    ; Now AX = Offset, CX = Stride, DrawLine expects that exactly
    
    ; Wait, but we need CX for the counter. Fuck. Could ditch LOOP and use CMP/Jxx...
    ;XOR DX, DX ; Okay, so we can safely clean this, we already calc'd the offset
    ;INC BX ; Implementation detail because JLE is weird
    MOV DX, BX
    MOV BX, CX  ; Backup the stride
    SHL EBP, 16 ; Okay, we're doing A LOT of weirdness here, we're using the stack pointer as general-putpose storage
    MOV BP, DX  ; Store the lines there
    .loop:
        CALL BASE_DrawImageLine ; Expects AX = Offset, CX = Stride, DS:SI = Input, Destroys DI
        ADD SI, CX  ; Increment the data ptr by stride
        ADD AX, 320 ; And increment the offset by 320 to wrap around
        ;INC DX ; Finally, DX is our line counter, so increment it by 1
        ;CMP DX, BX ; And compare it against lines + 1
        ;JNE .loop ; If not there yet, loop
        DEC DX
        JNZ .loop
    MOV AX, BX   ; And bring back the stride 
    MOV BX, BP   ; Now get back the lines from the base pointer
    SHR EBP, 16  ; And fix the base pointer or the program will... well...
    RET

; CX = X
; DX = Y
; RET: AX = Offset
BASE_CalculateBitmapOffset:
    PUSH DX
    MOV AX, DX        ; AX = Y
    MOV BX, 320
    MUL BX            ; DX:AX = Y * 320
    POP DX
    ADD AX, CX        ; AX = offset
    RET

; AX = Offset
; CX = Count/Stride (MUST be 4 or greater, and a power of 2)
; NOTE: Make sure the upper 16 bits of the 32-bit regs are clear and not important
; But, 16-bit registers are preserved
; Destroys DI
; DS:SI = Input
BASE_DrawImageLine:
    ; Okay, so
    ; First of all, MOVSx copies from DS:SI to ES:DI
    ; So we need DI to be the offset with regards to the framebuffer
    ; And that offset can be calculated via BASE_CalculateBitmapOffset
    MOV DI, AX
    SHL EBX, 16 ; Save BX in the upper 16b of EBX
    MOV BX, SI ; Then store the old SI in lower 16b of EBX
    ; ES is used quite a bit throughout the program, so we save it
    SHL EDX, 16 ; Save DX in the upper 16b of EDX
    MOV DX, ES ; Then save ES in DX
    
    SHL EAX, 16 ; Store AX in the upper 16 bits of EAX
    ; Then we set ES to the framebuffer segment address
    MOV AX, [CS:RAM_Framebuffer]    ; Working framebuffer
    MOV ES, AX ; DO NOT Assume CS = DS
    
    ;
    MOV AX, CX ; Store CX in the temporarily free AX
    
    ; === Note note note ===
    ; Don't touch EAX/AX/AL/AH before after the MOV CX, AX instruction
    ; Pretty much every procedure call messes with it, so just don't
    ; === Note note note ===

    ; Since we're using MOVSD, we need to do / 4
    SHR CX, 2

    ; And go ahead
    REP MOVSD ; DS:SI -> ES:DI

    MOV CX, AX ; Bring back CX and then
    SHR EAX, 16 ; Bring back the old AX
    
    MOV SI, BX ; Restore SI
    SHR EBX, 16 ; And restore BX

    MOV ES, DX ; Restore ES
    SHR EDX, 16 ; And restore DX
    RET

; AL = Color
; CX = X
; DX = Y
BASE_DrawPixel:
    
    PUSH AX
    MOV AX, [RAM_Framebuffer]    ; Segment A000h (VGA framebuffer)
    MOV ES, AX


    ; Coordinates
    ; MOV CX, 100       ; X
    ; MOV DX, 50        ; Y

    ; Calculate offset: offset = Y * 320 + X
    
    ;MOV AX, DX        ; AX = Y
    ;MOV BX, 320
    ;MUL BX            ; DX:AX = Y * 320
    ;ADD AX, CX        ; AX = offset
    
    CALL BASE_CalculateBitmapOffset

    MOV DI, AX
    POP AX
    ; MOV AL, 0x4F      ; Color index (some pinkish violet maybe?)
    STOSB             ; Write AL to ES:DI, inc DI
    RET

; Wait for the vertical blanking period, do nothing and return.
; Use BASE_Present instead for rendering
BASE_WaitForVerticalBlank:
    ;PUSHA
    PUSH AX
    PUSH DX
    MOV DX, 0x3DA
    .v1: IN AL, DX
         TEST AL, 0x08
         JZ .v1       ; Wait for VBLANK to begin
    .v2: IN AL, DX
         TEST AL, 0x08
         JNZ .v2      ; Wait for VBLANK to end
    ;POPA
    POP DX
    POP AX
    RET

; Wait for the vertical retrace, fill the VGA framebuffer, wait for the vertical retrace to end, and return
; No input
BASE_Present:
    PUSH AX ; Save AX
    PUSH DX ; Then save DX
    MOV DX, 0x3DA
    .v1: IN AL, DX
         TEST AL, 0x08
         JZ .v1       ; Wait for VBLANK to begin
    
    PUSH DS ; Importantly, store the old data segment because the code will crap itself if we don't bring it back
    MOV DS, [RAM_Framebuffer] ; Working buffer

    MOV AX, 0xA000
    MOV ES, AX ; VGA Framebuffer

    MOV CX, 16000 ; 320 * 200 / 4
    XOR SI, SI ; Clear source offset
    XOR DI, DI ; And clear destination offset
    REP MOVSD ; TIMES CX  DS:SI -> ES:DI
    POP DS ; Restore the data segment

    MOV DX, 0x3DA
    .v2: IN AL, DX
         TEST AL, 0x08
         JNZ .v2      ; Wait for VBLANK to end
    POP DX ; Restore old DX
    POP AX ; And restore old AX
    RET

; ES = Game file segment
; Exit on fail
BASE_VerifyGameFileMagic:
    ; Magic is DGF and then 0xCF
    MOV AL, 'D'
    MOV AH, [ES:0]
    CMP AL, AH
    JNE .badmagic
    MOV AL, 'G'
    MOV AH, [ES:1]
    CMP AL, AH
    JNE .badmagic
    MOV AL, 'F'
    MOV AH, [ES:2]
    CMP AL, AH
    JNE .badmagic
    MOV AL, 0xCF
    MOV AH, [ES:3]
    CMP AL, AH
    JNE .badmagic
    JMP .goodmagic
.badmagic:
    MOV DX, STR_ErrInvalidMagic
    CALL DOS_PrintString
    JMP Exit
.goodmagic:
    RET

; AL = 0 = 16K 1 = 32K 2 = 64L
; RET: AX = Segment or Early exit
BASE_AllocFileMem:
    ; Not using a lookup table because it'd be overkill
    CMP AL, 0
    JE .a16k
    CMP AL, 1
    JE .a32k
    CMP AL, 2
    JE .a64k
    MOV DX, STR_ErrInvalidAllocation
    CALL DOS_PrintString
    JMP Exit
    .a16k:
        MOV BX, 1024
        JMP .alnst
    .a32k:
        MOV BX, 2048
        JMP .alnst
    .a64k:
        MOV BX, 4096
    .alnst:
        CALL DOS_AllocateMemory
        JC ERROR_GeneralDOSError
    RET