//
//  ApollonianViewer.m
//  Apollonian Viewer
//
//  Created by Kean on 5/4/12.
//  Copyright 2012 Autodesk. All rights reserved.
//

#import "ApollonianViewer.h"

@implementation ApollonianViewer

// Our data member for the received data

NSMutableData * _receivedData = NULL;

// A response has been received from our web-service call

- (void)connection:(NSURLConnection *)connection
    didReceiveResponse:(NSURLResponse *)response
{
  // Initialise our member variable receiving data

  if (_receivedData == NULL)
    _receivedData = [[NSMutableData alloc] init];
  else
    [_receivedData setLength:0];
}

// Data has been received from our web-service call

- (void)connection:(NSURLConnection *)connection
    didReceiveData:(NSData *)data
{
  // Append the received data to our member
  
  [_receivedData appendData:data];
}

// The web-service connection failed

- (void)connection:(NSURLConnection *)connection
    didFailWithError:(NSError *)error
{
  // Report an error in the log
  
  NSLog(@"Connection failed: %@", [error description]);

  UIAlertView* alert =
    [[[UIAlertView alloc]
      initWithTitle:@"Apollonian Viewer"
      message:
        @"Unable to access the web-service. "
        "Please check you have internet connectivity."
      delegate:self
      cancelButtonTitle:@"Close"
      otherButtonTitles:nil
    ] autorelease];
  [alert show];
}

- (void)alertView
  :(UIAlertView *)alertView
  clickedButtonAtIndex:(NSInteger)buttonIndex
{
  if (buttonIndex == 0) exit (0);  
}

// The call to our web-service has completed

- (void)connectionDidFinishLoading
    :(NSURLConnection *)connection
{
  // Release the connection
  
  [connection release];
  
  // Get the response string from our data member then
  // release it
  
  NSString * responseString =
    [[NSString alloc]
      initWithData:_receivedData
      encoding:NSUTF8StringEncoding
    ];
  [_receivedData release];

  // Extract JSON data from our response string
  
  NSData * jsonData =
    [responseString
      dataUsingEncoding:NSUTF8StringEncoding];

  // Extract an array from our JSON data
  
  NSError * e = nil;
  NSArray * jsonArray =
    [NSJSONSerialization
      JSONObjectWithData: jsonData
      options: NSJSONReadingMutableContainers
      error: &e
    ];
  
  if (!jsonArray)
  {
    NSLog(@"Error parsing JSON: %@", e);
  }
  else
  {
    // Loop through our JSON array, extracting spheres

    for (NSDictionary *item in jsonArray)
    {
      // We'll need this data for each sphere
      double x, y, z, radius;
      int level;
      
      // We use a single NSNumber to extract the data
      
      NSNumber *num;

      num = [item objectForKey:@"X"];
      x = [num doubleValue];
      num = [item objectForKey:@"Y"];
      y = [num doubleValue];
      num = [item objectForKey:@"Z"];
      z = [num doubleValue];
      num = [item objectForKey:@"R"];
      radius = [num doubleValue];
      num = [item objectForKey:@"L"];
      level = [num intValue];
      
      // Only create spheres for those at the edge of the
      // outer sphere
      
      double length = sqrt(x*x + y*y + z*z);
      if (length + radius > 0.99f)
        [self createSphere:radius x:x y:y z:z level:level];
    }
    
    // Remove our progress meter
    
    UIActivityIndicatorView * vw =
      (UIActivityIndicatorView *)
        [[Isgl3dDirector sharedInstance].openGLView viewWithTag:1];
    [vw removeFromSuperview];
    
    // Trigger the rotation updates
    
    [self schedule:@selector(tick:)];
  }
}

// Our main scene initialization method

