//----------------------------------------------------------------------------
//
// TSDuck - The MPEG Transport Stream Toolkit
// Copyright (c) 2005-2026, Thierry Lelegard
// BSD-2-Clause license, see LICENSE.txt file or https://tsduck.io/license
//
//----------------------------------------------------------------------------
//!
//!  @file
//!  @ingroup libtscore system
//!  Various system utilities.
//!
//----------------------------------------------------------------------------

#pragma once
#include "tsUString.h"
#include "tsCerrReport.h"

#if defined(TS_UNIX)
    #include "tsBeforeStandardHeaders.h"
    #include <cerrno>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <pthread.h>
    #include <sched.h>
    #include "tsAfterStandardHeaders.h"
#endif

namespace ts {
    //!
    //! Get the error code of the last operating system call.
    //! The validity of the returned value may depends on specific conditions.
    //!
    //! Portability of error code representation: On UNIX, error codes are just @c int. On Windows, error
    //! codes are @c DWORD, which is compatible with @c int. In C++11, @c std::error_core uses @c int for
    //! error codes. Therefore, because of this new C++11 feature, we just use @c int.
    //!
    //! Windows note: According to Windows documentation, socket functions should call WSAGetLastError()
    //! instead of GetLastError() to retrieve the error code. This is an oddity from the old 16-bit
    //! Windows API. On Win32, various sources confirm that WSAGetLastError() just call GetLastError().
    //! Thus, in this application, we do not make the difference.
    //!
    //! @ingroup system
    //! @return The error code of the last operating system call.
    //!
    TSCOREDLL inline int LastSysErrorCode()
    {
#if defined(TS_WINDOWS)
        return ::GetLastError();
#else
        return errno;
#endif
    }

    //!
    //! Force the error code of the last operating system call.
    //! @ingroup system
    //! @param [in] error_code The error code to force.
    //!
    TSCOREDLL inline void SetLastSysErrorCode(int error_code)
    {
#if defined(TS_WINDOWS)
        ::SetLastError(::DWORD(error_code));
#else
        errno = error_code;
#endif
    }

    //!
    //! An error code value which means "no error" on all operating systems.
    //! Note that some Windows error codes indicate success without being equal to zero.
    //! This SYS_SUCCESS value can be used as a synthetic value to indicate success.
    //!
    constexpr int SYS_SUCCESS = 0;

    //!
    //! A synthetic error code value which means "unspecified error".
    //! This value is not a valid error code on any operating system.
    //! It can be used by application to indicate some error without further detail.
    //!
    constexpr int SYS_ERROR = -1;

    //!
    //! A synthetic error code value which means "canceled operation".
    //! This value is not a valid error code on any operating system.
    //! It can be used by application to indicate some kind of cancelation, without actual error.
    //!
    constexpr int SYS_CANCELED = -2;

    //!
    //! A synthetic error code value which means "end of file".
    //! This value is not a valid error code on any operating system.
    //! It can be used by application to indicate that an end of file or end of stream is reached.
    //!
    constexpr int SYS_EOF = -3;

    //!
    //! A synthetic error code value which means "rejected".
    //! This value is not a valid error code on any operating system.
    //! It can be used by application to indicate that some kind of operation was explicitly rejected.
    //!
    constexpr int SYS_REJECTED = -4;

    //!
    //! Translate an error code, mapping to some portable code.
    //! All successful status are translated into SYS_SUCCESS.
    //! Cancelation and end-of-file conditions are translated into SYS_CANCELED and SYS_EOF.
    //! @param [in] code An error code from the operating system.
    //! @return A translated error code.
    //!
    TSCOREDLL int TranslateError(int code);

    //!
    //! Format a system error code into a string.
    //! @ingroup system
    //! @param [in] code An error code from the operating system.
    //! Typically a result from @c errno (Unix) or @c GetLastError() (Windows).
    //! @param [in] category Error category, system by default.
    //! @return A string describing the error.
    //!
    TSCOREDLL std::string SysErrorCodeMessage(int code = LastSysErrorCode(), const std::error_category& category = std::system_category());

