;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;;	VIDEO HARDWARE MODULE
;;;;   (C) Copyright 1995 Gregory Ercolano                                
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INITIALIZE SCREEN DRIVER MODULE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	
initvideo	proc near

	;;; INIT SCROLLBACK
ifdef PROM
	;; ON A PROM? USE MOST OF A 64K SEGMENT FOR SCROLL BUFFER
	mov [scrbufin],0000h
	mov [scrbufinseg],1000h
	mov [scrbufstart],0000h
	mov [scrbufstartseg],1000h
	mov [scrbufend],SCRBUF_SIZE
	mov [scrbufendseg],1000h

	mov si,1000h
	mov di,0000h
	mov cx,SCRBUF_SIZE
	mov al,0
	call memset		;SI:DI filled with value in AL for CX bytes
else
	mov [scrbufin], offset scrbuf
	mov [scrbufinseg],cs
	mov [scrbufstart], offset scrbuf
	mov [scrbufstartseg],cs
	mov [scrbufend], offset scrbuf+SCRBUF_SIZE
	mov [scrbufendseg],cs

	mov si,[scrbufinseg]
	mov di,offset scrbuf
	mov cx,SCRBUF_SIZE
	mov al,0
	call memset		;SI:DI filled with value in AL for CX bytes
endif

	;;; INIT CURSOR'S APPEARANCE
	mov ah,0ah			; 6845 register for cursor set
	mov cx,000dh			; block cursor (all scanlines on)
	call send_6845

	;;; INIT MISC VIDEO VARIABLES
	mov [phystop],00d	; set physical top line
	mov [physbottom],23d	; set physical bottom line
	mov [physleft],00d	; set physical left edge
	mov [physright],79d	; set physical right edge
	call resetscrollregion	; set logical top/bottom/left/right edges
	mov [curx],0h
	mov [cury],0h		; home cursor
	mov [curxsave],0h
	mov [curysave],0h
	mov [vidbackscroll],0h	; back scroll index (0=in active display)
	mov [hexdebug_x],0h	; hex debug byte position on line
	mov [vidflags],NO_WRAP	; initial video flags
	call curupdate		; update hardware cursor
	mov [videsc],0		; clear esc modes
	call clearescbuf	; clear the escape buffer
	mov [vidcolor],07h	; normal screen color
	mov al,1
	call cleartabstops	; reset all tabstops to every 8 chars
	mov [vidblank],0	; no blanked screen

	;;; GET SEGMENT ADDRESS OF VIDEO RAM
	mov bx,0b800h		; assume graphics card
	push ds
	  mov ax,0040h
	  mov ds,ax
	  mov ax,[ds:0010h]	; equipment flag
	  and ax,30h		; CRT bits
	  cmp ax,30h		; monochrome?
	  jne iscga		; no
	  mov bx,0b000h
iscga:
	pop ds
	mov [vidseg],bx		; save segment address
	ret
initvideo endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PRINT CHARACTER IN AL
;;	Handles ANSI modes
;;	Handles DEBUG modes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
printchar	proc near
	test [vidflags],HEX_DEBUG
	jf realprintchar
	jmp hexdebug

realprintchar:
	push ax
	  mov al,[bottom]
	  cmp [cury],al		; ENSURE WE NEVER WRITE BELOW BOTTOM LINE
	  jle pc_nostat
	  mov [cury],al		; clamp to bottom line
pc_nostat:
	pop ax
	cmp [videsc],0		; first see if this is part of an ESC sequence
	je pc_xescmode
	call videscsequence	; HANDLE ESCAPE SEQUENCE IN PROGRESS
	jmp printchardone
pc_xescmode:

	cmp al,20h		; CONTROL CHAR? Do tests to handle it
	jl pc_xprintable

printrawchar:
	; WRITE THE RAW CHARACTER TO SCREEN, NO MATTER WHAT IT LOOKS LIKE
	;	Advance the cursor, as necessary.
	;
	push ax
	  call vidaddr		; AX returns offset address
	  mov di,ax
	pop ax
	mov ah,[vidcolor]	; hi byte is current attribute
	stosw			; write the character and attribute
	inc [curx]		; adjust cursor
	push ax
	  mov al,[right]
	  cmp [curx],al		; hit right edge?
	pop ax
	jg pc_offedge
	jmp printchardone
pc_offedge:
	test [vidflags],NO_WRAP	; don't wrap when we hit edge?
	jt pc_nowrap
	push ax
	  mov al,[left]		; wrap to left edge
	  mov [curx],al	
	pop ax
	jmp pc_lf		; advance to next line
pc_nowrap:
	push ax
	  mov al,[right]
	  mov [curx],al		; clamp cursor to right edge
	pop ax
	jmp printchardone
pc_xprintable:

	cmp al,00h		; VT100: 00h - NULL?
	jnz pc_x00
	jmp printchardone	; take no action (VT100 manual, pp.42)
pc_x00:

	cmp al,07h		; VT100: 07h - BELL?
	jne pc_x07

	; INVERT THE SCREEN FOR 4 TIMER TICKS
	call vidsavescrn
	call vidinvert
	mov cx,2		; 2 ticks is approx 1/8 sec
	call wait_ticks
	call vidresscrn		; restore screen/cursor

	jmp printchardone
pc_x07:

	cmp al,08h		; VT100: 08h - BACKSPACE?
	jne pc_x08
	push ax
	  mov al,[left]
	  cmp [curx],al		; already at or beyond left edge?
	  jg pc_bs
	  mov [curx],al		; clamp to left edge
	pop ax
	jmp printchardone	; do not wrap around (VT100 manual, pp.42)
pc_bs:
	pop ax
	dec [curx]		; move back
	jmp printchardone
pc_x08:

	cmp al,09h		; VT100: 09h - TAB?
	jne pc_x09
	mov ah,0
	mov al,[curx]
	mov si,offset tabstops
	add si,ax		; index into tabstop table

	; FIND THE NEXT NEAREST TABSTOP TO THE RIGHT