- (id) init
{  
  if ((self = [super init]))
  {
    _preSpin = true;
    _paused = false;
    _rotation = 0.0;
    _initialSpinAmount = 2;
    
    // Create recognizers handling scene-level gestures
    
    // Tap
    
    _tapGestureRecognizer =
      [[UITapGestureRecognizer alloc]
        initWithTarget:self
        action:@selector(tapGesture:)
      ];
    _tapGestureRecognizer.delegate = self;    
    [[Isgl3dDirector sharedInstance]
      addGestureRecognizer:_tapGestureRecognizer
      forNode:nil
    ];
    
    // Swipe

    // (Add a recognizer for each of 4 directions)

    _swipeLeftGestureRecognizer =
      [[UISwipeGestureRecognizer alloc]
        initWithTarget:self
        action:@selector(swipeGesture:)
      ];
    _swipeLeftGestureRecognizer.delegate = self;
    [_swipeLeftGestureRecognizer
      setDirection:UISwipeGestureRecognizerDirectionLeft
    ];    
    [[Isgl3dDirector sharedInstance]
      addGestureRecognizer:_swipeLeftGestureRecognizer
      forNode:nil
    ];
    
    _swipeRightGestureRecognizer =
      [[UISwipeGestureRecognizer alloc]
        initWithTarget:self
        action:@selector(swipeGesture:)
      ];
    _swipeRightGestureRecognizer.delegate = self;
    [_swipeRightGestureRecognizer
      setDirection:UISwipeGestureRecognizerDirectionRight
    ];    
    [[Isgl3dDirector sharedInstance]
      addGestureRecognizer:_swipeRightGestureRecognizer
      forNode:nil
    ];
    
    _swipeUpGestureRecognizer =
      [[UISwipeGestureRecognizer alloc]
        initWithTarget:self
        action:@selector(swipeGesture:)
      ];
    _swipeUpGestureRecognizer.delegate = self;
    [_swipeUpGestureRecognizer
      setDirection:UISwipeGestureRecognizerDirectionUp
    ];    
    [[Isgl3dDirector sharedInstance]
      addGestureRecognizer:_swipeUpGestureRecognizer
      forNode:nil
    ];

    _swipeDownGestureRecognizer =
      [[UISwipeGestureRecognizer alloc]
        initWithTarget:self
        action:@selector(swipeGesture:)
      ];
    _swipeDownGestureRecognizer.delegate = self;
    [_swipeDownGestureRecognizer
      setDirection:UISwipeGestureRecognizerDirectionDown
    ];    
    [[Isgl3dDirector sharedInstance]
      addGestureRecognizer:_swipeDownGestureRecognizer
      forNode:nil
    ];
    
    // Pinch
    
    _pinchGestureRecognizer =
      [[UIPinchGestureRecognizer alloc]
        initWithTarget:self
        action:@selector(pinchGesture:)
      ];
    _pinchGestureRecognizer.delegate = self;    
    [[Isgl3dDirector sharedInstance]
      addGestureRecognizer:_pinchGestureRecognizer
      forNode:nil
    ];
    
    // Rotate
    
    _rotationGestureRecognizer =
      [[UIRotationGestureRecognizer alloc]
        initWithTarget:self
        action:@selector(rotationGesture:)
      ];
    _rotationGestureRecognizer.delegate = self;    
    [[Isgl3dDirector sharedInstance]
      addGestureRecognizer:_rotationGestureRecognizer
      forNode:nil
    ];
    
    // Set up our web-service call
    
    NSURL * url =
      [NSURL
        URLWithString:
          @"http://apollonian.cloudapp.net/api/spheres/1/7"
      ];
    
    NSMutableURLRequest *request =
      [NSMutableURLRequest
        requestWithURL:url
        cachePolicy:NSURLRequestUseProtocolCachePolicy
        timeoutInterval:60.0
      ];
    
    [request setHTTPMethod:@"GET"];
    
    NSURLConnection * connection =
      [[NSURLConnection alloc]initWithRequest:request delegate:self];
    if (connection)
    {
      _receivedData = [[NSMutableData data] retain];
    }        
    
    // Move the default camera to the initial position
    
    [self.camera setPosition:iv3(0, 0, -5)];
    
    // Create a container for our spheres
    
    _container = [self.scene createNode];

    // We'll maintain an array of materials for our
    // levels. Define the colors for those levels
    
    NSArray * colors =
      [NSArray arrayWithObjects:
        /* white */       @"FFFFFF",
        /* red */         @"FF0000",
        /* yellow */      @"FFFF00",
        /* green */       @"00FF00",
        /* cyan */        @"00FFFF",
        /* blue */        @"0000FF",
        /* magenta */     @"FF00FF",
        /* dark gray */   @"A9A9A9",
        /* gray */        @"808080",
        /* light gray */  @"D3D3D3",
        /* white */       @"FFFFFF",
        nil
      ];

    // Create and populate the array of materials
    
    _materials = [[NSMutableArray alloc] init];
    
    for (int i=0; i < 12; i++)
    {
      // Anything we don't have a color for will be white
      
      NSString *col =
        (i <= 10) ? [colors objectAtIndex:i] : @"FFFFFF";
      
      // For simplicity, make the colors the same for
      // ambient, diffuse and specular lighting
      
      Isgl3dColorMaterial * mat =
        [[Isgl3dColorMaterial alloc]
          initWithHexColors:col
          diffuse:col
          specular:col
          shininess:0.7
        ];
      [_materials addObject:mat];
    }

    // Create a single sphere mesh
    
    _sphereMesh =
      [[Isgl3dSphere alloc] initWithGeometry:1 longs:9 lats:9];
    
    // Create a directional white light and add it to the scene
    
    Isgl3dLight * light =
      [Isgl3dLight
        lightWithHexColor:@"A0A0A0"
        diffuseColor:@"E9E9E9"
        specularColor:@"C0C0C0"
        attenuation:0
      ];
    
    light.lightType = DirectionalLight;
    light.position = iv3(4, 0, 8);
    [light setDirection:1 y:2 z:-5];
    [self.scene addChild:light];
    
    // Set the scene ambient color

    [self setSceneAmbient:@"000000"];
  }
  return self;
}

