//
//  PhidgetSpatialController.m
//  CocoaSpatial
//
//  Created by Patrick McNeil on 10-05-20.
//  Copyright 2010 Phidgets Inc. All rights reserved.
//

#import "PhidgetSpatialController.h"

//Define data rate to be 32ms
#define dataRate 32

int getmag(const char *mdfile, double longitude, double latitude, double alt, double *dres, double *fres);

//Event callback functions for C, which in turn call a method on the GUI object in it's thread context
int gotAttach(CPhidgetHandle phid, void *context) {
	[(id)context performSelectorOnMainThread:@selector(phidgetAdded:)
								  withObject:nil
							   waitUntilDone:NO];
	return 0;
}
int gotDetach(CPhidgetHandle phid, void *context) {
	[(id)context performSelectorOnMainThread:@selector(phidgetRemoved:)
								  withObject:nil
							   waitUntilDone:NO];
	return 0;
}

int gotError(CPhidgetHandle phid, void *context, int errcode, const char *error) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	[(id)context performSelectorOnMainThread:@selector(ErrorEvent:)
								  withObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:errcode], [NSString stringWithUTF8String:error], nil]
							   waitUntilDone:NO];
	[pool release];
	return 0;
}
int gotSpatialData(CPhidgetSpatialHandle phid, void *context, CPhidgetSpatial_SpatialEventDataHandle *data, int dataCount) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	//We just pass the first set of Spatial data - there could be up to 8 sets
	[(id)context performSelectorOnMainThread:@selector(SpatialData:)
								  withObject:[[PhidgetSpatialEventData alloc] initWithHandle:data[0]]
							   waitUntilDone:NO];
	[pool release];
	return 0;
}

@implementation PhidgetSpatialController

- (void)setPicture:(int)version:(CPhidget_DeviceID)devid
{
	NSImage *img = nil;
	
	switch(devid)
	{
		case PHIDID_SPATIAL_ACCEL_3AXIS:
			img = [NSImage imageNamed:@"1049_0"];
			break;
		case PHIDID_SPATIAL_ACCEL_GYRO_COMPASS:
			img = [NSImage imageNamed:@"1056_0"];
			break;
		default:
			break;
	}
	
	//don't set the dock icon to nil
	if(img)
		[NSApp setApplicationIconImage:img];
	
	//ok to set the picture to nil
	[pictureBox setImage:img];
}

- (void)phidgetAdded:(id)nothing
{
	int serial, version;
	const char *name;
	CPhidget_DeviceID devid;
	NSRect frame = [mainWindow frame];
	
	CPhidget_getSerialNumber((CPhidgetHandle)spatial, &serial);
	CPhidget_getDeviceVersion((CPhidgetHandle)spatial, &version);
	CPhidget_getDeviceName((CPhidgetHandle)spatial, &name);
	CPhidget_getDeviceID((CPhidgetHandle)spatial, &devid);
	CPhidgetSpatial_getAccelerationAxisCount(spatial, &numAccelAxes);
	CPhidgetSpatial_getGyroAxisCount(spatial, &numGyroAxes);
	CPhidgetSpatial_getCompassAxisCount(spatial, &numCompassAxes);
	
	[connectedField setStringValue:[NSString stringWithCString:name encoding:NSASCIIStringEncoding]];
	[serialField setIntValue:serial];
	[versionField setIntValue:version];
	[numAccelAxesField setIntValue:numAccelAxes];
	[numGyroAxesField setIntValue:numGyroAxes];
	[numCompassAxesField setIntValue:numCompassAxes];
	
	//filter the data a bit so it looks nicer
	CPhidgetSpatial_setDataRate(spatial, dataRate);
	accelFilter = [[LowpassFilter alloc] initWithSampleRate:1000.0/dataRate cutoffFrequency:7.5];
	magFieldFilter = [[LowpassFilter alloc] initWithSampleRate:1000.0/dataRate cutoffFrequency:7.5];
	compassBearing = [[CompassBearing alloc] init];
	[compassBearing setSampleRate:1000.0/dataRate cutoffFrequency:15.0];
	
	if([[northTypeButtons cellAtRow:0 column:1] state] == true)
		[compassBearing setUseDeclination:YES];
	
	int heightChange;
	int widthChange;
	switch(devid)
	{
		case PHIDID_SPATIAL_ACCEL_3AXIS:
			[accelBox setHidden:FALSE];
			heightChange = frame.size.height - 492;
			widthChange = frame.size.width - 317;
			break;
		case PHIDID_SPATIAL_ACCEL_GYRO_COMPASS:
			[accelBox setHidden:FALSE];
			[gyroBox setHidden:FALSE];
			[magFieldBox setHidden:FALSE];
			[compassBox setHidden:FALSE];
			[magFieldView setCircleRadius:magFieldStrength];
			heightChange = frame.size.height - 781;
			widthChange = frame.size.width - 602;
			[locationManager startUpdatingLocation];
			break;
		default:
			break;
	}
	
	frame.origin.y += heightChange;
	frame.size.height -= heightChange;
	frame.size.width -= widthChange;
	[mainWindow setMinSize:frame.size];
	[mainWindow setFrame:frame display:YES animate:NO];
	
	[self setPicture:version:devid];
}

