/*
 * idle_mac.cpp - detect desktop idle time
 * Copyright (C) 2003  Tarkvara Design Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

#include "idle.h"
#include <Carbon/Carbon.h>


// Why does Apple have to make this so complicated?
static OSStatus LoadFrameworkBundle(CFStringRef framework, CFBundleRef *bundlePtr) {
	OSStatus  err;
	FSRef   frameworksFolderRef;
	CFURLRef baseURL;
	CFURLRef bundleURL;

	if ( bundlePtr == nil ) return( -1 );

	*bundlePtr = nil;

	baseURL = nil;
	bundleURL = nil;

	err = FSFindFolder(kOnAppropriateDisk, kFrameworksFolderType, true, &frameworksFolderRef);
	if (err == noErr) {
		baseURL = CFURLCreateFromFSRef(kCFAllocatorSystemDefault, &frameworksFolderRef);
		if (baseURL == nil) {
			err = coreFoundationUnknownErr;
		}
	}
	if (err == noErr) {
		bundleURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, baseURL, framework, false);
		if (bundleURL == nil) {
			err = coreFoundationUnknownErr;
		}
	}
	if (err == noErr) {
		*bundlePtr = CFBundleCreate(kCFAllocatorSystemDefault, bundleURL);
		if (*bundlePtr == nil) {
			err = coreFoundationUnknownErr;
		}
	}
	if (err == noErr) {
		if ( ! CFBundleLoadExecutable( *bundlePtr ) ) {
			err = coreFoundationUnknownErr;
		}
	}

	// Clean up.
	if (err != noErr && *bundlePtr != nil) {
		CFRelease(*bundlePtr);
		*bundlePtr = nil;
	}
	if (bundleURL != nil) {
		CFRelease(bundleURL);
	}
	if (baseURL != nil) {
		CFRelease(baseURL);
	}

	return err;
}


class IdlePlatform::Private {
public:
	EventLoopTimerRef mTimerRef;
	int mSecondsIdle;

	Private() : mTimerRef(0), mSecondsIdle(0) {}

	static pascal void IdleTimerAction(EventLoopTimerRef, EventLoopIdleTimerMessage inState, void* inUserData);

};


pascal void IdlePlatform::Private::IdleTimerAction(EventLoopTimerRef, EventLoopIdleTimerMessage inState, void* inUserData) {
	switch (inState) {
		case kEventLoopIdleTimerStarted:
		case kEventLoopIdleTimerStopped:
    		// Get invoked with this constant at the start of the idle period,
			// or whenever user activity cancels the idle.
		   ((IdlePlatform::Private*)inUserData)->mSecondsIdle = 0;
			break;
		case kEventLoopIdleTimerIdling:
			// Called every time the timer fires (i.e. every second).
		   ((IdlePlatform::Private*)inUserData)->mSecondsIdle++;
			break;
	}
}


IdlePlatform::IdlePlatform() {
	d = new Private();
}

IdlePlatform::~IdlePlatform() {
	RemoveEventLoopTimer(d->mTimerRef);
	delete d;
}


// Typedef for the function we're getting back from CFBundleGetFunctionPointerForName.
typedef OSStatus (*InstallEventLoopIdleTimerPtr)(EventLoopRef inEventLoop,
																 EventTimerInterval   inFireDelay,
																 EventTimerInterval   inInterval,
																 EventLoopIdleTimerUPP    inTimerProc,
																 void *               inTimerData,
																 EventLoopTimerRef *  outTimer);


bool IdlePlatform::init() {
	// May already be init'ed.
	if (d->mTimerRef) {
		return true;
	}

	// According to the docs, InstallEventLoopIdleTimer is new in 10.2.
	// According to the headers, it has been around since 10.0.
	// One of them is lying.  We'll play it safe and weak-link the function.

	// Load the "Carbon.framework" bundle.
	CFBundleRef carbonBundle;
	if (LoadFrameworkBundle( CFSTR("Carbon.framework"), &carbonBundle ) != noErr) {
		return false;
	}

	// Load the Mach-O function pointers for the routine we will be using.
	InstallEventLoopIdleTimerPtr myInstallEventLoopIdleTimer = (InstallEventLoopIdleTimerPtr)CFBundleGetFunctionPointerForName(carbonBundle, CFSTR("InstallEventLoopIdleTimer"));
	if (myInstallEventLoopIdleTimer == 0) {
		return false;
	}

	EventLoopIdleTimerUPP timerUPP = NewEventLoopIdleTimerUPP(Private::IdleTimerAction);
	if ((*myInstallEventLoopIdleTimer)(GetMainEventLoop(), kEventDurationSecond, kEventDurationSecond, timerUPP, 0, &d->mTimerRef)) {
		return true;
	}

	return false;
}


int IdlePlatform::secondsIdle() {
	return d->mSecondsIdle;
}
