/*
 * Apple platform (macOS, iOS, watchOS, etc) support routines for PhysicsFS.
 *
 * Please see the file LICENSE.txt in the source's root directory.
 *
 *  This file written by Ryan C. Gordon.
 */

#define __PHYSICSFS_INTERNAL__
#include "physfs_platforms.h"

#ifdef PHYSFS_PLATFORM_APPLE

#include <Foundation/Foundation.h>
#include <dlfcn.h>

#include "physfs_internal.h"

int __PHYSFS_platformInit(void)
{
    return 1;  /* success. */
} /* __PHYSFS_platformInit */


void __PHYSFS_platformDeinit(void)
{
    /* no-op */
} /* __PHYSFS_platformDeinit */


char *__PHYSFS_platformCalcBaseDir(const char *argv0)
{
    @autoreleasepool
    {
        NSString *path = [[NSBundle mainBundle] bundlePath];
        BAIL_IF(!path, PHYSFS_ERR_OS_ERROR, NULL);
        size_t len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
        char *retval = (char *) allocator.Malloc(len + 2);
        BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
        [path getCString:retval maxLength:len+1 encoding:NSUTF8StringEncoding];
        retval[len] = '/';
        retval[len+1] = '\0';
        return retval;  /* whew. */
    } /* @autoreleasepool */
} /* __PHYSFS_platformCalcBaseDir */


char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
{
    @autoreleasepool
    {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, TRUE);
        BAIL_IF(!paths, PHYSFS_ERR_OS_ERROR, NULL);
        NSString *path = (NSString *) [paths objectAtIndex:0];
        BAIL_IF(!path, PHYSFS_ERR_OS_ERROR, NULL);
        size_t len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
        const size_t applen = strlen(app);
        char *retval = (char *) allocator.Malloc(len + applen + 3);
        BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
        [path getCString:retval maxLength:len+1 encoding:NSUTF8StringEncoding];
        snprintf(retval + len, applen + 3, "/%s/", app);
        return retval;  /* whew. */
    } /* @autoreleasepool */
} /* __PHYSFS_platformCalcPrefDir */


/* CD-ROM detection code... */

/*
 * Code based on sample from Apple Developer Connection:
 *  https://developer.apple.com/samplecode/Sample_Code/Devices_and_Hardware/Disks/VolumeToBSDNode/VolumeToBSDNode.c.htm
 */

#if !defined(PHYSFS_NO_CDROM_SUPPORT)

#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOCDMedia.h>
#include <IOKit/storage/IODVDMedia.h>
#include <sys/mount.h>

static int darwinIsWholeMedia(io_service_t service)
{
    int retval = 0;
    CFTypeRef wholeMedia;

    if (!IOObjectConformsTo(service, kIOMediaClass))
        return 0;
        
    wholeMedia = IORegistryEntryCreateCFProperty(service,
                                                 CFSTR(kIOMediaWholeKey),
                                                 NULL, 0);
    if (wholeMedia == NULL)
        return 0;

    retval = CFBooleanGetValue(wholeMedia);
    CFRelease(wholeMedia);

    return retval;
} /* darwinIsWholeMedia */


static int darwinIsMountedDisc(char *bsdName, mach_port_t mainPort)
{
    int retval = 0;
    CFMutableDictionaryRef matchingDict;
    kern_return_t rc;
    io_iterator_t iter;
    io_service_t service;

    if ((matchingDict = IOBSDNameMatching(mainPort, 0, bsdName)) == NULL)
        return 0;

    rc = IOServiceGetMatchingServices(mainPort, matchingDict, &iter);
    if ((rc != KERN_SUCCESS) || (!iter))
        return 0;

    service = IOIteratorNext(iter);
    IOObjectRelease(iter);
    if (!service)
        return 0;

    rc = IORegistryEntryCreateIterator(service, kIOServicePlane,
             kIORegistryIterateRecursively | kIORegistryIterateParents, &iter);
    
    if (!iter)
        return 0;

    if (rc != KERN_SUCCESS)
    {
        IOObjectRelease(iter);
        return 0;
    } /* if */

    IOObjectRetain(service);  /* add an extra object reference... */

    do
    {
        if (darwinIsWholeMedia(service))
        {
            if ( (IOObjectConformsTo(service, kIOCDMediaClass)) ||
                 (IOObjectConformsTo(service, kIODVDMediaClass)) )
            {
                retval = 1;
            } /* if */
        } /* if */
        IOObjectRelease(service);
    } while ((service = IOIteratorNext(iter)) && (!retval));
                
    IOObjectRelease(iter);
    IOObjectRelease(service);

    return retval;
} /* darwinIsMountedDisc */

#endif /* !defined(PHYSFS_NO_CDROM_SUPPORT) */


void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
{
#if !defined(PHYSFS_NO_CDROM_SUPPORT)
    /* macOS 12.0 changed "master" names to "main". */
    typedef kern_return_t (*ioMainPortFn)(mach_port_t, mach_port_t *);
    static ioMainPortFn ioMainPort = NULL;
    const char *devPrefix = "/dev/";
    const int prefixLen = strlen(devPrefix);
    mach_port_t mainPort = 0;
    struct statfs *mntbufp;
    int i, mounts;

    if (ioMainPort == NULL)
    {
        ioMainPort = (ioMainPortFn) dlsym(RTLD_DEFAULT, "IOMainPort");
        if (!ioMainPort)
            ioMainPort = (ioMainPortFn) dlsym(RTLD_DEFAULT, "IOMasterPort");
        if (!ioMainPort)
            return; /* oh well, no CD-ROMs for you. */
    } /* if */

    if (ioMainPort(MACH_PORT_NULL, &mainPort) != KERN_SUCCESS)
        BAIL(PHYSFS_ERR_OS_ERROR, ) /*return void*/;

    mounts = getmntinfo(&mntbufp, MNT_WAIT);  /* NOT THREAD SAFE! */
    for (i = 0; i < mounts; i++)
    {
        char *dev = mntbufp[i].f_mntfromname;
        char *mnt = mntbufp[i].f_mntonname;
        if (strncmp(dev, devPrefix, prefixLen) != 0)  /* a virtual device? */
            continue;

        dev += prefixLen;
        if (darwinIsMountedDisc(dev, mainPort))
            cb(data, mnt);
    } /* for */
#endif /* !defined(PHYSFS_NO_CDROM_SUPPORT) */
} /* __PHYSFS_platformDetectAvailableCDs */

#endif /* PHYSFS_PLATFORM_APPLE */

/* end of physfs_platform_apple.m ... */

