; async variables somewhere in RAM
def vAsyncAF equ ASYNC_VARS_START
def vAsyncHL EQU vAsyncAF+2
def vAsyncDE EQU vAsyncHL+2
def vAsyncBC equ vAsyncDE+2
def vAsyncPC equ vAsyncBC+2
def vAsyncNext equ vAsyncPC+2
def vAsyncAfter equ vAsyncNext+2
def vAsyncProgress equ vAsyncAfter+2
def vAsyncMainSP equ vAsyncProgress+2
def vAsyncThreadSP equ vAsyncMainSP+2

; canonical ordering to push should be: AF, BC, DE, HL, 

def ASYNC_STACK_TOP equ $ffc0
; stack top is ffc0
; first value on the stack is the early return pointer, at ffbe = ffc0-2
; second value is the destination of the async call, at ffbc = ffbe-2 = ffc0-2-2
def ASYNC_THREAD_CALL equ ASYNC_STACK_TOP - 2 - 2 

def SAFE_ASYNC_START EQU 148
def SAFE_ASYNC_END EQU 153

Async_Spawn_HL:
  di
  ld a, l
  ld [ASYNC_THREAD_CALL], a 
  ld a, h
  ld [ASYNC_THREAD_CALL+1], a 
Async_Spawn: 
  di
  nop
  
  ; save all the registers
  push af
  ld a, l 
  ld [vAsyncHL], a
  ld a, h
  ld [vAsyncHL+1], a
  ld a, e
  ld [vAsyncDE], a
  ld a, d
  ld [vAsyncDE+1], a
  ld a, c 
  ld [vAsyncBC], a
  ld a, b 
  ld [vAsyncBC+1], a
  pop hl
  ld a, l 
  ld [vAsyncAF], a 
  ld a, h 
  ld [vAsyncAF+1], a 
  
  ; save main sp
  ld hl, sp+0
  ld a, l 
  ld [vAsyncMainSP], a 
  ld a, h 
  ld [vAsyncMainSP+1], a 
  
  ; switch to thread sp
  ld a, LOW(ASYNC_STACK_TOP)
  ld l, a 
  ld a, HIGH(ASYNC_STACK_TOP)
  ld h, a 
  ld sp, hl 
  
  
  ; push early return onto thread stack
  ld l, LOW(Async_EarlyReturn)
  ld h, HIGH(Async_EarlyReturn)
  push hl
  
  ; requestedd pc is expected to be set at ASYNC_THREAD_CALL by the calling func
  ld hl, sp-2
  ld sp, hl
  
  ; push registers
  ; canonical ordering to push should be: AF, BC, DE, HL, 
  ld a, [vAsyncAF]
  ld l, a 
  ld a, [vAsyncAF+1]
  ld h, a 
  push hl 
  
  ld a, [vAsyncBC]
  ld l, a 
  ld a, [vAsyncBC+1]
  ld h, a 
  push hl 
  
  ld a, [vAsyncDE]
  ld l, a 
  ld a, [vAsyncDE+1]
  ld h, a 
  push hl 
  
  ld a, [vAsyncHL]
  ld l, a 
  ld a, [vAsyncHL+1]
  ld h, a 
  push hl 
  
  ; save current sp to vAsyncThreadSP
  ld hl, sp+0
  ld a, l
  ld [vAsyncThreadSP], a 
  ld a, h 
  ld [vAsyncThreadSP+1], a 
  
  ; now the stack looks like: early return, ASYNC_THREAD_CALL, AF, BC, DE, HL
  ; switch back to main thread.
  
  ; register enterthread
  ld a, LOW(Async_EnterThread)
  ld [INTERRUPT_LCD], a
  ld a, HIGH(Async_EnterThread)
  ld [INTERRUPT_LCD + 1], a; set interrupt handler to "ENTER THREAD"
  ld a, SAFE_ASYNC_START ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING
  ld [rLYC], a ; set LYC
  ld hl, rIE  
  set 1, [hl] ; enable vblank
  ld hl, rSTAT
  set 6, [hl] ; set the stat interrupt to LYC mode
  ld hl, rIF 
  res 1, [hl] ; clear the interrupt so we don't immediately fire it
  
  
  
  ; restore main sp
  ld a, [vAsyncMainSP]
  ld l, a 
  ld a, [vAsyncMainSP+1]
  ld h, a 
  ld sp, hl
  
  ; restore registers from memory
  ld a, [vAsyncAF]
  ld l, a 
  ld a, [vAsyncAF+1]
  ld h, a 
  push hl ; this is so we can pop af when we're done
  
  ld a, [vAsyncHL]
  ld l, a 
  ld a, [vAsyncHL+1]
  ld h, a 
  
  ld a, [vAsyncDE]
  ld e, a 
  ld a, [vAsyncDE+1]
  ld d, a 
  
  ld a, [vAsyncBC]
  ld c, a 
  ld a, [vAsyncBC+1]
  ld b, a 
  
  pop af
 
  ei
  ret 

