; Макрос для локальных переменных
%macro local 2
    %assign __local_offset __local_offset - %2
    %define %1 (__local_offset)
%endmacro

; Подключаем автоматически сгенерированные offsets из C структур
%include "offsets.inc"

section .rodata
PI:             dd 3.1415926
TWO_PI:         dd 6.2831852
NEG_ONE_CONST:  dd -1.0
ONE_CONST:      dd 1.0
ZERO_CONST:     dd 0.0
ABS_MASK:       dd 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff
ANG_COLLIDE_COEF: dd 0.2
ANG_BOOST_FACTOR: dd 0.01
    ; Скорость углового обновления (можно регулировать независимо от FIG_SPEED)
    ANG_SPEED:       dd 15.0
    ANG_MAX:         dd 0.03
    ANG_SWITCH_FACTOR: dd 0.2
    ANG_MAX_DELTA:    dd 0.006
    ANG_FRICTION:     dd 0.94

section .text

; void figure_animation_step(struct window_draw_info* draw_info);
; Параметры:
;   rdi - указатель на struct window_draw_info
%assign __local_offset 0
global figure_animation_step
figure_animation_step:
    enter 0, 0
    
    ; Отработка коллизий
    push rdi
    call figure_handle_collision
    pop rdi

    ; Вычислить нормализующий коэффициент: 1.0 / height_pixels
    cvtsi2ss xmm4, dword [rdi + WDI_HEIGHT]
    movss xmm5, [rel ONE_CONST]
    divss xmm5, xmm4  ; xmm5 = 1.0 / height

    ; Обновить позицию: position += (velocity * speed) / height_pixels
    ; pos_x += (vel_x * speed) / height
    movss xmm0, [rdi + WDI_FIGURE + FIG_VELOCITY]
    mulss xmm0, [rdi + WDI_FIGURE + FIG_SPEED]
    mulss xmm0, xmm5  ; нормализовать скорость
    addss xmm0, [rdi + WDI_FIGURE + FIG_POSITION]
    movss [rdi + WDI_FIGURE + FIG_POSITION], xmm0

    ; pos_y += (vel_y * speed) / height
    movss xmm0, [rdi + WDI_FIGURE + FIG_VELOCITY + 4]
    mulss xmm0, [rdi + WDI_FIGURE + FIG_SPEED]
    mulss xmm0, xmm5  ; нормализовать скорость
    addss xmm0, [rdi + WDI_FIGURE + FIG_POSITION + 4]
    movss [rdi + WDI_FIGURE + FIG_POSITION + 4], xmm0

    ; Обновить угол: angle += angular_velocity * ANG_SPEED (локальная константа)
    movss xmm0, [rdi + WDI_FIGURE + FIG_ANG_VEL]
    mulss xmm0, [rel ANG_SPEED]
    addss xmm0, [rdi + WDI_FIGURE + FIG_ANGLE]
    movss [rdi + WDI_FIGURE + FIG_ANGLE], xmm0

    ; Apply angular friction to slow down rotation over time
    movss xmm0, [rdi + WDI_FIGURE + FIG_ANG_VEL]
    mulss xmm0, [rel ANG_FRICTION]
    movss [rdi + WDI_FIGURE + FIG_ANG_VEL], xmm0

    leave
    ret

; Функция для обработки коллизии, изменяет velocity при обнаружении коллизии с границами
; Параметры:
;   rdi - указатель на struct window_draw_info
%assign __local_offset 0
figure_handle_collision:
    enter 128,0

    local point_buffer, 128

    ; Сохранить регистры
    push r12
    push r13
    push r14
    push r15

    mov r12, rdi

    ; Нормализовать радиус: radius_normalized = radius_pixels / height_pixels
    movss xmm3, [r12 + WDI_FIGURE + FIG_RADIUS]
    cvtsi2ss xmm4, dword [r12 + WDI_HEIGHT]
    divss xmm3, xmm4

    ; Вызов place_points_on_circle
    movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION]
    movss xmm1, [r12 + WDI_FIGURE + FIG_POSITION + 4]
    movss xmm2, [r12 + WDI_FIGURE + FIG_ANGLE]  ; смещение угла = 0
    mulss xmm2, [rel NEG_ONE_CONST]

    ; Установка правильного количества точек
    mov eax, dword [r12 + WDI_FIGURE + FIG_TYPE]
    cmp eax, 1                ; FIGURE_TRIANGLE
    je .figure_triangle
    cmp eax, 2                ; FIGURE_SQUARE
    je .figure_square
    ; default (FIGURE_CIRCLE and others): 16 points
    mov rsi, 16
    jmp .figure_points_done