// Create a single sphere at the desired position with
// the desired radius and level

- (void)createSphere
  :(double)radius
  x:(double)x y:(double)y z:(double)z
  level:(int)level
{
  // Create the sphere based on our single mesh
  
  Isgl3dMeshNode * sphere =
    [_container
      createNodeWithMesh:_sphereMesh
      andMaterial:[_materials objectAtIndex:level]
    ];
  
  // Position and scale it
  
  sphere.position = iv3(x, y, z);
  [sphere setScale:radius];
}

- (void) dealloc
{
  // Make sure we release our materials and sphere mesh
  
  [_materials release];
  [_sphereMesh release];
    
  [super dealloc];
}

// Respond to our timer tick by rotating the model

- (void) tick:(float)dt
{
  // Rotate around the appropriate axis

  if (!_paused && !_preSpin)
  {
    // Reset any rotation around Z, first
    
    if (abs(_container.rotationZ) > 0)
    {
      _container.rotationZ = 0;
      _rotation = 0;
    }
        
    if (_spinAroundY)
    {
      // If spinning around Y, reset any X rotation
      
      if (abs(_container.rotationX) > 0)
        _container.rotationX = 0;
      
      _container.rotationY += _spinIncrement;
    }
    else
    {
      // If spinning around X, reset any Y rotation
      
      if (abs(_container.rotationY) > 0)
        _container.rotationY = 0;
      
      _container.rotationX += _spinIncrement;
    }
  }
}

// Method to specify combination of gesture recognizers