Async_Kill:
  ld hl, EndOfInstructions
  call Async_Spawn_HL
  ret
  
Async_EnterThread:
  ;stack looks like: 
  ;c113 (SMC int @ LYC 90 pc), 004b (hw int @ LYC 90 pc), outer context pc

  push af
  push bc
  push de
  push hl
  
  ; check if there's anything queued up for executing; if no, just clean up
  ld a, LOW(ASYNC_STACK_TOP) ; we're checkng if the current async stack is empty
  ld hl, vAsyncThreadSP ; comparing to the current thread sp
  cp a, [hl] ; are they equal?
  jp z, Async_CleanUpThread
  
  ;af, de, bc, hl, c113 (SMC interrupt pc), 004b (hardwired interrput pc), outer context pc
  
  ld a, LOW(Async_ExitThread)
  ld [INTERRUPT_LCD], a 
  ld a, HIGH(Async_ExitThread)
  ld [INTERRUPT_LCD+1], a 
  ld a, SAFE_ASYNC_END ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING
  ld [rLYC], a  ; set lcd interrupt handler to EXIT SAFE MODE on line 153

  ; save main thread stack pointer
  ld hl, sp+0
  ld a, l 
  ld [vAsyncMainSP], a 
  ld a, h
  ld [vAsyncMainSP+1], a 
  
  ; load side thread stack pointer
  ld a, [vAsyncThreadSP]
  ld l, a 
  ld a, [vAsyncThreadSP+1]
  ld h, a 
  ld sp, hl
  
  ; pop registers
  ; canonical ordering to push should be: AF, BC, DE, HL, 
  ; pop is HL, DE, BC, AF
  pop hl
  pop de
  pop bc
  pop af

  reti ; "return" to the vAsyncPC wee put on the stack previously.
  ; this is more or less a jump, not a return.
  ; is that the source of our problems?
  ; after this instruction executes, stack looks like:
  ; early return, af, de, bc, hl, PC (smc int), PC (hardwired int), PC (outer context)
  ; and we'll be in the async thread, executing

Async_ExitThread:
  ; save interrupt registers (AF, BC, DE, HL)
  push af
  push bc
  push de
  push hl
  
  ;af, de, bc, hl, c113 (SMC interrupt pc), 004b (hardwired interrput pc), thread pc, return
  
  ld a, LOW(Async_EnterThread)
  ld [INTERRUPT_LCD], a 
  ld a, HIGH(Async_EnterThread)
  ld [INTERRUPT_LCD+1], a 
  ld a, SAFE_ASYNC_START ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING
  ld [rLYC], a  ; set lcd interrupt handler to EXIT SAFE MODE on line 153

  ; save side thread stack pointer
  ld hl, sp+0
  ld a, l 
  ld [vAsyncThreadSP], a 
  ld a, h
  ld [vAsyncThreadSP+1], a 
  
  ; load main thread stack pointer
  ld a, [vAsyncMainSP]
  ld l, a 
  ld a, [vAsyncMainSP+1]
  ld h, a 
  ld sp, hl
  
  ; pop registers
  ; canonical ordering to push should be: AF, BC, DE, HL, 
  ; pop is HL, DE, BC, AF
  pop hl
  pop de
  pop bc
  pop af
  
  reti
  
  
Async_EarlyReturn:
  di
  ; don't care about current registers bc we're done executing.
  
  ; store side thread SP, so everyone knows that the side thread stack is empty
  ld hl, sp+0
  ld a, l 
  ld [vAsyncThreadSP], a 
  ld a, h
  ld [vAsyncThreadSP+1], a 
  
  ; unset next call
  ld hl, rIE  
  res 1, [hl] ; disable vblank
  ld hl, rIF 
  res 1, [hl] ; clear the interrupt 
  
  ; load main thread stack pointer
  ld a, [vAsyncMainSP]
  ld l, a 
  ld a, [vAsyncMainSP+1]
  ld h, a 
  ld sp, hl
  
   ; restore the pre-interrupt registers, return and enable interrupts
  pop af 
  pop de
  pop bc
  pop hl
  
  reti



Async_CleanUpThread:
  ;stack looks like: 
  ;hl, de, bc, af, c113 (SMC int @ LYC 90 pc), 004b (hw int @ LYC 90 pc), outer context pc
  pop hl
  pop de
  pop bc
  pop af 
  
  reti