.figure_triangle:
    mov rsi, 3
    jmp .figure_points_done
.figure_square:
    mov rsi, 4
.figure_points_done:

    lea rdi, [rbp + point_buffer]
    call place_points_on_circle

    ; Вычислить canvas_width = width_pixels / height_pixels
    cvtsi2ss xmm0, dword [r12 + WDI_WIDTH]
    cvtsi2ss xmm1, dword [r12 + WDI_HEIGHT]
    divss xmm0, xmm1
    movss xmm14, xmm0  ; сохраняем canvas_width в xmm14
    movss xmm13, [rel ONE_CONST]  ; canvas_height = 1.0 в xmm13

    ; Инициализация: r14 = маска коллизий (OR всех точек), r15 = указатель на текущую точку
    xor r14, r14
    lea r15, [rbp + point_buffer]
    mov rcx, rsi

.point_check:
    ; Загрузить координаты точки
    movss xmm2, [r15]      ; x
    movss xmm3, [r15 + 4]  ; y

    ; Вызвать check_collision_mask(canvas_width, canvas_height, x, y)
    movss xmm0, xmm14      ; width
    movss xmm1, xmm13      ; height = 1.0
    push rcx
    call check_collision_mask
    pop rcx

    ; Объединить маску коллизий
    or r14, rax

    ; Перейти к следующей точке
    add r15, 8
    loop .point_check

    ; Проверить, были ли коллизии
    test r14, r14
    jz .no_collision

    ; -----------------------
    ; Обновить угловую скорость при коллизии
    ; Формула: delta = |relevant_velocity| * speed * ANG_COLLIDE_COEF
    ; Знак delta зависит от границы и направления движения.
    ; Если итоговое направление совпадает с текущим - даём небольшой буст.
    ; -----------------------

    ; Сохраняем старую угловую скорость и обнуляем суммарный эффект
    movss xmm6, [r12 + WDI_FIGURE + FIG_ANG_VEL]  ; old ang vel
    xorps xmm7, xmm7                              ; total delta

    ; LEFT (bit 0): use vertical motion (vel_y)
    test r14, 0x1
    jz .skip_left_ang
    movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY + 4] ; vel_y
    movss xmm1, xmm0
    ; Broadcast ABS_MASK (0x7fffffff) into xmm2 and AND to get abs
    mov eax, dword [rel ABS_MASK]
    movd xmm2, eax
    pshufd xmm2, xmm2, 0x0
    andps xmm1, xmm2                        ; abs(vel_y)
    mulss xmm1, [r12 + WDI_FIGURE + FIG_SPEED]
    mulss xmm1, [rel ANG_COLLIDE_COEF]
    ucomiss xmm0, [rel ZERO_CONST]
    jb .left_up_ang     ; vel_y < 0 -> moving UP
    ; moving DOWN -> clockwise (+)
    addss xmm7, xmm1
    jmp .skip_left_ang
.left_up_ang:
    ; moving UP -> anticlockwise (-)
    movss xmm2, [rel NEG_ONE_CONST]
    mulss xmm1, xmm2
    addss xmm7, xmm1
.skip_left_ang:

    ; RIGHT (bit 2): use vertical motion (vel_y)
    test r14, 0x4
    jz .skip_right_ang
    movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY + 4] ; vel_y
    movss xmm1, xmm0
    mov eax, dword [rel ABS_MASK]
    movd xmm2, eax
    pshufd xmm2, xmm2, 0x0
    andps xmm1, xmm2
    mulss xmm1, [r12 + WDI_FIGURE + FIG_SPEED]
    mulss xmm1, [rel ANG_COLLIDE_COEF]
    ucomiss xmm0, [rel ZERO_CONST]
    jb .right_up_ang    ; vel_y < 0 -> moving UP
    ; moving DOWN -> anticlockwise (-)
    movss xmm2, [rel NEG_ONE_CONST]
    mulss xmm1, xmm2
    addss xmm7, xmm1
    jmp .skip_right_ang
.right_up_ang:
    ; moving UP -> clockwise (+)
    addss xmm7, xmm1
.skip_right_ang:

    ; TOP (bit 1): use horizontal motion (vel_x)
    test r14, 0x2
    jz .skip_top_ang
    movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY] ; vel_x
    movss xmm1, xmm0
    mov eax, dword [rel ABS_MASK]
    movd xmm2, eax
    pshufd xmm2, xmm2, 0x0
    andps xmm1, xmm2
    mulss xmm1, [r12 + WDI_FIGURE + FIG_SPEED]
    mulss xmm1, [rel ANG_COLLIDE_COEF]
    ucomiss xmm0, [rel ZERO_CONST]
    ja .top_right_ang   ; vel_x > 0 -> moving RIGHT
    ; moving LEFT -> clockwise (+)
    addss xmm7, xmm1
    jmp .skip_top_ang
