;
; PXString
;

%define PEXL_RTL_PXSTRING_ASM
%include "pexl_rtl.h.asm"

;                                                   ptr here
;                                                    v
; pxstring := (refct allocation_size destructor_fun) strbuff strbuffsize strlen
; Using double indirection allows for resizing without impacting the actual
;   pointer, so 24 bytes of overhead for every string (C beats us and that's OK)
; So the main pxstring is a pxobject, but strbuff is NOT, it MUST be a separate
;   memory allocation (that can be realloc'd/resized independently)

;

; protected void pxo_string_new()
;  Allocate a new string with a null string buffer
; @return pxobj*
DECLARE_MANGLED pxo_string_new, 0
	;
	push ebp
	mov ebp, esp
	
	; return pxo_new( 3*sizeof(int) )
	push PXSTRING_SIZE
	call pxo_new
	
	mov dword[eax + PXSTRING_OFFSET_BUFFER  ], 0 ;buffer
	mov dword[eax + PXSTRING_OFFSET_BUFFSIZE], 0 ;bufflen
	mov dword[eax + PXSTRING_OFFSET_STRLEN  ], 0 ;strlen
	
	mov dword[eax - PXO_SIZE + PXO_OFFSET_DESTRUCTOR], pxo_string_dtor
	
	leave
	ret 0
	
;private void pxo_string_dtor(pxstring* this)
pxo_string_dtor:
	;
	push ebp
	mov ebp, esp
	
	;free(this + PXSTRING_OFFSET_BUFFER)	
	mov eax, dword[ebp+8]
	push dword[eax + PXSTRING_OFFSET_BUFFER]
	call pxl_rtl_free
	;
	leave
	ret 4 ; 1 argument

; protected void pxo_string_dup(pxobj* other)
;  Duplicate a PXString. Used whenever referencing constants (since otherwise
;   assigning a string from a constant would render it unmodifyable)
;  Allocates only the minimal buffer (shrinks string from BUFFSIZE down to STRLEN).
;  Arguably should be used to implement operator= for string types - in general,
;   should operator= perform a value copy?
;
; @param pxobj* other
; @return pxobj* new
DECLARE_MANGLED pxo_string_dup, 4
	;
	push ebp
	mov ebp, esp
	push ebx
	push ecx
	
	; [ebp-8] := new PXString;
	call pxo_string_new
	push eax ; ebp-12 {pushed two registers}
	
	; new->strlen  = other->strlen
	; new->bufflen = other->strlen
	mov ebx, dword[ebp+8] ;other
	mov ecx, dword[ebx + PXSTRING_OFFSET_STRLEN]
	mov dword[eax + PXSTRING_OFFSET_BUFFSIZE], ecx
	mov dword[eax + PXSTRING_OFFSET_STRLEN],   ecx
	
	; new->buffer  = malloc(other->strlen)
	push dword[ebx + PXSTRING_OFFSET_STRLEN]
	call pxl_rtl_malloc
	
	push eax ;ebp-16
	mov eax, dword[ebp-12]
	mov ecx, dword[ebp-16]
	mov dword[eax + PXSTRING_OFFSET_BUFFER], ecx
		
	; memcpy(dest, source, numbytes)
	push dword[eax + PXSTRING_OFFSET_STRLEN]   ;numbytes
	push dword[ebx + PXSTRING_OFFSET_BUFFER]   ;source
	push dword[eax + PXSTRING_OFFSET_BUFFER]   ;dest
	call pxl_rtl_memcpy
	
	; return [dest]
	mov eax, dword[ebp-12]
	
	;
	pop ecx
	pop ebx
	leave
	ret 4 ; one argument
	
	
; protected int pxo_string_cmp
;  Compares two strings. Returns 0 for equal, or 1/-1 for which is greater.
; @param pxstring* left
; @param pxstring* right
; @return int
DECLARE_MANGLED pxo_string_cmp, 8

	push ebp
	mov ebp, esp
	;pusha ; trash 8 registers [ebp-4 through ebp-32 inclusive], free stack starts at esp=ebp-36

	;
	mov eax, dword[ebp+ 8] ;left
	mov ebx, dword[ebp+12] ;right
		
	; Get smaller of two lengths
	; SSE4 has PMAXUD 
	mov ecx, dword[eax + PXSTRING_OFFSET_STRLEN]
	mov edx, dword[ebx + PXSTRING_OFFSET_STRLEN]
	cmp ecx, edx	
	push ecx ;ebp-4: the shorter of the two lengths
	jl .after1
	mov dword[ebp-4], edx	
.after1:
	
	; Call memcmp
	push dword[ebp-4]                        ; numbytes
	push dword[ebx + PXSTRING_OFFSET_BUFFER] ; right
	push dword[eax + PXSTRING_OFFSET_BUFFER] ; left
	call pxl_rtl_memcmp
		
	; If result was equal and lengths were not equal
	cmp eax, 0
	jne .done ;{
	
		; Equal until this point - just return longest string
		mov eax, dword[ebp +8]
		mov ebx, dword[ebp+12]
		mov ecx, dword[eax + PXSTRING_OFFSET_STRLEN]
		mov edx, dword[ebx + PXSTRING_OFFSET_STRLEN]
		cmp ecx, edx
		mov eax, 0
		je .after2
		mov eax, 1
		jl .after2
			mov eax, -1
.after2:
		
	
	;}
	
.done:
	add esp, 4 ; pop local so that we can popa
	;popa
	leave
	ret 8 ; two arguments
	
	
; protected pxstring pxo_string_substr(sz, endpos, startpos)
;   Creates a new string, based on a slice of an existing string.
;   Parameters are in a strange order to elide copies in the compiler!
; @param pxstring* sz
; @param int       endpos
; @param int       startpos
; @return pxstring*
DECLARE_MANGLED pxo_string_substr, 12
%push

	push ebp
	mov ebp, esp
	
	%define sz dword[ebp+8]
	%define endpos dword[ebp+12]
	%define startpos dword[ebp+16]
	
	; strlen
	mov eax, sz
	mov ebx, dword[eax + PXSTRING_OFFSET_STRLEN]
	
	; Verify boundaries
	
	; if (strlen < startpos) return "";
	cmp ebx, startpos
	jl .blank
	
	; if (strlen < endpos) endpos = strlen;
	cmp ebx, endpos
	jg .notless
		mov endpos, ebx
.notless:
	
	; if (endpos < startpos) return "";
	mov ecx, endpos
	cmp ecx, startpos
	jl .blank
	
	; local1 = endpos - startpos
	sub ecx, startpos 
	push ecx ; SAVE
	%define local1 dword[ebp-4]
	
	; edx = malloc(ecx)
	push ecx
	call pxl_rtl_malloc ; clobbers ecx,edx
	push eax ; SAVE
	%define local2 dword[ebp-8]
	
	; memcpy(source:sz->buff, dest:edx, length:ecx)
	mov eax, sz
	mov ebx, dword[eax + PXSTRING_OFFSET_BUFFER]
	add ebx, startpos
	push local1 ; length
	push ebx ; source
	push local2 ; dest
	call pxl_rtl_memcpy ; clobbers ecx+edx
	
	; eax = new PXString(); - no args
	call pxo_string_new
	
	; eax->{buff, buflen, strlen} = ...
	mov ecx, local1
	mov edx, local2
	mov dword[eax + PXSTRING_OFFSET_BUFFER], edx
	mov dword[eax + PXSTRING_OFFSET_BUFFSIZE], ecx
	mov dword[eax + PXSTRING_OFFSET_STRLEN], ecx
	
	; return eax
	jmp .done
	
.blank:
	call pxo_string_new
	
.done:
	leave
	ret 12 ; three arguments
%pop
	
; protected pxstring pxo_string_concat
;   Allocate and return a new pxo_string by concatenating the two arguments.
;   The order of arguments is not what you might expect! The order is reversed
;   in order to elide copies in the compiler.
; @param pxstring* right
; @param pxstring* left
; @return pxstring
DECLARE_MANGLED pxo_string_concat, 8
	;
	push ebp
	mov ebp,esp
	
	; ebp+8 left
	; ebp+12 right
	
	; Get strlen of each string
	mov eax, dword[ebp + 12]
	mov eax, dword[eax + PXSTRING_OFFSET_STRLEN]
	
	mov ebx, dword[ebp + 8]
	mov ebx, dword[ebx + PXSTRING_OFFSET_STRLEN]
	
	add eax, ebx
	push eax; ebp-4 total strlen
	
	; FIXME: This will crash if the length overflows. Well, there's probably not
	;  any better solution.
		
	; Allocate buffer space for new string
	push eax
	call pxl_rtl_malloc ; pops 1
	
	push eax ; ebp-8
	
	; memcpy in the left string
	mov eax, dword[ebp + 12]
	push dword[eax + PXSTRING_OFFSET_STRLEN] ; numbytes
	push dword[eax + PXSTRING_OFFSET_BUFFER] ; source
	push dword[ebp-8]                        ; dest
	call pxl_rtl_memcpy
	
	; memcpy in the right string after the left string's offset
	;mov eax, dword[ebp + 8] ; memcpy clobbered it ; EDIT: memcpy doesn't clobber eax
	mov ebx, dword[ebp + 8]
	push dword[ebx + PXSTRING_OFFSET_STRLEN] ; numbytes
	push dword[ebx + PXSTRING_OFFSET_BUFFER] ; source
	
	; push (left->strlen + destbuff)
	mov ebx, dword[eax + PXSTRING_OFFSET_STRLEN]
	add ebx, dword[ebp-8] ;destbuff
	push ebx ; dest
	
	call pxl_rtl_memcpy
	
	; Allocate new pxobject
	; [ebp-8] := new PXString;
	call pxo_string_new
	
	; set new pxstring's properties (char* data, bufflen, strlen)
	; x86 can't mov indirect to indirect
	mov ebx, dword[ebp - 8]
	mov dword[eax + PXSTRING_OFFSET_BUFFER], ebx
	mov ebx, dword[ebp - 4]
	mov dword[eax + PXSTRING_OFFSET_BUFFSIZE], ebx
	mov dword[eax + PXSTRING_OFFSET_STRLEN], ebx
	
	; done
	leave
	ret 8 ; two arguments
	
	
%if 0
; public void pxo_string_append
;  Only called for foo = foo + bar if foo has refcount 1. Otherwise, use pxo_string_concat
;  If buffer size is insufficient and less than 8192, increase to next biggest power of two
;  If buffer size is insufficient and greater than 8192, add 8192. or something like that
; @param pxobj* self
; @param pxobj* other
; @return void
pxo_string_append:
	;
	push ebp
	mov ebp, esp
	push ebx
	
	; ebx := next-greatest-power-of-two( eax );
	; http://stackoverflow.com/a/1322548
	xor ebx, ebx
	shr eax, 1 ; 1
	or ebx, eax
	shr eax, 1 ; 2
	or ebx, eax
	shr eax, 2 ; 4
	or ebx, eax
	shr eax, 4 ; 8
	or ebx, eax
	shr eax, 8 ; 16
	or ebx, eax
	add ebx, 1
	
	; TODO
	
	; 
	pop ebx
	leave
	ret 8 ; two arguments
%endif