- (void)phidgetRemoved:(id)nothing
{
	[connectedField setStringValue:@"Nothing"];
	[serialField setStringValue:@""];
	[versionField setStringValue:@""];
	[numAccelAxesField setStringValue:@""];
	[numGyroAxesField setStringValue:@""];
	[numCompassAxesField setStringValue:@""];
	
	[pictureBox setImage:nil];
	
	[accelFilter release];
	[magFieldFilter release];
	[compassBearing release];
	
	[accelBox setHidden:TRUE];
	[gyroBox setHidden:TRUE];
	[magFieldBox setHidden:TRUE];
	[compassBox setHidden:TRUE];
	
	NSRect frame = [mainWindow frame];
	int heightChange = frame.size.height - 203;
	int widthChange = frame.size.width - 317;
	frame.origin.y += heightChange;
	frame.size.height -= heightChange;
	frame.size.width -= widthChange;
	[mainWindow setMinSize:frame.size];
	[mainWindow setFrame:frame display:YES animate:NO];
}

- (IBAction)changeNorthType:(id)sender
{
	if([[[sender selectedCell] title] isEqualToString:@"Magnetic"])
		[compassBearing setUseDeclination:NO];
	else
		[compassBearing setUseDeclination:YES];

}

- (IBAction)zeroGyro:(id)sender
{
	CPhidgetSpatial_zeroGyro(spatial);
	gyroHeading[0]=gyroHeading[1]=gyroHeading[2] = 0;
}

- (void)calculateGyroHeadings:(PhidgetSpatialEventData *)data
{
	double gyro = 0;
	for (int i = 0; i < numGyroAxes; i++)
	{
		double timestampSeconds = [data Data]->timestamp.seconds + [data Data]->timestamp.microseconds/1000000.0;
		gyro = [data Data]->angularRate[i];
		
		if (lastMsCountGood[i] == true)
		{
			//calculate heading
			double timechange = timestampSeconds - lastMsCount[i];
			
			gyroHeading[i] += timechange * gyro;
		}
		
		lastMsCount[i] = timestampSeconds;
		lastMsCountGood[i] = true;
	}
}

- (void)SpatialData:(PhidgetSpatialEventData *)spatialData
{
	if(numAccelAxes > 0)
	{
		[accelFilter addSpatialVector:[spatialData Data]->acceleration[0] :[spatialData Data]->acceleration[1] :[spatialData Data]->acceleration[2]];
		if([accelFilterEnabled state])
		{
			[accelView setVector:[accelFilter x]: [accelFilter y]: [accelFilter z]];
		}
		else
		{
			[accelView setVector:[spatialData Data]->acceleration[0] :[spatialData Data]->acceleration[1] :[spatialData Data]->acceleration[2]];
		}
	}
	
	//Need to make sure that the magnetic field data exists, it will be PUNK_DBL briefly every two seconds
	if(numCompassAxes > 0 && [spatialData Data]->magneticField[0] != PUNK_DBL)
	{
		[magFieldFilter addSpatialVector:[spatialData Data]->magneticField[0] :[spatialData Data]->magneticField[1] :[spatialData Data]->magneticField[2]];
		if([magFieldFilterEnabled state])
		{
			[magFieldView setVector:[magFieldFilter x] :[magFieldFilter y] :[magFieldFilter z]];
		}
		else
		{
			[magFieldView setVector:[spatialData Data]->magneticField[0] :[spatialData Data]->magneticField[1] :[spatialData Data]->magneticField[2]];
		}
		[compassBearing setAccelerationVector:[spatialData Data]->acceleration[0] :[spatialData Data]->acceleration[1] :[spatialData Data]->acceleration[2]];
		[compassBearing setMagneticFieldVector:[spatialData Data]->magneticField[0] :[spatialData Data]->magneticField[1] :[spatialData Data]->magneticField[2]];
		[compassBearing setAccelerationVector:[accelFilter x]: [accelFilter y]: [accelFilter z]];
		[compassBearing setMagneticFieldVector:[magFieldFilter x] :[magFieldFilter y] :[magFieldFilter z]];
		[compassBearing setDeclination:magDeclination];
		[compassBearing updateBearing];
		
		[bearingField setStringValue:[NSString stringWithFormat:@"%0.1lf°",[compassBearing bearingDegrees]]];
		[compassView setBearing:[compassBearing bearing]];
	}
	
	if(numGyroAxes > 0)
	{
		[self calculateGyroHeadings: spatialData];
		[gyroView setHeadings:gyroHeading[0] :gyroHeading[1] :gyroHeading[2]];
	}

}

