
#import "TUICameraViewController.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CMMetadata.h>
#import <Photos/Photos.h>

#import "TUICaptureImagePreviewController.h"
#import "TUICaptureVideoPreviewViewController.h"
#import "TUICameraView.h"

#import "TUICameraManager.h"
#import "TUIMotionManager.h"
#import "TUIMovieManager.h"
#import "TUICaptureTimer.h"
#import "TUICommonModel.h"
#import "TUIDefine.h"

@interface TUICameraViewController ()<AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate, TUICameraViewDelegate>
{
    AVCaptureSession          *_session;
    AVCaptureDeviceInput      *_deviceInput;

    AVCaptureConnection       *_videoConnection;
    AVCaptureConnection       *_audioConnection;
    AVCaptureVideoDataOutput  *_videoOutput;
    AVCaptureStillImageOutput *_imageOutput;

    BOOL                       _recording;
}

@property(nonatomic, strong) TUICameraView   *cameraView;
@property(nonatomic, strong) TUIMovieManager  *movieManager;
@property(nonatomic, strong) TUICameraManager *cameraManager;
@property(nonatomic, strong) TUIMotionManager *motionManager;
@property(nonatomic, strong) AVCaptureDevice *activeCamera;
@property(nonatomic, strong) AVCaptureDevice *inactiveCamera;

@property (nonatomic) BOOL isFirstShow;
@property (nonatomic) BOOL lastPageBarHidden;
@property (nonatomic) BOOL lastPopGestureEnabled;
@end

@implementation TUICameraViewController

- (instancetype)init {
    self = [super init];
    if (self) {
        _motionManager = [[TUIMotionManager alloc] init];
        _cameraManager = [[TUICameraManager alloc] init];
        
        _type = TUICameraMediaTypePhoto;
        _aspectRatio = TUICameraViewAspectRatio16x9;
        _videoMaximumDuration = 15.0;
        _videoMinimumDuration = 3.0;
        _isFirstShow = YES;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.cameraView = [[TUICameraView alloc] initWithFrame:self.view.bounds];
    self.cameraView.type = self.type;
    self.cameraView.aspectRatio = self.aspectRatio;
    self.cameraView.delegate = self;
    [self.view addSubview:self.cameraView];
    
    NSError *error;
    [self setupSession:&error];
    if (!error) {
        [self.cameraView.previewView setCaptureSessionsion:_session];
        [self startCaptureSession];
    } else {
//        NSAssert1(NO, @"Camera Initialize Failed : %@", error.localizedDescription);
//        [self showErrorStr:error.localizedDescription];
    }
}

- (void)dealloc
{
    [self stopCaptureSession];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    if (self.isFirstShow) {
        self.isFirstShow = NO;
        
        self.lastPopGestureEnabled = self.navigationController.interactivePopGestureRecognizer.enabled;
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        
        self.lastPageBarHidden = self.navigationController.navigationBarHidden;
        self.navigationController.navigationBarHidden = YES;
    }
}

#pragma mark - - Input Device
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices) {
        if (device.position == position) {
            return device;
        }
    }
    return nil;
}

- (AVCaptureDevice *)activeCamera {
    return _deviceInput.device;
}

- (AVCaptureDevice *)inactiveCamera {
    AVCaptureDevice *device = nil;
    if ([[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count] > 1) {
        if ([self activeCamera].position == AVCaptureDevicePositionBack) {
            device = [self cameraWithPosition:AVCaptureDevicePositionFront];
        } else {
            device = [self cameraWithPosition:AVCaptureDevicePositionBack];
        }
    }
    return device;
}

#pragma mark - - Configuration
- (void)setupSession:(NSError **)error {
    _session = [[AVCaptureSession alloc]init];
    _session.sessionPreset = AVCaptureSessionPresetHigh;
    
    [self setupSessionInputs:error];
    [self setupSessionOutputs:error];
}


- (void)setupSessionInputs:(NSError **)error {
    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    if (videoInput) {
        if ([_session canAddInput:videoInput]) {
            [_session addInput:videoInput];
        }
    }
    _deviceInput = videoInput;
    if (_type == TUICameraMediaTypeVideo) {
        AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
        AVCaptureDeviceInput *audioIn = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:error];
        if ([_session canAddInput:audioIn]) {
            [_session addInput:audioIn];
        }
    }
}

