/*
 * smslib.m
 * 
 * SMSLib Sudden Motion Sensor Access Library
 * Copyright (c) 2010 Suitable Systems
 * All rights reserved.
 * 
 * Developed by: Daniel Griscom
 *               Suitable Systems
 *               http://www.suitable.com
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal with the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimers.
 * 
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimers in the
 * documentation and/or other materials provided with the distribution.
 * 
 * - Neither the names of Suitable Systems nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this Software without specific prior written permission.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
 *
 * For more information about SMSLib, see
 *		<http://www.suitable.com/tools/smslib.html>
 * or contact
 *		Daniel Griscom
 *		Suitable Systems
 *		1 Centre Street, Suite 204
 *		Wakefield, MA 01880
 *		(781) 665-0053
 *
 */

#import <IOKit/IOKitLib.h>
#import <sys/sysctl.h>
#import <math.h>
#import "smslib.h"

#pragma mark Internal structures

// Represents a single axis of a type of sensor.
typedef struct axisStruct {
	int enabled;				// Non-zero if axis is valid in this sensor
	int index;					// Location in struct of first byte
	int size;					// Number of bytes
	float zerog;				// Value meaning "zero g"
	float oneg;					// Change in value meaning "increase of one g"
								// (can be negative if axis sensor reversed)
} axisStruct;

// Represents the configuration of a type of sensor.
typedef struct sensorSpec {
	const char *model;			// Prefix of model to be tested
	const char *name;			// Name of device to be read
	unsigned int function;		// Kernel function index
	int recordSize;				// Size of record to be sent/received
	axisStruct axes[3];			// Description of three axes (X, Y, Z)
} sensorSpec;
	
