/* ports.c

   Copyright (c) 2003-2026 HandBrake Team
   This file is part of the HandBrake source code
   Homepage: <http://handbrake.fr/>.
   It may be used under the terms of the GNU General Public License v2.
   For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
 */

#include "handbrake/project.h"

#ifdef SYS_MINGW
#define _WIN32_WINNT 0x0601
#endif

#ifdef SYS_LINUX
#define _GNU_SOURCE
#include <sched.h>
#endif
#include <pthread.h>

#if defined(SYS_DARWIN) || defined(SYS_FREEBSD) || defined(SYS_NETBSD) || defined(SYS_OPENBSD)
#include <sys/types.h>
#include <sys/sysctl.h>
#if HB_PROJECT_FEATURE_QSV && defined(SYS_FREEBSD)
#include <libdrm/drm.h>
#include <fcntl.h>
#endif
#endif

#ifdef SYS_MINGW
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <dlfcn.h>
#endif

#ifdef SYS_CYGWIN
#include <windows.h>
#endif

#ifdef SYS_MINGW
#include <pthread.h>
#include <windows.h>
#include <wchar.h>
#include <mbctype.h>
#include <locale.h>
#include <shlobj.h>
#endif

#ifdef SYS_SunOS
#include <sys/processor.h>
#endif

#include <time.h>
#include <sys/time.h>
#include <ctype.h>

#if defined( SYS_LINUX )
#include <linux/cdrom.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#if HB_PROJECT_FEATURE_QSV
#include <libdrm/drm.h>
#endif
#endif

#if defined(SYS_LINUX) || defined(SYS_FREEBSD) || defined(SYS_NETBSD) || defined(SYS_OPENBSD)
#include <sys/utsname.h>
#endif

#ifdef __APPLE__
#include <CoreServices/CoreServices.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#endif

#include <stddef.h>
#include <unistd.h>

#include "handbrake/handbrake.h"
#include "libavutil/cpu.h"

/************************************************************************
 * hb_get_date()
 ************************************************************************
 * Returns the current date in milliseconds.
 * On Win32, we implement a gettimeofday emulation here because
 * libdvdread and libmp4v2 use it without checking.
 ************************************************************************/
/*
#ifdef SYS_CYGWIN
struct timezone
{
};

int gettimeofday( struct timeval * tv, struct timezone * tz )
{
    int tick;
    tick        = GetTickCount();
    tv->tv_sec  = tick / 1000;
    tv->tv_usec = ( tick % 1000 ) * 1000;
    return 0;
}
#endif
*/

int hb_dvd_region(const char *device, int *region_mask)
{
#if defined( DVD_LU_SEND_RPC_STATE ) && defined( DVD_AUTH )
    struct stat  st;
    dvd_authinfo ai;
    int          fd, ret;

    fd = open( device, O_RDONLY );
    if ( fd < 0 )
        return -1;
    if ( fstat( fd, &st ) < 0 )
	{
        close( fd );
        return -1;
	}
    if ( !( S_ISBLK( st.st_mode ) || S_ISCHR( st.st_mode ) ) )
	{
        close( fd );
        return -1;
	}

    ai.type = DVD_LU_SEND_RPC_STATE;
    ret = ioctl(fd, DVD_AUTH, &ai);
    close( fd );
    if ( ret < 0 )
        return ret;

    *region_mask = ai.lrpcs.region_mask;
    return 0;
#else
    return -1;
#endif
}

uint64_t hb_get_date()
{
    struct timeval tv;
    gettimeofday( &tv, NULL );
    return( (uint64_t) tv.tv_sec * 1000 + (uint64_t) tv.tv_usec / 1000 );
}

uint64_t hb_get_time_us()
{
#ifdef SYS_MINGW
    static LARGE_INTEGER frequency;
    LARGE_INTEGER cur_time;

    if (frequency.QuadPart == 0)
    {
          QueryPerformanceFrequency(&frequency);
    }

    QueryPerformanceCounter(&cur_time);

    return (uint64_t)(1000000 * cur_time.QuadPart / frequency.QuadPart);
#else
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return ((uint64_t)tv.tv_sec * 1000000 + (uint64_t)tv.tv_usec);
#endif
}

/************************************************************************
 * hb_snooze()
 ************************************************************************
 * Waits <delay> milliseconds.
 ************************************************************************/
void hb_snooze( int delay )
{
    if( delay < 1 )
    {
        return;
    }
#if defined( SYS_CYGWIN ) || defined( SYS_MINGW )
    Sleep( delay );
#else
    usleep( 1000 * delay );
#endif
}

/************************************************************************
 * Get information about the operating system
 ************************************************************************/
static void init_system_info();
struct
{
    const char *name;
    const char *version;
    const char *build;
} hb_system_info;

static void init_system_info()
{
    if (hb_system_info.name != NULL)
    {
        return;
    }

#if defined(SYS_DARWIN)
    char buf[256];
    size_t buflen = sizeof(buf);

    if (sysctlbyname("kern.osproductversion", &buf, &buflen, NULL, 0) == 0)
    {
        hb_system_info.version = strdup(buf);
    }

    buflen = sizeof(buf);
    if (sysctlbyname("kern.osversion", &buf, &buflen, NULL, 0) == 0)
    {
        hb_system_info.build = strdup(buf);
    }

    hb_system_info.name = "macOS";
#elif defined(SYS_LINUX) || defined(SYS_FREEBSD) || defined(SYS_NETBSD) || defined(SYS_OPENBSD)
    struct utsname uts;
    if (uname(&uts) == 0)
    {
        hb_system_info.name    = strdup(uts.sysname);
        hb_system_info.version = strdup(uts.release);
        hb_system_info.build   = strdup(uts.version);
    }
#elif defined(SYS_MINGW)
    NTSYSAPI NTSTATUS RtlGetVersion(PRTL_OSVERSIONINFOW);

    OSVERSIONINFOW vi = {0};
    vi.dwOSVersionInfoSize = sizeof(vi);
    char buf[32];

    if (RtlGetVersion(&vi) == 0)
    {
        snprintf(buf, sizeof(buf), "%lu.%lu", vi.dwMajorVersion, vi.dwMinorVersion);
        hb_system_info.version = strdup(buf);

        snprintf(buf, sizeof(buf), "%lu", vi.dwBuildNumber);
        hb_system_info.build = strdup(buf);
    }

    hb_system_info.name = "Windows";
#else
    hb_system_info.name    = NULL;
    hb_system_info.version = NULL;
    hb_system_info.build   = NULL;
#endif
}