- (void)setupSessionOutputs:(NSError **)error {
    dispatch_queue_t captureQueue = dispatch_queue_create("com.tui.captureQueue", DISPATCH_QUEUE_SERIAL);

    AVCaptureVideoDataOutput *videoOut = [[AVCaptureVideoDataOutput alloc] init];
    [videoOut setAlwaysDiscardsLateVideoFrames:YES];
    [videoOut setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA]}];
    [videoOut setSampleBufferDelegate:self queue:captureQueue];
    if ([_session canAddOutput:videoOut]){
        [_session addOutput:videoOut];
    }
    _videoOutput = videoOut;
    _videoConnection = [videoOut connectionWithMediaType:AVMediaTypeVideo];

    if (_type == TUICameraMediaTypeVideo) {
        AVCaptureAudioDataOutput *audioOut = [[AVCaptureAudioDataOutput alloc] init];
        [audioOut setSampleBufferDelegate:self queue:captureQueue];
        if ([_session canAddOutput:audioOut]){
            [_session addOutput:audioOut];
        }
        _audioConnection = [audioOut connectionWithMediaType:AVMediaTypeAudio];
    }
    
    AVCaptureStillImageOutput *imageOutput = [[AVCaptureStillImageOutput alloc] init];            
    imageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
    if ([_session canAddOutput:imageOutput]) {
        [_session addOutput:imageOutput];
    }
    _imageOutput = imageOutput;
}

#pragma mark - - Session Control
- (void)startCaptureSession {
    if (!_session.isRunning) {
        [_session startRunning];
    }
}

- (void)stopCaptureSession {
    if (_session.isRunning) {
        [_session stopRunning];
    }
}

#pragma mark - - Camera Operation
-(void)zoomAction:(TUICameraView *)cameraView factor:(CGFloat)factor {
    NSError *error = [_cameraManager zoom:[self activeCamera] factor:factor];
    if (error) NSLog(@"%@", error);
}

-(void)focusAction:(TUICameraView *)cameraView point:(CGPoint)point handle:(void (^)(NSError *))handle {
    NSError *error = [_cameraManager focus:[self activeCamera] point:point];
    handle(error);
    NSLog(@"%f", [self activeCamera].activeFormat.videoMaxZoomFactor);
}

-(void)exposAction:(TUICameraView *)cameraView point:(CGPoint)point handle:(void (^)(NSError *))handle {
    NSError *error = [_cameraManager expose:[self activeCamera] point:point];
    handle(error);
}

-(void)autoFocusAndExposureAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle {
    NSError *error = [_cameraManager resetFocusAndExposure:[self activeCamera]];
    handle(error);
}

-(void)flashLightAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle {
    BOOL on = [_cameraManager flashMode:[self activeCamera]] == AVCaptureFlashModeOn;
    AVCaptureFlashMode mode = on ? AVCaptureFlashModeOff : AVCaptureFlashModeOn;
    NSError *error = [_cameraManager changeFlash:[self activeCamera] mode: mode];
    handle(error);
}

-(void)torchLightAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle {
    BOOL on = [_cameraManager torchMode:[self activeCamera]] == AVCaptureTorchModeOn;
    AVCaptureTorchMode mode = on ? AVCaptureTorchModeOff : AVCaptureTorchModeOn;
    NSError *error = [_cameraManager changeTorch:[self activeCamera] model:mode];
    handle(error);
}

- (void)swicthCameraAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle {
    NSError *error;
    AVCaptureDevice *videoDevice = [self inactiveCamera];
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
    if (videoInput) {
        CATransition *animation = [CATransition animation];
        animation.type = @"oglFlip";
        animation.subtype = kCATransitionFromLeft;
        animation.duration = 0.5;
        [self.cameraView.previewView.layer addAnimation:animation forKey:@"flip"];

        AVCaptureFlashMode mode = [_cameraManager flashMode:[self activeCamera]];

        _deviceInput = [_cameraManager switchCamera:_session old:_deviceInput new:videoInput];

        _videoConnection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];

        [_cameraManager changeFlash:[self activeCamera] mode:mode];
    }
    handle(error);
}

#pragma mark - - Taking Photo
- (void)takePhotoAction:(TUICameraView *)cameraView {
    AVCaptureConnection *connection = [_imageOutput connectionWithMediaType:AVMediaTypeVideo];
    if (connection.isVideoOrientationSupported) {
        connection.videoOrientation = [self currentVideoOrientation];
    }
    [_imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef _Nullable imageDataSampleBuffer, NSError * _Nullable error) {
        if (error) {
            [self showErrorStr:error.localizedDescription];
            return;
        }
        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
        UIImage *image = [[UIImage alloc]initWithData:imageData];
        TUICaptureImagePreviewController *vc = [[TUICaptureImagePreviewController alloc]initWithImage:image];
        [self.navigationController pushViewController:vc animated:YES];
        __weak __typeof(self) weakSelf = self;
        vc.commitBlock = ^{
            __strong __typeof(weakSelf) strongSelf = weakSelf;
            UIGraphicsBeginImageContext(CGSizeMake(image.size.width, image.size.height));
            [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
            UIImage *convertToUpImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            NSData *data = UIImageJPEGRepresentation(convertToUpImage, 0.75);
            [strongSelf.delegate cameraViewController:strongSelf didFinishPickingMediaWithImageData:data];
            [strongSelf popViewControllerAnimated:YES];
        };
        vc.cancelBlock = ^{
            __strong __typeof(weakSelf) strongSelf = weakSelf;
            [strongSelf.navigationController popViewControllerAnimated:YES];
        };
    }];
}