.top_right_ang:
    ; moving RIGHT -> anticlockwise (-)
    movss xmm2, [rel NEG_ONE_CONST]
    mulss xmm1, xmm2
    addss xmm7, xmm1
.skip_top_ang:

    ; BOTTOM (bit 3): use horizontal motion (vel_x)
    test r14, 0x8
    jz .skip_bottom_ang
    movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY] ; vel_x
    movss xmm1, xmm0
    mov eax, dword [rel ABS_MASK]
    movd xmm2, eax
    pshufd xmm2, xmm2, 0x0
    andps xmm1, xmm2
    mulss xmm1, [r12 + WDI_FIGURE + FIG_SPEED]
    mulss xmm1, [rel ANG_COLLIDE_COEF]
    ucomiss xmm0, [rel ZERO_CONST]
    ja .bottom_right_ang ; vel_x > 0 -> moving RIGHT
    ; moving LEFT -> anticlockwise (-)
    movss xmm2, [rel NEG_ONE_CONST]
    mulss xmm1, xmm2
    addss xmm7, xmm1
    jmp .skip_bottom_ang
.bottom_right_ang:
    ; moving RIGHT -> clockwise (+)
    addss xmm7, xmm1
.skip_bottom_ang:

    ; Если суммарный эффект нулевой - ничего не делаем
    ucomiss xmm7, [rel ZERO_CONST]
    je .ang_no_change

    ; Invert direction rules to match drawing coordinate system
    ; (User requested flip — so we reverse sign of computed delta)
    movss xmm0, [rel NEG_ONE_CONST]
    mulss xmm7, xmm0

    ; Decide: same direction or switch sign
    ucomiss xmm6, [rel ZERO_CONST]
    jb .old_neg_dir
    ; old >= 0
    ucomiss xmm7, [rel ZERO_CONST]
    jae .same_dir
    jmp .switch_dir
.old_neg_dir:
    ; old < 0
    ucomiss xmm7, [rel ZERO_CONST]
    jb .same_dir
    jmp .switch_dir

; If same direction -> boost and add
.same_dir:
    mulss xmm7, [rel ANG_BOOST_FACTOR]
    ; Clamp delta magnitude to ANG_MAX_DELTA
    movss xmm0, xmm7
    movss xmm1, xmm0
    mov eax, dword [rel ABS_MASK]
    movd xmm2, eax
    pshufd xmm2, xmm2, 0x0
    andps xmm1, xmm2            ; xmm1 = abs(delta)
    movss xmm3, [rel ANG_MAX_DELTA]
    ucomiss xmm1, xmm3
    ja .cap_delta_same
    jmp .after_cap_same
.cap_delta_same:
    ; Set abs(delta) = ANG_MAX_DELTA, preserve sign
    movss xmm1, [rel ANG_MAX_DELTA]
    ucomiss xmm0, [rel ZERO_CONST]
    jae .cap_delta_same_pos
    movss xmm4, [rel NEG_ONE_CONST]
    mulss xmm1, xmm4
.cap_delta_same_pos:
    movss xmm7, xmm1
.after_cap_same:
    addss xmm6, xmm7
    jmp .finish_dir_logic

; Switch sign -> compute new magnitude using old magnitude and delta
.switch_dir:
    ; xmm6 = old, xmm7 = delta
    ; abs_old = abs(xmm6)
    movss xmm0, xmm6
    movss xmm1, xmm0
    mov eax, dword [rel ABS_MASK]
    movd xmm2, eax
    pshufd xmm2, xmm2, 0x0
    andps xmm1, xmm2          ; xmm1 = abs_old

    ; abs_delta = abs(xmm7)
    movss xmm3, xmm7
    movss xmm4, xmm3
    andps xmm4, xmm2          ; xmm4 = abs_delta
    ; Clamp abs_delta
    movss xmm5, [rel ANG_MAX_DELTA]
    ucomiss xmm4, xmm5
    ja .cap_delta_switch
    jmp .delta_not_capped
.cap_delta_switch:
    movss xmm4, xmm5
.delta_not_capped:

    ; abs_old *= ANG_SWITCH_FACTOR
    movss xmm5, [rel ANG_SWITCH_FACTOR]
    mulss xmm1, xmm5

    ; new_mag = abs_old + abs_delta
    addss xmm1, xmm4

    ; apply sign of delta (xmm7)
    ucomiss xmm7, [rel ZERO_CONST]
    jae .switch_positive
    ; negative
    movss xmm5, [rel NEG_ONE_CONST]
    mulss xmm1, xmm5
    movss xmm6, xmm1
    jmp .finish_dir_logic