    //!
    //! Portable type for ioctl() request parameter.
    //! @ingroup system
    //!
    #if defined(DOXYGEN)
        using ioctl_request_t = platform-dependent;
    #elif defined(TS_WINDOWS)
        // Second parameter of ::DeviceIoControl().
        using ioctl_request_t = ::DWORD;
    #elif defined(TS_ANDROID)
        using ioctl_request_t = int;
    #else
        // Extract the type of the second parameter of ::ioctl().
        // It is "unsigned long" on most Linux systems but "int" on Alpine Linux.
        template<typename T>
        T request_param_type(int (*ioctl_syscall)(int, T, ...));
        using ioctl_request_t = decltype(request_param_type(&::ioctl));
    #endif

    //!
    //! Get the name of the current application executable file.
    //! @ingroup system
    //! @return The full path of the executable file which is run in the current process.
    //!
    TSCOREDLL fs::path ExecutableFile();

    //!
    //! Get the name of the executable or shared library file containing the caller code.
    //! @ingroup system
    //! @return The full path of the file or empty in case of error or if not supported.
    //!
    TSCOREDLL fs::path CallerLibraryFile();

    //!
    //! Check if the current user is privileged (root on UNIX, an administrator on Windows).
    //! @ingroup system
    //! @return True if the current user is privileged.
    //!
    TSCOREDLL bool IsPrivilegedUser();

    //!
    //! Get the CPU time of the process in milliseconds.
    //! @ingroup system
    //! @return The CPU time of the process in milliseconds.
    //! @throw ts::Exception on error.
    //!
    TSCOREDLL cn::milliseconds GetProcessCpuTime();

    //!
    //! Get the virtual memory size of the process in bytes.
    //! @ingroup system
    //! @return The virtual memory size of the process in bytes.
    //! @throw ts::Exception on error.
    //!
    TSCOREDLL size_t GetProcessVirtualSize();

    //!
    //! Ensure that writing to a broken pipe does not kill the current process.
    //! @ingroup system
    //!
    //! On UNIX systems, writing to a <i>broken pipe</i>, i.e. a pipe with
    //! no process reading from it, kills the current process. This may not
    //! be what you want. This functions prevents this.
    //!
    //! <strong>UNIX Systems:</strong> This function ignores SIGPIPE.
    //! Writing to a broken pipe will now return an error instead of killing
    //! the process.
    //!
    //! <strong>Windows systems:</strong> This function does nothing (because
    //! there is no need to do anything).
    //!
    TSCOREDLL void IgnorePipeSignal();

    // Implementation of SetTimersPrecision() using nanoseconds.
    //! @cond nodoxygen
    TSCOREDLL cn::nanoseconds::rep _SetTimersPrecisionNanoSecond(cn::nanoseconds::rep precision);
    //! @endcond

    //!
    //! Request a minimum resolution for the system timers.
    //! @ingroup system
    //! @param [in,out] precision On input, specify the requested minimum resolution in any std::chrono::duration units.
    //! On output, return the obtained guaranteed minimum resolution. The guaranteed precision value can be equal to or
    //! greater than the requested value. The default system resolution is 20 ms on Win32, which can be too long for applications.
    //!
    template <class Rep, class Period>
    void SetTimersPrecision(cn::duration<Rep,Period>& precision)
    {
        const cn::nanoseconds::rep ns_in = cn::duration_cast<cn::nanoseconds>(precision).count();
        precision = cn::duration_cast<cn::duration<Rep,Period>>(cn::nanoseconds(_SetTimersPrecisionNanoSecond(ns_in)));
    }

    //!
    //! Check if the standard input is a terminal.
    //! @ingroup system
    //! @return True if the standard input is a terminal.
    //!
    TSCOREDLL bool StdInIsTerminal();

    //!
    //! Check if the standard output is a terminal.
    //! @ingroup system
    //! @return True if the standard output is a terminal.
    //!
    TSCOREDLL bool StdOutIsTerminal();

    //!
    //! Check if the standard error is a terminal.
    //! @ingroup system
    //! @return True if the standard error is a terminal.
    //!
    TSCOREDLL bool StdErrIsTerminal();