const char * hb_get_system_name()
{
    init_system_info();
    return hb_system_info.name;
}

const char * hb_get_system_version()
{
    init_system_info();
    return hb_system_info.version;
}

const char * hb_get_system_build()
{
    init_system_info();
    return hb_system_info.build;
}

/************************************************************************
 * Get information about the CPU (number of cores, name, platform name)
 ************************************************************************/
static void init_cpu_info();
static int  init_cpu_count();
struct
{
    enum hb_cpu_platform platform;
    const char *name;
    union
    {
        char buf[48];
        uint32_t buf4[12];
    };
    int count;
} hb_cpu_info;

int hb_get_cpu_count()
{
    init_cpu_info();
    return hb_cpu_info.count;
}

int hb_get_cpu_platform()
{
    init_cpu_info();
    return hb_cpu_info.platform;
}

const char* hb_get_cpu_name()
{
    init_cpu_info();
    return hb_cpu_info.name;
}

const char* hb_get_cpu_platform_name()
{
    init_cpu_info();
    switch (hb_cpu_info.platform)
    {
        case HB_CPU_PLATFORM_INTEL_BNL:
            return "Intel microarchitecture Bonnell";
        case HB_CPU_PLATFORM_INTEL_SNB:
            return "Intel microarchitecture Sandy Bridge";
        case HB_CPU_PLATFORM_INTEL_IVB:
            return "Intel microarchitecture Ivy Bridge";
        case HB_CPU_PLATFORM_INTEL_SLM:
            return "Intel microarchitecture Silvermont";
        case HB_CPU_PLATFORM_INTEL_HSW:
            return "Intel microarchitecture Haswell";
        case HB_CPU_PLATFORM_INTEL_BDW:
            return "Intel microarchitecture Broadwell";
        case HB_CPU_PLATFORM_INTEL_SKL:
            return "Intel microarchitecture Skylake";
        case HB_CPU_PLATFORM_INTEL_CHT:
            return "Intel microarchitecture Airmont";
        case HB_CPU_PLATFORM_INTEL_KBL:
            return "Intel microarchitecture Kaby Lake";
        case HB_CPU_PLATFORM_INTEL_CML:
            return "Intel microarchitecture Comet Lake";
        case HB_CPU_PLATFORM_INTEL_ICL:
            return "Intel microarchitecture Ice Lake";
        case HB_CPU_PLATFORM_INTEL_TGL:
            return "Intel microarchitecture Tiger Lake";
        case HB_CPU_PLATFORM_INTEL_ADL:
            return "Intel microarchitecture Alder Lake performance hybrid architecture";
        default:
            return NULL;
    }
}

#if ARCH_X86_64
#    define REG_b "rbx"
#    define REG_S "rsi"
#elif ARCH_X86_32
#    define REG_b "ebx"
#    define REG_S "esi"
#endif // ARCH_X86_32

#if ARCH_X86_64 || ARCH_X86_32
#define cpuid(index, eax, ebx, ecx, edx)                        \
    __asm__ volatile (                                          \
        "mov    %%"REG_b", %%"REG_S" \n\t"                      \
        "cpuid                       \n\t"                      \
        "xchg   %%"REG_b", %%"REG_S                             \
        : "=a" (*eax), "=S" (*ebx), "=c" (*ecx), "=d" (*edx)    \
        : "0" (index))
#endif // ARCH_X86_64 || ARCH_X86_32

