; ***************************************************************************
; copyright   : (C) 2025 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_COMMON_LIST_H
#define AQH_AVR_COMMON_LIST_H


; ---------------------------------------------------------------------------
; This code implements a simple single-linked list.
; It assumes that the first 2 bytes of an object managed by this code contain
; a 2 byte pointer to the next object.
; ---------------------------------------------------------------------------




; ***************************************************************************
; defs

.equ LIST_OFFS_NEXT_LO = 0
.equ LIST_OFFS_NEXT_HI = 1
.equ LIST_SIZE         = 2





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

.cseg



; ---------------------------------------------------------------------------
; @routine List_InitObject @global
;
; Reset list object fields.
; @param Y pointer to object
; @clobbers r16

List_InitObject:
  clr r16      ; set this->NEXT to NULL
  std Y+LIST_OFFS_NEXT_LO, r16
  std Y+LIST_OFFS_NEXT_HI, r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine List_FiniObject @global
;
; @param Y pointer to object
; @clobbers r16

List_FiniObject:
  clr r16      ; set this->NEXT to NULL
  std Y+LIST_OFFS_NEXT_LO, r16
  std Y+LIST_OFFS_NEXT_HI, r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine List_GetNextObject @global

; @param  Y pointer to object
; @return X pointer to successor object
; @clobbers none

List_GetNextObject:
  ldd xl, Y+LIST_OFFS_NEXT_LO
  ldd xh, Y+LIST_OFFS_NEXT_HI
  ret
; @end



; ---------------------------------------------------------------------------
; @routine List_GetLastObject

; @param  X      pointer to one object in a list
; @return X      pointer to last object which has an empty NEXT pointer
; @clobbers r16, r17, X, Y

List_GetLastObject:
  clr yl
  clr yh
  rjmp List_GetPredecessorFor
; @end



; ---------------------------------------------------------------------------
; @routine List_GetPredecessorFor

; @param  Y      pointer to this object
; @param  X      pointer to first object in a list
; @return X      pointer to object whose NEXT pointer points to the given object (or NULL)
; @clobbers r16, r17, X

List_GetPredecessorFor:
    mov r16, xl
    or r16, xh
    breq List_GetPredecessorFor_ret
    ld r16, X+
    ld r17, X+
    cp r16, yl
    brne List_GetPredecessorFor_next
    cp r17, yh
    breq List_GetPredecessorFor_haveIt
List_GetPredecessorFor_next:
    mov xl, r16
    mov xh, r17
  rjmp List_GetPredecessorFor
List_GetPredecessorFor_haveIt:
  sbiw xh:xl, 2
List_GetPredecessorFor_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine List_AddObject

; @param X      pointer to first object in a list
; @param Y      pointer to object to add
; @clobbers r16, r17, x

List_AddObject:
  push yl
    push yh
      rcall List_GetLastObject ; (r16, r17, X, Y)
    pop yh
  pop yl
  mov r16, xl
  or r16, xh
  clc
  breq List_AddObject_ret
  st X+, yl     ; LIST_OFFS_NEXT_LO
  st X+, yh     ; LIST_OFFS_NEXT_HI
  sec
List_AddObject_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine List_UnlinkObject

; @param X      pointer to first object in a list
; @param Y      pointer to object to remove
; @clobbers r16, r17, x

List_UnlinkObject:
  push yl
    push yh
      rcall List_GetPredecessorFor ; (r16, r17, X)
    pop yh
  pop yl
  mov r16, xl
  or r16, xh
  breq List_UnlinkObject_ret
  ldd r16, Y+LIST_OFFS_NEXT_LO ; get this->NEXT
  ldd r17, Y+LIST_OFFS_NEXT_HI
  st X+, r16   ; store as NEXT in predecessor
  st X, r17
  clr r16      ; set this->NEXT to NULL
  std Y+LIST_OFFS_NEXT_LO, r16
  std Y+LIST_OFFS_NEXT_HI, r16
List_UnlinkObject_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine List_UnlinkAllObjects

; @param Y      pointer to first object in a list
; @clobbers r16, r17, r18, Y

List_UnlinkAllObjects:
  clr r18
List_UnlinkAllObjects_loop:
    ldd r16, Y+LIST_OFFS_NEXT_LO
    ldd r17, Y+LIST_OFFS_NEXT_HI
    std Y+LIST_OFFS_NEXT_LO, r18
    std Y+LIST_OFFS_NEXT_HI, r18
    mov yl, r16
    mov yh, r17
    or r16, r17
  brne List_UnlinkAllObjects_loop
  ret
; @end



; ---------------------------------------------------------------------------
; @routine List_ForEveryObject
;
; Calls the given function for every object until it
; returns with a set carry flag or until the full list
; is handled.
; All registers that can be used by the given function, however 
; R16, R18, R19 and Y are modified between function calls.
;
; @param Y      pointer to first object in a list
; @param Z      routine to call for every object
; @clobbers r16, r17, r18, r19, X, Y (r24, r25)

List_ForEveryObject:
List_ForEveryObject_loop:
    ldd r18, Y+LIST_OFFS_NEXT_LO  ; next
    ldd r19, Y+LIST_OFFS_NEXT_HI  ; next
    push r18                      ; next
      push r19                    ; next
        rcall List_ForEveryObject_callZ
      pop r19                     ; next
    pop r18                       ; next
    brcs List_ForEveryObject_ret
    mov yl, r18
    mov yh, r19
    or r18, r19
  brne List_ForEveryObject_loop
List_ForEveryObject_ret:
  ret
List_ForEveryObject_callZ:
  ijmp
; @end



; ---------------------------------------------------------------------------
; @routine List_GetItemAt

; @param  Y     pointer to first object in a list
; @param  r16   idx (starting with 0)
; @return CFLAG set if found, cleared otherwise
; @return X     list element at given idx (if CFLAG set)
; @clobbers r16, r17

List_GetItemAt:
  push yl
    push yh
      mov r17, yl
      or r17, yh
      clc
      breq List_GetItemAt_done
List_GetItemAtLoop:
        tst r16
        breq List_GetItemAt_found
        ldd r17, Y+LIST_OFFS_NEXT_LO
        ldd yh, Y+LIST_OFFS_NEXT_HI
        mov yl, r17
        or r17, yh
        clc
        breq List_GetItemAt_done
        dec r16
      rjmp List_GetItemAtLoop
List_GetItemAt_found:
      mov xl, yl
      mov xh, yh
      sec
List_GetItemAt_done:
    pop yh
  pop yl
  ret
; @end




#endif ; AQH_AVR_COMMON_LIST_H