- (BOOL)gestureRecognizer:
(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer
{
  // If the gesture recognizers are on different views,
  // don't allow simultaneous recognition
  
  if (gestureRecognizer.view != otherGestureRecognizer.view)
    return NO;
  
  // Also stop combination of rotation with other gestures
  
  if ((gestureRecognizer == _rotationGestureRecognizer) ||
      (otherGestureRecognizer == _rotationGestureRecognizer))
    return NO;
  
  return YES;
}

// Action methods for our gestures

// Tap-pause/play

- (void)tapGesture
  :(UITapGestureRecognizer *)gestureRecognizer
{
  // Toggle pause (if we have already had at least one spin)
  
  if (!_preSpin)
    _paused = !_paused;
}

// Swipe-spin

- (void)swipeGesture
  :(UISwipeGestureRecognizer *)gestureRecognizer
{
  switch(gestureRecognizer.direction)
  {    
    case UISwipeGestureRecognizerDirectionDown:
      if (
        _preSpin || _spinAroundY ||
        (!_spinAroundY && _spinIncrement > 0)
      )
      {
        // Reset the axis and spin amount
        
        _spinAroundY = false;
        _spinIncrement = -_initialSpinAmount;
      }
      else
      {
        // Speed up the rate of spin
        
        if (abs(_spinIncrement * 2) < 10)
          _spinIncrement *= 2;
      }
      _preSpin = false;
      _paused = false;
      break;
      
    case UISwipeGestureRecognizerDirectionUp:
      if (
        _preSpin || _spinAroundY ||
        (!_spinAroundY && _spinIncrement < 0)
      )
      {
        // Reset the axis and spin amount
        
        _spinAroundY = false;
        _spinIncrement = _initialSpinAmount;
      }
      else
      {
        // Speed up the rate of spin
        
        if (abs(_spinIncrement * 2) < 10)
          _spinIncrement *= 2;
      }
      _preSpin = false;
      _paused = false;
      break;
      
    case UISwipeGestureRecognizerDirectionLeft:
      if (
        _preSpin || !_spinAroundY ||
        (_spinAroundY && _spinIncrement > 0)
      )
      {
        // Reset the axis and spin amount
        
        _spinAroundY = true;
        _spinIncrement = -_initialSpinAmount;
      }
      else
      {
        // Speed up the rate of spin
        
        if (abs(_spinIncrement * 2) < 10)
          _spinIncrement *= 2;
      }
      _preSpin = false;
      _paused = false;
      break;
      
    case UISwipeGestureRecognizerDirectionRight:
      if (
        _preSpin || !_spinAroundY ||
        (_spinAroundY && _spinIncrement < 0)
      )
      {
        // Reset the axis and spin amount
        
        _spinAroundY = true;
        _spinIncrement = _initialSpinAmount;
      }
      else
      {
        // Speed up the rate of spin
        
        if (abs(_spinIncrement * 2) < 10)
          _spinIncrement *= 2;
      }
      _preSpin = false;
      _paused = false;
      break;
      
    default:
      break;
  }
}

// Pinch-zoom

- (void)pinchGesture
  :(UIPinchGestureRecognizer *)gestureRecognizer
{
  if (
    [gestureRecognizer state] == UIGestureRecognizerStateBegan ||
    [gestureRecognizer state] == UIGestureRecognizerStateChanged
  )
  {
    // Adjust the camera position based on the zoom scale
    
    [self.camera setZ:self.camera.z * (1/gestureRecognizer.scale)];
    [gestureRecognizer setScale:1];
  }
}

// Rotate-rotate :-)

- (void)rotationGesture
  :(UIRotationGestureRecognizer *)gestureRecognizer
{
  if (
    [gestureRecognizer state] == UIGestureRecognizerStateBegan ||
    [gestureRecognizer state] == UIGestureRecognizerStateChanged
  )
  {
    // Adjust the rotation around Z based on the rotation amount
    
    if (_paused || _preSpin)
    {
      _rotation += (gestureRecognizer.rotation * 180.0 / M_PI);
      [_container setRotationZ:_rotation];
      [gestureRecognizer setRotation:0];
    }
  }
}
      
@end

