pub mod c_types;
pub mod constants;
mod events;
mod pauser;
mod poll;
pub mod sig_action;
pub mod sig_disposition;
pub mod sig_mask;
pub mod sig_num;
pub mod sig_queues;
pub mod signals;

pub use events::{SigEvents, SigEventsFilter};
pub use pauser::Pauser;
pub use poll::{Pollee, Poller};

use core::mem;

use align_ext::AlignExt;
use jinux_frame::{cpu::UserContext, task::Task};

use self::c_types::siginfo_t;
use self::sig_mask::SigMask;
use self::sig_num::SigNum;
use crate::current_thread;
use crate::process::posix_thread::PosixThreadExt;
use crate::process::signal::c_types::ucontext_t;
use crate::process::signal::sig_action::SigActionFlags;
use crate::process::{do_exit_group, TermStatus};
use crate::util::{write_bytes_to_user, write_val_to_user};
use crate::{
    prelude::*,
    process::signal::sig_action::{SigAction, SigDefaultAction},
};

/// Handle pending signal for current process
pub fn handle_pending_signal(context: &mut UserContext) -> Result<()> {
    let current = current!();
    let current_thread = current_thread!();
    let posix_thread = current_thread.as_posix_thread().unwrap();
    let pid = current.pid();
    let sig_mask = *posix_thread.sig_mask().lock();
    // We first deal with signal in current thread, then signal in current process.
    let signal = if let Some(signal) = posix_thread.dequeue_signal(&sig_mask) {
        Some(signal)
    } else {
        current.dequeue_signal(&sig_mask)
    };
    if let Some(signal) = signal {
        let sig_num = signal.num();
        trace!("sig_num = {:?}, sig_name = {}", sig_num, sig_num.sig_name());
        let sig_action = current.sig_dispositions().lock().get(sig_num);
        trace!("sig action: {:x?}", sig_action);
        match sig_action {
            SigAction::Ign => {
                trace!("Ignore signal {:?}", sig_num);
            }
            SigAction::User {
                handler_addr,
                flags,
                restorer_addr,
                mask,
            } => handle_user_signal(
                sig_num,
                handler_addr,
                flags,
                restorer_addr,
                mask,
                context,
                signal.to_info(),
            )?,
            SigAction::Dfl => {
                let sig_default_action = SigDefaultAction::from_signum(sig_num);
                trace!("sig_default_action: {:?}", sig_default_action);
                match sig_default_action {
                    SigDefaultAction::Core | SigDefaultAction::Term => {
                        warn!(
                            "{:?}: terminating on signal {}",
                            current.executable_path(),
                            sig_num.sig_name()
                        );
                        do_exit_group(TermStatus::Killed(sig_num));
                        // We should exit current here, since we cannot restore a valid status from trap now.
                        Task::current().exit();
                    }
                    SigDefaultAction::Ign => {}
                    SigDefaultAction::Stop => {
                        let mut status = current_thread.status().lock();
                        if status.is_running() {
                            status.set_stopped();
                        }
                        drop(status);
                    }
                    SigDefaultAction::Cont => {
                        let mut status = current_thread.status().lock();
                        if status.is_stopped() {
                            status.set_running();
                        }
                        drop(status);
                    }
                }
            }
        }
    }
    Ok(())
}

