// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: MIT

use core::arch::asm;
use core::ptr::addr_of_mut;

use intbits::Bits;
use lazy_static::lazy_static;
use x86_64::structures::gdt;
use x86_64::structures::gdt::*;
use x86_64::structures::tss::TaskStateSegment;

use crate::log::*;
use crate::memory::alloc::format;
use crate::{log_info, log_trace};

trait ModifyFlags {
    fn set_flags(self, flags: DescriptorFlags) -> Self;
}

// All this is necessary because there's no fucking method to change the
// flags in a segment descriptor. WHY??????
impl ModifyFlags for Descriptor {
    fn set_flags(self, flags: DescriptorFlags) -> Descriptor {
        match self {
            Descriptor::UserSegment(a) => {
                Descriptor::UserSegment(a.with_bits(52..=55, flags.bits().bits(0..=3)))
            }
            Descriptor::SystemSegment(a, b) => {
                Descriptor::SystemSegment(a.with_bits(52..=55, flags.bits().bits(0..=3)), b)
            }
        }
    }
}

pub struct Selectors {
    pub sel_kernel_code: SegmentSelector,
    pub sel_kernel_stack: SegmentSelector,
    pub sel_user_code: SegmentSelector,
    pub sel_user_stack: SegmentSelector,
    #[allow(unused)]
    pub sel_tss: SegmentSelector,
    pub gdt: GlobalDescriptorTable<48>,
}

lazy_static! {
    pub static ref TSS: TaskStateSegment = TaskStateSegment::new();
    pub static ref SELECTORS: Selectors = {
        let mut gdt = GlobalDescriptorTable::empty();
        // DO NOT REORDER
        let sel_k_cs = gdt.append(Descriptor::kernel_code_segment());
        // DO NOT REORDER
        let sel_k_ss = gdt.append(
            Descriptor::kernel_data_segment().set_flags(DescriptorFlags::from_bits_retain(0b1010)),
        );
        // DO NOT REORDER
        let sel_u_ss = gdt.append(
            Descriptor::user_data_segment().set_flags(DescriptorFlags::from_bits_retain(0b1010)),
        );
        // DO NOT REORDER
        let sel_u_cs = gdt.append(Descriptor::user_code_segment());
        // DO NOT REORDER
        let sel_tss = gdt.append(
            Descriptor::tss_segment(&TSS).set_flags(DescriptorFlags::from_bits_retain(0b1010)),
        );
        Selectors {
            sel_kernel_code: sel_k_cs,
            sel_kernel_stack: sel_k_ss,
            sel_user_code: sel_u_cs,
            sel_user_stack: sel_u_ss,
            sel_tss,
            gdt,
        }
    };
}

#[allow(dead_code)]
pub trait GdtEntryRead {
    fn base(&self) -> usize;
    fn limit(&self) -> usize;
    fn flags(&self) -> u8;
    fn access(&self) -> u8;
}

impl GdtEntryRead for Entry {
    fn base(&self) -> usize {
        ((self.raw().bits(56..=63) << 24) & self.raw().bits(16..=39)) as usize
    }
    fn limit(&self) -> usize {
        ((self.raw().bits(48..=51) << 16) & self.raw().bits(16..=31)) as usize
    }
    fn access(&self) -> u8 {
        self.raw().bits(40..=47) as u8
    }
    fn flags(&self) -> u8 {
        self.raw().bits(52..=55) as u8
    }
}

#[allow(dead_code)]
pub fn read_gdt() -> GlobalDescriptorTable {
    let mut addr: u128 = 0;
    unsafe {
        asm! {
            "sgdt [rax]",
            in("rax") addr_of_mut!(addr),
        }
    }
    // x86 gdtr: Lower 2 8-bit bytes (lower 16 bits) is the size of the GDT,
    // in 8-bit bytes, minus one.
    // Bits 16 through 79 are the virtual address of the GDT.
    let limit = addr.bits(0..=15) as usize + 1;
    let base = addr.bits(16..=79) as u64;
    let gdt_raw: &[u64] =
        unsafe { core::slice::from_raw_parts(base as *const u64, (limit / 8) - 1) };
    GlobalDescriptorTable::from_raw_entries(gdt_raw)
}

pub fn dump_gdt() {
    let gdt = crate::arch::x86_64::gdt::read_gdt();
    log_info!("GDT length: {}", gdt.limit() + 1);
    for entry in gdt.entries() {
        let flags = x86_64::structures::gdt::DescriptorFlags::from_bits_retain(entry.raw());
        let seg_type = SegmentType::try_from(entry);
        log_trace!(
            "GDT entry: type: {:?} (ring {}), width: {}, length: 0x{:x}, raw flags: 0b{:04b}, access: 0b{:08b}",
            seg_type.unwrap(),
            entry.access().bits(5..=6),
            if flags.contains(gdt::DescriptorFlags::DEFAULT_SIZE) {
                "32-bit"
            } else if flags.contains(gdt::DescriptorFlags::LONG_MODE) {
                "64-bit"
            } else {
                "16-bit"
            },
            entry.limit(),
            entry.raw().bits(52..=55),
            entry.access(),
        );
    }
}

#[derive(Debug)]
enum SegmentType {
    Null,
    Code,
    Data,
    Ldt,
    Tss16Avail,
    Tss16Busy,
    Tss32Avail,
    Tss32Busy,
    Tss64Avail,
    Tss64Busy,
}

impl TryFrom<&gdt::Entry> for SegmentType {
    type Error = ();
    fn try_from(value: &gdt::Entry) -> Result<Self, ()> {
        let flags = x86_64::structures::gdt::DescriptorFlags::from_bits_retain(value.raw());
        if value.access().bit(4) {
            if value.access().bit(3) {
                Ok(SegmentType::Code)
            } else {
                Ok(SegmentType::Data)
            }
        } else {
            match value.access().bits(0..=3) {
                0b0000 => Ok(SegmentType::Null),
                0b0001 => Ok(SegmentType::Tss16Avail),
                0b0010 => Ok(SegmentType::Ldt),
                0b0011 => Ok(SegmentType::Tss16Busy),
                0b1001 => {
                    if flags.contains(gdt::DescriptorFlags::LONG_MODE) {
                        Ok(SegmentType::Tss64Avail)
                    } else {
                        Ok(SegmentType::Tss32Avail)
                    }
                }
                0b1011 => {
                    if flags.contains(gdt::DescriptorFlags::LONG_MODE) {
                        Ok(SegmentType::Tss64Busy)
                    } else {
                        Ok(SegmentType::Tss32Busy)
                    }
                }
                _ => Err(()),
            }
        }
    }
}
