//
//  UIAccelerometer_Additions.m
//  BoringMaze
//
//

#import "UIAccelerometer_Additions.h"

#if TARGET_OS_EMBEDDED

@implementation UIAccelerometer ( Additions )
- (void)simulateRandomGravity {
}
@end

#else

#pragma mark UISimulatedAcceleration

/* UISimulatedAcceleration
 *   Subclass of UIAcceleration that allows setting acceleration values
 *   During simulation, this allows us to create UIAcceleration objects
 */

@interface UISimulatedAcceleration : UIAcceleration {
@private
    UIAccelerationValue x_, y_, z_;
    NSTimeInterval timestamp_;
}
- (id)initWithAccelerationX:(UIAccelerationValue)x Y:(UIAccelerationValue)y Z:(UIAccelerationValue)z;
@end

@implementation UISimulatedAcceleration

- (id)initWithAccelerationX:(UIAccelerationValue)x Y:(UIAccelerationValue)y Z:(UIAccelerationValue)z {
    if ((self = [super init])) {
        x_ = x;
        y_ = y;
        z_ = z;
        timestamp_ = [NSDate timeIntervalSinceReferenceDate];
    }
    return self;
}

- (UIAccelerationValue)x {
    return x_;
}

- (UIAccelerationValue)y {
    return y_;
}

- (UIAccelerationValue)z {
    return z_;
}

- (NSTimeInterval)timestamp {
    return timestamp_;
}

@end

#pragma mark UIAccelerometerSimulator

/* UIAccelerometerSimulator
 *   An object that manages simulating the accelerometer by randomly
 *   picking acceleration values and notifying the accelerometer delegate.
 */

@interface UIAccelerometerSimulator : NSObject
{
    CFAbsoluteTime nextAllowedDirectionChangeTime;
    UIAccelerationValue lastX;
    UIAccelerationValue lastY;
    UIAccelerationValue lastZ;
    UIAccelerometer *accelerometer;
    
    NSTimer *simulationTimer;
    SEL simulationSEL;
}
+ (UIAccelerometerSimulator *)sharedInstance;
- (void)simulateRandomGravity;
@end

@implementation UIAccelerometerSimulator

#pragma mark Initialization and Deallocation

+ (UIAccelerometerSimulator *)sharedInstance {
    // not thread safe...
    static UIAccelerometerSimulator *sharedInstance = nil;
    if (!sharedInstance) {
        sharedInstance = [[UIAccelerometerSimulator alloc] init];
    }
    return sharedInstance;
}

- (id)init {
    if ((self = [super init])) {
        lastX = lastY = 0;
        lastZ = -1;
        nextAllowedDirectionChangeTime = [[NSDate distantPast] timeIntervalSinceReferenceDate];
        accelerometer = [[UIAccelerometer sharedAccelerometer] retain];
        [accelerometer addObserver:self forKeyPath:@"updateInterval" options:0 context:nil];
        [accelerometer addObserver:self forKeyPath:@"delegate" options:0 context:nil];
    }
    return self;
}

- (void)dealloc {
    [accelerometer removeObserver:self forKeyPath:@"updateInterval"];
    [accelerometer release];
    [simulationTimer invalidate];
    [simulationTimer release];
    [super dealloc];
}

#pragma mark Timer Management

- (void)cancelSimulations {
    [simulationTimer invalidate];
    [simulationTimer release];
    simulationTimer = nil;
    simulationSEL = NULL;
}

- (void)resetTimer {
    [simulationTimer invalidate];
    [simulationTimer release];
    simulationTimer = nil;
    if ([accelerometer delegate]) {
        simulationTimer = [[NSTimer scheduledTimerWithTimeInterval:[accelerometer updateInterval] target:self selector:simulationSEL userInfo:nil repeats:YES] retain];
    }
}

- (void)beginSimulation:(SEL)inSimulationSEL {
    [self cancelSimulations];
    simulationSEL = inSimulationSEL;
    simulationTimer = [[NSTimer scheduledTimerWithTimeInterval:[accelerometer updateInterval] target:self selector:simulationSEL userInfo:nil repeats:YES] retain];
}

#pragma mark KVO

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (object == accelerometer) {
        if ([keyPath isEqual:@"updateInterval"] || [keyPath isEqual:@"delegate"]) {
            [self resetTimer];        
        }
    }
}

#pragma mark Simulation Tick

#define ARC4RANDOM_MAX      0x100000000

static double randomDoubleBetweenValues(double min, double max) {
    double delta = max - min;
    double randnum = ((double)arc4random() / ARC4RANDOM_MAX);
    double result = min + delta * randnum;
    return result;
}

#define FILTERFACTOR 0.1
#define RAND_RANGE   1.0
#define MIN_TIME_BETWEEN_DIRECTION_CHANGE 0.25f
#define MAX_TIME_BETWEEN_DIRECTION_CHANGE 1.5f

- (void)simulateGravityTick {
    double xextent, yextent, zextent, filterfactor;
    
    if (CFAbsoluteTimeGetCurrent() > nextAllowedDirectionChangeTime) {
        // Every once in a while, make a sharp change in direction
        // But only if a certain amount of time has passed
        //
        nextAllowedDirectionChangeTime = CFAbsoluteTimeGetCurrent() + randomDoubleBetweenValues(MIN_TIME_BETWEEN_DIRECTION_CHANGE, MAX_TIME_BETWEEN_DIRECTION_CHANGE);
        filterfactor = 1.0f;
        xextent = 1.0f;
        yextent = 1.0f;
        zextent = 1.0f;
    } else {
        // We want to randomly change the acceleration.
		// We'll use the FILTERFACTOR to keep ourselves somewhat along the current trajectory.
        //
        xextent = 1.0f - lastX;
        yextent = 1.0f - lastY;
        zextent = 1.0f - lastZ;
        filterfactor = FILTERFACTOR;
    }
    
    double dx = randomDoubleBetweenValues(-xextent, xextent);
    double dy = randomDoubleBetweenValues(-yextent, yextent);
    double dz = randomDoubleBetweenValues(-zextent, zextent);
    
    double length = sqrt( dx*dx + dy*dy + dz*dz );
    dx /= length;
    dy /= length;
    dz /= length;
    
    UIAccelerationValue x = (dx * filterfactor) + (lastX * (1.0 - filterfactor));
    UIAccelerationValue y = (dy * filterfactor) + (lastY * (1.0 - filterfactor));
    UIAccelerationValue z = (dz * filterfactor) + (lastZ * (1.0 - filterfactor));
    
    length = sqrt( x*x + y*y + z*z );
    x /= length;
    y /= length;
    z /= length;
        
    lastX = x;
    lastY = y;
    lastZ = z;

    id <UIAccelerometerDelegate> delegate = [accelerometer delegate]; 
    [delegate accelerometer:accelerometer didAccelerate:[[[UISimulatedAcceleration alloc] initWithAccelerationX:x Y:y Z:z] autorelease]];
}

- (void)simulateRandomGravity {
    [self beginSimulation:@selector(simulateGravityTick)];
}

@end


@implementation UIAccelerometer ( Additions )

- (void)simulateRandomGravity {
    [[UIAccelerometerSimulator sharedInstance] simulateRandomGravity];
}

@end

#endif