static void init_cpu_info()
{
    if (hb_cpu_info.count != 0)
        return;

    hb_cpu_info.name     = "Unknown";
    hb_cpu_info.count    = init_cpu_count();
    hb_cpu_info.platform = HB_CPU_PLATFORM_UNSPECIFIED;

#if ARCH_X86_64 || ARCH_X86_32
    if (av_get_cpu_flags() & AV_CPU_FLAG_SSE)
    {
        int eax, ebx, ecx, edx, family, model;

        cpuid(1, &eax, &ebx, &ecx, &edx);
        family = ((eax >> 8) & 0xf) + ((eax >> 20) & 0xff);
        model  = ((eax >> 4) & 0xf) + ((eax >> 12) & 0xf0);

        // Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 4/April 2022
        // Table 2-1. CPUID Signature Values of DisplayFamily_DisplayModel
        switch (family)
        {
            case 0x06:
            {
                switch (model)
                {
                    case 0x1C:
                    case 0x26:
                    case 0x27:
                    case 0x35:
                    case 0x36:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_BNL;
                        break;
                    case 0x2A:
                    case 0x2D:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_SNB;
                        break;
                    case 0x3A:
                    case 0x3E:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_IVB;
                        break;
                    case 0x37:
                    case 0x4A:
                    case 0x4D:
                    case 0x5A:
                    case 0x5D:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_SLM;
                        break;
                    case 0x3C:
                    case 0x3F:
                    case 0x45:
                    case 0x46:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_HSW;
                        break;
                    case 0x3D:
                    case 0x4F:
                    case 0x56:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_BDW;
                        break;
                    case 0x4C:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_CHT;
                        break;
                    case 0x4E:
                    case 0x5E:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_SKL;
                        break;
                    case 0x8E:
                    case 0x9E:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_KBL;
                        break;
                    case 0xA5:
                    case 0xA6:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_CML;
                        break;
                    case 0x7D:
                    case 0x7E:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_ICL;
                        break;
                    case 0x8C:
                    case 0x8D:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_TGL;
                        break;
                    case 0x97:
                    case 0x9A:
                        hb_cpu_info.platform = HB_CPU_PLATFORM_INTEL_ADL;
                        break;
                    default:
                        break;
                }
            } break;

            default:
                break;
        }

        // Intel 64 and IA-32 Architectures Software Developer's Manual, Vol. 2A
        // Figure 3-8: Determination of Support for the Processor Brand String
        // Table 3-17: Information Returned by CPUID Instruction
        cpuid(0x80000000, &eax, &ebx, &ecx, &edx);
        if ((eax & 0x80000004) < 0x80000004)
        {
            cpuid(0x80000002,
                  &hb_cpu_info.buf4[ 0],
                  &hb_cpu_info.buf4[ 1],
                  &hb_cpu_info.buf4[ 2],
                  &hb_cpu_info.buf4[ 3]);
            cpuid(0x80000003,
                  &hb_cpu_info.buf4[ 4],
                  &hb_cpu_info.buf4[ 5],
                  &hb_cpu_info.buf4[ 6],
                  &hb_cpu_info.buf4[ 7]);
            cpuid(0x80000004,
                  &hb_cpu_info.buf4[ 8],
                  &hb_cpu_info.buf4[ 9],
                  &hb_cpu_info.buf4[10],
                  &hb_cpu_info.buf4[11]);

            hb_cpu_info.name = hb_cpu_info.buf;
        }
    }
#elif defined(SYS_DARWIN)
    size_t buflen = sizeof(hb_cpu_info.buf);
    if (sysctlbyname("machdep.cpu.brand_string", &hb_cpu_info.buf, &buflen, NULL, 0) == 0)
    {
        hb_cpu_info.name = hb_cpu_info.buf;
    }
#endif

    // ensure string is null-terminated and trim trailing whitespace
    int ii = sizeof(hb_cpu_info.buf) - 1;
    do {
        hb_cpu_info.buf[ii] = '\0';
        ii -= 1;
    }
    while (ii > 0 && isspace(hb_cpu_info.buf[ii]));

    while (isspace(*hb_cpu_info.name))
    {
        // skip leading whitespace to prettify
        hb_cpu_info.name++;
    }
}

/*
 * Whenever possible, returns the number of CPUs on the current computer.
 * Returns 1 otherwise.
 */
static int init_cpu_count()
{
    int cpu_count = 1;

#if defined(SYS_CYGWIN) || defined(SYS_MINGW)
    cpu_count = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);

#elif defined(SYS_LINUX)
    unsigned int bit;
    cpu_set_t p_aff;
    memset( &p_aff, 0, sizeof(p_aff) );
    sched_getaffinity( 0, sizeof(p_aff), &p_aff );
    for( cpu_count = 0, bit = 0; bit < sizeof(p_aff); bit++ )
         cpu_count += (((uint8_t *)&p_aff)[bit / 8] >> (bit % 8)) & 1;

#elif defined(SYS_DARWIN) || defined(SYS_FREEBSD) || defined(SYS_NETBSD) || defined(SYS_OPENBSD)
    size_t length = sizeof( cpu_count );
#if defined SYS_OPENBSD || defined(SYS_NETBSD)
#ifdef HW_NCPUONLINE
    int mib[2] = { CTL_HW, HW_NCPUONLINE };
#else
    int mib[2] = { CTL_HW, HW_NCPU };
#endif
    if( sysctl(mib, 2, &cpu_count, &length, NULL, 0) )
#else
    if( sysctlbyname("hw.ncpu", &cpu_count, &length, NULL, 0) )
#endif
    {
        cpu_count = 1;
    }

#elif defined( SYS_SunOS )
    {
        processorid_t cpumax;
        int i,j=0;

        cpumax = sysconf(_SC_CPUID_MAX);

        for(i = 0; i <= cpumax; i++ )
        {
            if(p_online(i, P_STATUS) != -1)
            {
                j++;
            }
        }
        cpu_count=j;
    }
#endif

    cpu_count = MAX( 1, cpu_count );
    cpu_count = MIN( cpu_count, 384 );

    return cpu_count;
}

int hb_platform_init()
{
    int result = 0;

#if defined(SYS_MINGW) && defined(PTW32_VERSION)
    result = !pthread_win32_process_attach_np();
    if (result)
    {
        hb_error("pthread_win32_process_attach_np() failed!");
        return -1;
    }
#endif

#if defined(_WIN32) || defined(__MINGW32__)
    /*
     * win32 _IOLBF (line-buffering) is the same as _IOFBF (full-buffering).
     * force it to unbuffered otherwise informative output is not easily parsed.
     */
    result = setvbuf(stdout, NULL, _IONBF, 0);
    if (result)
    {
        hb_error("setvbuf(stdout, NULL, _IONBF, 0) failed!");
        return -1;
    }
    result = setvbuf(stderr, NULL, _IONBF, 0);
    if (result)
    {
        hb_error("setvbuf(stderr, NULL, _IONBF, 0) failed!");
        return -1;
    }
#endif

    init_cpu_info();

    return result;
}

/************************************************************************
 * Get app data config directory
 ***********************************************************************/
void hb_get_user_config_directory( char path[512] )
{
    /* Create the base */
#if defined( SYS_CYGWIN ) || defined( SYS_MINGW )
#ifndef CSIDL_FLAG_DONT_UNEXPAND
    /*
     * XXX: some old MinGW toolchains don't have SHGetKnownFolderPath.
     *
     * SHGetFolderPath is deprecated, but this should be no problem in practice.
     *
     * Note: explicitly call the Unicode/WCHAR function SHGetFolderPathW.
     */
    WCHAR wide_path[MAX_PATH];

    if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, wide_path) == S_OK &&
        WideCharToMultiByte(CP_UTF8, 0, wide_path, -1, path, 512, NULL, NULL) != 0)
    {
        path[511] = 0;
        return;
    }