.switch_positive:
    movss xmm6, xmm1

.finish_dir_logic:
    ; Clamp angular velocity: |xmm6| <= ANG_MAX
    movss xmm0, xmm6
    movss xmm1, xmm0
    mov eax, dword [rel ABS_MASK]
    movd xmm2, eax
    pshufd xmm2, xmm2, 0x0
    andps xmm1, xmm2            ; xmm1 = abs(xmm0)
    movss xmm2, [rel ANG_MAX]
    ucomiss xmm1, xmm2
    ja .ang_clamp_needed
    movss [r12 + WDI_FIGURE + FIG_ANG_VEL], xmm6
    jmp .ang_no_change2
.ang_clamp_needed:
    ; If xmm0 >= 0 -> set +ANG_MAX else set -ANG_MAX
    ucomiss xmm0, [rel ZERO_CONST]
    jae .ang_positive_clamp
    ; negative
    movss xmm3, [rel ANG_MAX]
    movss xmm4, [rel NEG_ONE_CONST]
    mulss xmm3, xmm4
    movss xmm6, xmm3
    movss [r12 + WDI_FIGURE + FIG_ANG_VEL], xmm6
    jmp .ang_no_change2
.ang_positive_clamp:
    movss xmm3, [rel ANG_MAX]
    movss xmm6, xmm3
    movss [r12 + WDI_FIGURE + FIG_ANG_VEL], xmm6
.ang_no_change2:
.ang_no_change:

    ; Обработка коллизий: инвертировать velocity только если движемся к границе
    
; Обработка velocity
    ; Проверка left (bit 0): инвертировать velocity.x только если vel.x < 0
    test r14, 0x1
    jz .check_right
    
    movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY]
    movss xmm1, [rel ZERO_CONST]
    ucomiss xmm0, xmm1
    jae .check_right       ; если vel.x >= 0, пропускаем
    
    ; vel.x < 0, инвертируем
    movss xmm1, [rel NEG_ONE_CONST]
    mulss xmm0, xmm1
    movss [r12 + WDI_FIGURE + FIG_VELOCITY], xmm0

.check_right:
    ; Проверка right (bit 2): инвертировать velocity.x только если vel.x > 0
    test r14, 0x4
    jz .check_top
    
    movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY]
    movss xmm1, [rel ZERO_CONST]
    ucomiss xmm0, xmm1
    jbe .check_top         ; если vel.x <= 0, пропускаем
    
    ; vel.x > 0, инвертируем
    movss xmm1, [rel NEG_ONE_CONST]
    mulss xmm0, xmm1
    movss [r12 + WDI_FIGURE + FIG_VELOCITY], xmm0

.check_top:
    ; Проверка top (bit 1): инвертировать velocity.y только если vel.y < 0
    test r14, 0x2
    jz .check_bottom
    
    movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY + 4]
    movss xmm1, [rel ZERO_CONST]
    ucomiss xmm0, xmm1
    jae .check_bottom      ; если vel.y >= 0, пропускаем
    
    ; vel.y < 0, инвертируем
    movss xmm1, [rel NEG_ONE_CONST]
    mulss xmm0, xmm1
    movss [r12 + WDI_FIGURE + FIG_VELOCITY + 4], xmm0

.check_bottom:
    ; Проверка bottom (bit 3): инвертировать velocity.y только если vel.y > 0
    test r14, 0x8
    jz .no_collision
    
    movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY + 4]
    movss xmm1, [rel ZERO_CONST]
    ucomiss xmm0, xmm1
    jbe .no_collision      ; если vel.y <= 0, пропускаем
    
    ; vel.y > 0, инвертируем
    movss xmm1, [rel NEG_ONE_CONST]
    mulss xmm0, xmm1
    movss [r12 + WDI_FIGURE + FIG_VELOCITY + 4], xmm0

.no_collision:
    ; Костыль: если центр фигуры вышел за границу, вернуть его на границу
    
    ; Проверка pos_x < 0
    movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION]
    movss xmm1, [rel ZERO_CONST]
    ucomiss xmm0, xmm1
    jae .check_pos_x_max
    movss [r12 + WDI_FIGURE + FIG_POSITION], xmm1  ; pos_x = 0

.check_pos_x_max:
    ; Проверка pos_x > canvas_width
    movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION]
    ucomiss xmm0, xmm14
    jbe .check_pos_y_min
    movss [r12 + WDI_FIGURE + FIG_POSITION], xmm14  ; pos_x = canvas_width

