#import "CameraUpdateItem.h"
#import "CameraEasing.h"

@interface MLRNCameraWithPadding : MLNMapCamera

@property (nonatomic) MLNMapCamera *_Nonnull camera;
@property (nonatomic) UIEdgeInsets boundsPadding;

@end

@implementation MLRNCameraWithPadding

@end

@implementation CameraUpdateItem

- (void)execute:(MLRNMapView *)mapView withCompletionHandler:(void (^)(void))completionHandler {
  NSInteger easing = [_cameraStop.easing integerValue];

  switch (easing) {
    case MLRNCameraEasingLinear:
      [self _moveCamera:mapView animated:YES ease:NO withCompletionHandler:completionHandler];
      break;
    case MLRNCameraEasingEase:
      [self _moveCamera:mapView animated:YES ease:YES withCompletionHandler:completionHandler];
      break;
    case MLRNCameraEasingFly:
      [self _flyToCamera:mapView withCompletionHandler:completionHandler];
      break;
    case MLRNCameraEasingNone:
    default:
      [self _moveCamera:mapView animated:NO ease:NO withCompletionHandler:completionHandler];
      break;
  }
}

- (void)_flyToCamera:(MLRNMapView *)mapView
    withCompletionHandler:(void (^)(void))completionHandler {
  MLRNCameraWithPadding *nextCamera = [self _makeCamera:mapView];

  [mapView flyToCamera:nextCamera.camera
            edgePadding:nextCamera.boundsPadding
           withDuration:_cameraStop.duration
      completionHandler:completionHandler];
}

- (void)_moveCamera:(MLRNMapView *)mapView
                 animated:(BOOL)animated
                     ease:(BOOL)ease
    withCompletionHandler:(void (^)(void))completionHandler {
  MLRNCameraWithPadding *nextCamera = [self _makeCamera:mapView];
  NSString *easeFunctionName =
      ease ? kCAMediaTimingFunctionEaseInEaseOut : kCAMediaTimingFunctionLinear;

  [mapView setCamera:nextCamera.camera
                 withDuration:animated ? _cameraStop.duration : 0
      animationTimingFunction:[CAMediaTimingFunction functionWithName:easeFunctionName]
                  edgePadding:nextCamera.boundsPadding
            completionHandler:completionHandler];
}

- (MLRNCameraWithPadding *)_makeCamera:(MLRNMapView *)mapView {
  MLNMapCamera *nextCamera = [mapView.camera copy];

  UIEdgeInsets padding = [self _clippedPadding:_cameraStop.padding forView:mapView];
  if (padding.top <= 0 && padding.bottom <= 0) {
    // TODO: Report Bug
    // If all padding properties are 0 in the update, and the bounds and centerCoordinate do not
    // change, the padding doesn't change either. This seems to be a bug in the iOS SDK.
    padding.top = 1.0;
    padding.bottom = 1.0;
  }

  bool hasSetAltitude = false;

  if ([self _isCenterValid:_cameraStop.center]) {
    MLNCoordinateBounds boundsFromCoord = {.sw = _cameraStop.center, .ne = _cameraStop.center};
    MLNMapCamera *boundsCamera = [mapView camera:nextCamera
                         fittingCoordinateBounds:boundsFromCoord
                                     edgePadding:padding];
    nextCamera.centerCoordinate = boundsCamera.centerCoordinate;
  } else if ([self _areBoundsValid:_cameraStop.bounds]) {
    MLNMapCamera *boundsCamera = [mapView camera:nextCamera
                         fittingCoordinateBounds:_cameraStop.bounds
                                     edgePadding:padding];
    nextCamera.centerCoordinate = boundsCamera.centerCoordinate;
    nextCamera.altitude = boundsCamera.altitude;
    hasSetAltitude = true;
  }

  if (_cameraStop.bearing != nil) {
    nextCamera.heading = [_cameraStop.bearing floatValue];
  }

  if (_cameraStop.zoom != nil && hasSetAltitude == false) {
    nextCamera.altitude = [mapView altitudeFromZoom:[_cameraStop.zoom doubleValue]
                                         atLatitude:nextCamera.centerCoordinate.latitude
                                            atPitch:nextCamera.pitch];
  }

  if (_cameraStop.pitch != nil) {
    nextCamera.pitch = [_cameraStop.pitch floatValue];
  }

  MLRNCameraWithPadding *cameraWithPadding = [[MLRNCameraWithPadding alloc] init];
  cameraWithPadding.camera = nextCamera;
  cameraWithPadding.boundsPadding = padding;

  return cameraWithPadding;
}

- (UIEdgeInsets)_clippedPadding:(UIEdgeInsets)padding forView:(MLRNMapView *)mapView {
  UIEdgeInsets result = padding;

  double width = mapView.frame.size.width;
  double height = mapView.frame.size.height;

  if ((padding.top + padding.bottom) >= height) {
    double totalPadding = padding.top + padding.bottom;
    double extra = totalPadding - height + 1.0;
    result.top -= (padding.top * extra) / totalPadding;
    result.bottom -= (padding.bottom * extra) / totalPadding;
  }

  if ((padding.left + padding.right) >= width) {
    double totalPadding = padding.left + padding.right;
    double extra = totalPadding - width + 1.0;
    result.left -= (padding.left * extra) / totalPadding;
    result.right -= (padding.right * extra) / totalPadding;
  }

  return result;
}

- (BOOL)_areBoundsValid:(MLNCoordinateBounds)bounds {
  if ([self _isLatitudeValid:bounds.ne.latitude] && [self _isLatitudeValid:bounds.sw.latitude]) {
    return YES;
  }

  return NO;
}

- (BOOL)_isLatitudeValid:(CLLocationDegrees)latitude {
  return latitude >= -90 && latitude <= 90;
}

- (BOOL)_isCenterValid:(CLLocationCoordinate2D)coord {
  BOOL isValid = CLLocationCoordinate2DIsValid(coord);

  if (!isValid) {
    return NO;
  }

  return YES;
}

- (BOOL)_hasCenterAndZoom {
  BOOL isValid = CLLocationCoordinate2DIsValid(_cameraStop.center) && _cameraStop.zoom != nil;

  if (!isValid) {
    return NO;
  }

  return [self _isCenterValid:_cameraStop.center];
}

@end