#else
    WCHAR *wide_path;

    if (SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &wide_path) == S_OK &&
        WideCharToMultiByte(CP_UTF8, 0, wide_path, -1, path, 512, NULL, NULL) != 0)
    {
        CoTaskMemFree(wide_path);
        path[511] = 0;
        return;
    }
    else if (wide_path != NULL)
    {
        CoTaskMemFree(wide_path);
    }
#endif // !defined CSIDL_FLAG_DONT_UNEXPAND
#elif defined( SYS_LINUX )
    char *p;

    if ((p = getenv("XDG_CONFIG_HOME")) != NULL)
    {
        strncpy(path, p, 511);
        path[511] = 0;
        return;
    }
    else if ((p = getenv("HOME")) != NULL)
    {
        strncpy(path, p, 511);
        path[511] = 0;
        int len = strlen(path);
        strncpy(path + len, "/.config", 511 - len - 1);
        path[511] = 0;
        return;
    }
#elif defined( __APPLE__ )
    if (macOS_get_user_config_directory(path) == 0)
    {
        return;
    }
#endif

    hb_error("Failed to lookup user config directory!");
    path[0] = 0;
}

/************************************************************************
 * Get a user config filename for HB
 ***********************************************************************/
void hb_get_user_config_filename( char name[1024], char *fmt, ... )
{
    va_list args;

    hb_get_user_config_directory( name );
#if defined( SYS_CYGWIN ) || defined( SYS_MINGW )
    strcat( name, "\\" );
#else
    strcat( name, "/" );
#endif

    va_start( args, fmt );
    vsnprintf( &name[strlen(name)], 1024 - strlen(name), fmt, args );
    va_end( args );
}

/************************************************************************
 * Creates a uniquely-named temporary directory for this process. On
 * POSIX systems, mkdtemp(3) is used to ensure the directory name is
 * unique even if multiple HandBrake instances have the same PID.
 ***********************************************************************/
static pthread_once_t tmp_control = PTHREAD_ONCE_INIT;
static char *tmp_dirname = NULL;
static const char *tmp_override = NULL;

static void
hb_init_temporary_directory (void)
{
    char *path = NULL, *base = NULL, *p;

#if defined( SYS_CYGWIN ) || defined( SYS_MINGW )
    if (tmp_override != NULL && tmp_override[0] != '\0')
    {
        base = strdup(tmp_override);
    }
    else
    {
        DWORD i_size = 0;
        WCHAR wide_base[MAX_PATH + 1];

        i_size = GetTempPathW(MAX_PATH, wide_base);
        if (i_size > 0 && i_size <= MAX_PATH)
        {
            int base_size = WideCharToMultiByte(CP_UTF8, 0, wide_base, -1,
                                                NULL, 0, 0, NULL);
            if (base_size)
            {
                base = malloc(base_size);
                WideCharToMultiByte(CP_UTF8, 0, wide_base, -1,
                                    base, base_size, 0, NULL);
            }
        }
        
        if (base == NULL)
        {
            base = malloc(MAX_PATH + 1);
            if (getcwd(base, MAX_PATH) == NULL)
            strcpy(base, "c:"); /* Bad fallback but ... */
        }
    }
    /* c:/path/ works like a charm under cygwin(win32?) so use it */
    while ((p = strchr(base, '\\')))
        *p = '/';

    /* I prefer to remove eventual last '/' (for cygwin) */
    if (base[strlen(base)-1] == '/')
        base[strlen(base)-1] = '\0';

    path = hb_strdup_printf("%s/HandBrake-%d", base, (int)getpid());
    hb_mkdir(path);
    free(base);
#else
    if (tmp_override != NULL && tmp_override[0] != '\0')
        base = strdup(tmp_override);
    else if ((p = getenv("TMPDIR")) != NULL ||
             (p = getenv("TEMP"))   != NULL)
        base = strdup(p);
    else
        base = strdup("/tmp");

    if (base[strlen(base)-1] == '/')
        base[strlen(base)-1] = '\0';

    /* Create a new randomly-named directory from the template */
    path = hb_strdup_printf("%s/handbrake-XXXXXX", base);
    if (!mkdtemp(path))
    {
        /* We still use the path even if directory creation fails */
        hb_error("Failed to create a temporary directory at %s\n", path);
    }
    free(base);
#endif
    tmp_dirname = path;
}

/************************************************************************
 * Sets the location of the temporary directory. This function must be
 * called before the first use of hb_get_temporary_directory().
 ***********************************************************************/
void
hb_set_temporary_directory (const char *tmp_dir)
{
    tmp_override = tmp_dir;
    pthread_once(&tmp_control, hb_init_temporary_directory);
    tmp_override = NULL;
}

const char *
hb_get_temporary_directory (void)
{
    /* Ensure the directory name is only created once */
    pthread_once(&tmp_control, hb_init_temporary_directory);
    return tmp_dirname;
}

/************************************************************************
 * Get a temporary filename for HB
 ***********************************************************************/
char * hb_get_temporary_filename( char *fmt, ... )
{
    va_list   args;
    char    * name, * path;

    va_start( args, fmt );
    name = hb_strdup_vaprintf(fmt, args);
    va_end( args );

    path = hb_strdup_printf("%s/%s", hb_get_temporary_directory(), name);
    free(name);

    return path;
}

/************************************************************************
 * hb_stat
 ************************************************************************
 * Wrapper to the real stat, needed to handle utf8 filenames on
 * windows.
 ***********************************************************************/
int hb_stat(const char *path, hb_stat_t *sb)
{
#ifdef SYS_MINGW
    wchar_t path_utf16[MAX_PATH];
    if (!MultiByteToWideChar(CP_UTF8, 0, path, -1, path_utf16, MAX_PATH))
        return -1;
    return _wstat64( path_utf16, sb );
#else
    return stat(path, sb);
#endif
}

/************************************************************************
 * hb_fopen
 ************************************************************************
 * Wrapper to the real fopen, needed to handle utf8 filenames on
 * windows.
 ***********************************************************************/