pub fn handle_user_signal(
    sig_num: SigNum,
    handler_addr: Vaddr,
    flags: SigActionFlags,
    restorer_addr: Vaddr,
    mut mask: SigMask,
    context: &mut UserContext,
    sig_info: siginfo_t,
) -> Result<()> {
    debug!("sig_num = {:?}, signame = {}", sig_num, sig_num.sig_name());
    debug!("handler_addr = 0x{:x}", handler_addr);
    debug!("flags = {:?}", flags);
    debug!("restorer_addr = 0x{:x}", restorer_addr);
    // FIXME: How to respect flags?
    if flags.contains_unsupported_flag() {
        panic!("Unsupported Signal flags");
    }
    if !flags.contains(SigActionFlags::SA_NODEFER) {
        // add current signal to mask
        let current_mask = SigMask::from(sig_num);
        mask.block(current_mask.as_u64());
    }
    let current_thread = current_thread!();
    let posix_thread = current_thread.as_posix_thread().unwrap();
    // block signals in sigmask when running signal handler
    posix_thread.sig_mask().lock().block(mask.as_u64());

    // Set up signal stack in user stack,
    // to avoid corrupting user stack, we minus 128 first.
    let mut user_rsp = context.rsp() as u64;
    user_rsp -= 128;

    // 1. write siginfo_t
    user_rsp -= mem::size_of::<siginfo_t>() as u64;
    write_val_to_user(user_rsp as _, &sig_info)?;
    let siginfo_addr = user_rsp;
    // debug!("siginfo_addr = 0x{:x}", siginfo_addr);

    // 2. write ucontext_t.
    user_rsp = alloc_aligned_in_user_stack(user_rsp, mem::size_of::<ucontext_t>(), 16)?;
    let mut ucontext = ucontext_t {
        uc_sigmask: mask.as_u64(),
        ..Default::default()
    };
    ucontext.uc_mcontext.inner.gp_regs = *context.general_regs();
    let mut sig_context = posix_thread.sig_context().lock();
    if let Some(sig_context_addr) = *sig_context {
        ucontext.uc_link = sig_context_addr;
    } else {
        ucontext.uc_link = 0;
    }
    // TODO: store fp regs in ucontext
    write_val_to_user(user_rsp as _, &ucontext)?;
    let ucontext_addr = user_rsp;
    // Store the ucontext addr in sig context of current process.
    *sig_context = Some(ucontext_addr as Vaddr);
    // current.sig_context().lock().push_back(ucontext_addr as _);

    // 3. Set the address of the trampoline code.
    if flags.contains(SigActionFlags::SA_RESTORER) {
        // If contains SA_RESTORER flag, trampoline code is provided by libc in restorer_addr.
        // We just store restorer_addr on user stack to allow user code just to trampoline code.
        user_rsp = write_u64_to_user_stack(user_rsp, restorer_addr as u64)?;
        trace!("After set restorer addr: user_rsp = 0x{:x}", user_rsp);
    } else {
        // Otherwise we create a trampoline.
        // FIXME: This may cause problems if we read old_context from rsp.
        const TRAMPOLINE: &[u8] = &[
            0xb8, 0x0f, 0x00, 0x00, 0x00, // mov eax, 15(syscall number of rt_sigreturn)
            0x0f, 0x05, // syscall (call rt_sigreturn)
            0x90, // nop (for alignment)
        ];
        user_rsp -= TRAMPOLINE.len() as u64;
        let trampoline_rip = user_rsp;
        write_bytes_to_user(user_rsp as Vaddr, TRAMPOLINE)?;
        user_rsp = write_u64_to_user_stack(user_rsp, trampoline_rip)?;
    }
    // 4. Set correct register values
    context.set_rip(handler_addr as _);
    context.set_rsp(user_rsp as usize);
    // parameters of signal handler
    context.set_rdi(sig_num.as_u8() as usize); // signal number
    if flags.contains(SigActionFlags::SA_SIGINFO) {
        context.set_rsi(siginfo_addr as usize); // siginfo_t* siginfo
        context.set_rdx(ucontext_addr as usize); // void* ctx
    } else {
        context.set_rsi(0);
        context.set_rdx(0);
    }

    Ok(())
}

fn write_u64_to_user_stack(rsp: u64, value: u64) -> Result<u64> {
    let rsp = rsp - 8;
    write_val_to_user(rsp as Vaddr, &value)?;
    Ok(rsp)
}

/// alloc memory of size on user stack, the return address should respect the align argument.
fn alloc_aligned_in_user_stack(rsp: u64, size: usize, align: usize) -> Result<u64> {
    if !align.is_power_of_two() {
        return_errno_with_message!(Errno::EINVAL, "align must be power of two");
    }
    let start = (rsp - size as u64).align_down(align as u64);
    Ok(start)
}
