// Syd: rock-solid application kernel
// src/ioctl.rs: ioctl(2) request decoder
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::sync::{LazyLock, Once, OnceLock};

use libc::c_ulong;
use libseccomp::ScmpArch;

use crate::hash::SydHashMap;

/// This type represents an ioctl(2) request.
pub type Ioctl = c_ulong;

/// This type represents an ioctl(2) list.
pub type IoctlList = &'static [(&'static str, Ioctl)];

// Include auto-generated ioctl(2) requests.
include!("ioctl/ioctls_aarch64.rs");
include!("ioctl/ioctls_arm.rs");
include!("ioctl/ioctls_loongarch64.rs");
include!("ioctl/ioctls_m68k.rs");
include!("ioctl/ioctls_mips.rs");
include!("ioctl/ioctls_mips64.rs");
include!("ioctl/ioctls_mips64n32.rs");
include!("ioctl/ioctls_mipsel.rs");
include!("ioctl/ioctls_mipsel64.rs");
include!("ioctl/ioctls_mipsel64n32.rs");
include!("ioctl/ioctls_ppc.rs");
include!("ioctl/ioctls_ppc64.rs");
include!("ioctl/ioctls_ppc64le.rs");
include!("ioctl/ioctls_riscv64.rs");
include!("ioctl/ioctls_s390.rs");
include!("ioctl/ioctls_s390x.rs");
include!("ioctl/ioctls_x32.rs");
include!("ioctl/ioctls_x86.rs");
include!("ioctl/ioctls_x8664.rs");

const ARCH_TABLES: &[(ScmpArch, IoctlList)] = &[
    (ScmpArch::Aarch64, IOCTL_ARCH_AARCH64),
    (ScmpArch::Arm, IOCTL_ARCH_ARM),
    (ScmpArch::Loongarch64, IOCTL_ARCH_LOONGARCH64),
    (ScmpArch::M68k, IOCTL_ARCH_M68K),
    (ScmpArch::Mips, IOCTL_ARCH_MIPS),
    (ScmpArch::Mips64, IOCTL_ARCH_MIPS64),
    (ScmpArch::Mips64N32, IOCTL_ARCH_MIPS64N32),
    (ScmpArch::Mipsel, IOCTL_ARCH_MIPSEL),
    (ScmpArch::Mipsel64, IOCTL_ARCH_MIPSEL64),
    (ScmpArch::Mipsel64N32, IOCTL_ARCH_MIPSEL64N32),
    (ScmpArch::Ppc, IOCTL_ARCH_PPC),
    (ScmpArch::Ppc64, IOCTL_ARCH_PPC64),
    (ScmpArch::Ppc64Le, IOCTL_ARCH_PPC64LE),
    (ScmpArch::Riscv64, IOCTL_ARCH_RISCV64),
    (ScmpArch::S390, IOCTL_ARCH_S390),
    (ScmpArch::S390X, IOCTL_ARCH_S390X),
    (ScmpArch::X32, IOCTL_ARCH_X32),
    (ScmpArch::X86, IOCTL_ARCH_X86),
    (ScmpArch::X8664, IOCTL_ARCH_X8664),
];

/// This type represents an ioctl(2) names map.
pub type IoctlNamesMap = SydHashMap<Ioctl, Vec<&'static str>>;

/// This type represents an ioctl(2) values map.
pub type IoctlValueMap = SydHashMap<&'static str, Ioctl>;

type ArchNamesMap = SydHashMap<ScmpArch, IoctlNamesMap>;
type ArchValueMap = SydHashMap<ScmpArch, IoctlValueMap>;

static INIT: Once = Once::new();
static NAMES_MAP: OnceLock<ArchNamesMap> = OnceLock::new();
static VALUE_MAP: OnceLock<ArchValueMap> = OnceLock::new();

fn build_maps() -> (ArchNamesMap, ArchValueMap) {
    let mut v2n_outer = ArchNamesMap::default();
    let mut n2v_outer = ArchValueMap::default();

    for &(arch, table) in ARCH_TABLES {
        let v2n = v2n_outer.entry(arch).or_insert_with(IoctlNamesMap::default);
        let n2v = n2v_outer.entry(arch).or_insert_with(IoctlValueMap::default);

        for &(name, val) in table {
            let val = Ioctl::from(val);

            v2n.entry(val).or_default().push(name);

            // Keep first mapping for a given name,
            // if conflicting values exist.
            n2v.entry(name).or_insert(val);
        }

        // Stable per-arch normalization.
        for names in v2n.values_mut() {
            names.sort_unstable();
            names.dedup();
        }
    }

    (v2n_outer, n2v_outer)
}

