// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
fn signal_from_native(sig : Int32) -> Signal? {
  match sig {
    1 => Some(Hangup)
    2 => Some(Interrupt)
    3 => Some(Quit)
    4 => Some(Illegal)
    5 => Some(Trap)
    6 => Some(Abort)
    8 => Some(FloatingPointException)
    9 => Some(Kill)
    10 => Some(User1)
    11 => Some(Segv)
    12 => Some(User2)
    13 => Some(Pipe)
    14 => Some(Alarm)
    15 => Some(Term)
    17 => Some(Child)
    18 => Some(Continue)
    19 => Some(Stop)
    20 => Some(TSTP)
    21 => Some(TTIN)
    22 => Some(TTOU)
    23 => Some(Urgent)
    24 => Some(XCPU)
    25 => Some(XFSZ)
    26 => Some(VirtualAlarm)
    27 => Some(Profiling)
    28 => Some(Winch)
    29 => Some(IO)
    30 => Some(Power)
    31 => Some(Sys)
    _ => None
  }
}

///|
fn decode_wait_status(status : Int32) -> ExitStatus {
  let term_signal = status & 0x7f
  if term_signal == 0 {
    let code = (status >> 8) & 0xff
    ExitStatus::from_code(code)
  } else {
    match signal_from_native(term_signal) {
      Some(signal) => ExitStatus::from_signal(signal)
      None => { code: None, signal: None, success: false, }
    }
  }
}

///|
pub fn Process::kill(self : Process) -> Bool {
  self.kill_with(Term) == Some(true)
}

///|
pub fn Process::kill_with(self : Process, signal : Signal) -> Bool? {
  if !is_supported_system {
    return None
  }
  let rc = kill_pid_ffi(self.pid.to_int(), map_signal_to_native(signal))
  Some(rc == 0)
}

///|
fn wait_until_process_exits(pid : Pid, max_attempts : Int32) -> Bool {
  for i = 0; i < max_attempts; i = i + 1 {
    if !process_exists(pid) {
      return true
    }
    sleep_millis_native(50)
  }
  !process_exists(pid)
}

///|
pub fn Process::wait(self : Process) -> ExitStatus? {
  if !is_supported_system {
    return None
  }
  let status = Ref(0)
  let rc = wait_pid_ffi(self.pid.to_int(), 0, status)
  if rc == self.pid.to_int() {
    Some(decode_wait_status(status.val))
  } else if rc < 0 && wait_until_process_exits(self.pid, 1200) {
    if get_os_tag() == "linux" {
      None
    } else {
      Some(ExitStatus::from_code(0))
    }
  } else {
    None
  }
}

///|
pub fn Process::kill_and_wait(self : Process) -> Result[ExitStatus?, KillError] {
  self.kill_with_and_wait(Term)
}

///|
pub fn Process::kill_with_and_wait(
  self : Process,
  signal : Signal,
) -> Result[ExitStatus?, KillError] {
  let wnohang = 1
  match self.kill_with(signal) {
    None => Err(Unsupported)
    Some(false) => Err(PermissionDenied)
    Some(true) => {
      let max_attempts = 80
      for i = 0; i < max_attempts; i = i + 1 {
        let status = Ref(0)
        let rc = wait_pid_ffi(self.pid.to_int(), wnohang, status)
        if rc == self.pid.to_int() {
          return Ok(Some(decode_wait_status(status.val)))
        }
        if rc < 0 {
          if wait_until_process_exits(self.pid, max_attempts - i) {
            return Ok(None)
          }
          return Err(WaitFailed)
        }
        // keep polling for child process completion
        sleep_millis_native(50)
      }
      Err(Timeout)
    }
  }
}