.check_pos_y_min:
    ; Проверка pos_y < 0
    movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION + 4]
    movss xmm1, [rel ZERO_CONST]
    ucomiss xmm0, xmm1
    jae .check_pos_y_max
    movss [r12 + WDI_FIGURE + FIG_POSITION + 4], xmm1  ; pos_y = 0

.check_pos_y_max:
    ; Проверка pos_y > canvas_height (1.0)
    movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION + 4]
    ucomiss xmm0, xmm13
    jbe .position_clamped
    movss [r12 + WDI_FIGURE + FIG_POSITION + 4], xmm13  ; pos_y = 1.0

.position_clamped:
    ; Восстановить регистры
    pop r15
    pop r14
    pop r13
    pop r12

    leave
    ret


; Функция для расположения точек на окружности
; Вход:
;   xmm0 - pos_x
;   xmm1 - pos_y
;   xmm2 - смещение точек на окружности в радианах
;   xmm3 - радиус
;   rdi  - адрес буфера для точек
;   rsi  - количество точек
; Уничтожает: rax, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7
global place_points_on_circle
place_points_on_circle:
    enter 0, 0

    ; Сохранить координаты центра
    movss xmm6, xmm0
    movss xmm7, xmm1

    ; Рассчитать TWO_PI / rsi и сохранить в xmm4
    movss   xmm4, [rel TWO_PI] 
    cvtsi2ss xmm5, rsi         
    divss   xmm4, xmm5          

    movss xmm5, [rel ZERO_CONST]    ; счётчик
    mov rcx, rsi
.loop:
    movss xmm0, xmm5

    mulss   xmm0, xmm4          ; Счётчик*шаг    
    addss   xmm0, xmm2          ; Прибавить смещение
    call sincos_f32_rbp         ; Посчитать sincos
    mulss   xmm0, xmm3          ; sin *= radius
    mulss   xmm1, xmm3          ; cos *= radius

    addss   xmm1, xmm6          ; x = center_x + cos*radius
    addss   xmm0, xmm7          ; y = center_y + sin*radius

    movss   [rdi], xmm1        
    movss   [rdi + 4], xmm0    
    add     rdi, 8            


    addss xmm5, [rel ONE_CONST]          
    loop .loop    

    leave
    ret


; Функция для рассчёта sin и cos
; Вход:  
;   xmm0 - угол в радианах (float)
; Выход: 
;   xmm0 - sin(angle)
;   xmm1 - cos(angle)
; Уничтожает: rax, flags
sincos_f32_rbp:
    enter   24, 0          ; 24 байта локального места:
                           ;   [rbp-8]  — временно угол (для fld)
                           ;   [rbp-16] — sin
                           ;   [rbp-24] — cos
                           ; (выравнивание по 16 будет соблюдено за счёт enter)

    ; Сохраняем входной угол как float32 в стек для загрузки в x87
    movss   [rbp-8], xmm0
    fld     dword [rbp-8]          ; ST(0) = angle (в extended precision)

    fsincos                        ; ST(0) = cos, ST(1) = sin

    ; Сохраняем результаты обратно в память как float32
    fstp    dword [rbp-24]         ; pop cos → [rbp-24]
    fstp    dword [rbp-16]         ; pop sin → [rbp-16]

    ; Загружаем результаты в xmm0 и xmm1
    movss   xmm0, [rbp-16]         ; xmm0 = sin
    movss   xmm1, [rbp-24]         ; xmm1 = cos

    leave                          ; эквивалент: mov rsp, rbp / pop rbp
    ret


; Функция проверки выхода за границы с маской
; Вход:  
;   xmm0 - width
;   xmm1 - height
;   xmm2 - x
;   xmm3 - y
; Выход: 
;   rax - битовая маска границ (left=1, top=2, right=4, bottom=8)
check_collision_mask:
    xor rax, rax            ; очистим rax (маска)

    movss xmm4, [rel ZERO_CONST]

    ; left: x < 0
    ucomiss xmm2, xmm4
    jb .set_left

.next_left:
    ; right: x > width
    ucomiss xmm2, xmm0
    ja .set_right

.next_right:
    ; top: y < 0
    ucomiss xmm3, xmm4
    jb .set_top

.next_top:
    ; bottom: y > height
    ucomiss xmm3, xmm1
    ja .set_bottom

    ret

.set_left:
    or rax, 1
    jmp .next_left

.set_top:
    or rax, 2
    jmp .next_top

.set_right:
    or rax, 4
    jmp .next_right

.set_bottom:
    or rax, 8
    ret
