; ***************************************************************************
; copyright   : (C) 2026 by Martin Preuss
; email       : martin@libchipcard.de
;
; ***************************************************************************
; * This file is part of the project "AqHome".                              *
; * Please see toplevel file COPYING of that project for license details.   *
; ***************************************************************************

#ifndef AQH_AVR_GUI2_OBJECT_ASM
#define AQH_AVR_GUI2_OBJECT_ASM


; ***************************************************************************
; defines

; Object in flash
.equ OBJECT_OFFS_MAGIC_LO     =  0
.equ OBJECT_OFFS_MAGIC_HI     =  1
.equ OBJECT_OFFS_NEXT_LO      =  2  ; byte address (for LPM!)
.equ OBJECT_OFFS_NEXT_HI      =  3
.equ OBJECT_OFFS_PARENT_LO    =  4  ; byte address (for LPM!)
.equ OBJECT_OFFS_PARENT_HI    =  5
.equ OBJECT_OFFS_CHILD_LO     =  6  ; byte address (for LPM!)
.equ OBJECT_OFFS_CHILD_HI     =  7
.equ OBJECT_OFFS_TARGET_LO    =  8  ; byte address (for LPM!)
.equ OBJECT_OFFS_TARGET_HI    =  9
.equ OBJECT_OFFS_SELECTOR     = 10
.equ OBJECT_OFFS_UNUSED       = 11
.equ OBJECT_OFFS_SIGNALMAP_LO = 12  ; byte address (for LPM!)
.equ OBJECT_OFFS_SIGNALMAP_HI = 13
.equ OBJECT_SIZE              = 14

; SignalMap entries
.equ OBJECT_SIGNALMAP_OFFS_SELECTOR   = 0
.equ OBJECT_SIGNALMAP_OFFS_SIGNAL     = 1 ; end if 0
.equ OBJECT_SIGNALMAP_OFFS_HANDLER_LO = 2
.equ OBJECT_SIGNALMAP_OFFS_HANDLER_HI = 3

; signals
.equ OBJECT_SIGNAL_NONE        =  0
.equ OBJECT_SIGNAL_CREATE      =  1
.equ OBJECT_SIGNAL_DESTROY     =  2
.equ OBJECT_SIGNAL_TIMER       =  3
.equ OBJECT_SIGNAL_RECVMSG     =  4  ; X=msg
.equ OBJECT_SIGNAL_NEXTFREE    =  5




; ***************************************************************************
; code

.cseg



; ---------------------------------------------------------------------------
; @routine OBJ_IsObject @global
;
; @param  Z  byte address of object (for LPM!)
; @return CFLAG set if is widget, cleared otherwise
; @clobbers none

OBJ_IsObject:
  push r22
    push r23
      mov r22, zl
      or r22, zh
      clc
      breq OBJ_IsObject_ret
      lpm r22, Z+
      lpm r23, Z
      sbiw zh:zl, 1
      cpi r22, 0x55
      clc
      brne OBJ_IsObject_ret
      cpi r23, 0xaa
      clc
      brne OBJ_IsObject_ret
      sec
OBJ_IsObject_ret:
    pop r23
  pop r22  
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetParent @global
;
; @param Z byte address of object (for LPM!)
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object (byte address for LPM!), NULL otherwise
; @clobbers none

OBJ_GetParent:
  rcall OBJ_IsObject
  brcc OBJ_GetParent_ret

  adiw zh:zl, OBJECT_OFFS_PARENT_LO
  lpm r18, Z+
  lpm r19, Z
  sbiw zh:zl, OBJECT_OFFS_PARENT_LO+1
  tst r18
  brne OBJ_GetParent_secRet
  tst r19
  clc
  breq OBJ_GetParent_ret
OBJ_GetParent_secRet:
  sec
OBJ_GetParent_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetFirstChild @global
;
; @param Z byte address of object (for LPM!)
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object (byte address for LPM!), NULL otherwise
; @clobbers none

OBJ_GetFirstChild:
  rcall OBJ_IsObject
  brcc OBJ_GetFirstChild_ret
  adiw zh:zl, OBJECT_OFFS_CHILD_LO
  lpm r18, Z+
  lpm r19, Z
  sbiw zh:zl, OBJECT_OFFS_CHILD_LO+1
  tst r18
  brne OBJ_GetFirstChild_secRet
  tst r19
  clc
  breq OBJ_GetFirstChild_ret
OBJ_GetFirstChild_secRet:
  sec
OBJ_GetFirstChild_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetNext @global
;
; @param Z byte address of object (for LPM!)
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object (byte address for LPM!), NULL otherwise
; @clobbers none

OBJ_GetNext:
  rcall OBJ_IsObject
  brcc OBJ_GetNext_ret

  adiw zh:zl, OBJECT_OFFS_NEXT_LO
  lpm r18, Z+
  lpm r19, Z
  sbiw zh:zl, OBJECT_OFFS_NEXT_LO+1
  tst r18
  brne OBJ_GetNext_secRet
  tst r19
  clc
  breq OBJ_GetNext_ret