FILE * hb_fopen(const char *path, const char *mode)
{
#ifdef SYS_MINGW
    FILE *f;
    wchar_t path_utf16[MAX_PATH];
    wchar_t mode_utf16[16];
    if (!MultiByteToWideChar(CP_UTF8, 0, path, -1, path_utf16, MAX_PATH))
        return NULL;
    if (!MultiByteToWideChar(CP_UTF8, 0, mode, -1, mode_utf16, 16))
        return NULL;
    errno_t ret = _wfopen_s(&f, path_utf16, mode_utf16);
    if (ret)
        return NULL;
    return f;
#else
    return fopen(path, mode);
#endif
}

HB_DIR* hb_opendir(const char *path)
{
#ifdef SYS_MINGW
    HB_DIR *dir;
    wchar_t path_utf16[MAX_PATH];

    if (!MultiByteToWideChar(CP_UTF8, 0, path, -1, path_utf16, MAX_PATH))
        return NULL;
    dir = malloc(sizeof(HB_DIR));
    if (dir == NULL)
        return NULL;
    dir->wdir = _wopendir(path_utf16);
    if (dir->wdir == NULL)
    {
        free(dir);
        return NULL;
    }
    return dir;
#else
    return opendir(path);
#endif
}

int hb_closedir(HB_DIR *dir)
{
#ifdef SYS_MINGW
    int ret;

    ret = _wclosedir(dir->wdir);
    free(dir);
    return ret;
#else
    return closedir(dir);
#endif
}

struct dirent * hb_readdir(HB_DIR *dir)
{
#ifdef SYS_MINGW
    struct _wdirent *entry;
    entry = _wreaddir(dir->wdir);
    if (entry == NULL)
        return NULL;

    int len = WideCharToMultiByte(CP_UTF8, 0, entry->d_name, -1,
                                  dir->entry.d_name, sizeof(dir->entry.d_name),
                                  NULL, NULL );
    dir->entry.d_ino = entry->d_ino;
    dir->entry.d_reclen = entry->d_reclen;
    dir->entry.d_namlen = len - 1;
    return &dir->entry;
#else
    return readdir(dir);
#endif
}

void hb_rewinddir(HB_DIR *dir)
{
#ifdef SYS_MINGW
    _wrewinddir(dir->wdir);
#else
    return rewinddir(dir);
#endif
}

char * hb_strr_dir_sep(const char *path)
{
#ifdef SYS_MINGW
    char *sep = strrchr(path, '/');
    if (sep == NULL)
        sep = strrchr(path, '\\');
    return sep;
#else
    return strrchr(path, '/');
#endif
}

/************************************************************************
 * hb_mkdir
 ************************************************************************
 * Wrapper to the real mkdir, needed only because it doesn't take a
 * second argument on Win32. Grrr.
 ***********************************************************************/
int hb_mkdir(const char * path)
{
#ifdef SYS_MINGW
    wchar_t path_utf16[MAX_PATH];
    if (!MultiByteToWideChar(CP_UTF8, 0, path, -1, path_utf16, MAX_PATH))
        return -1;
    return _wmkdir(path_utf16);
#else
    return mkdir(path, 0755);
#endif
}

/************************************************************************
 * Portable thread implementation
 ***********************************************************************/
struct hb_thread_s
{
    char          * name;
    int             priority;
    thread_func_t * function;
    void          * arg;

    hb_lock_t     * lock;
    int             exited;
    pthread_t       thread;
};

/* Get a unique identifier to thread and represent as 64-bit unsigned.
 * If unsupported, the value 0 is be returned.
 * Caller should use result only for display/log purposes.
 */
static uint64_t hb_thread_to_integer( const hb_thread_t* t )
{
#if defined( SYS_CYGWIN )
    return (uint64_t)t->thread;
#elif defined( _WIN32 ) || defined( __MINGW32__ )
# if defined(PTW32_VERSION)
    return (uint64_t)(ptrdiff_t)t->thread.p;
# else
    return (uint64_t)t->thread;
# endif
#elif defined( SYS_DARWIN )
    return (unsigned long)t->thread;
#else
    return (uint64_t)t->thread;
#endif
}

/************************************************************************
 * hb_thread_func()
 ************************************************************************
 * We use it as the root routine for any thread, for two reasons:
 *  + To set the thread priority on macOS (pthread_setschedparam() could
 *    be called from hb_thread_init(), but it's nicer to do it as we
 *    are sure it is done before the real routine starts)
 *  + Get informed when the thread exits, so we know whether
 *    hb_thread_close() will block or not.
 ***********************************************************************/
static void attribute_align_thread hb_thread_func( void * _t )
{
    hb_thread_t * t = (hb_thread_t *) _t;

#if (defined( SYS_DARWIN ) && !defined(__aarch64__)) || defined( SYS_FREEBSD ) || \
    defined( SYS_NETBSD ) || defined( SYS_OPENBSD )
    /* Set the thread priority
       Do not change priority on Darwin arm systems */
    struct sched_param param;
    memset( &param, 0, sizeof( struct sched_param ) );
    param.sched_priority = t->priority;
    pthread_setschedparam( pthread_self(), SCHED_OTHER, &param );
#endif

#if defined( SYS_DARWIN )
    pthread_setname_np( t->name );
#endif

    /* Start the actual routine */
    t->function( t->arg );

    /* Inform that the thread can be joined now */
    hb_deep_log( 2, "thread %"PRIx64" exited (\"%s\")", hb_thread_to_integer( t ), t->name );
    hb_lock( t->lock );
    t->exited = 1;
    hb_unlock( t->lock );
}

/************************************************************************
 * hb_thread_init()
 ************************************************************************
 * name:     user-friendly name
 * function: the thread routine
 * arg:      argument of the routine
 * priority: HB_LOW_PRIORITY or HB_NORMAL_PRIORITY
 ***********************************************************************/
