use core::num::NonZeroI32;

use abi_lib::SyscallRegister;
use abi_serde::{
    des::{DeserializeError, Deserializer},
    ser::Serializer,
    Deserialize, Serialize,
};

use crate::debug::TraceEvent;

use super::Signal;

/// Code signalled by a process when it finishes or is terminated by a signal
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum ExitCode {
    /// Process exited normally, either with an error or with a 0
    Exited(i32),
    /// Process was killed by a signal
    BySignal(Result<Signal, u32>),
}

/// Event signalled by a thread
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub enum ThreadEvent {
    /// Thread has exited
    Exited,
    /// Thread was interrupted by a trace event
    Trace(TraceEvent),
}

impl ExitCode {
    /// Returned when a process has exited successfully
    pub const SUCCESS: Self = Self::Exited(0);

    const SIGNAL_OFFSET: u32 = 128;

    /// Returns `true` if this ExitCode signals a successful exit
    pub const fn is_success(self) -> bool {
        matches!(self, Self::Exited(0))
    }

    /// Returns `Some(...)` if the [ExitCode] represents a process killed by a signal
    pub fn signal(&self) -> Option<Result<Signal, u32>> {
        match self {
            Self::BySignal(sig) => Some(*sig),
            _ => None,
        }
    }

    /// Returns [Some] if the process exited with an error or was killed by a signal
    pub fn as_failure(self) -> Option<NonZeroI32> {
        NonZeroI32::new(self.into())
    }
}

impl From<ExitCode> for i32 {
    fn from(value: ExitCode) -> Self {
        match value {
            ExitCode::Exited(n) => n,
            ExitCode::BySignal(Ok(sig)) => (sig.into_raw() + ExitCode::SIGNAL_OFFSET) as i32,
            ExitCode::BySignal(Err(sig)) => (sig + ExitCode::SIGNAL_OFFSET) as i32,
        }
    }
}

impl From<i32> for ExitCode {
    fn from(value: i32) -> Self {
        match value {
            128.. => match Signal::try_from(value as u32 - ExitCode::SIGNAL_OFFSET) {
                Ok(sig) => Self::BySignal(Ok(sig)),
                Err(_) => Self::BySignal(Err(value as u32 - ExitCode::SIGNAL_OFFSET)),
            },
            _ => Self::Exited(value),
        }
    }
}

impl SyscallRegister for ExitCode {
    fn into_syscall_register(self) -> usize {
        i32::from(self) as usize
    }

    fn from_syscall_register(value: usize) -> Self {
        Self::from(value as i32)
    }
}

impl Serialize for ExitCode {
    fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
        match *self {
            Self::Exited(status) => {
                serializer.write_u8(0)?;
                status.serialize(serializer)
            }
            Self::BySignal(signal) => {
                serializer.write_u8(1)?;
                signal.serialize(serializer)
            }
        }
    }
}

impl<'de> Deserialize<'de> for ExitCode {
    fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
        match deserializer.read_u8()? {
            0 => Deserialize::deserialize(deserializer).map(Self::Exited),
            1 => Deserialize::deserialize(deserializer).map(Self::Exited),
            _ => Err(D::Error::INVALID_ENUM_VARIANT),
        }
    }
}