OBJ_GetNext_secRet:
  sec
OBJ_GetNext_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetBelow @global
;
; @param Z byte address of object (for LPM!)
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object (byte address for LPM!), NULL otherwise
; @clobbers r16

OBJ_GetBelow:
  push zl
    push zh
      rcall OBJ_IsObject
      brcc OBJ_GetBelow_ret
OBJ_GetBelow_get:
      rcall OBJ_GetFirstChild
      brcs OBJ_GetBelow_ret    ; jmp if found
      rcall OBJ_GetNext
      brcs OBJ_GetBelow_ret    ; jmp if found
    
OBJ_GetBelow_loop:
        rcall OBJ_GetParent
        brcc OBJ_GetBelow_ret  ; jmp if no parent
        ; use parent
        mov zl, r18
        mov zh, r19
        ; don't check for first child here, we came from there!
        rcall OBJ_GetNext
        brcs OBJ_GetBelow_ret
      rjmp OBJ_GetBelow_loop
OBJ_GetBelow_ret:
    pop zh
  pop zl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetBelowParent @global
;
; @param Z byte address of object (for LPM!)
; @param r21:20 stay below this object
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object (byte address for LPM!), NULL otherwise
; @clobbers r16

OBJ_GetBelowParent:
  push zl
    push zh
      rcall OBJ_IsObject
      brcc OBJ_GetBelowParent_ret
OBJ_GetBelowParent_get:
      rcall OBJ_GetFirstChild
      brcs OBJ_GetBelowParent_ret    ; jmp if found
      rcall OBJ_GetNext
      brcs OBJ_GetBelowParent_ret    ; jmp if found
    
OBJ_GetBelowParent_loop:
        rcall OBJ_GetParent
        brcc OBJ_GetBelowParent_ret  ; jmp if no parent
        mov zl, r18
        mov zh, r19
        rcall objZIsNotR21R20
        brcc OBJ_GetBelowParent_ret    ; Z is avoidable parent
        ; don't check for first child here, we came from there!
        rcall OBJ_GetNext
        brcs OBJ_GetBelowParent_ret
      rjmp OBJ_GetBelowParent_loop
OBJ_GetBelowParent_ret:
    pop zh
  pop zl
  ret
; @end



objZIsNotR21R20:
  cp zl, r20
  brne objZIsNotR21R20_secRet
  cp zh, r21
  brne objZIsNotR21R20_secRet
  clc
  rjmp objZIsNotR21R20_ret
objZIsNotR21R20_secRet:
  sec
objZIsNotR21R20_ret:
  ret


; ---------------------------------------------------------------------------
; @routine OBJ_GetBelowSkipChildren @global
;
; Same as @ref OBJ_GetBelow but skips children of given object.
;
; @param Z byte address of object (for LPM!)
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object (byte address for LPM!), NULL otherwise
; @clobbers r16

OBJ_GetBelowSkipChildren:
  push zl
    push zh
      tst zh
      brne OBJ_GetBelowSkipChildren_get
      tst zl
      clc
      breq OBJ_GetBelowSkipChildren_ret
OBJ_GetBelowSkipChildren_get:
      rcall OBJ_GetNext
      brcs OBJ_GetBelowSkipChildren_ret    ; jmp if found
    
OBJ_GetBelowSkipChildren_loop:
        rcall OBJ_GetParent
        brcc OBJ_GetBelowSkipChildren_ret  ; jmp if no parent
        ; use parent
        mov zl, r18
        mov zh, r19
        ; don't check for first child here, we came from there!
        rcall OBJ_GetNext
        brcs OBJ_GetBelowSkipChildren_ret
      rjmp OBJ_GetBelowSkipChildren_loop
OBJ_GetBelowSkipChildren_ret:
    pop zh
  pop zl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_EmitSignal @global
;
; @param Z byte address of object (for LPM!)
; @param R16 signal number
; @param xl param1
; @param xh param2
; @clobbers any, !X, !Y, !Z

OBJ_EmitSignal:
  rcall OBJ_IsObject   ; (none)
  brcc OBJ_EmitSignal_ret
  push zl
    push zh
      adiw zh:zl, OBJECT_OFFS_SELECTOR
      lpm r17, Z
      sbiw zh:zl, OBJECT_OFFS_SELECTOR
      adiw zh:zl, OBJECT_OFFS_TARGET_LO
      lpm r23, Z+
      lpm zh, Z
      mov zl, r23
      rcall OBJ_HandleSignal   ; (any, !X, !Y, !Z)
    pop zh
  pop zl
OBJ_EmitSignal_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_TreeHandleSignal @global
;
; @param  Z byte address of object (for LPM!)
; @param  R16 signal number
; @param  R17 selector
; @param  xl param1
; @param  xh param2
; @return CFLAG set if handled, cleared otherwise
; @clobbers any, !R16, !R17, !X, !Z