#[inline]
fn ensure_init() {
    INIT.call_once(|| {
        let (a, b) = build_maps();
        let _ = NAMES_MAP.set(a);
        let _ = VALUE_MAP.set(b);
    });
}

/// Return the architecture specific Ioctl map.
pub fn ioctl_map_get(arch: ScmpArch) -> Option<IoctlNamesMap> {
    ensure_init();

    NAMES_MAP.get().and_then(|outer| outer.get(&arch)).cloned()
}

/// Return symbol names for the given Ioctl.
#[inline]
pub fn ioctl_names_get(value: Ioctl, arch: ScmpArch) -> Option<Vec<&'static str>> {
    ensure_init();

    NAMES_MAP
        .get()
        .and_then(|outer| outer.get(&arch))
        .and_then(|inner| inner.get(&value))
        .cloned()
}

/// Return Ioctl request number for the given symbol name.
#[inline]
pub fn ioctl_value_get(name: &str, arch: ScmpArch) -> Option<Ioctl> {
    ensure_init();

    VALUE_MAP
        .get()
        .and_then(|outer| outer.get(&arch))
        .and_then(|inner| inner.get(name))
        .copied()
}

/*
 * Default ioctl constants
 */

/// TCGETS2 ioctl(2) request
pub static TCGETS2: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TCGETS2", ScmpArch::native()));
/// TCSETS2 ioctl(2) request
pub static TCSETS2: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TCSETS2", ScmpArch::native()));
/// TCSETSW2 ioctl(2) request
pub static TCSETSW2: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TCSETSW2", ScmpArch::native()));
/// TCSETSF2 ioctl(2) request
pub static TCSETSF2: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TCSETSF2", ScmpArch::native()));

/// TCGETS ioctl(2) request
pub static TCGETS: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TCGETS", ScmpArch::native()));
/// TCSETS ioctl(2) request
pub static TCSETS: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TCSETS", ScmpArch::native()));
/// TCSETSW ioctl(2) request
pub static TCSETSW: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TCSETSW", ScmpArch::native()));
/// TCSETSF ioctl(2) request
pub static TCSETSF: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TCSETSF", ScmpArch::native()));
/// TIOCGWINSZ ioctl(2) request
pub static TIOCGWINSZ: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCGWINSZ", ScmpArch::native()));
/// TIOCSWINSZ ioctl(2) request
pub static TIOCSWINSZ: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCSWINSZ", ScmpArch::native()));
/// TIOCGEXCL ioctl(2) request
pub static TIOCGEXCL: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCGEXCL", ScmpArch::native()));
/// TIOCEXCL ioctl(2) request
pub static TIOCEXCL: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCEXCL", ScmpArch::native()));
/// TIOCNXCL ioctl(2) request
pub static TIOCNXCL: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCNXCL", ScmpArch::native()));
/// TIOCSETD ioctl(2) request
pub static TIOCSETD: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCSETD", ScmpArch::native()));
/// TIOCSTI ioctl(2) request
pub static TIOCSTI: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCSTI", ScmpArch::native()));
/// TIOCCONS ioctl(2) request
pub static TIOCCONS: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCCONS", ScmpArch::native()));
/// TIOCLINUX ioctl(2) request
pub static TIOCLINUX: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCLINUX", ScmpArch::native()));
/// TIOCGPTPEER ioctl(2) request
pub static TIOCGPTPEER: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("TIOCGPTPEER", ScmpArch::native()));

/// FIOQSIZE ioctl(2) request
pub static FIOQSIZE: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("FIOQSIZE", ScmpArch::native()));
/// FIFREEZE ioctl(2) request
pub static FIFREEZE: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("FIFREEZE", ScmpArch::native()));
/// FITHAW ioctl(2) request
pub static FITHAW: LazyLock<Option<Ioctl>> =
    LazyLock::new(|| ioctl_value_get("FITHAW", ScmpArch::native()));