pc_tabloop:
	inc si			; move to right one char
	inc al
	cmp al,[right]		; hit right edge?
	jle pc_xtabhitedge
	mov al,[right]		; stop at right edge
	jmp pc_tabsetpos
pc_xtabhitedge:
	cmp byte ptr [ds:si],1		; found tabstop yet?
	jne pc_tabloop
pc_tabsetpos:
	mov [curx],al
	jmp printchardone
pc_x09:

	cmp al,0ah		; VT100: 0ah - LINEFEED?
	jne pc_x0a
pc_lf:
	call linefeed
	jmp printchardone
pc_x0a:

	cmp al,0bh		; VT100: 0bh - VERTICAL TAB?
	je pc_lf		; handle as line feed, VT100 Manual, pp.42

	cmp al,0ch		; VT100: 0ch - FORM FEED?
	je pc_lf		; handle as line feed, VT100 Manual, pp.42

	cmp al,0dh		; VT100: 0dh - CARRIAGE RETURN?
	jne pc_x0d
	push ax
	  mov al,[left]
	  mov [curx],al		; force cursor to left edge
	pop ax
	jmp printchardone
pc_x0d:

	cmp al,1bh		; VT100: 1bh - ESC?
	jne pc_x1b
	call videscsequence
	jmp printchardone	; and otherwise ignore.
pc_x1b:

	cmp al,7fh		; VT100: 7fh - DEL?
	jne pc_x7f
	jmp printchardone	; do not print, VT100, pp.42
pc_x7f:
	jmp printrawchar	; whatever the char is, it's printable after all

printchardone:
	call curupdate		; update hardware cursor, whatever we did to it
	ret			; done
printchar	endp

;
; HANDLE ESCAPE SEQUENCE CHARACTERS
;	Buffered escape sequence includes leading ESC..
;
videscsequence	proc near
	; BUFFER THE ESC SEQUENCE
	push si
	  mov si,[videsc]
	  add si,offset escbuf
	  mov [ds:si],al		; buffer the sequence
	  inc si
	  inc [videsc]
	  cmp si,offset escbufend	; buffer overrun?
	pop si
	jl ves_xescbufend		; no.
	jmp ves_clearesc		; buffer overrun. cancel esc sequence
ves_xescbufend:
	cmp [videsc],1			; first character in ESC sequence?
	jnz ves_x0
	;;;;; HANDLE FIRST CHARACTER IN ESC SEQUENCE
	ret				; do nothing if just the ESC received
ves_x0:
	cmp [videsc],2			; second character in ESC sequence?
	je ves_2
	jmp ves_x2

ves_2:
	;;;;; HANDLE SECOND CHARACTER IN ESC SEQUENCE
	cmp al,'['			; VT100: ESC '['?
	jne ves_xbrackchar
	ret				; ignore. (part of larger sequence)
ves_xbrackchar:

	cmp al,'#'			; VT100: ESC '#'?
	jne ves_xpndchar
	ret				; ignore. (part of larger sequence)
ves_xpndchar:

	cmp al,'Z'			; VT100: ESC 'Z'? (TERMINAL IDENT)
	jne ves_xescz
	mov si,offset esczresponsemsg
	call serialoutstring
	jmp ves_clearesc
ves_xescz:

	cmp al,'='			; VT100: ESC '='? (IGNORE: APPL MODE)
	jne ves_xescequal
	; NOT SUPPORTED: keypad application mode
	jmp ves_clearesc

ves_xescequal:
	cmp al,'>'			; VT100: ESC '>'? (IGNORE: NUM MODE)
	jne ves_xescgt
	; NOT SUPPORTED: keypad numeric mode
	jmp ves_clearesc
ves_xescgt:

	cmp al,'7'			; VT100: ESC '7'? (SAVE CURSOR)
	jne ves_xseven
	; SAVE CURSOR POSITION
	mov al,[curx]			; save cursor position
	mov [curxsave],al
	mov al,[cury]
	mov [curysave],al
	jmp ves_clearesc
ves_xseven:

	cmp al,'8'			; VT100: ESC '8'? (RESTORE CURSOR)
	jne ves_xesc8
	; RESTORE PREVIOUS CURSOR POSITION
	mov al, [curxsave]		; restore cursor position
	mov [curx],al
	mov al, [curysave]
	mov [cury],al
	jmp ves_clearesc
ves_xesc8:

	cmp al,'D'			; VT100: ESC 'D'? (LINE FEED)
	jne ves_xescd
	call linefeed			; handle like a LF (VT100, pp.53)
	jmp ves_clearesc
ves_xescd:

	cmp al,'E'			; VT100: ESC 'E'? (CRLF)
	jne ves_xesce
	mov [curx],0
	call linefeed			; handle like a CRLF (VT100, pp.53)
	jmp ves_clearesc
ves_xesce:

	cmp al,'H'			; VT100: ESC 'H'? (SET TAB STOP)
	jne ves_xesch
	mov si,offset tabstops
	mov al,[curx]
	mov ah,0
	mov byte ptr [ds:si],1		; set a tab stop at current xpos
	jmp ves_clearesc
ves_xesch:

	cmp al,'M'			; VT100: ESC 'M'? (REVERSE LF)
	jne ves_xesccapm
	call revlinefeed		; handle as reverse LF (VT100, pp.53)
	jmp ves_clearesc
ves_xesccapm:

	cmp al,'c'			; VT100: ESC 'c'? (RESET TERMINAL)
	jne ves_xescc
	jmp master_init			; 'reset terminal'
ves_xescc:

	; UNKNOWN ESC <> SEQUENCE. CANCEL MODE
	jmp ves_clearesc

ves_x2:
	cmp byte ptr [escbuf+1],'#'	; are we in ESC '#' mode?
	je ves_pnd
	jmp ves_xpnd
ves_pnd:

	cmp [videsc],3			; third char in sequence?
	je ves_pnd3rd
	jmp ves_pndx3rd