OBJ_TreeHandleSignal:
  rcall OBJ_IsObject   ; (none)
  brcc OBJ_TreeHandleSignal_ret
  push zl
    push zh
      mov r18, zl
      or r18, zh
      clc
      breq OBJ_TreeHandleSignal_done
      rcall OBJ_HandleSignalSaveInRegs
      brcs OBJ_TreeHandleSignal_done
      
      rcall OBJ_GetFirstChild
OBJ_TreeHandleSignal_loop:
        brcc OBJ_TreeHandleSignal_done
        mov zl, r18
        mov zh, r19
        rcall OBJ_TreeHandleSignal           ; recursion
        brcs OBJ_TreeHandleSignal_done
        rcall OBJ_GetNext
      rjmp OBJ_TreeHandleSignal_loop

OBJ_TreeHandleSignal_done:
    pop zh
  pop zl
OBJ_TreeHandleSignal_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_TreeAllHandleSignal @global
;
; @param  Z byte address of object (for LPM!)
; @param  R16 signal number
; @param  R17 selector
; @param  xl param1
; @param  xh param2
; @clobbers any, !R16, !R17, !X, !Z

OBJ_TreeAllHandleSignal:
  rcall OBJ_IsObject   ; (none)
  brcc OBJ_TreeAllHandleSignal_ret
  push zl
    push zh

OBJ_TreeAllHandleSignal_loop:
        rcall OBJ_HandleSignalSaveInRegs        ; (any, !R16, !R17, !X, !Z)
        push r16
          rcall OBJ_GetBelow                    ; (R16)
        pop r16
        brcc OBJ_TreeAllHandleSignal_done
        mov zl, r18
        mov zh, r19
      rjmp OBJ_TreeAllHandleSignal_loop
OBJ_TreeAllHandleSignal_done:
    pop zh
  pop zl
OBJ_TreeAllHandleSignal_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_HandleSignalSaveInRegs
;
; @param Z byte address of object (for LPM!)
; @param R16 signal number
; @param R17 selector
; @param xl param1
; @param xh param2
; @return CFLAG set if handled, cleared otherwise
; @clobbers any, !R16, !R17, !X, !Z

OBJ_HandleSignalSaveInRegs:
  push r16
    push r17
      rcall OBJ_HandleSignal   ; (any, !X, !Y, !Z)
    pop r17
  pop r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_HandleSignal
;
; @param Z byte address of object (for LPM!)
; @param R16 signal number
; @param R17 selector
; @param xl param1
; @param xh param2
; @return CFLAG set if handled, cleared otherwise
; @clobbers any, !X, !Y, !Z

OBJ_HandleSignal:
  rcall OBJ_IsObject                      ; (none)
  brcc OBJ_HandleSignal_ret
  push zl
    push zh
      rcall objGetHandlerFromSignalMap    ; CF set: R19:R18=handler (r22, r23)
      brcc OBJ_HandleSignal_done
      push xl
        push xh
          push yl
            push yh
              rcall OBJ_HandleSignal_jmpR19R18
            pop yh
          pop yl
        pop xh
      pop xl
OBJ_HandleSignal_done:
    pop zh
  pop zl
OBJ_HandleSignal_ret:
  ret
OBJ_HandleSignal_jmpR19R18:
  ; jmp to r19:r18 via stack
  push r18
  push r19
  ret
; @end



; ---------------------------------------------------------------------------
; @routine objGetHandlerFromSignalMap
;
; @param Z byte address of object (for LPM!)
; @param R16 signal number
; @param R17 selector
; @param xl param1
; @param xh param2
; @return CFLAG set if found, cleared otherwise
; @return R19:R18 handler found
; @clobbers r22, r23

objGetHandlerFromSignalMap:
  push zl
    push zh
      ; z:=signal map
      adiw zh:zl, OBJECT_OFFS_SIGNALMAP_LO
      lpm r23, Z+
      lpm zh, Z
      mov zl, r23
      or r23, zh
      clc
      breq objGetHandlerFromSignalMap_done ; no signal map
objGetHandlerFromSignalMap_loop:
        lpm r22, Z+                           ; selector
        lpm r23, Z+                           ; signal (0=end of table)
        lpm r18, Z+                           ; handler LOW
        lpm r19, Z+                           ; handler HIGH
        tst r23                               ; end of table?
        clc                        
        breq objGetHandlerFromSignalMap_done  ; yes, jmp
        cp r16, r23                                  ; signal match?
        brne objGetHandlerFromSignalMap_next         ; no, next
        tst r22
        breq objGetHandlerFromSignalMap_checkHandler ; accept any selector if it is 0 in table
        cp r17, r22                                  ; selector match?
        brne objGetHandlerFromSignalMap_next         ; no, next
objGetHandlerFromSignalMap_checkHandler:
        mov r23, r18                                 ; handler==NULL?
        or r23, r19
        clc
        breq objGetHandlerFromSignalMap_done         ; yes, done
        sec                                          ; found handler, return in r19:r18
        rjmp objGetHandlerFromSignalMap_done
objGetHandlerFromSignalMap_next:
      rjmp objGetHandlerFromSignalMap_loop

objGetHandlerFromSignalMap_done:
    pop zh
  pop zl
  ret
; @end




#endif
