#Require the 'wref'-gem if it hasnt already been.
require "wref" if !Kernel.const_defined?(:Wref)

#This class handels various stuff regarding Unix-processes.
class Knj::Unix_proc
  attr_reader :data

  PROCS = Wref::Map.new
  MUTEX = Mutex.new

  #Spawns a process if it doesnt already exist in the wrap-map.
  def self.spawn(data)
    pid = data["pid"].to_i

    begin
      proc_ele = PROCS.get!(pid)
      proc_ele.update_data(data)
    rescue Wref::Recycled
      proc_ele = Knj::Unix_proc.new(data)
      PROCS[pid] = proc_ele
    end

    return proc_ele
  end

  #Returns an array with (or yields if block given) Unix_proc. Hash-arguments as 'grep'.
  def self.list(args = {})
    cmdstr = "ps aux"
    grepstr = ""

    if args["grep"]
      grepstr = "grep #{args["grep"]}" #used for ignoring the grep-process later.
      cmdstr << " | grep #{Knj::Strings.unixsafe(args["grep"])}"
    end

    MUTEX.synchronize do
      ret = [] unless block_given?

      if args["psaux_str"]
        res = args["psaux_str"]
      else
        res = Knj::Os.shellcmd(cmdstr)
      end

      res.scan(/^(\S+)\s+([0-9]+)\s+([0-9.]+)\s+([0-9.]+)\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+ (.+)($|\n)/) do |match|
        pid = match[1]

        data = {
          "user" => match[0],
          "pid" => pid,
          "cpu_last" => match[2],
          "ram_last" => match[3],
          "cmd" => match[4],
          "app" => File.basename(match[4])
        }

        next if (!args.key?("ignore_self") or args["ignore_self"]) and match[1].to_i == $$.to_i
        next if grepstr.length > 0 and match[4].index(grepstr) != nil #dont return current process.

        if args.key?("pids")
          found = false
          args["pids"].each do |pid_given|
            if pid_given.to_s == pid.to_s
              found = true
              break
            end
          end

          next if !found
        end

        if args["yield_data"]
          yield(data)
        else
          proc_obj = Knj::Unix_proc.spawn(data)

          if block_given?
            yield(proc_obj)
          else
            ret << proc_obj
          end
        end
      end

      PROCS.clean

      if block_given?
        return nil
      else
        return ret
      end
    end
  end

  #Returns the "Knj::Unix_proc" for the current process.
  def self.find_self
    procs = Knj::Unix_proc.list("ignore_self" => false)
    pid_find = Process.pid

    proc_find = false
    procs.each do |proc_ele|
      if proc_ele["pid"].to_i == pid_find.to_i
        proc_find = proc_ele
        break
      end
    end

    return proc_find
  end

  #Return true if the given PID is running.
  def self.pid_running?(pid)
    begin
      Process.getpgid(pid)
      return true
    rescue Errno::ESRCH
      return false
    end
  end

  #Initializes various data for a Unix_proc-object. This should not be called manually but through "Unix_proc.list".
  def initialize(data)
    @data = data
  end

  #Updates the data. This should not be called manually, but is exposed because of various code in "Unix_proc.list".
  def update_data(data)
    @data = data
  end

  #Returns the PID of the process.
  def pid
    return @data["pid"].to_i
  end

  #Returns a key from the data or raises an error.
  def [](key)
    raise "No such data: #{key}" if !@data.key?(key)
    return @data[key]
  end

  #Kills the process.
  def kill
    Process.kill("TERM", @data["pid"].to_i)
  end

  #Kills the process with 9.
  def kill!
    Process.kill(9, @data["pid"].to_i)
  end

  #Hash-compatible.
  def to_h
    return @data.clone
  end

  #Tries to kill the process gently, waits a couple of secs to check if the process is actually dead, then sends -9 kill signals.
  def kill_ensure(args = {})
    begin
      self.kill
      sleep 0.1
      return nil if !self.alive?

      args[:sleep] = 2 if !args.key(:sleep)

      0.upto(5) do
        sleep args[:sleep]
        self.kill!
        sleep 0.1
        return nil if !self.alive?
      end

      raise "Could not kill the process."
    rescue Errno::ESRCH
      return nil
    end
  end

  #Returns true if the process is still alive.
  def alive?
    return Knj::Unix_proc.pid_running?(@data["pid"].to_i)
  end
end