<?php

namespace Safe;

use Safe\Exceptions\ExecException;

/**
 * exec executes the given
 * command.
 *
 * @param string $command The command that will be executed.
 * @param array|null $output If the output argument is present, then the
 * specified array will be filled with every line of output from the
 * command.  Trailing whitespace, such as \n, is not
 * included in this array.  Note that if the array already contains some
 * elements, exec will append to the end of the array.
 * If you do not want the function to append elements, call
 * unset on the array before passing it to
 * exec.
 * @param int|null $result_code If the result_code argument is present
 * along with the output argument, then the
 * return status of the executed command will be written to this
 * variable.
 * @return string The last line from the result of the command.  If you need to execute a
 * command and have all the data from the command passed directly back without
 * any interference, use the passthru function.
 *
 * Returns FALSE on failure.
 *
 * To get the output of the executed command, be sure to set and use the
 * output parameter.
 * @throws ExecException
 *
 */
function exec(string $command, ?array &$output = null, ?int &$result_code = null): string
{
    error_clear_last();
    $safeResult = \exec($command, $output, $result_code);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * proc_close is similar to pclose
 * except that it only works on processes opened by
 * proc_open.
 * proc_close waits for the process to terminate, and
 * returns its exit code.  Open pipes to that process are closed
 * when this function is called, in
 * order to avoid a deadlock - the child process may not be able to exit
 * while the pipes are open.
 *
 * @param resource $process The proc_open resource that will
 * be closed.
 * @return int Returns the termination status of the process that was run.
 * @throws ExecException
 *
 */
function proc_close($process): int
{
    error_clear_last();
    $safeResult = \proc_close($process);
    if ($safeResult === -1) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * proc_nice changes the priority of the current
 * process by the amount specified in priority. A
 * positive priority will lower the priority of the
 * current process, whereas a negative priority
 * will raise the priority.
 *
 * proc_nice is not related to
 * proc_open and its associated functions in any way.
 *
 * @param int $priority The new priority value, the value of this may differ on platforms.
 *
 * On Unix, a low value, such as -20 means high priority
 * wheras a positive value have a lower priority.
 *
 * For Windows the priority parameter have the
 * following meanings:
 * @throws ExecException
 *
 */
function proc_nice(int $priority): void
{
    error_clear_last();
    $safeResult = \proc_nice($priority);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
}


/**
 * proc_open is similar to popen
 * but provides a much greater degree of control over the program execution.
 *
 * @param string $cmd The commandline to execute as string. Special characters have to be properly escaped,
 * and proper quoting has to be applied.
 *
 * As of PHP 7.4.0, cmd may be passed as array of command parameters.
 * In this case the process will be opened directly (without going through a shell)
 * and PHP will take care of any necessary argument escaping.
 *
 * On Windows, the argument escaping of the array elements assumes that the
 * command line parsing of the executed command is compatible with the parsing
 * of command line arguments done by the VC runtime.
 * @param array $descriptorspec An indexed array where the key represents the descriptor number and the
 * value represents how PHP will pass that descriptor to the child
 * process. 0 is stdin, 1 is stdout, while 2 is stderr.
 *
 * Each element can be:
 *
 * An array describing the pipe to pass to the process. The first
 * element is the descriptor type and the second element is an option for
 * the given type. Valid types are pipe (the second
 * element is either r to pass the read end of the pipe
 * to the process, or w to pass the write end) and
 * file (the second element is a filename).
 *
 *
 * A stream resource representing a real file descriptor (e.g. opened file,
 * a socket, STDIN).
 *
 *
 *
 * The file descriptor numbers are not limited to 0, 1 and 2 - you may
 * specify any valid file descriptor number and it will be passed to the
 * child process. This allows your script to interoperate with other
 * scripts that run as "co-processes". In particular, this is useful for
 * passing passphrases to programs like PGP, GPG and openssl in a more
 * secure manner. It is also useful for reading status information
 * provided by those programs on auxiliary file descriptors.
 * @param null|resource[] $pipes Will be set to an indexed array of file pointers that correspond to
 * PHP's end of any pipes that are created.
 * @param null|string $cwd The initial working dir for the command. This must be an
 * absolute directory path, or NULL
 * if you want to use the default value (the working dir of the current
 * PHP process)
 * @param array|null $env An array with the environment variables for the command that will be
 * run, or NULL to use the same environment as the current PHP process
 * @param array|null $other_options Allows you to specify additional options. Currently supported options
 * include:
 *
 *
 * suppress_errors (windows only): suppresses errors
 * generated by this function when it's set to TRUE
 *
 *
 * bypass_shell (windows only): bypass
 * cmd.exe shell when set to TRUE
 *
 *
 * blocking_pipes (windows only): force
 * blocking pipes when set to TRUE
 *
 *
 * create_process_group (windows only): allow the
 * child process to handle CTRL events when set to TRUE
 *
 *
 * create_new_console (windows only): the new process
 * has a new console, instead of inheriting its parent's console
 *
 *
 * @return resource Returns a resource representing the process, which should be freed using
 * proc_close when you are finished with it. On failure
 * returns FALSE.
 * @throws ExecException
 *
 */
function proc_open(string $cmd, array $descriptorspec, ?array &$pipes, ?string $cwd = null, ?array $env = null, ?array $other_options = null)
{
    error_clear_last();
    if ($other_options !== null) {
        $safeResult = \proc_open($cmd, $descriptorspec, $pipes, $cwd, $env, $other_options);
    } elseif ($env !== null) {
        $safeResult = \proc_open($cmd, $descriptorspec, $pipes, $cwd, $env);
    } elseif ($cwd !== null) {
        $safeResult = \proc_open($cmd, $descriptorspec, $pipes, $cwd);
    } else {
        $safeResult = \proc_open($cmd, $descriptorspec, $pipes);
    }
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * This function is identical to the backtick operator.
 *
 * @param string $command The command that will be executed.
 * @return null|string A string containing the output from the executed command or NULL if an error occurs or the command produces no output.
 * @throws ExecException
 *
 */
function shell_exec(string $command): ?string
{
    error_clear_last();
    $safeResult = \shell_exec($command);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * system is just like the C version of the
 * function in that it executes the given
 * command and outputs the result.
 *
 * The system call also tries to automatically
 * flush the web server's output buffer after each line of output if
 * PHP is running as a server module.
 *
 * If you need to execute a command and have all the data from the
 * command passed directly back without any interference, use the
 * passthru function.
 *
 * @param string $command The command that will be executed.
 * @param int|null $result_code If the result_code argument is present, then the
 * return status of the executed command will be written to this
 * variable.
 * @return string Returns the last line of the command output on success.
 * @throws ExecException
 *
 */
function system(string $command, ?int &$result_code = null): string
{
    error_clear_last();
    $safeResult = \system($command, $result_code);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}