    //!
    //! Put the standard input stream in binary mode.
    //! @ingroup system
    //!
    //! On UNIX systems, this does not make any difference.
    //! On Windows systems, however, in a stream which is not open in
    //! binary mode, there is automatic translation between LF and CR-LF.
    //! The standard input is open in text mode (non-binary).
    //! This function forces it into binary mode.
    //!
    //! @param [in,out] report Where to report errors.
    //! @return True on success, false on error.
    //! If @a report is a subclass of ts::Args, terminate the application on error.
    //! @see SetBinaryModeStdout()
    //!
    TSCOREDLL bool SetBinaryModeStdin(Report& report = CERR);

    //!
    //! Put the standard output stream in binary mode.
    //! @ingroup system
    //!
    //! On UNIX systems, this does not make any difference.
    //! On Windows systems, however, in a stream which is not open in
    //! binary mode, there is automatic translation between LF and CR-LF.
    //! The standard output is open in text mode (non-binary).
    //! This function forces it into binary mode.
    //!
    //! @param [in,out] report Where to report errors.
    //! @return True on success, false on error.
    //! If @a report is a subclass of ts::Args, terminate the application on error.
    //! @see SetBinaryModeStdout()
    //!
    TSCOREDLL bool SetBinaryModeStdout(Report& report = CERR);

    //!
    //! Close a file descriptor on @c fork().
    //! @ingroup system
    //!
    //! On UNIX systems, open file descriptors are inherited on @c fork() and @c exec().
    //! Most of the time (but not always), a file descriptor has no meaning outside the process
    //! which created it and it is better to close it after @c fork() (in the child process) and
    //! after @c exec() (in the new program). It is possible to automatically close a file descriptor
    //! after @c exec() using the @c FD_CLOEXEC and @c O_CLOEXEC flags. However, there is no
    //! equivalent flog to close the file descriptor in a child process after @c fork(). This
    //! function is a solution to that problem.
    //!
    //! The provided file descriptor is stored in a global list. After @c fork(), in the child process,
    //! all file descriptors from that list are closed.
    //!
    //! Warning 1: There is still a possible race condition if a child process is created between the
    //! creation of the file descriptor and the call to AddCloseOnFork().
    //!
    //! Warning 2: Do not forget to call RemoveCloseOnFork() just before closing the file descriptor.
    //! If you fail to do so, the file descriptor may be reallocated to a new device and it would be
    //! unintentionally closed on @c fork(). Be sure to call RemoveCloseOnFork() before closing the
    //! file descriptor, not after. If called after, there is a possible race condition if the file
    //! descriptor is immediately reallocated by the system.
    //!
    //! This function is specific to UNIX systems and does nothing on Windows.
    //!
    //! @param [in] fd File descriptor to close on @c fork().
    //! @param [in] cloexec When true (the default), the flag @c FD_CLOEXEC is also set on the file
    //! descriptor to enforce a close on @c exec().
    //! @see RemoveCloseOnFork()
    //!
    TSCOREDLL void AddCloseOnForkExec(int fd, bool cloexec = true);

    //!
    //! Remove a file descriptor from the list of those to close on @c fork().
    //! @ingroup system
    //!
    //! This function is specific to UNIX systems and does nothing on Windows.
    //!
    //! @param [in] fd File descriptor to no longer close after @a fork().
    //! @see AddCloseOnFork()
    //!
    TSCOREDLL void RemoveCloseOnForkExec(int fd);

    //!
    //! Get the name of a class from the @c type_index of a class.
    //! The result may be not portable.
    //! @ingroup cpp
    //! @param [in] index The @c type_index of a class.
    //! @return An implementation-specific name of the class.
    //!
    TSCOREDLL UString ClassName(const std::type_index index);

    //!
    //! Get the name of a class from the @c type_info of an object.
    //! The result may be not portable.
    //! @ingroup cpp
    //! @param [in] info The @c type_info of an object.
    //! @return An implementation-specific name of the object class.
    //!
    inline TSCOREDLL UString ClassName(const std::type_info& info)
    {
        return ClassName(std::type_index(info));
    }
}
