//
//  SCManagedCaptureDeviceFaceDetectionAutoFocusHandler.m
//  Snapchat
//
//  Created by Jiyang Zhu on 3/7/18.
//  Copyright © 2018 Snapchat, Inc. All rights reserved.
//

#import "SCManagedCaptureDeviceFaceDetectionAutoFocusHandler.h"

#import "AVCaptureDevice+ConfigurationLock.h"
#import "SCCameraTweaks.h"
#import "SCManagedCaptureFaceDetectionAdjustingPOIResource.h"
#import "SCManagedCapturer.h"
#import "SCManagedCapturerListener.h"

#import <SCFoundation/SCAssertWrapper.h>
#import <SCFoundation/SCTrace.h>
#import <SCFoundation/SCTraceODPCompatible.h>

@interface SCManagedCaptureDeviceFaceDetectionAutoFocusHandler () <SCManagedCapturerListener>

@property (nonatomic, strong) AVCaptureDevice *device;
@property (nonatomic, weak) id<SCCapturer> managedCapturer;
@property (nonatomic, assign) CGPoint focusPointOfInterest;

@property (nonatomic, assign) BOOL isVisible;
@property (nonatomic, assign) BOOL isContinuousAutofocus;
@property (nonatomic, assign) BOOL focusLock;

@property (nonatomic, copy) NSDictionary<NSNumber *, NSValue *> *faceBoundsByFaceID;
@property (nonatomic, strong) SCManagedCaptureFaceDetectionAdjustingPOIResource *resource;

@end

@implementation SCManagedCaptureDeviceFaceDetectionAutoFocusHandler

- (instancetype)initWithDevice:(AVCaptureDevice *)device
               pointOfInterest:(CGPoint)pointOfInterest
               managedCapturer:(id<SCCapturer>)managedCapturer
{
    if (self = [super init]) {
        SCAssert(device, @"AVCaptureDevice should not be nil.");
        SCAssert(managedCapturer, @"id<SCCapturer> should not be nil.");
        _device = device;
        _focusPointOfInterest = pointOfInterest;
        SCManagedCaptureDevicePosition position =
            (device.position == AVCaptureDevicePositionFront ? SCManagedCaptureDevicePositionFront
                                                             : SCManagedCaptureDevicePositionBack);
        _resource = [[SCManagedCaptureFaceDetectionAdjustingPOIResource alloc]
             initWithDefaultPointOfInterest:pointOfInterest
            shouldTargetOnFaceAutomatically:SCCameraTweaksTurnOnFaceDetectionFocusByDefault(position)];
        _managedCapturer = managedCapturer;
    }
    return self;
}

- (CGPoint)getFocusPointOfInterest
{
    return self.focusPointOfInterest;
}

// called when user taps on a point on screen, to re-adjust camera focus onto that tapped spot.
// this re-adjustment is always necessary, regardless of scenarios (recording video, taking photo, etc),
// therefore we don't have to check self.focusLock in this method.
- (void)setAutofocusPointOfInterest:(CGPoint)pointOfInterest
{
    SCTraceODPCompatibleStart(2);
    pointOfInterest = [self.resource updateWithNewProposedPointOfInterest:pointOfInterest fromUser:YES];
    SC_GUARD_ELSE_RETURN(!CGPointEqualToPoint(pointOfInterest, self.focusPointOfInterest) ||
                         self.isContinuousAutofocus);
    [self _actuallySetFocusPointOfInterestIfNeeded:pointOfInterest
                                     withFocusMode:AVCaptureFocusModeAutoFocus
                                          taskName:@"set autofocus"];
}

- (void)continuousAutofocus
{
    SCTraceODPCompatibleStart(2);
    SC_GUARD_ELSE_RETURN(!self.isContinuousAutofocus);
    CGPoint pointOfInterest = [self.resource updateWithNewProposedPointOfInterest:CGPointMake(0.5, 0.5) fromUser:NO];
    [self _actuallySetFocusPointOfInterestIfNeeded:pointOfInterest
                                     withFocusMode:AVCaptureFocusModeContinuousAutoFocus
                                          taskName:@"set continuous autofocus"];
}

- (void)setFocusLock:(BOOL)focusLock
{
    // Disabled focus lock for face detection and focus handler.
}

- (void)setSmoothFocus:(BOOL)smoothFocus
{
    SCTraceODPCompatibleStart(2);
    SC_GUARD_ELSE_RETURN(smoothFocus != self.device.smoothAutoFocusEnabled);
    [self.device runTask:@"set smooth autofocus"
        withLockedConfiguration:^() {
            [self.device setSmoothAutoFocusEnabled:smoothFocus];
        }];
}

- (void)setVisible:(BOOL)visible
{
    SCTraceODPCompatibleStart(2);
    SC_GUARD_ELSE_RETURN(_isVisible != visible);
    self.isVisible = visible;
    if (visible) {
        [[SCManagedCapturer sharedInstance] addListener:self];
    } else {
        [[SCManagedCapturer sharedInstance] removeListener:self];
        [self.resource reset];
    }
}

- (void)_actuallySetFocusPointOfInterestIfNeeded:(CGPoint)pointOfInterest
                                   withFocusMode:(AVCaptureFocusMode)focusMode
                                        taskName:(NSString *)taskName
{
    SCTraceODPCompatibleStart(2);
    SC_GUARD_ELSE_RETURN(!CGPointEqualToPoint(pointOfInterest, self.focusPointOfInterest) &&
                         [self.device isFocusModeSupported:focusMode] && [self.device isFocusPointOfInterestSupported]);
    [self.device runTask:taskName
        withLockedConfiguration:^() {
            // Set focus point before changing focus mode
            // Be noticed that order does matter
            self.device.focusPointOfInterest = pointOfInterest;
            self.device.focusMode = focusMode;
        }];

    self.focusPointOfInterest = pointOfInterest;
    self.isContinuousAutofocus = (focusMode == AVCaptureFocusModeContinuousAutoFocus);
}

#pragma mark - SCManagedCapturerListener
- (void)managedCapturer:(id<SCCapturer>)managedCapturer
    didDetectFaceBounds:(NSDictionary<NSNumber *, NSValue *> *)faceBoundsByFaceID
{
    SCTraceODPCompatibleStart(2);
    SC_GUARD_ELSE_RETURN(self.isVisible);
    CGPoint pointOfInterest = [self.resource updateWithNewDetectedFaceBounds:faceBoundsByFaceID];
    // If pointOfInterest is equal to CGPointMake(0.5, 0.5), it means no valid face is found, so that we should reset to
    // AVCaptureFocusModeContinuousAutoFocus. Otherwise, focus on the point and set the mode as
    // AVCaptureFocusModeAutoFocus.
    // TODO(Jiyang): Refactor SCManagedCaptureFaceDetectionAdjustingPOIResource to include focusMode and exposureMode.
    AVCaptureFocusMode focusMode = CGPointEqualToPoint(pointOfInterest, CGPointMake(0.5, 0.5))
                                       ? AVCaptureFocusModeContinuousAutoFocus
                                       : AVCaptureFocusModeAutoFocus;
    [self _actuallySetFocusPointOfInterestIfNeeded:pointOfInterest
                                     withFocusMode:focusMode
                                          taskName:@"set autofocus from face detection"];
}

@end