ves_pnd3rd:

	cmp al,'8'			; VT100: ESC # 8? (SCREEN OF 'E'S)
	jne ves_xescpnd8
	; FILL SCREEN WITH E's
	mov ax,0745h
	call vidclear
	jmp ves_clearesc
ves_xescpnd8:
	; UNKNOWN ESC'#'<> SEQUENCE. CANCEL MODE
	jmp ves_clearesc

ves_pndx3rd:
	; UNKNOWN ESC'#'<><>.. SEQUENCE. CANCEL MODE
	jmp ves_clearesc

ves_xpnd:
	cmp byte ptr [escbuf+1],'['	; are we in ESC '[' mode?
	je ves_brackmode
	jmp ves_xescbrackmode
ves_brackmode:

	cmp al,';'			; HANDLE NUMBERIC ###;### ARGS
	je ves_brackarg
	cmp al,'9'
	jg ves_xbrackarg
	cmp al,'0'
	jl ves_xbrackarg
ves_brackarg:
	ret				; simply return, with the arg buffered
ves_xbrackarg:

	cmp al,'A'			; VT100: ESC [ Ps A (CURSOR UP)
	jne ves_xescbracka

	mov si,offset escbuf
	add si,2			; point to Ps
	mov ax,0100h			; default
	call escparseint
	mov al,[cury]
	sub al,ah
ves_ywrap:
	cmp al,0
	jge ves_escbracka_ywrap
	mov al,0
ves_escbracka_ywrap:
	cmp al,[physbottom]
	jle ves_escbracka_ywrap2
	mov al,[physbottom]
ves_escbracka_ywrap2:
	mov [cury],al
	call curupdate
	jmp ves_clearesc
ves_xescbracka:

	cmp al,'B'			; VT100: ESC [ Ps B (CURSOR DOWN)
	jne ves_xescbrackb

	mov si,offset escbuf
	add si,2			; point to Ps
	mov ax,0100h			; default
	call escparseint
	mov al,[cury]
	add al,ah
	jmp ves_ywrap

ves_xescbrackb:
	cmp al,'C'			; VT100: ESC [ Ps C (CURSOR RIGHT)
	jne ves_xescbrackc

	mov si,offset escbuf
	add si,2			; point to Ps
	mov ax,0100h			; default
	call escparseint
	mov al,[curx]
	add al,ah
ves_xwrap:
	cmp al,0
	jge ves_escbracka_xwrap
	mov al,0
ves_escbracka_xwrap:
	cmp al,[physright]
	jle ves_escbracka_xwrap2
	mov al,[physright]
ves_escbracka_xwrap2:
	mov [curx],al
	call curupdate
	jmp ves_clearesc

ves_xescbrackc:
	cmp al,'D'			; VT100: ESC [ Ps D (CURSOR LEFT)
	jne ves_xescbrackd

	mov si,offset escbuf
	add si,2			; point to Ps
	mov ax,0100h			; default
	call escparseint
	mov al,[curx]
	sub al,ah
	jmp ves_xwrap
ves_xescbrackd:

	cmp al,'K'			; VT100: ESC [ Ps K (CLEOL)
	jne ves_xescbrackk
	mov si,offset escbuf
	add si,2			; point to Ps
	mov ax,0000h			; default
	call escparseint
	;
	;	00 - clear to end of line
	;	01 - clear to start of line
	;	02 - clear line
	;
	cmp ah,00d
	jne ves_escbrackk_x00

	push ax				; VT100: ESC [ 0 K (CLEAR END OF LINE)
	push bx
	  mov al,[curx]
	  mov ah,[cury]
	  mov bl,[physright]
	  mov bh,[cury]
	  mov dx,CLEAR_SCRN_WORD
	  call clearxy2xy
	pop bx
	pop ax
	jmp ves_clearesc

ves_escbrackk_x00:
	cmp ah,01d
	jne ves_escbrackk_x01

	push ax				; VT100: ESC [ 1 K (CLEAR START OF LINE)
	push bx
	  mov al,[physleft]
	  mov ah,[cury]
	  mov bl,[curx]
	  mov bh,[cury]
	  mov dx,CLEAR_SCRN_WORD
	  call clearxy2xy
	pop bx
	pop ax
	jmp ves_clearesc
ves_escbrackk_x01:

	cmp ah,02d
	jne ves_escbrackk_x01

	push ax				; VT100: ESC [ 2 K (CLEAR CURRENT LINE)
	push bx
	  mov al,[physleft]
	  mov ah,[cury]
	  mov bl,[physright]
	  mov bh,[cury]
	  mov dx,CLEAR_SCRN_WORD
	  call clearxy2xy
	pop bx
	pop ax
ves_escbrackk_x02:
	jmp ves_clearesc
ves_xescbrackk:

	cmp al,'J'			; VT100: ESC [ Ps J (CLEAR TO EOS)
	je ves_escbrackj
	jmp ves_xescbrackj

ves_escbrackj:
	mov si,offset escbuf
	add si,2			; point to Ps
	mov ax,0000h			; default
	call escparseint
	;
	;	00 - erase from cursor to EOS inclusive (DEFAULT)
	;	01 - erase from cursor to TOS inclusive
	;	02 - erase entire screen
	;
	cmp ah,00d
	jne ves_xescbrackj00
	push ax				; VT100: ESC [ 0 J (CLEAR TO EOS)
	push bx
	  mov al,[curx]
	  mov ah,[cury]
	  mov bl,[physright]
	  mov bh,[physbottom]
	  mov dx,CLEAR_SCRN_WORD
	  call clearxy2xy
	pop bx
	pop ax
	jmp ves_clearesc

ves_xescbrackj00:
	cmp ah,01d
	jne ves_xescbrackj01
	push ax				; VT100: ESC [ 1 J (CLEAR TO TOS)
	push bx
	  mov al,[physleft]
	  mov ah,[phystop]
	  mov bl,[curx]
	  mov bh,[cury]
	  mov dx,CLEAR_SCRN_WORD
	  call clearxy2xy
	pop bx
	pop ax
	jmp ves_clearesc

ves_xescbrackj01:
	cmp ah,02d
	jne ves_xescbrackj02
	push ax				; VT100: ESC [ 2 J (CLEAR SCREEN)
	push bx
	  mov al,[physleft]
	  mov ah,[phystop]
	  mov bl,[physright]
	  mov bh,[physbottom]
	  mov dx,CLEAR_SCRN_WORD
	  call clearxy2xy
	pop bx
	pop ax
	jmp ves_clearesc

ves_xescbrackj02:
	jmp ves_clearesc

ves_xescbrackj:
	cmp al,'f'			; VT100: ESC [ Pn;Pn f  (CURSOR POSN)
	je ves_escbrackh		; same as ESC [ Pn; Pn H

	cmp al,'H'			; VT100: ESC [ Pn;Pn H
	je ves_escbrackh
	jmp ves_xescbrackh
ves_escbrackh:
	mov si,offset escbuf
	add si,2			; point to Ps
	mov ax,0101h			; default if numbers unspecified
	call escparseint		; AL=X pos, AH=Y pos
	call setcurpos			; handle 1 based #s, ORIGIN mode, etc
	jmp ves_clearesc
ves_xescbrackh:

	cmp al,'g'			; VT100: ESC [ Pv g (CLEAR TAB STOPS)
	jne ves_xescbrackg		; (TAB STOPS)
	mov si,offset escbuf
	add si,2
	mov al,0			; assume clear all tabs to _nothing_
	cmp byte ptr [ds:si],'3'	; Pv = 3? clear ALL tabs
	jne ves_escgx3
	mov al,1			; use default tabstops every 8
ves_escgx3:
	call cleartabstops		; AL=0 clear tabs, AL=1 default tabstops
	jmp ves_clearesc
ves_xescbrackg:
;
; I YAM HERE: 
;	ENSURE WE SUPPORT MULTIPLE 'Pv' SPECS WITHIN ONE ESC [ SEQUENCE!
;
	cmp al,'m'			; VT100: ESC [ Pv m (SET COLOR)
	je ves_escm
	jmp ves_xescbrackm
ves_escm:
	mov si,offset escbuf
	add si,2			; point to Ps
	mov ax,0000h			; default is reset to normal
	call escparseint		; AH=subfunction index

	mov al,ah
	cmp al,00h			; VT100: ESC [ 0 m  (RESET COLOR)
	jne ves_escm_x0
	mov [vidcolor],07h		; 'normal' video
	jmp ves_escm_done
ves_escm_x0:

	cmp al,01h			; VT100: ESC [ 1 m  (BOLD COLOR)
	jne ves_escm_x1
	or byte ptr [vidcolor],00001000b		; set bold bit
	jmp ves_escm_done
ves_escm_x1:

	cmp al,02h			; VT100: ESC [ 2 m  (HALF INTENS)
	jne ves_escm_x2
	and byte ptr [vidcolor],11110111b	; clear bold bit
	jmp ves_escm_done
ves_escm_x2:

	cmp al,04h			; VT100: ESC [ 4 m  (UNDERLINE)
	jne ves_escm_x4			;
	or byte ptr [vidcolor],00000001b; set underline bit (on MDA only)
	jmp ves_escm_done
ves_escm_x4:

	cmp al,05h			; VT100: ESC [ 5 m  (BLINK)
	jne ves_escm_x5
	or byte ptr [vidcolor],10000000b; set blink bit
	jmp ves_escm_done
ves_escm_x5:

	cmp al,07h			; VT100: ESC [ 7 m  (REVERSE VIDEO)
	jne ves_escm_x7
	mov al,[vidcolor]
	mov ah,al
	and al,01110111b		; get RGB bits only
	and ah,10001000b		; get intensity/blink bits only
	rol al,1			; swap RGB fg with bg
	rol al,1
	rol al,1
	rol al,1
	or al,ah
	mov [vidcolor],al
	jmp ves_escm_done
ves_escm_x7:
	cmp al,30d			; VT100: ESC [ 30 m  thru ESC [ 37 m
	jl ves_escm_x30			; 30-37=black,red,grn,yel,
	cmp al,37d			;       blu,mag,cya,wht respectively
	jg ves_escm_x30
	mov ah,0
	mov si,ax
	sub si,30d
	add si,offset textcolortable
	mov al,[vidcolor]
	and al,11111000b
	or al,[ds:si]
	mov [vidcolor],al
	jmp ves_escm_done
ves_escm_x30:

	cmp al,40d			; VT100: ESC [ 40 m  thru ESC [ 47 m
	jl ves_escm_x40			; 40-47=black,red,grn,yel,
	cmp al,47d			;       blu,mag,cya,wht respectively
	jg ves_escm_x40
	mov ah,0
	mov si,ax
	sub si,40d
	add si,offset pagecolortable
	mov al,[vidcolor]
	and al,10001111b
	or al,[ds:si]
	mov [vidcolor],al
	jmp ves_escm_done
ves_escm_x40:

ves_escm_done:
	jmp ves_clearesc

ves_xescbrackm:
	cmp al,'%'			; DEBUG
	jne ves_xdebug
	mov si,offset escbuf
	add si,2			; point to Ps
	mov ax,0809h			; default if numbers unspecified
	call escparseint
	int 3
ves_xdebug:

	; UNKNOWN ESC '[' SEQUENCE. CANCEL MODE.
	jmp ves_clearesc

ves_xescbrackmode:
	; UNKNOWN ESC SEQUENCE. CANCEL MODE.
	jmp ves_clearesc

; CLEAR ESCAPE SEQUENCE, DONE
ves_clearesc:
	call clearescbuf
	jmp printchardone
videscsequence	endp


;
; CLEAR ANY PENDING ESCAPE MODES
;
clearescbuf	proc near
	  mov [videsc],0

	  ; ZERO OUT ESCAPE BUFFER
	  push cx
	  push si
	  push di
	    mov si,offset escbuf
	    push cs
	    pop di
	    mov cx,ESCBUF_SIZE
	    mov al,0
	    call memset
	  pop di
	  pop si
	  pop cx
	  ret
clearescbuf	endp

; CREATE A VIDEO ADDRESS BASED ON CURRENT X/Y LOCATION
;
;	AX returns address offset into video memory
;	All other regs preserved.
;
vidaddr	proc near
	mov al,[curx]
	mov ah,[cury]
;
; CREATE A VIDEO ADDRESS BASED ON X/Y IN AL/AH RESPECTIVELY
;
;	AL=X
;	AH=Y
;
vidaddrax:
	push bx
	push ds
	  push ax
	    mov ax,0040h
	    mov ds,ax
	  pop ax
	  mov bx,ax
	  mov al,ah
	  mul byte ptr [ds:004ah]	;BIOS: width of hardware screen
	  mov bh,0
	  add ax,bx
	  sal ax,1
	pop ds
	pop bx
	ret
vidaddr	endp

; SEND WORD TO 6845 VIDEO CONTROLLER
;
;	AH=6845 register number to send word
; 	CX=word to send
;	All other regs preserved
;
send_6845	proc near
	push dx
	  push ds
	  push ax
	    mov ax,0040h	; BIOS area
	    mov ds,ax
	    mov dx,[ds:0063h]	; BIOS: 6845 base address for video card
          pop ax
	  pop ds

	  mov al,ah		; reg#
	  out dx,al		; send it
	  inc dx
	  mov al,ch		; hi byte of data sent first
	  out dx,al
	  dec dx
	  mov al,ah		; reg#+1
	  inc al
	  out dx,al
	  inc dx
	  mov al,cl		; lo byte of data sent last
	  out dx,al

	pop dx
	ret
send_6845	endp

; SCROLL THE ENTIRE SCREEN UP ONE LINE
;	All registers preserved
;
scrollup	proc near
	push ax
	push bx
	push cx
	push dx
	push si
	push di

          call savetopline	; save the top line before scrolling

	  ;;; LET'S CHEAT, AND USE THE BIOS FOR SCROLLING
	  mov cl,[left]
	  mov ch,[top]
	  mov dl,[right]
	  mov dh,[bottom]
	  mov bh,[vidcolor]
	  mov al,1
	  mov ah,6
	  int 10h

	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
	ret
scrollup	endp

; SCROLL THE ENTIRE SCREEN DOWN ONE LINE
;	All registers preserved
;
scrolldown	proc near
	push ax
	push bx
	push cx
	push dx
	push si
	push di

	  ;;; LET'S CHEAT, AND USE THE BIOS FOR SCROLLING
	  mov cl,[left]
	  mov ch,[top]
	  mov dl,[right]
	  mov dh,[bottom]
	  mov bh,[vidcolor]
	  mov al,1
	  mov ah,7
	  int 10h

	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
	ret
scrolldown	endp

;
; CLEAR THE ENTIRE PHYSICAL SCREEN INCLUDING STATUS LINE
;	Used for screen blanking
;
;	ax contains characters to use
;	Preserves all registers, except AX
;
vidclearall	proc near
	push ax
	push bx
	push dx

	  mov dx,ax
	  mov ah,[phystop]
	  mov al,[physleft]
	  mov bh,[physbottom]
	  mov bl,[physright]
	  add bh,1			; status line
	  call clearxy2xy

	pop dx
	pop bx
	pop ax
        ret
vidclearall	endp

; CLEAR THE USER SCREEN
;	(status line not altered)
;
;	ax contains characters to use
;	Preserves all registers
;
vidclear	proc near
	push ax
	push bx
	push dx

	  mov dx,ax
	  mov ah,[phystop]
	  mov al,[physleft]
	  mov bh,[physbottom]
	  mov bl,[physright]
	  call clearxy2xy

	pop dx
	pop bx
	pop ax
        ret
vidclear	endp

;
;	INVERT THE ENTIRE SCREEN
;	Used by screen flash routine
;
vidinvert	proc near
	push ax
	push bx

	  mov ah,[phystop]
	  mov al,[physleft]
	  mov bh,[physbottom]
	  mov bl,[physright]
	  inc bh		; get status line too
	  call invertxy2xy

	pop bx
	pop ax
        ret
vidinvert	endp

; DO A LINE FEED, SCROLL AS NECESSARY
;
linefeed	proc near
	inc [cury]		; move cursor down one line
	push ax
	  mov al,[bottom]
	  cmp [cury],al		; hit bottom of logical screen?
	  jle lf_done
	  mov [cury],al		; keep cursor at bottom edge
	  call scrollup		; scroll up one line, done
lf_done:
	pop ax
	ret
linefeed	endp

; DO A REVERSE LINE FEED, SCROLL AS NECESSARY
;
revlinefeed	proc near
	mov al,[top]
	dec [cury]		; move cursor up one line
	cmp [cury],al		; hit top?
	jge rlf_done		; no
	mov [cury],al		; clamp cursor to top
	call scrolldown		; scroll down one line
rlf_done:
	ret
revlinefeed	endp

vidstatusline	proc near
	push es
	push di
	push cx
	  mov al,0cdh		; double line
	  mov ah,07h
	  mov es,[vidseg]
	  mov di,80d*2*24d	; PUT STATUS ON LAST PHYSICAL LINE (line #25)
	  mov cx,80d		; #words to write
	  cld
	  rep stosw
	pop cx
	pop di
	pop es
        ret
vidstatusline	endp

; PRINT CHARACTER IN AL BOTH AS A HEX DIGIT AND RAW ASCII CHARACTER
hexdebug	proc near

	; PRINT CHARACTER AS RAW ASCII
	mov bl,[curx]
	mov [curxsave],bl

	push ax
	  mov al,3*17d		; calc index for raw characters on right
	  add al,[hexdebug_x]	; byte # on debug line
	  mov [curx],al		; set curpos for raw characters
	pop ax

	push ax
	  call printrawchar
	  mov bl,[curxsave]
	  mov [curx],bl
	pop ax

	push ax
	  ; PRINT HEX MSN
	  mov cl,4
	  shr al,cl
	  call hd_printnib	; MSN
	pop ax

	; PRINT HEX LSN
	call hd_printnib

	; PRINT SPACE FOLLOWING HEX BYTE
	mov al,' '
	call printrawchar

	; ADVANCE HEX DEBUG POSITION ON LINE
	inc [hexdebug_x]
	cmp [hexdebug_x],16d
	jl hd_done
	mov [hexdebug_x],0
	mov [curx],0
	call linefeed
hd_done:
	call curupdate
	ret
	
hd_printnib:
	and al,0fh
	cmp al,09h
	jle hd_phn
	sub al,0ah
	add al,'A'
	jmp printrawchar
hd_phn: add al,'0'
	jmp printrawchar
hexdebug	endp

; PRINT A NULL TERMINATED STRING TO THE SCREEN
;	SI contains offset address of message to print.
;	Use full ANSI compatible character out routines.
;
printstring	proc near
	mov al,[ds:si]
	cmp al,0
	je pb_done
	push si
	  call printchar
	pop si
	inc si
	jmp printstring
pb_done:
	ret
printstring	endp

; SAVE THE TOP LINE OF SCREEN INTO BACKSCROLL BUFFER
;	Used just before a line is to be scrolled off the top of the screen.
;	Top line is pushed into a large 'ring buffer' that can traverse
;	segments..
;
savetopline	proc near
	push bx
	push cx
	push si
	push di
	push es
	push ds

	  mov bx,[scrbufend]		; (save for ringwrap test)

	  mov di,[scrbufin]
	  mov es,[scrbufinseg]

	  ; COMPUTE SI - TOP LINE
	  push ax
	    mov al,[left]
	    mov ah,[top]
	    call vidaddrax
	    mov si,ax
	  pop ax
	  mov ds,[vidseg]
	  mov cx,80d			; #words to move
	  cld
stl_loop:
	  movsw				; save character and attribute

	  cmp di,bx			; ring wrap?
	  jc stl_noringwrap
	  sub di,SCRBUF_SIZE		; ring wrap back to beginning
stl_noringwrap:
	  loop stl_loop

	  pop ds
	  push ds
	  mov [scrbufin],di		; update in point
	pop ds
	pop es
	pop di
	pop si
	pop cx
	pop bx
	ret
savetopline	endp

; SAVE THE ENTIRE ACTIVE VIDEO SCREEN IN A 4K BUFFER
;	Used when viewing backscroll screens
;
vidsavescrn	proc near
	push ax
	push cx
	push si
	push di
	push es
	push ds

	  mov al,[curx]			; save cursor position too
	  mov [curxsave],al
	  mov al,[cury]
	  mov [curysave],al
	  
	  mov si,0000h			; top of screen
	  mov di,offset scrsavebuf	; 4k buffer
	  mov ax,ds
	  mov es,ax			; dest is buffer
	  mov ds,[vidseg]		; source is video scrn
	  mov cx,80d*25d		; #words to move
	  cld
	  rep movsw

	pop ds
	pop es
	pop di
	pop si
	pop cx
	pop ax
        ret

vidsavescrn	endp
; RESTORE THE ENTIRE ACTIVE VIDEO SCREEN FROM 4K BUFFER
;	Used when restoring active screen from a backscroll
;
vidresscrn	proc near
	push ax
	push cx
	push ds
	push es
	push si
	push di
	  mov al,[curxsave]			; restore cursor position
	  mov [curx],al
	  mov al,[curysave]
	  mov [cury],al
	  call curupdate

	  mov si,offset scrsavebuf
	  mov di,0000h			; top of screen
	  mov es,[vidseg]		; dest is screen
	  mov ax,cs
	  mov ds,ax			; source is buffer
	  mov cx,80d*25d		; #words to move
	  cld
	  rep movsw

	pop di
	pop si
	pop es
        pop ds
	pop cx
	pop ax
        ret

vidresscrn	endp

; TOGGLE THE DEBUG MODE
;	Resets the debug mode cursor position
;
vidtoggledebug	proc near
	xor [vidflags],HEX_DEBUG	; toggle debug mode
	mov [hexdebug_x],0h		; hex debug byte position zeroed
	; NEW LINE
	mov al,0dh
	call realprintchar
	mov al,0ah
	call realprintchar
	ret
vidtoggledebug	endp

; UPDATE THE BACKSCROLL BUFFER TO THE DISPLAY
;	Use the vidbackscroll value to figure out how many lines
;	back into the buffer we want to display.
;
vidupdatebackscroll	proc near
	push ax
	push bx
	push cx
	push dx
	push si
	push es
	push ds

	  ; COMPUTE STARTING LOCATION
	  mov cx,[vidbackscroll]	; number of lines to scroll back
	  mov si,[scrbufin]
	  mov dx,si			; save [scrbufin] address
vubs_compute:
	  sub si,80d*2
	  cmp [scrbufstart],si
	  jz vubs_nowrap
	  jc vubs_nowrap
	  add si,SCRBUF_SIZE		; wrap around
vubs_nowrap:
	  loop vubs_compute

	  ; SI NOW CONTAINS STARTING ADDRESS IN BACKSCROLL BUFFER FOR TOP LINE
	  mov bx,[scrbufend]		; (save end offset for ringwrap check)

	  mov di,0000h			; top of screen
	  mov es,[vidseg]
	  mov ds,[scrbufinseg]
	  mov cx,80d*24d		;# words to move
	  cld
vubs_loop:
	  movsw

	  cmp bx,0001h			; switched?
	  jz vubs_noringwrap		; yes, no ring wrap checking

	  cmp si,dx			; wrapped into display screen?
	  jne vubs_nosw
	  mov si,offset scrsavebuf	; switch to displaying saved screen
	  mov bx,0001h			; flag successive loops
	  jmp vubs_noringwrap
vubs_nosw:
	  cmp si,bx			; ring wrap?
	  jc vubs_noringwrap
	  sub si,SCRBUF_SIZE		; backup
vubs_noringwrap:
	  loop vubs_loop

	pop ds
	pop es
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
	ret
vidupdatebackscroll	endp

; CLEAR MEMORY TO A CERTAIN VALUE
; 	Similar to C's memset() function
;	si:di filled with value in AL for CX bytes
;	All regs preserved
;
memset	proc near
	push cx
	push si
	push di
	push es
	  push si
	  pop es
	  cld
	  rep stosb
	pop es
	pop di
	pop si
	pop cx
	ret
memset	endp

; CONVERT ASCII STRING(S) AT [ds:si] TO INTEGER IN AX
;	si returns pointing to next non-numeric character
atoi	proc near

	push bx
	push cx
	push dx
	push di

	; FIND END OF ASCII INTEGER STRING
	  mov cx,0
	  mov bx,si
atoi_loop:
	  mov al,[ds:bx]
	  cmp al,'0'
	  jl atoi_nan
	  cmp al,'9'
	  jg atoi_nan
	  inc bx
	  inc cx
	  cmp cx,5		; no more than 5 digits can be parsed
	  jle atoi_loop

atoi_nan:
	  cmp bx,si		; no digits found?
	  jnz atoi_parsenum
	  mov si,bx		; return char position in si
	  mov ax,0ffffh		; AX=0

atoi_done:
	pop di
	pop dx
	pop cx
	pop bx
	ret

atoi_parsenum:
	  push bx		; save char position
	    mov dx,0000h
	    mov di,offset dectable

	  ; PARSE THE DIGITS IN ASCENDING ORDER (RT TO LEFT)
atoi_parseloop:
	    dec bx
	    mov al,[ds:bx]		; get next ascii digit
	    and al,0fh			; 31->01, 39->09
	    mul byte ptr [ds:di]	; apply digit position
	    add dx,ax			; compile result in dx
	    add di,2
	    loop atoi_parseloop

	    mov ax,dx			; return result
	  pop si			; restore char position
	  jmp atoi_done

atoi	endp

; CONVERT INTEGER IN AX TO AN ASCII STRING AT [ds:di]
;
itoa	proc near
	push ax
	push cx
	push dx
	push si
	push di
	  mov si,10d
	  xor cx,cx
itoa_xzero:
	  xor dx,dx
	  div si
	  push dx
	  inc cx
	  or ax,ax
	  jne itoa_xzero
itoa_loop:
	  pop ax
	  or al,30h		; convert to printable int
	  mov [ds:di],al	; save it
	  inc di
	  loop itoa_loop
	  mov byte ptr [ds:di],0	; terminate string
	pop di
	pop si
	pop dx
	pop cx
	pop ax
	ret

itoa	endp

; PARSE TWO ';' SEPARATED INTEGERS AT [ds:si], RETURN LHS->AH, RHS->AL
;	AH preserved if LHS not specified
;	AL preserved if RHS not specified
;
;	;		; AH=<old-AH>, AL=<old-AL>
;	01;		; AH=01,       AL=<old-AL>
;	;02		; AH=<old-AH>, AL=02
;	01;02		; AH=01,       AL=02
;
escparseint	proc near
	push bx
	  mov bx,ax			; save previous al/ah
	  call atoi			; AL will be FF if LHS not specified
	  mov ah,al
	  cmp ah,0ffh			; LHS?
	  jne epi_xalff			; yes, handle it
	  mov ah,bh			; no LHS, maintain previous ah
epi_xalff:
	  cmp byte ptr [ds:si],';'	; RHS?
	  je epi_2			; yes, handle it
	  mov al,bl			; no RHS, maintain previous al
	pop bx
	ret
epi_2:
	  mov bh,ah			; save mods so far
	  inc si
	  call atoi
	  mov ah,bh
	  cmp al,0ffh
	  jne epi_xahff
	  mov al,bl
epi_xahff:
	pop bx
	ret

; CONVERT ASCII STRING(S) AT [ds:si] TO INTEGER IN AX
escparseint	endp

; CLEAR ALL TABSTOPS
;
;	AL=0	- clear all tabstops to nothing(!)
;	AL=1	- clear all tabstops to every 8 chars
;
cleartabstops	proc near
	push cx
	push si
	  mov cx,0
	  mov si,offset tabstops
cts_loop:
	  push cx
	    and cx,0111b
	    cmp cx,0111b
	  pop cx
	  jne cts_notabstop
	  mov [ds:si],al	; tab stop every 8 chars
	  jmp cts_tabstop
cts_notabstop:
	  mov byte ptr [ds:si],0
cts_tabstop:
	  inc si
	  inc cx
	  cmp cx,TABSTOP_SIZE
	  jl cts_loop		; (jle because 1 based)
	pop si
	pop cx
	ret
cleartabstops	endp

;
; REVERT LOGICAL SCROLL WINDOW TO PHYSICAL DEFAULTS
;
resetscrollregion	proc near
	push ax
	  mov al,[phystop]
	  mov [top],al

	  mov al,[physbottom]
	  mov [bottom],al

	  mov al,[physleft]
	  mov [left],al

	  mov al,[physright]
	  mov [right],al
	pop ax
	ret
resetscrollregion	endp

;
; SET CURSOR POSITION, AL=X, AH=Y	(1 based!!)
;	
;	Take into account ORIGIN mode, ie. absolute vs relative cursor 
;	addressing with respect to logical screen edges.
;	If AL is -1, cursor is disabled.
;
setcurpos	proc near
	push ax
	  cmp al,-1
	  jz scp_save
	  test [vidflags],RELATIVE_MODE
	  jf scp_norelative
	  add al,[left]			; apply offsets
	  add ah,[top]
scp_norelative:
	  cmp al,0
	  jz scp_alzero
	  dec al			; 1 base -> 0 base
scp_alzero:
	  cmp ah,0
	  jz scp_ahzero
	  dec ah
scp_ahzero:
scp_save:
	  mov [curx],al			; save curpos
	  mov [cury],ah
	  call curupdate		; update hardware cursor
	pop ax
	ret
setcurpos	endp

; UPDATE THE HARDWARE CURSOR POSITION
;	All regs saved
;	Physical limits are enforced
;	If [curx] == -1, cursor is disabled.
;
curupdate	proc near
	push ax
	  cmp [curx],-1
	  jne cu_nm1
	  push cx
	    mov cx,80d*26d	; move cursor off the screen
	    mov ah,0eh
	    call send_6845
	  pop cx
        pop ax
        ret
cu_nm1:
	  mov al,[physright]
	  cmp [curx],al		; enforce physical screen limits
	  jle cu_okx
	  mov [curx],al
cu_okx:	  mov al,[physbottom]
	  cmp [cury],al
	  jle cu_oky
	  mov [cury],al
cu_oky:   push cx
	    call vidaddr	; get video memory address offset
	    mov cx,ax
	    sar cx,1		; /2 for character offset
	    mov ah,0eh		; 6845 register# to set cursor position
	    call send_6845
cu_done:
	  pop cx
	pop ax
	ret
curupdate	endp

;
; HANDLE SCROLL BACK CHANGES
;     AX=#lines to scroll back (neg #s scroll down)
;
scrollbackadjust	proc near
	cmp byte ptr [vidbackscroll],0
	jnz sba_add			; already in backscroll mode? skip
	test ax,10000000b		; negative number (scroll down)?
	jt sba_done			; nowhere to go
	call vidsavescrn		; transition -> backscroll mode
	push ax
	  mov al,-1
	  mov ah,-1
	  call setcurpos
	pop ax
sba_add:
	add [vidbackscroll],ax
	cmp [vidbackscroll],0
	jg sba_noactive	
	mov [vidbackscroll],0		; down too far? (gone below zero)
	call vidresscrn
sba_done:
	ret
sba_noactive:
	cmp [vidbackscroll],SCRBUF_LINES	; up too high?
	jl sba_xoverrun
	mov [vidbackscroll],SCRBUF_LINES	; clamp to limit
sba_xoverrun:
	call vidupdatebackscroll		; update backscroll
	ret
scrollbackadjust	endp

blankupdate	proc near
	test byte ptr [vidblank],1		; blank the screen?
	jt bu_blank
	jmp vidresscrn				; restore screen/cursor
bu_blank:
	cmp byte ptr [vidbackscroll],0
	jz bu_nobackscroll
	mov byte ptr [vidbackscroll],0		; ensure not in backscroll mode
	call scrollbackadjust
bu_nobackscroll:
	call vidsavescrn			; save screen in present state
	mov ax,0000h				; clear to blanks
	call vidclearall

	mov [curx],0ffh				; move cursor off the screen
	call curupdate
	ret
blankupdate	endp

;
; CLEAR A REGION OF THE SCREEN
;
;	al/ah	- X/Y upper left screen to clear (inclusive)
;	bl/bh	- X/Y lower right screen to clear (inclusive)
;	dl/dh	- character/attribute to use for clear
;

clearxy2xy	proc near
	push ax
	push bx
	push cx
	push dx
	push si
	push di
	push ds
	push es

	  call vidaddrax
	  mov di,ax		; upper left address -> DI

	  mov ax,bx
	  call vidaddrax
	  mov cx,ax		; lower right address -> CX
	  sub cx,di		; convert CX to a count of bytes
	  shr cx,1		; divide by 2 for byte -> word convert
	  add cx,1		; handle 'inclusive' requirement

	  mov es,[vidseg]
	  mov ax,dx		; char/attrib pair to use for clear
	  cld
	  rep stosw		; clear the region

	pop es
	pop ds
	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
	ret
clearxy2xy	endp

;
; INVERT A REGION OF THE SCREEN
;
;	al/ah	- X/Y upper left screen to invert (inclusive)
;	bl/bh	- X/Y lower right screen to invert (inclusive)
;

invertxy2xy	proc near
	push ax
	push bx
	push cx
	push dx
	push si
	push di
	push ds
	push es

	  call vidaddrax
	  mov di,ax		; upper left address -> DI
	  mov si,ax

	  mov ax,bx
	  call vidaddrax
	  mov cx,ax		; lower right address -> CX
	  sub cx,di		; convert CX to a count of bytes
	  shr cx,1		; range of bytes -> words
	  add cx,1		; handle 'inclusive' requirement

	  mov es,[vidseg]
	  mov ax,[vidseg]
	  mov ds,ax
	  inc si		; address the attributes only
	  inc di
	  cld

invloop:
	  lodsb			; get attrib
	  mov ah,al
	  shl al,1		; swap the hi and low 4 bits to do invert
	  shl al,1
	  shl al,1
	  shl al,1
	  shr ah,1
	  shr ah,1
	  shr ah,1
	  shr ah,1
	  or al,ah
	  stosb	
	  inc si
	  inc di
	  loop invloop

	pop es
	pop ds
	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
	ret
invertxy2xy	endp

;
; al is number of characters to delete
;	bl = X
;	bh = Y
;

deletechar	proc near
	
	rep movsb
deletechar	endp

; WAIT SO MANY TICKS OF THE 18-TICK-PER-SECOND CLOCK
;	CX is number of ticks to wait
;	NOTE: first tick will be short..
;
wait_ticks	proc near
	push ax
	push bx
	push ds

	  mov ax,0040h			;; bios
	  mov ds,ax

wt_nexttick:
	  mov bx,[ds:006ch]		;; timer low counter
wt_wait:
	  mov ax,[ds:006ch]
	  cmp ax,bx			;; see if it changed
	  je wt_wait
	  loop wt_nexttick		;; it did, count down tick counter

	pop ds
	pop bx
	pop ax
	ret
wait_ticks	endp