// Configuration of all known types of sensors. The configurations are
// tried in order until one succeeds in returning data.
// All default values are set here, but each axis' zerog and oneg values 
// may be changed to saved (calibrated) values.
//
// These values came from SeisMaCalibrate calibration reports. In general I've
// found the following:
//	- All Intel-based SMSs have 250 counts per g, centered on 0, but the signs
//		are different (and in one case two axes are swapped)
//	- PowerBooks and iBooks all have sensors centered on 0, and reading
//		50-53 steps per gravity (but with differing polarities!)
//	- PowerBooks and iBooks of the same model all have the same axis polarities
//	- PowerBook and iBook access methods are model- and OS version-specific
//
// So, the sequence of tests is:
//	- Try model-specific access methods. Note that the test is for a match to the
//		beginning of the model name, e.g. the record with model name "MacBook"
//		matches computer models "MacBookPro1,2" and "MacBook1,1" (and ""
//		matches any model).
//	- If no model-specific record's access fails, then try each model-independent
//		access method in order, stopping when one works.
static const sensorSpec sensors[] = {
	// ****** Model-dependent methods ******
	// The PowerBook5,6 is one of the G4 models that seems to lose
	// SMS access until the next reboot.
	{"PowerBook5,6", "IOI2CMotionSensor", 21, 60, {
			{1, 0, 1, 0,  51.5},
			{1, 1, 1, 0, -51.5},
			{1, 2, 1, 0, -51.5}
		}
	},
	// The PowerBook5,7 is one of the G4 models that seems to lose
	// SMS access until the next reboot.
	{"PowerBook5,7", "IOI2CMotionSensor", 21, 60, {
			{1, 0, 1, 0,  51.5},
			{1, 1, 1, 0,  51.5},
			{1, 2, 1, 0,  51.5}
		}
	},
	// Access seems to be reliable on the PowerBook5,8
	{"PowerBook5,8", "PMUMotionSensor", 21, 60, {
			{1, 0, 1, 0, -51.5},
			{1, 1, 1, 0,  51.5},
			{1, 2, 1, 0, -51.5}
		}
	},
	// Access seems to be reliable on the PowerBook5,9
	{"PowerBook5,9", "PMUMotionSensor", 21, 60, {
			{1, 0, 1, 0,  51.5},
			{1, 1, 1, 0, -51.5},
			{1, 2, 1, 0, -51.5}
		}
	},
	// The PowerBook6,7 is one of the G4 models that seems to lose
	// SMS access until the next reboot.
	{"PowerBook6,7", "IOI2CMotionSensor", 21, 60, {
			{1, 0, 1, 0,  51.5},
			{1, 1, 1, 0,  51.5},
			{1, 2, 1, 0,  51.5}
		}
	},
	// The PowerBook6,8 is one of the G4 models that seems to lose
	// SMS access until the next reboot.
	{"PowerBook6,8", "IOI2CMotionSensor", 21, 60, {
			{1, 0, 1, 0,  51.5},
			{1, 1, 1, 0,  51.5},
			{1, 2, 1, 0,  51.5}
		}
	},
	// MacBook Pro Core 2 Duo 17". Note the reversed Y and Z axes.
	{"MacBookPro2,1", "SMCMotionSensor", 5, 40, {
			{1, 0, 2, 0,  251},
			{1, 2, 2, 0, -251},
			{1, 4, 2, 0, -251}
		}
	},
	// MacBook Pro Core 2 Duo 15" AND 17" with LED backlight, introduced June '07.
	// NOTE! The 17" machines have the signs of their X and Y axes reversed
	// from this calibration, but there's no clear way to discriminate between
	// the two machines.
	{"MacBookPro3,1", "SMCMotionSensor", 5, 40, {
			{1, 0, 2, 0, -251},
			{1, 2, 2, 0,  251},
			{1, 4, 2, 0, -251}
		}
	},
	// ... specs?
	{"MacBook5,2", "SMCMotionSensor", 5, 40, {
			{1, 0, 2, 0, -251},
			{1, 2, 2, 0,  251},
			{1, 4, 2, 0, -251}
		}
	},
	// ... specs?
	{"MacBookPro5,1", "SMCMotionSensor", 5, 40, {
			{1, 0, 2, 0, -251},
			{1, 2, 2, 0, -251},
			{1, 4, 2, 0,  251}
		}
	},
	// ... specs?
	{"MacBookPro5,2", "SMCMotionSensor", 5, 40, {
			{1, 0, 2, 0, -251},
			{1, 2, 2, 0, -251},
			{1, 4, 2, 0,  251}
		}
	},
	// This is speculative, based on a single user's report. Looks like the X and Y axes
	// are swapped. This is true for no other known Appple laptop.
	{"MacBookPro5,3", "SMCMotionSensor", 5, 40, {
			{1, 2, 2, 0, -251},
			{1, 0, 2, 0, -251},
			{1, 4, 2, 0, -251}
		}
	},
	// ... specs?
	{"MacBookPro5,4", "SMCMotionSensor", 5, 40, {
			{1, 0, 2, 0, -251},
			{1, 2, 2, 0, -251},
			{1, 4, 2, 0,  251}
		}
	},
	// ****** Model-independent methods ******
	// Seen once with PowerBook6,8 under system 10.3.9; I suspect
	// other G4-based 10.3.* systems might use this
	{"", "IOI2CMotionSensor", 24, 60, {
			{1, 0, 1, 0, 51.5},
			{1, 1, 1, 0, 51.5},
			{1, 2, 1, 0, 51.5}
		}
	},
	// PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8
	// under OS X 10.4.*
	{"", "IOI2CMotionSensor", 21, 60, {
			{1, 0, 1, 0, 51.5},
			{1, 1, 1, 0, 51.5},
			{1, 2, 1, 0, 51.5}
		}
	},
	// PowerBook5,8 , PowerBook5,9 under OS X 10.4.*
	{"", "PMUMotionSensor", 21, 60, {
			// Each has two out of three gains negative, but it's different
			// for the different models. So, this will be right in two out
			// of three axis for either model.
			{1, 0, 1,  0, -51.5},
			{1, 1, 1, -6, -51.5},
			{1, 2, 1,  0, -51.5}
		}
	},
	// All MacBook, MacBookPro models. Hardware (at least on early MacBookPro 15")
	// is Kionix KXM52-1050 three-axis accelerometer chip. Data is at
	// http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP models
	// that use this are: 
	//		MacBook1,1
	//		MacBook2,1
	//		MacBook3,1
	//		MacBook4,1
	//		MacBook5,1
	//		MacBook6,1
	//		MacBookAir1,1
	//		MacBookPro1,1
	//		MacBookPro1,2
	//		MacBookPro4,1
	//		MacBookPro5,5
	{"", "SMCMotionSensor", 5, 40, {
			{1, 0, 2, 0, 251},
			{1, 2, 2, 0, 251},
			{1, 4, 2, 0, 251}
		}
	}
};

