/*
 * 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.
 *
 */

#ifdef COMPILE_ON_MAC

#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() const
{
	return d->mSecondsIdle;
}
#endif
