#include "lapic.h"
#include "arch.h"
#include "fb.h"
#include "printf.h"

#define IA32_APIC_BASE 0x1B

/* Смещения регистров LAPIC (MMIO) */
#define LAPIC_ID        0x20
#define LAPIC_VERSION   0x30
#define LAPIC_TPR       0x80
#define LAPIC_EOI       0xB0
#define LAPIC_SVR       0xF0
#define LAPIC_LVT_TIMER 0x320
#define LAPIC_LVT_LINT0 0x350
#define LAPIC_LVT_LINT1 0x360
#define LAPIC_LVT_ERROR 0x370
#define LAPIC_TIMER_INITCNT 0x380
#define LAPIC_TIMER_CURCNT  0x390
#define LAPIC_TIMER_DIV     0x3E0

static uint64_t lapic_base;
static int use_x2apic;
static int calibrated;

static volatile uint32_t *lapic_mmio(void) {
    return (volatile uint32_t*)(uintptr_t)lapic_base;
}

void lapic_write(int reg, uint32_t val) {
    if(use_x2apic) {
        wrmsr(0x800 + (reg >> 4), val);
    } else {
        lapic_mmio()[reg / 4] = val;
    }
}

uint32_t lapic_read(int reg) {
    if(use_x2apic) {
        return (uint32_t)rdmsr(0x800 + (reg >> 4));
    } else {
        return lapic_mmio()[reg / 4];
    }
}

uint32_t lapic_id(void) {
    if(use_x2apic) {
        uint32_t eax, edx;
        __asm__ volatile("rdmsr" : "=a"(eax), "=d"(edx) : "c"(0x802));
        return eax;
    } else {
        return lapic_read(LAPIC_ID) >> 24;
    }
}

void lapic_eoi(void) {
    lapic_write(LAPIC_EOI, 0);
}

void lapic_init(uint64_t apic_base) {
    lapic_base = apic_base;

    /* Проверка x2APIC */
    uint64_t apic_msr = rdmsr(IA32_APIC_BASE);
    use_x2apic = (apic_msr >> 10) & 1;

    if(!use_x2apic) {
        /* Проверяем, включён ли LAPIC */
        if(!(apic_msr & (1ULL << 11))) {
            fb_printf("lapic: enabling LAPIC via IA32_APIC_BASE\n");
            wrmsr(IA32_APIC_BASE, apic_msr | (1ULL << 11));
        }

        /* Отображаем MMIO-область LAPIC — уже сделано в paging_init */
        fb_printf("lapic: MMIO base = %p\n", lapic_base);
    } else {
        fb_printf("lapic: using x2APIC\n");
    }

    /* Включаем LAPIC через SVR */
    uint32_t svr = lapic_read(LAPIC_SVR);
    svr |= (1 << 8);           /* Включение LAPIC */
    svr |= 0xFF;               /* Вектор ложного прерывания 0xFF */
    lapic_write(LAPIC_SVR, svr);

    /* Маскируем все записи LVT */
    lapic_write(LAPIC_LVT_LINT0, 0x10000);
    lapic_write(LAPIC_LVT_LINT1, 0x10000);
    lapic_write(LAPIC_LVT_ERROR, 0x10000 | 0xFE);
    lapic_write(LAPIC_LVT_TIMER, 0x10000);

    /* Устанавливаем TPR в 0 (принимаем все прерывания) */
    lapic_write(LAPIC_TPR, 0);

    /* Устанавливаем регистр делителя в 1 (деление на 1) */
    lapic_write(LAPIC_TIMER_DIV, 0x0B); /* 0x0B = деление на 1 */

    fb_printf("lapic: initialized, ID=%x, version=%x\n",
        lapic_read(LAPIC_ID), lapic_read(LAPIC_VERSION));
}

void lapic_timer_calibrate(void) {
    /* Калибровка через RDTSC: таймер в режим one-shot, ждём ~10 мс, читаем остаток */
    uint64_t tsc_start, tsc_end;
    uint32_t initial = 0xFFFFFFFF;

    lapic_write(LAPIC_LVT_TIMER, 0x10000); /* one-shot, замаскирован */

    /* Подход с калибровкой через PIT: используем канал 2 PIT для замера ~10 мс */
    /* На самом деле используем более простой подход: калибровка через RDTSC */
    /* В QEMU таймер LAPIC работает на частоте шины ~100 МГц, TSC обычно ~2-3 ГГц */
    /* Используем цикл ожидания с RDTSC */

    /* Ставим таймер в периодический режим, замаскированным, с большим начальным счётчиком */
    lapic_write(LAPIC_TIMER_INITCNT, initial);

    tsc_start = rdtsc();
    /* Ждём ~1 мс простым циклом — неточно, но работает в QEMU */
    /* QEMU обычно работает на адекватной скорости, цикл из ~1000000 итераций — это ~1 мс */
    for(volatile uint64_t i = 0; i < 1000000; i++) { __asm__ volatile(""); }
    tsc_end = rdtsc();

    uint32_t current = lapic_read(LAPIC_TIMER_CURCNT);
    uint32_t elapsed = initial - current;

    fb_printf("lapic: calibration: initial=%x, remaining=%x, elapsed=%x, TSC delta=%llu\n",
        initial, current, elapsed, tsc_end - tsc_start);

    if(elapsed == 0) elapsed = 1;

    /* Сохраняем калибровочный коэффициент (отсчётов за тик при текущем делителе) */
    /* Используем фиксированные значения для совместимости с QEMU */

    lapic_write(LAPIC_LVT_TIMER, 0x10000); /* маскируем таймер */
    calibrated = 1;
}

void lapic_timer_init(int vector, int hz) {
    if(!calibrated) lapic_timer_calibrate();

    /* Для QEMU тактовая частота шины обычно ~100 МГц с делителем=1 */
    /* 100 МГц / hz = начальный счётчик для периодического режима */
    /* При делении на 1 таймер считает на частоте шины */
    uint32_t counts = 100000000 / hz; /* Приближение для QEMU */

    /* Устанавливаем периодический режим, без маски, с нашим вектором */
    lapic_write(LAPIC_LVT_TIMER, vector | 0x20000); /* periodic mode */

    /* Устанавливаем регистр делителя */
    lapic_write(LAPIC_TIMER_DIV, 0x0B); /* деление на 1 */

    /* Устанавливаем начальный счётчик */
    lapic_write(LAPIC_TIMER_INITCNT, counts);

    fb_printf("lapic: timer initialized at %d Hz (count=%x)\n", hz, counts);
}