#define SENSOR_COUNT (sizeof(sensors)/sizeof(sensorSpec))

#pragma mark Internal prototypes

static int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector);
static float getAxis(int which, int calibrated);
static int signExtend(int value, int size);
static NSString *getModelName(void);
static NSString *getOSVersion(void);
static BOOL loadCalibration(void);
static void storeCalibration(void);
static void defaultCalibration(void);
static void deleteCalibration(void);
static int prefIntRead(NSString *prefName, BOOL *success);
static void prefIntWrite(NSString *prefName, int prefValue);
static float prefFloatRead(NSString *prefName, BOOL *success);
static void prefFloatWrite(NSString *prefName, float prefValue);
static void prefDelete(NSString *prefName);
static void prefSynchronize(void);
// static long getMicroseconds(void);
float fakeData(NSTimeInterval time);

#pragma mark Static variables

static int debugging = NO;		// True if debugging (synthetic data)
static io_connect_t connection;	// Connection for reading accel values
static int running = NO;		// True if we successfully started
static unsigned int sensorNum = 0;		// The current index into sensors[]
static const char *serviceName;	// The name of the current service
static char *iRecord, *oRecord;	// Pointers to read/write records for sensor
static int recordSize;			// Size of read/write records
static unsigned int function;	// Which kernel function should be used
static float zeros[3];			// X, Y and Z zero calibration values
static float onegs[3];			// X, Y and Z one-g calibration values

#pragma mark Defines

// Pattern for building axis letter from axis number
#define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z")
// Name of configuration for given axis' zero (axis specified by integer)
#define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)]
// Name of configuration for given axis' oneg (axis specified by integer)
#define ONEG_NAME(a) [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)]
// Name of "Is calibrated" preference
#define CALIBRATED_NAME (@"Calibrated")
// Application domain for SeisMac library
#define APP_ID ((CFStringRef)@"com.suitable.SeisMacLib")

// These #defines make the accelStartup code a LOT easier to read.
#undef LOG
#define LOG(message) \
	if (logObject) { \
		[logObject performSelector:logSelector withObject:message]; \
	}
#define LOG_ARG(format, var1) \
	if (logObject) { \
		[logObject performSelector:logSelector \
			withObject:[NSString stringWithFormat:format, var1]]; \
	}
#define LOG_2ARG(format, var1, var2) \
	if (logObject) { \
		[logObject performSelector:logSelector \
			withObject:[NSString stringWithFormat:format, var1, var2]]; \
	}