- (void)cancelAction:(TUICameraView *)cameraView {
    [self.delegate cameraViewControllerDidCancel:self];
    
    [self popViewControllerAnimated:YES];
}

#pragma mark - - Record
-(void)startRecordVideoAction:(TUICameraView *)cameraView {
   
    /**
     * 每次重新创建，避免之前的信息未释放导致的 Crash
     * Recreate each time to avoid Crash caused by unreleased previous information
     */
    _movieManager  = [[TUIMovieManager alloc]  init];
    _recording = YES;
    _movieManager.currentDevice = [self activeCamera];
    _movieManager.currentOrientation = [self currentVideoOrientation];
    @weakify(self)
    [_movieManager start:^(NSError * _Nonnull error) {
        @strongify(self)
        @weakify(self)
        dispatch_async(dispatch_get_main_queue(), ^{
            @strongify(self)
            if (error) [self showErrorStr:error.localizedDescription];
        });
    }];
}

-(void)stopRecordVideoAction:(TUICameraView *)cameraView RecordDuration:(CGFloat)duration {
    
    _recording = NO;
    @weakify(self)
    [_movieManager stop:^(NSURL * _Nonnull url, NSError * _Nonnull error) {
        @strongify(self)
        @weakify(self)
        dispatch_async(dispatch_get_main_queue(), ^{
            @strongify(self)
            if (duration < self.videoMinimumDuration) {
                [self showErrorStr:TUIKitLocalizableString(TUIKitMoreVideoCaptureDurationTip)];
            }
            else if (error) {
                [self showErrorStr:error.localizedDescription];
            }
            else {
                TUICaptureVideoPreviewViewController *videoPreviewController = [[TUICaptureVideoPreviewViewController alloc] initWithVideoURL:url];
                [self.navigationController pushViewController:videoPreviewController animated:YES];
                @weakify(self)
                videoPreviewController.commitBlock = ^{
                    @strongify(self)
                    [self.delegate cameraViewController:self didFinishPickingMediaWithVideoURL:url];
                    [self popViewControllerAnimated:YES];
                };
                videoPreviewController.cancelBlock = ^{
                    @strongify(self)
                    [self.navigationController popViewControllerAnimated:YES];
                };
            }
        });
    }];
}

#pragma mark - - AVCaptureVideoDataOutputSampleBufferDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
    if (_recording) {
        [_movieManager writeData:connection video:_videoConnection audio:_audioConnection buffer:sampleBuffer];
    }
}

#pragma mark - - Others
- (AVCaptureVideoOrientation)currentVideoOrientation {
    AVCaptureVideoOrientation orientation;
    switch (self.motionManager.deviceOrientation) { 
        case UIDeviceOrientationPortrait:
            orientation = AVCaptureVideoOrientationPortrait;
            break;
        case UIDeviceOrientationLandscapeLeft:
            orientation = AVCaptureVideoOrientationLandscapeRight;
            break;
        case UIDeviceOrientationLandscapeRight:
            orientation = AVCaptureVideoOrientationLandscapeLeft;
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            orientation = AVCaptureVideoOrientationPortraitUpsideDown;
            break;
        default:
            orientation = AVCaptureVideoOrientationPortrait;
            break;
    }
    return orientation;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)popViewControllerAnimated:(BOOL)animated {
    
    
    NSUInteger index = [self.navigationController.viewControllers indexOfObject:self];
    index --;
    UIViewController *lastVC = nil;
    if (index > 0 && index < self.navigationController.viewControllers.count) {
        lastVC = self.navigationController.viewControllers[index];
    }
    
    self.navigationController.interactivePopGestureRecognizer.enabled = self.lastPopGestureEnabled;
    
    self.navigationController.navigationBarHidden = self.lastPageBarHidden;
    
    if (lastVC) {
        [self.navigationController popToViewController:lastVC animated:animated];
    } else {
        [self.navigationController popViewControllerAnimated:animated];
    }
}

- (void)showErrorStr:(NSString *)errStr {
    [TUITool makeToast:errStr duration:1 position:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0)];
}

@end