- (void)openCmdLine
{
	int serial = -1, remote = 0;
	NSArray *args = [[NSProcessInfo processInfo] arguments];
	if([args count] > 1)
	{
		if([[args objectAtIndex:1] isEqualToString:@"remote"])
			remote = 1;
		serial = [[args objectAtIndex:[args count]-1] intValue];
		if(serial == 0) serial = -1;
	}
	
	if(remote)
		CPhidget_openRemote((CPhidgetHandle)spatial, serial, NULL, [[passwordField stringValue] UTF8String]);
	else
		CPhidget_open((CPhidgetHandle)spatial, serial);
}

/*
 * This gets run when the GUI gets displayed
 */
- (void)awakeFromNib
{
	[mainWindow setDelegate:self];
	
	//We want to use Core Location to calculate magnetic field strengh and declination, so MacOS 10.6 only for this example.
	magFieldStrength = 0.5; //default
	magDeclination = 0.0; //default
	locationManager = [[CLLocationManager alloc] init];
	[locationManager setDelegate:self];
	
	CPhidgetSpatial_create(&spatial);
	
	CPhidget_set_OnAttach_Handler((CPhidgetHandle)spatial, gotAttach, self);
	CPhidget_set_OnDetach_Handler((CPhidgetHandle)spatial, gotDetach, self);
	CPhidgetSpatial_set_OnSpatialData_Handler(spatial, gotSpatialData, self);
	CPhidget_set_OnError_Handler((CPhidgetHandle)spatial, gotError, self);
	
	[self openCmdLine];
}

- (void)windowWillClose:(NSNotification *)aNotification {
	
	//Because this event is called twice
	if(spatial)
	{
		//Stop all events before closing
		CPhidget_set_OnAttach_Handler((CPhidgetHandle)spatial, NULL, NULL);
		CPhidget_set_OnDetach_Handler((CPhidgetHandle)spatial, NULL, NULL);
		CPhidgetSpatial_set_OnSpatialData_Handler(spatial, NULL, NULL);
		
		CPhidget_close((CPhidgetHandle)spatial);
		CPhidget_delete((CPhidgetHandle)spatial);
		spatial = NULL;
	}
	[NSApp terminate:self];
}

//Update declination and expected field strength from the current lat/long
- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation
{
	double d, f;
    //NSLog(@"Location: %@", [newLocation description]);
	if(!getmag(
		   (char*)[[[NSBundle bundleForClass:[self class]] pathForResource:@"IGRF11.COF" ofType:nil] cStringUsingEncoding:NSASCIIStringEncoding],
		   newLocation.coordinate.longitude, newLocation.coordinate.latitude,
		   newLocation.altitude, &d, &f
		   ))
	{
		magDeclination = d;
		magFieldStrength = f / 100000.0; //nT to Gauss
	}
}

- (void)locationManager:(CLLocationManager *)manager
	   didFailWithError:(NSError *)error
{
	NSLog(@"Location Error: %@", [error description]);
}

int errorCounter = 0;
- (void)ErrorEvent:(NSArray *)errorEventData
{
	int errorCode = [[errorEventData objectAtIndex:0] intValue];
	NSString *errorString = [errorEventData objectAtIndex:1];
	
	switch(errorCode)
	{
		case EEPHIDGET_BADPASSWORD:
			CPhidget_close((CPhidgetHandle)spatial);
			[NSApp runModalForWindow:passwordPanel];
			break;
		case EEPHIDGET_BADVERSION:
			CPhidget_close((CPhidgetHandle)spatial);
			NSRunAlertPanel(@"Version mismatch", [NSString stringWithFormat:@"%@\nApplication will now close.", errorString], nil, nil, nil);
			[NSApp terminate:self];
			break;
		default:
			errorCounter++;
			
			NSAttributedString *string = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",errorString]];
			
			[[errorEventLog textStorage] beginEditing];
			[[errorEventLog textStorage] appendAttributedString:string];
			[[errorEventLog textStorage] endEditing];
			
			[errorEventLogCounter setIntValue:errorCounter];
			if(![errorEventLogWindow isVisible])
				[errorEventLogWindow setIsVisible:YES];
			break;
	}
}

- (IBAction)clearErrorLog:(id)sender
{
	[[errorEventLog textStorage] setAttributedString:[[NSAttributedString alloc] initWithString:@""]];
	[errorEventLogCounter setIntValue:0];
	errorCounter = 0;
}

- (IBAction)passwordOK:(id)sender
{
	[passwordPanel setIsVisible:NO];
	[NSApp stopModal];
	[self openCmdLine];
	[passwordField setStringValue:@""];
}

- (IBAction)passwordCancel:(id)sender
{
	[passwordPanel setIsVisible:NO];
	[NSApp stopModal];
	[NSApp terminate:self];
}
@end