#define LOG_3ARG(format, var1, var2, var3) \
	if (logObject) { \
		[logObject performSelector:logSelector \
			withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \
	}

#pragma mark Function definitions

// This starts up the accelerometer code, trying each possible sensor
// specification. Note that for logging purposes it
// takes an object and a selector; the object's selector is then invoked
// with a single NSString as argument giving progress messages. Example
// logging method:
//		- (void)logMessage: (NSString *)theString
// which would be used in accelStartup's invocation thusly:
//		result = accelStartup(self, @selector(logMessage:));
// If the object is nil, then no logging is done. Sets calibation from built-in
// value table. Returns ACCEL_SUCCESS for success, and other (negative)
// values for various failures (returns value indicating result of
// most successful trial).
int smsStartup(id logObject, SEL logSelector) {
	io_iterator_t iterator;
	io_object_t device;
	kern_return_t result;
	sms_acceleration accel;
	int failure_result = SMS_FAIL_MODEL;
		
	running = NO;
	debugging = NO;
	
	NSString *modelName = getModelName();
	
	LOG_ARG(@"Machine model: %@\n", modelName);
	LOG_ARG(@"OS X version: %@\n", getOSVersion());
	LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION);
		
	for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) {
		
		// Set up all specs for this type of sensor
		serviceName = sensors[sensorNum].name;
		recordSize = sensors[sensorNum].recordSize;
		function = sensors[sensorNum].function;
		
		LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n",
				serviceName, function, recordSize);
		
		NSString *targetName = [NSString stringWithCString:sensors[sensorNum].model
												  encoding:NSMacOSRomanStringEncoding];
		LOG_ARG(@"    Comparing model name to target \"%@\": ", targetName);
		if ([targetName length] == 0 || [modelName hasPrefix:targetName]) {
			LOG(@"success.\n");
		} else {
			LOG(@"failure.\n");
			// Don't need to increment failure_result.
			continue;
		}
		
		LOG(@"    Fetching dictionary for service: ");
		CFMutableDictionaryRef dict = IOServiceMatching(serviceName);
		
		if (dict) {
			LOG(@"success.\n");
		} else {
			LOG(@"failure.\n");
			if (failure_result < SMS_FAIL_DICTIONARY) {
				failure_result = SMS_FAIL_DICTIONARY;
			}
			continue;
		}
		
		LOG(@"    Getting list of matching services: ");
		result = IOServiceGetMatchingServices(kIOMasterPortDefault, 
										 dict, 
										 &iterator);
		
		if (result == KERN_SUCCESS) {
			LOG(@"success.\n");
		} else {
			LOG_ARG(@"failure, with return value 0x%x.\n", result);
			if (failure_result < SMS_FAIL_LIST_SERVICES) {
				failure_result = SMS_FAIL_LIST_SERVICES;
			}
			continue;
		}
		
		LOG(@"    Getting first device in list: ");
		device = IOIteratorNext(iterator);	
		
		if (device == 0) {
			LOG(@"failure.\n");
			if (failure_result < SMS_FAIL_NO_SERVICES) {
				failure_result = SMS_FAIL_NO_SERVICES;
			}
			continue;
		} else {
			LOG(@"success.\n");
			LOG(@"    Opening device: ");
		}
		
		result = IOServiceOpen(device, mach_task_self(), 0, &connection);
		
		if (result != KERN_SUCCESS) {
			LOG_ARG(@"failure, with return value 0x%x.\n", result);
			IOObjectRelease(device);
			if (failure_result < SMS_FAIL_OPENING) {
				failure_result = SMS_FAIL_OPENING;
			}
			continue;
		} else if (connection == 0) {
			LOG_ARG(@"'success', but didn't get a connection (return value was: 0x%x).\n", result);
			IOObjectRelease(device);
			if (failure_result < SMS_FAIL_CONNECTION) {
				failure_result = SMS_FAIL_CONNECTION;
			}
			continue;
		} else {
			IOObjectRelease(device);
			LOG(@"success.\n");
		}
		LOG(@"    Testing device.\n");
		
		defaultCalibration();
		
		iRecord = (char*) malloc(recordSize);
		oRecord = (char*) malloc(recordSize);
		
		running = YES;
		result = getData(&accel, true, logObject, logSelector);
		running = NO;
		
		if (result) {
			LOG_ARG(@"    Failure testing device, with result 0x%x.\n", result);
			free(iRecord);
			iRecord = 0;
			free(oRecord);
			oRecord = 0;
			if (failure_result < SMS_FAIL_ACCESS) {
				failure_result = SMS_FAIL_ACCESS;
			}
			continue;
		} else {
			LOG(@"    Success testing device!\n");
			running = YES;
			return SMS_SUCCESS;
		}
	}
	return failure_result;
}

// This starts up the library in debug mode, ignoring the actual hardware.
// Returned data is in the form of 1Hz sine waves, with the X, Y and Z
// axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5);
// "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0,
// Z axes centered on 1 (calibrated) or 256 (uncalibrated). 
// Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS.
int smsDebugStartup(id logObject, SEL logSelector) {
	LOG(@"Starting up in debug mode\n");
	debugging = YES;
	return SMS_SUCCESS;
}

// Returns the current calibration values.
void smsGetCalibration(sms_calibration *calibrationRecord) {
	int x;
	
	for (x = 0; x < 3; x++) {
		calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]);
		calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]);
	}
}

// Sets the calibration, but does NOT store it as a preference. If the argument
// is nil then the current calibration is set from the built-in value table.
void smsSetCalibration(sms_calibration *calibrationRecord) {
	int x;
	
	if (!debugging) {
		if (calibrationRecord) {
			for (x = 0; x < 3; x++) {
				zeros[x] = calibrationRecord->zeros[x];
				onegs[x] = calibrationRecord->onegs[x];
			}
		} else {
			defaultCalibration();
		}
	}
}

// Stores the current calibration values as a stored preference.
void smsStoreCalibration(void) {
	if (!debugging)
		storeCalibration();
}

// Loads the stored preference values into the current calibration.
// Returns YES if successful.
BOOL smsLoadCalibration(void) {
	if (debugging) {
		return YES;
	} else if (loadCalibration()) {
		return YES;
	} else {
		defaultCalibration();
		return NO;
	}
}

