The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
//
//  Created by Дмитрий Бахвалов on 13.07.15.
//  Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
//

#ifndef __HIREDIS_MACOSX_H__
#define __HIREDIS_MACOSX_H__

#include <CoreFoundation/CoreFoundation.h>

#include "../hiredis.h"
#include "../async.h"

typedef struct {
    redisAsyncContext *context;
    CFSocketRef socketRef;
    CFRunLoopSourceRef sourceRef;
} RedisRunLoop;

static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {
    if( redisRunLoop != NULL ) {
        if( redisRunLoop->sourceRef != NULL ) {
            CFRunLoopSourceInvalidate(redisRunLoop->sourceRef);
            CFRelease(redisRunLoop->sourceRef);
        }
        if( redisRunLoop->socketRef != NULL ) {
            CFSocketInvalidate(redisRunLoop->socketRef);
            CFRelease(redisRunLoop->socketRef);
        }
        free(redisRunLoop);
    }
    return REDIS_ERR;
}

static void redisMacOSAddRead(void *privdata) {
    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
    CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
}

static void redisMacOSDelRead(void *privdata) {
    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
    CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
}

static void redisMacOSAddWrite(void *privdata) {
    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
    CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
}

static void redisMacOSDelWrite(void *privdata) {
    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
    CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
}

static void redisMacOSCleanup(void *privdata) {
    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
    freeRedisRunLoop(redisRunLoop);
}

static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) {
    redisAsyncContext* context = (redisAsyncContext*) info;

    switch (callbackType) {
        case kCFSocketReadCallBack:
            redisAsyncHandleRead(context);
            break;

        case kCFSocketWriteCallBack:
            redisAsyncHandleWrite(context);
            break;

        default:
            break;
    }
}

static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) {
    redisContext *redisCtx = &(redisAsyncCtx->c);

    /* Nothing should be attached when something is already attached */
    if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;

    RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop));
    if( !redisRunLoop ) return REDIS_ERR;

    /* Setup redis stuff */
    redisRunLoop->context = redisAsyncCtx;

    redisAsyncCtx->ev.addRead  = redisMacOSAddRead;
    redisAsyncCtx->ev.delRead  = redisMacOSDelRead;
    redisAsyncCtx->ev.addWrite = redisMacOSAddWrite;
    redisAsyncCtx->ev.delWrite = redisMacOSDelWrite;
    redisAsyncCtx->ev.cleanup  = redisMacOSCleanup;
    redisAsyncCtx->ev.data     = redisRunLoop;

    /* Initialize and install read/write events */
    CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL };

    redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd,
                                                       kCFSocketReadCallBack | kCFSocketWriteCallBack,
                                                       redisMacOSAsyncCallback,
                                                       &socketCtx);
    if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop);

    redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0);
    if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop);

    CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode);

    return REDIS_OK;
}

#endif