.include "common.S"

.set SCREEN_WIDTH,	80
.set SCREEN_HEIGHT,	25

.code16

.section .stage1

# prints character to screen
# al:		ascii character to print
.global putc
putc:
	pushw %ax
	pushw %bx
	movb $0x0E, %ah
	xorb %bh, %bh
	int $0x10
	popw %bx
	popw %ax
	ret

# prints null terminated string to screen
# ds:si:	string address
.global puts
puts:
	pushw %si
	pushw %bx
	pushw %ax

	movb $0x0E, %ah
	xorb %bh, %bh

 .puts_loop:
	lodsb

	test %al, %al
	jz .puts_done

	int $0x10
	jmp .puts_loop

 .puts_done:
	popw %ax
	popw %bx
	popw %si
	ret

# compares memory between addresses
# si:		ptr1
# di:		ptr2
# cx:		bytes count
# return:
#	al: 1 if equal, 0 otherwise
.global memcmp
memcmp:
	# NOTE: using pusha + popa to save space
	pusha
	cld
	repe cmpsb
	popa
	setzb %al
	ret


.section .stage2

# read a character from keyboard
# return:
#	al: ascii
#	ah: bios scan code
.global getc
getc:
	movb $0x00, %ah
	int $0x16
	ret

# prints newline to screen
.global print_newline
print_newline:
	pushw %ax
	movb $'\r', %al
	call putc
	movb $'\n', %al
	call putc
	pop %ax
	ret

# prints backspace to screen, can go back a line
.global print_backspace
print_backspace:
	pushw %ax
	pushw %bx
	pushw %cx
	pushw %dx

	# get cursor position
	movb $0x03, %ah
	movb $0x00, %bh
	int $0x10

	# don't do anyting if on first row
	testb %dh, %dh
	jz .print_backspace_done

	# go one line up if on first column
	test %dl, %dl
	jz .print_backspace_go_line_up

	# otherwise decrease column
	decb %dl
	jmp .print_backspace_do_print

 .print_backspace_go_line_up:
	# decrease row and set column to the last one
	decb %dh
	movb $(SCREEN_WIDTH - 1), %dl

 .print_backspace_do_print:
	# set cursor position
	movb $0x02, %ah
	int $0x10

	# print 'empty' character (space)
	mov $' ', %al
	call putc

	# set cursor position
	movb $0x02, %ah
	int $0x10

 .print_backspace_done:
	popw %dx
	popw %cx
	popw %bx
	popw %ax
	ret

# print number to screen
# ax:	number to print
# bx:	number base
# cx:	min width (zero pads if shorter)
.global print_number
print_number:
	pusha
	pushl %ebp
	movl %esp, %ebp

	# save min width
	subl $4, %esp
	movw %cx, (%esp)

	movw $print_number_buffer, %si
	xorw %cx, %cx

 .print_number_fill_loop:
	# fill buffer with all remainders ax % bx
	xorw %dx, %dx
	divw %bx
	movb %dl, (%si)
	incw %si
	incw %cx
	testw %ax, %ax
	jnz .print_number_fill_loop

	# check if zero pad is required
	cmpw (%esp), %cx
	jae .print_number_print_loop

	# dx: saved number count
	# cx: zero pad count
	movw %cx, %dx
	movw (%esp), %cx
	subw %dx, %cx
	movb $'0', %al

 .print_number_pad_zeroes:
	call putc
	loop .print_number_pad_zeroes

	# restore number count
	movw %dx, %cx

 .print_number_print_loop:
	decw %si
	movb (%si), %al
	cmpb $10, %al
	jae .print_number_hex
	addb $'0', %al
	jmp .print_number_do_print
 .print_number_hex:
	addb $('a' - 10), %al
 .print_number_do_print:
	call putc
	loop .print_number_print_loop

	leavel
	popa
	ret

# prints 8 bit hexadecimal number to screen
#	al:		number to print
.global print_hex8
print_hex8:
	pushw %ax
	pushw %bx
	pushw %cx

	movw $16, %bx
	movw $2,  %cx
	andw $0xFF, %ax
	call print_number

	popw %cx
	popw %bx
	popw %ax
	ret

# prints 16 bit hexadecimal number to screen
#	ax:		number to print
.global print_hex16
print_hex16:
	pushw %bx
	pushw %cx

	movw $16, %bx
	movw $4,  %cx
	call print_number

	popw %cx
	popw %bx
	ret

# prints 32 bit hexadecimal number to screen
#	eax:	number to print
.global print_hex32
print_hex32:
	pushl %eax
	pushw %dx

	movw %ax, %dx

	shrl $16, %eax;
	call print_hex16

	movw %dx, %ax
	call print_hex16

	popw %dx
	popl %eax
	ret

# prints 64 bit hexadecimal number to screen
#	edx:eax:	number to print
.global print_hex64
print_hex64:
	xchgl %eax, %edx
	call print_hex32
	xchgl %eax, %edx
	call print_hex32
	ret

# test if character is printable ascii
#	al: character to test
# return:
#	al: 1 if is printable, 0 otherwise
.global isprint
isprint:
	subb $0x20, %al
	cmpb $(0x7E - 0x20), %al
	ja .isprint_not_printable
	movb $1, %al
	ret
 .isprint_not_printable:
	movb $0, %al
	ret


# memset with 32 bit registers
#   edi:    destination address
#   ecx:    bytes count
#   al:     value to set
# return:
#   edi:    destination address + bytes count
#   ecx:    0
#   other:  preserved
.global memset32
memset32:
	testl %ecx, %ecx
	jz .memset32_done

	pushf; cli
	pushw %es
	pushl %eax
	pushl %ebx
	pushl %edx

	movl %cr0, %ebx
	orb $1, %bl
	movl %ebx, %cr0

	ljmpl $GDT_CODE32, $.memset32_pmode32

 .code32
 .memset32_pmode32:
	movw $GDT_DATA32, %dx
	movw %dx, %es

	rep stosb %es:(%edi)

	ljmpl $GDT_CODE16, $.memset32_pmode16

 .code16
 .memset32_pmode16:
	andb $0xFE, %bl
	movl %ebx, %cr0
	ljmpl $0x00, $.memset32_rmode16

 .memset32_rmode16:
	popl %edx
	popl %ebx
	popl %eax
	popw %es
	popf

 .memset32_done:
	ret

# memcpy with 32 bit registers
#   esi:    source address
#   edi:    destination address
#   ecx:    bytes count
# return:
#   esi:    source address + bytes count
#   edi:    destination address + bytes count
#   ecx:    0
#   other:  preserved
.global memcpy32
memcpy32:
	testl %ecx, %ecx
	jz .memcpy32_done

	pushf; cli
	pushw %ds
	pushw %es
	pushl %ebx
	pushl %edx

	movl %cr0, %ebx
	orb $1, %bl
	movl %ebx, %cr0

	ljmpl $GDT_CODE32, $.memcpy32_pmode32

 .code32
 .memcpy32_pmode32:
	movw $GDT_DATA32, %dx
	movw %dx, %ds
	movw %dx, %es

	rep movsb %ds:(%esi), %es:(%edi)

	ljmpl $GDT_CODE16, $.memcpy32_pmode16

 .code16
 .memcpy32_pmode16:
	andb $0xFE, %bl
	movl %ebx, %cr0
	ljmpl $0x00, $.memcpy32_rmode16

 .memcpy32_rmode16:
	popl %edx
	popl %ebx
	popw %es
	popw %ds
	popf

 .memcpy32_done:
	ret

.section .bss

# enough for base 2 printing
print_number_buffer:
	.skip 16