// Deletes any stored calibration, and then takes the current calibration values
// from the built-in value table.
void smsDeleteCalibration(void) {
	if (!debugging) {
		deleteCalibration();
		defaultCalibration();
	}
}

// Fills in the accel record with calibrated acceleration data. Takes
// 1-2ms to return a value. Returns 0 if success, error number if failure.
int smsGetData(sms_acceleration *accel) {
	NSTimeInterval time;
	if (debugging) {
		usleep(1500);						// Usually takes 1-2 milliseconds
		time = [NSDate timeIntervalSinceReferenceDate];
		accel->x = fakeData(time)/5;
		accel->y = fakeData(time - 1)/5;
		accel->z = fakeData(time - 2)/5 + 1.0;
		return true;
	} else {
		return getData(accel, true, nil, nil);
	}
}

// Fills in the accel record with uncalibrated acceleration data.
// Returns 0 if success, error number if failure.
int smsGetUncalibratedData(sms_acceleration *accel) {
	NSTimeInterval time;
	if (debugging) {
		usleep(1500);						// Usually takes 1-2 milliseconds
		time = [NSDate timeIntervalSinceReferenceDate];
		accel->x = fakeData(time) * 256 / 5;
		accel->y = fakeData(time - 1) * 256 / 5;
		accel->z = fakeData(time - 2) * 256 / 5 + 256;
		return true;
	} else {
		return getData(accel, false, nil, nil);
	}
}

// Returns the length of a raw block of data for the current type of sensor.
int smsGetBufferLength(void) {
	if (debugging) {
		return 0;
	} else if (running) {
		return sensors[sensorNum].recordSize;
	} else {
		return 0;
	}
}

// Takes a pointer to accelGetRawLength() bytes; sets those bytes
// to return value from sensor. Make darn sure the buffer length is right!
void smsGetBufferData(char *buffer) {
	IOItemCount iSize = recordSize;
	IOByteCount oSize = recordSize;
	kern_return_t result;

	if (debugging || running == NO) {
		return;
	}

	memset(iRecord, 1, iSize);
	memset(buffer, 0, oSize);
#if __MAC_OS_X_VERSION_MIN_REQUIRED  >= 1050
	const size_t InStructSize = recordSize;
	size_t OutStructSize = recordSize;
	result = IOConnectCallStructMethod(connection,
						function,				// magic kernel function number
						(const void *)iRecord,
						InStructSize,
						(void *)buffer,
						&OutStructSize
					);
#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
	result = IOConnectMethodStructureIStructureO(connection,
						function,				// magic kernel function number
						iSize,
						&oSize,
						iRecord,
						buffer
					);
#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
	
	if (result != KERN_SUCCESS) {
		running = NO;
	}
}

// This returns an NSString describing the current calibration in
// human-readable form. Also include a description of the machine.
NSString *smsGetCalibrationDescription(void) {
	BOOL success;
	NSMutableString *s = [[NSMutableString alloc] init];
	
	if (debugging) {
		[s release];
		return @"Debugging!";
	}
	
	[s appendString:@"---- SeisMac Calibration Record ----\n \n"];
	[s appendFormat:@"Machine model: %@\n", 
		getModelName()];
	[s appendFormat:@"OS X build: %@\n", 
		getOSVersion()];
	[s appendFormat:@"SeisMacLib version %s, record %d\n \n", 
		SMSLIB_VERSION, sensorNum];
	[s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n",
		serviceName, function, recordSize];
	if (prefIntRead(CALIBRATED_NAME, &success) && success) {
		[s appendString:@"Calibration values (from calibration):\n"];
	} else {
		[s appendString:@"Calibration values (from defaults):\n"];
	}
	[s appendFormat:@"    X-Axis-Zero  = %.2f\n", zeros[0]];
	[s appendFormat:@"    X-Axis-One-g = %.2f\n", onegs[0]];
	[s appendFormat:@"    Y-Axis-Zero  = %.2f\n", zeros[1]];
	[s appendFormat:@"    Y-Axis-One-g = %.2f\n", onegs[1]];
	[s appendFormat:@"    Z-Axis-Zero  = %.2f\n", zeros[2]];
	[s appendFormat:@"    Z-Axis-One-g = %.2f\n \n", onegs[2]];
	[s appendString:@"---- End Record ----\n"];
	return s;
}