hb_thread_t * hb_thread_init( const char * name, void (* function)(void *),
                              void * arg, int priority )
{
    hb_thread_t * t = calloc( sizeof( hb_thread_t ), 1 );

    t->name     = strdup( name );
    t->function = function;
    t->arg      = arg;
    t->priority = priority;

    t->lock     = hb_lock_init();

    /* Create and start the thread */
    pthread_create( &t->thread, NULL,
                    (void * (*)( void * )) hb_thread_func, t );

    hb_deep_log( 2, "thread %"PRIx64" started (\"%s\")", hb_thread_to_integer( t ), t->name );
    return t;
}

/************************************************************************
 * hb_thread_close()
 ************************************************************************
 * Joins the thread and frees memory.
 ***********************************************************************/
void hb_thread_close( hb_thread_t ** _t )
{
    hb_thread_t * t = *_t;

    /* Join the thread */
    pthread_join( t->thread, NULL );

    hb_deep_log( 2, "thread %"PRIx64" joined (\"%s\")", hb_thread_to_integer( t ), t->name );

    hb_lock_close( &t->lock );
    free( t->name );
    free( t );
    *_t = NULL;
}

/************************************************************************
 * hb_thread_has_exited()
 ************************************************************************
 * Returns 1 if the thread can be joined right away, 0 otherwise.
 ***********************************************************************/
int hb_thread_has_exited( hb_thread_t * t )
{
    int exited;

    hb_lock( t->lock );
    exited = t->exited;
    hb_unlock( t->lock );

    return exited;
}

/************************************************************************
 * Portable mutex implementation
 ***********************************************************************/
struct hb_lock_s
{
    pthread_mutex_t mutex;
};

/************************************************************************
 * hb_lock_init()
 * hb_lock_close()
 * hb_lock()
 * hb_unlock()
 ************************************************************************
 * Basic wrappers to OS-specific semaphore or mutex functions.
 ***********************************************************************/
hb_lock_t * hb_lock_init()
{
    hb_lock_t * l = calloc( sizeof( hb_lock_t ), 1 );

    pthread_mutexattr_t mta;

    pthread_mutexattr_init(&mta);

#if defined( SYS_CYGWIN ) || defined( SYS_FREEBSD ) || defined( SYS_NETBSD ) || \
    defined( SYS_OPENBSD )
    pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_NORMAL);
#endif

    pthread_mutex_init( &l->mutex, &mta );

    return l;
}

void hb_lock_close( hb_lock_t ** _l )
{
    hb_lock_t * l = *_l;

    if (l == NULL)
    {
        return;
    }
    pthread_mutex_destroy( &l->mutex );
    free( l );

    *_l = NULL;
}

void hb_lock( hb_lock_t * l )
{
    pthread_mutex_lock( &l->mutex );
}

void hb_unlock( hb_lock_t * l )
{
    pthread_mutex_unlock( &l->mutex );
}

/************************************************************************
 * Portable condition variable implementation
 ***********************************************************************/
struct hb_cond_s
{
    pthread_cond_t      cond;
};

/************************************************************************
 * hb_cond_init()
 * hb_cond_close()
 * hb_cond_wait()
 * hb_cond_signal()
 ************************************************************************
 * Win9x is not supported by this implementation (SignalObjectAndWait()
 * only available on Windows 2000/XP).
 ***********************************************************************/
hb_cond_t * hb_cond_init()
{
    hb_cond_t * c = calloc( sizeof( hb_cond_t ), 1 );

    if( c == NULL )
        return NULL;

    pthread_cond_init( &c->cond, NULL );
    return c;
}

void hb_cond_close( hb_cond_t ** _c )
{
    hb_cond_t * c = *_c;

    if (c == NULL)
    {
        return;
    }
    pthread_cond_destroy( &c->cond );
    free( c );

    *_c = NULL;
}

void hb_cond_wait( hb_cond_t * c, hb_lock_t * lock )
{
    pthread_cond_wait( &c->cond, &lock->mutex );
}

void hb_clock_gettime( struct timespec *tp )
{
    struct timeval tv;

    gettimeofday( &tv, NULL );
    tp->tv_sec = tv.tv_sec;
    tp->tv_nsec = tv.tv_usec * 1000;
}

void hb_yield(void)
{
    sched_yield();
}

void hb_cond_timedwait( hb_cond_t * c, hb_lock_t * lock, int msec )
{
    struct timespec ts;
    hb_clock_gettime(&ts);
    ts.tv_nsec += (msec % 1000) * 1000000;
    ts.tv_sec += msec / 1000 + (ts.tv_nsec / 1000000000);
    ts.tv_nsec %= 1000000000;
    pthread_cond_timedwait( &c->cond, &lock->mutex, &ts );
}

void hb_cond_signal( hb_cond_t * c )
{
    pthread_cond_signal( &c->cond );
}

void hb_cond_broadcast( hb_cond_t * c )
{
    pthread_cond_broadcast( &c->cond );
}

/************************************************************************
 * Network
 ***********************************************************************/

struct hb_net_s
{
    int socket;
};

hb_net_t * hb_net_open( char * address, int port )
{
    hb_net_t * n = calloc( sizeof( hb_net_t ), 1 );

    struct sockaddr_in   sock;
    struct hostent     * host;

#ifdef SYS_MINGW
    WSADATA wsaData;
    int iResult, winsock_init = 0;

    // Initialize Winsock
    if (!winsock_init)
    {
        iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (iResult != 0)
        {
            hb_log("WSAStartup failed: %d", iResult);
            free(n);
            return NULL;
        }
        winsock_init = 1;
    }
#endif

    /* TODO: find out why this doesn't work on Win32 */
    if( !( host = gethostbyname( address ) ) )
    {
        hb_log( "gethostbyname failed (%s)", address );
        free( n );
        return NULL;
    }

    memset( &sock, 0, sizeof( struct sockaddr_in ) );
    sock.sin_family = host->h_addrtype;
    sock.sin_port   = htons( port );
    memcpy( &sock.sin_addr, host->h_addr, host->h_length );

    if( ( n->socket = socket( host->h_addrtype, SOCK_STREAM, 0 ) ) < 0 )
    {
        hb_log( "socket failed" );
        free( n );
        return NULL;
    }

    if( connect( n->socket, (struct sockaddr *) &sock,
                 sizeof( struct sockaddr_in ) ) < 0 )
    {
        hb_log( "connect failed" );
        free( n );
        return NULL;
    }

    return n;
}