// Shuts down the accelerometer.
void smsShutdown(void) {
	if (!debugging) {
		running = NO;
		if (iRecord) free(iRecord);
		if (oRecord) free(oRecord);
		IOServiceClose(connection);
	}
}

#pragma mark Internal functions

// Loads the current calibration from the stored preferences.
// Returns true iff successful.
BOOL loadCalibration(void) {
	BOOL thisSuccess, allSuccess;
	int x;
	
	prefSynchronize();
	
	if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) {
		// Calibrated. Set all values from saved values.
		allSuccess = YES;
		for (x = 0; x < 3; x++) {
			zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess);
			allSuccess &= thisSuccess;
			onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess);
			allSuccess &= thisSuccess;
		}
		return allSuccess;
	}
	
	return NO;
}

// Stores the current calibration into the stored preferences.
static void storeCalibration(void) {
	int x;
	prefIntWrite(CALIBRATED_NAME, 1);
	for (x = 0; x < 3; x++) {
		prefFloatWrite(ZERO_NAME(x), zeros[x]);
		prefFloatWrite(ONEG_NAME(x), onegs[x]);
	}	
	prefSynchronize();
}


// Sets the calibration to its default values.
void defaultCalibration(void) {
	int x;
	for (x = 0; x < 3; x++) {
		zeros[x] = sensors[sensorNum].axes[x].zerog;
		onegs[x] = sensors[sensorNum].axes[x].oneg;
	}
}

// Deletes the stored preferences.
static void deleteCalibration(void) {
	int x;
	
	prefDelete(CALIBRATED_NAME);
	for (x = 0; x < 3; x++) {
		prefDelete(ZERO_NAME(x));
		prefDelete(ONEG_NAME(x));
	}
	prefSynchronize();
}

// Read a named floating point value from the stored preferences. Sets
// the success boolean based on, you guessed it, whether it succeeds.
static float prefFloatRead(NSString *prefName, BOOL *success) {
	float result = 0.0f;
	
	CFPropertyListRef ref = CFPreferencesCopyAppValue((CFStringRef)prefName, 
													   APP_ID);
	// If there isn't such a preference, fail
	if (ref == NULL) {
		*success = NO;
		return result;
	}
	CFTypeID typeID = CFGetTypeID(ref);
	// Is it a number?
	if (typeID == CFNumberGetTypeID()) {
		// Is it a floating point number?
		if (CFNumberIsFloatType((CFNumberRef)ref)) {
			// Yup: grab it.
			*success = CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result);
		} else {
			// Nope: grab as an integer, and convert to a float.
			long num;
			if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) {
				result = num;
				*success = YES;
			} else {
				*success = NO;
			}
		}
	// Or is it a string (e.g. set by the command line "defaults" command)?
	} else if (typeID == CFStringGetTypeID()) {
		result = (float)CFStringGetDoubleValue((CFStringRef)ref);
		*success = YES;
	} else {
		// Can't convert to a number: fail.
		*success = NO;
	}
	CFRelease(ref);
	return result;
}

// Writes a named floating point value to the stored preferences.
static void prefFloatWrite(NSString *prefName, float prefValue) {
	CFNumberRef cfFloat = CFNumberCreate(kCFAllocatorDefault,
										 kCFNumberFloatType,
										 &prefValue);
	CFPreferencesSetAppValue((CFStringRef)prefName,
							 cfFloat,
							 APP_ID);
	CFRelease(cfFloat);
}

// Reads a named integer value from the stored preferences.
static int prefIntRead(NSString *prefName, BOOL *success) {
	Boolean internalSuccess;
	CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName,
													 APP_ID, 
													 &internalSuccess);
	*success = internalSuccess;
	
	return result;
}

// Writes a named integer value to the stored preferences.
static void prefIntWrite(NSString *prefName, int prefValue) {
	CFPreferencesSetAppValue((CFStringRef)prefName,
							 (CFNumberRef)[NSNumber numberWithInt:prefValue],
							 APP_ID);
}

// Deletes the named preference values.
static void prefDelete(NSString *prefName) {
		CFPreferencesSetAppValue((CFStringRef)prefName,
								 NULL,
								 APP_ID);
}

// Synchronizes the local preferences with the stored preferences.
static void prefSynchronize(void) {
	CFPreferencesAppSynchronize(APP_ID);
}

// Internal version of accelGetData, with logging
int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector) {
	IOItemCount iSize = recordSize;
	IOByteCount oSize = recordSize;
	kern_return_t result;
	
	if (running == NO) {
		return -1;
	}
	
	memset(iRecord, 1, iSize);
	memset(oRecord, 0, oSize);
	
	LOG_2ARG(@"    Querying device (%u, %d): ", 
			 sensors[sensorNum].function, sensors[sensorNum].recordSize);
	
#if __MAC_OS_X_VERSION_MIN_REQUIRED  >= 1050
	const size_t InStructSize = recordSize;
	size_t OutStructSize = recordSize;
	result = IOConnectCallStructMethod(connection,
						function,				// magic kernel function number
						(const void *)iRecord,
						InStructSize,
						(void *)oRecord,
						&OutStructSize
					);
#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
	result = IOConnectMethodStructureIStructureO(connection,
						function,				// magic kernel function number
						iSize,
						&oSize,
						iRecord,
						oRecord
					);
#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050

	if (result != KERN_SUCCESS) {
		LOG(@"failed.\n");
		running = NO;
		return result;
	} else {
		LOG(@"succeeded.\n");
		
		accel->x = getAxis(0, calibrated);
		accel->y = getAxis(1, calibrated);
		accel->z = getAxis(2, calibrated);
		return 0;
	}
}

// Given the returned record, extracts the value of the given axis. If
// calibrated, then zero G is 0.0, and one G is 1.0.
float getAxis(int which, int calibrated) {
	// Get various values (to make code cleaner)
	int indx = sensors[sensorNum].axes[which].index;
	int size = sensors[sensorNum].axes[which].size;
	float zerog = zeros[which];
	float oneg = onegs[which];
	// Storage for value to be returned
	int value = 0;
	
	// Although the values in the returned record should have the proper
	// endianness, we still have to get it into the proper end of value.
#if (BYTE_ORDER == BIG_ENDIAN)
	// On PowerPC processors
	memcpy(((char *)&value) + (sizeof(int) - size), &oRecord[indx], size);
#endif
#if (BYTE_ORDER == LITTLE_ENDIAN)
	// On Intel processors
	memcpy(&value, &oRecord[indx], size);
#endif
	
	value = signExtend(value, size);
	
	if (calibrated) {
		// Scale and shift for zero.
		return ((float)(value - zerog)) / oneg;
	} else {
		return value;
	}
}

// Extends the sign, given the length of the value.
int signExtend(int value, int size) {
	// Extend sign
	switch (size) {
		case 1:
			if (value & 0x00000080)
				value |= 0xffffff00;
			break;
		case 2:
			if (value & 0x00008000)
				value |= 0xffff0000;
			break;
		case 3:
			if (value & 0x00800000)
				value |= 0xff000000;
			break;
	}
	return value;
}

// Returns the model name of the computer (e.g. "MacBookPro1,1")
NSString *getModelName(void) {
	char model[32];
	size_t len = sizeof(model);
	int name[2] = {CTL_HW, HW_MODEL};
	NSString *result;
	
	if (sysctl(name, 2, &model, &len, NULL, 0) == 0) {
		result = [NSString stringWithFormat:@"%s", model];
	} else {
		result = @"";
	}
	
	return result;
}

// Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)")
NSString *getOSVersion(void) {
	NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:
		@"/System/Library/CoreServices/SystemVersion.plist"];
	NSString *versionString = [dict objectForKey:@"ProductVersion"];
	NSString *buildString = [dict objectForKey:@"ProductBuildVersion"];
	NSString *wholeString = [NSString stringWithFormat:@"%@ (build %@)", 
		versionString, buildString];
	return wholeString;
}

// Returns time within the current second in microseconds.
// long getMicroseconds() {
//	struct timeval t;
//	gettimeofday(&t, 0);
//	return t.tv_usec;
//}

// Returns fake data given the time. Range is +/-1.
float fakeData(NSTimeInterval time) {
	long secs = lround(floor(time));
	int secsMod3 = secs % 3;
	double angle = time * 10 * M_PI * 2;
	double mag = exp(-(time - (secs - secsMod3)) * 2);
	return sin(angle) * mag;
}
	