int hb_net_send( hb_net_t * n, char * buffer )
{
    return send( n->socket, buffer, strlen( buffer ), 0 );
}

int hb_net_recv( hb_net_t * n, char * buffer, int size )
{
    return recv( n->socket, buffer, size - 1, 0 );
}

void hb_net_close( hb_net_t ** _n )
{
    hb_net_t * n = (hb_net_t *) *_n;
    close( n->socket );
    free( n );
    *_n = NULL;
}

/************************************************************************
* OS Backup Include / Exclude
***********************************************************************/

void hb_system_backup_set_excluded(const char *path, int exclude)
{
#ifdef __APPLE__
    if (path != NULL)
    {
        CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
                                                               (const UInt8 *)path,
                                                               strlen(path),
                                                               false);
        if (url != NULL)
        {
            CSBackupSetItemExcluded(url, exclude, false);
            CFRelease(url);
        }
    }
#endif
}

/************************************************************************
* OS Sleep Allow / Prevent
***********************************************************************/

void* hb_system_sleep_opaque_init()
{
    void *opaque = NULL;
#ifdef __APPLE__
    opaque = calloc(sizeof(IOPMAssertionID), 1);
    if (opaque == NULL)
    {
        hb_error("hb_system_sleep: failed to allocate opaque");
        return NULL;
    }

    IOPMAssertionID *assertionID = (IOPMAssertionID*)opaque;
    *assertionID = -1;
#endif
    return opaque;
}

void hb_system_sleep_opaque_close(void **opaque)
{
    if (*opaque != NULL)
    {
        hb_system_sleep_private_enable(*opaque);
    }
#ifdef __APPLE__
    if (*opaque != NULL)
    {
        IOPMAssertionID *assertionID = (IOPMAssertionID*)*opaque;
        free(assertionID);
    }
#endif
    *opaque = NULL;
}

void hb_system_sleep_private_enable(void *opaque)
{
#ifdef __APPLE__
    if (opaque == NULL)
    {
        hb_error("hb_system_sleep: opaque is NULL");
        return;
    }

    IOPMAssertionID *assertionID = (IOPMAssertionID*)opaque;
    if (*assertionID == -1)
    {
        // nothing to do
        return;
    }

    IOReturn success = IOPMAssertionRelease(*assertionID);
    if (success == kIOReturnSuccess)
    {
        hb_deep_log(3,
                    "hb_system_sleep: assertion %d released, sleep allowed",
                    *assertionID);
        *assertionID = -1;
    }
    else
    {
        hb_log("hb_system_sleep: failed to allow system sleep");
    }
#endif
}

void hb_system_sleep_private_disable(void *opaque)
{
#ifdef __APPLE__
    if (opaque == NULL)
    {
        hb_error("hb_system_sleep: opaque is NULL");
        return;
    }

    IOPMAssertionID *assertionID = (IOPMAssertionID*)opaque;
    if (*assertionID != -1)
    {
        // nothing to do
        return;
    }

    // 128 chars limit for IOPMAssertionCreateWithName
    CFStringRef reasonForActivity =
    CFSTR("HandBrake is currently scanning and/or encoding");

    IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertPreventUserIdleSystemSleep,
                                                   kIOPMAssertionLevelOn,
                                                   reasonForActivity,
                                                   assertionID);
    if (success == kIOReturnSuccess)
    {
        hb_deep_log(3,
                    "hb_system_sleep: assertion %d created, sleep prevented",
                    *assertionID);
    }
    else
    {
        hb_log("hb_system_sleep: failed to prevent system sleep");
    }
#endif
}

void * hb_dlopen(const char *name)
{
#ifdef SYS_MINGW
    HMODULE h = LoadLibraryExA(name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
#else
    void *h = dlopen(name, RTLD_LAZY | RTLD_LOCAL);
#endif

    return h;
}

void * hb_dlsym(void *h, const char *name)
{
#ifdef SYS_MINGW
    FARPROC p = GetProcAddress(h, name);
#else
    void *p = dlsym(h, name);
#endif
    return p;
}

int hb_dlclose(void *h)
{
#ifdef SYS_MINGW
    return FreeLibrary(h);
#else
    return dlclose(h);
#endif
}

size_t hb_getline(char ** lineptr, size_t * n, FILE * fp)
{
#ifdef SYS_MINGW
    char   * bufptr = NULL;
    char   * p      = bufptr;
    size_t   size;
    int      c;

    if (lineptr == NULL)
    {
        return -1;
    }
    if (fp == NULL)
    {
        return -1;
    }
    if (n == NULL)
    {
        return -1;
    }
    bufptr = *lineptr;
    size   = *n;

    c = fgetc(fp);
    if (c == EOF)
    {
        return -1;
    }
    if (bufptr == NULL)
    {
        bufptr = malloc(128);
        if (bufptr == NULL)
        {
            return -1;
        }
        size = 128;
    }
    p = bufptr;
    while (c != EOF)
    {
        if ((p - bufptr) >= (size - 1))
        {
            char * tmp;
            size = size + 128;
            tmp = realloc(bufptr, size);
            if (tmp == NULL)
            {
                free(bufptr);
                return -1;
            }
            p = tmp + (p - bufptr);
            bufptr = tmp;
        }
        *p++ = c;
        if (c == '\n')
        {
            break;
        }
        c = fgetc(fp);
    }

    *p++ = '\0';
    *lineptr = bufptr;
    *n = size;

    return p - bufptr - 1;
#else
    return getline(lineptr, n, fp);
#endif
}

char * hb_strndup(const char * src, size_t len)
{
#ifdef SYS_MINGW
    char * result, * end;

    if (src == NULL)
    {
        return NULL;
    }

    end = memchr(src, 0, len);
    if (end != NULL)
    {
        len = end - src;
    }

    result = malloc(len + 1);
    if (result == NULL)
    {
        return NULL;
    }
    memcpy(result, src, len);
    result[len] = 0;

    return result;
#else
    return strndup(src, len);
#endif
}

#if HB_PROJECT_FEATURE_QSV
#if defined(SYS_LINUX) || defined(SYS_FREEBSD)

#define MAX_NODES             16
#define DRI_RENDER_NODE_START 128
#define DRI_RENDER_NODE_LAST  (DRI_RENDER_NODE_START + MAX_NODES - 1)
#define DRI_CARD_NODE_START   0
#define DRI_CARD_NODE_LAST    (DRI_CARD_NODE_START + MAX_NODES - 1)

const char* DRI_PATH = "/dev/dri/";
const char* DRI_NODE_RENDER = "renderD";
const char* DRI_NODE_CARD = "card";

static int try_adapter(const char * name, const char * dir,
                const char * prefix, int node_start, int node_last)
{
    int             node;
    int             len        = strlen(name);
    char          * driverName = malloc(len + 1);
    drm_version_t   version = {};

    version.name_len = len + 1;
    version.name     = driverName;
    for (node = node_start; node <= node_last; node++)
    {
        char * adapter = hb_strdup_printf("%s%s%d", dir, prefix, node);
        int    fd      = open(adapter, O_RDWR);

        free(adapter);
        if (fd < 0)
        {
            continue;
        }

        if (!ioctl(fd, DRM_IOCTL_VERSION, &version) &&
            version.name_len == len && !strncmp(driverName, name, len))
        {
            free(driverName);
            return fd;
        }
        close(fd);
    }

    free(driverName);
    return -1;
}

static int open_adapter(const char * name, const uint dri_render_node)
{
    int fd;
    // If dri_render_node is unknown enumerate across the predefined range of renders
    if (dri_render_node == 0)
    {
        fd = try_adapter(name, DRI_PATH, DRI_NODE_RENDER,
                            DRI_RENDER_NODE_START, DRI_RENDER_NODE_LAST);
    }
    else
    {
        fd = try_adapter(name, DRI_PATH, DRI_NODE_RENDER,
                            dri_render_node, dri_render_node);
    }
    if (fd < 0)
    {
        fd = try_adapter(name, DRI_PATH, DRI_NODE_CARD,
                         DRI_CARD_NODE_START, DRI_CARD_NODE_LAST);
    }
    return fd;
}

static int try_va_interface(hb_display_t * hbDisplay,
                            const char * interface_name)
{
    if (interface_name != NULL)
    {
        vaSetDriverName(hbDisplay->vaDisplay, (char *) interface_name);
    }

    hbDisplay->vaDisplay = vaGetDisplayDRM(hbDisplay->vaFd);
    if (hbDisplay->vaDisplay == NULL)
    {
        return -1;
    }

    int major = 0, minor = 0;
    VAStatus vaRes = vaInitialize(hbDisplay->vaDisplay, &major, &minor);
    if (vaRes != VA_STATUS_SUCCESS)
    {
        vaTerminate(hbDisplay->vaDisplay);
        return -1;
    }
    hbDisplay->handle = hbDisplay->vaDisplay;
    hbDisplay->mfxType = MFX_HANDLE_VA_DISPLAY;

    return 0;
}

hb_display_t * hb_display_init(const char         * driver_name,
                               const uint32_t       dri_render_node,
                               const char * const * interface_names)
{
    hb_display_t * hbDisplay = calloc(sizeof(hb_display_t), 1);
    char         * env;
    int            ii;

    hbDisplay->vaDisplay = NULL;
    hbDisplay->vaFd      = open_adapter(driver_name, dri_render_node);
    if (hbDisplay->vaFd < 0)
    {
        hb_deep_log( 3, "hb_va_display_init: no display found" );
        free(hbDisplay);
        return NULL;
    }

    if ((env = getenv("LIBVA_DRIVER_NAME")) != NULL)
    {
        // Use only environment if it's set
        hb_log("hb_display_init: using VA driver '%s'", env);
        if (try_va_interface(hbDisplay, NULL) == 0)
        {
            return hbDisplay;
        }
    }
    else
    {
        // Try default
        hb_log("hb_display_init: attempting VA default driver");
        if (try_va_interface(hbDisplay, NULL) == 0)
        {
            return hbDisplay;
        }
        // Try list of VA driver names
        for (ii = 0; interface_names[ii] != NULL; ii++)
        {
            hb_log("hb_display_init: attempting VA driver '%s'",
                   interface_names[ii]);
            if (try_va_interface(hbDisplay, interface_names[ii]) == 0)
            {
                return hbDisplay;
            }
        }
    }
    // No working VA driver found
    close(hbDisplay->vaFd);
    free(hbDisplay);
    return NULL;
}

void hb_display_close(hb_display_t ** _d)
{
    hb_display_t * hbDisplay = *_d;

    if (hbDisplay == NULL)
    {
        return;
    }
    if (hbDisplay->vaDisplay)
    {
        vaTerminate(hbDisplay->vaDisplay);
    }
    if (hbDisplay->vaFd >= 0)
    {
        close(hbDisplay->vaFd);
    }
    free(hbDisplay);

    *_d = NULL;
}

#else // !SYS_LINUX && !SYS_FREEBSD

hb_display_t * hb_display_init(const char         * driver_name,
                               const uint32_t       dri_render_node,
                               const char * const * interface_names)
{
    return NULL;
}

void hb_display_close(hb_display_t ** _d)
{
    (void)_d;
}

#endif // SYS_LINUX || SYS_FREEBSD
#else // !HB_PROJECT_FEATURE_QSV

hb_display_t * hb_display_init(const char         * driver_name,
                               const uint32_t       dri_render_node,
                               const char * const * interface_names)
{
    return NULL;
}

void hb_display_close(hb_display_t ** _d)
{
    (void)_d;
}

#endif // HB_PROJECT_FEATURE_QSV
