//------------------------------------------------------------------------
//  Copyright 2010 (c) Jeff Brown <spadix@users.sourceforge.net>
//
//  This file is part of the ZBar Bar Code Reader.
//
//  The ZBar Bar Code Reader is free software; you can redistribute it
//  and/or modify it under the terms of the GNU Lesser Public License as
//  published by the Free Software Foundation; either version 2.1 of
//  the License, or (at your option) any later version.
//
//  The ZBar Bar Code Reader is distributed in the hope that it will be
//  useful, but WITHOUT ANY WARRANTY; without even the implied warranty
//  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser Public License for more details.
//
//  You should have received a copy of the GNU Lesser Public License
//  along with the ZBar Bar Code Reader; if not, write to the Free
//  Software Foundation, Inc., 51 Franklin St, Fifth Floor,
//  Boston, MA  02110-1301  USA
//
//  http://sourceforge.net/projects/zbar
//------------------------------------------------------------------------

#import <ZBarSDK/ZBarReaderViewController.h>
#import <ZBarSDK/ZBarReaderView.h>
#import <ZBarSDK/ZBarCaptureReader.h>
#import <ZBarSDK/ZBarHelpController.h>
#import <ZBarSDK/ZBarCameraSimulator.h>

#define MODULE ZBarReaderViewController
#import "debug.h"

static inline AVCaptureDevicePosition
AVPositionForUICamera (UIImagePickerControllerCameraDevice camera)
{
    switch(camera) {
    case UIImagePickerControllerCameraDeviceRear:
        return(AVCaptureDevicePositionBack);
    case UIImagePickerControllerCameraDeviceFront:
        return(AVCaptureDevicePositionFront);
    }
    return(-1);
}

static inline UIImagePickerControllerCameraDevice
UICameraForAVPosition (AVCaptureDevicePosition position)
{
    switch(position)
    {
    case AVCaptureDevicePositionBack:
        return(UIImagePickerControllerCameraDeviceRear);
    case AVCaptureDevicePositionFront:
        return(UIImagePickerControllerCameraDeviceFront);
    }
    return(-1);
}

static inline AVCaptureDevice*
AVDeviceForUICamera (UIImagePickerControllerCameraDevice camera)
{
    AVCaptureDevicePosition position = AVPositionForUICamera(camera);
    if(position < 0)
        return(nil);

#if !TARGET_IPHONE_SIMULATOR
    NSArray *allDevices =
        [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo];
    for(AVCaptureDevice *device in allDevices)
        // FIXME how to quantify "best" of several (theoretical) possibilities
        if(device.position == position)
            return(device);
#endif
    return(nil);
}

static inline AVCaptureTorchMode
AVTorchModeForUIFlashMode (UIImagePickerControllerCameraFlashMode mode)
{
    switch(mode)
    {
    case UIImagePickerControllerCameraFlashModeAuto:
        return(AVCaptureTorchModeAuto);
    case UIImagePickerControllerCameraFlashModeOn:
        return(AVCaptureTorchModeOn);
    case UIImagePickerControllerCameraFlashModeOff:
        break;
    }
    return(AVCaptureTorchModeOff);
}

static inline NSString*
AVSessionPresetForUIVideoQuality (UIImagePickerControllerQualityType quality)
{
#if !TARGET_IPHONE_SIMULATOR
    switch(quality)
    {
    case UIImagePickerControllerQualityTypeHigh:
        return(AVCaptureSessionPresetHigh);
    case UIImagePickerControllerQualityType640x480:
        return(AVCaptureSessionPreset640x480);
    case UIImagePickerControllerQualityTypeMedium:
        return(AVCaptureSessionPresetMedium);
    case UIImagePickerControllerQualityTypeLow:
        return(AVCaptureSessionPresetLow);
    case UIImagePickerControllerQualityTypeIFrame1280x720:
        return(AVCaptureSessionPresetiFrame1280x720);
    case UIImagePickerControllerQualityTypeIFrame960x540:
        return(AVCaptureSessionPresetiFrame960x540);
    }
#endif
    return(nil);
}


@implementation ZBarReaderViewController

@synthesize scanner, readerDelegate, showsZBarControls,
    supportedOrientationsMask, tracksSymbols, enableCache, cameraOverlayView,
    cameraViewTransform, cameraDevice, cameraFlashMode, videoQuality,
    readerView, scanCrop;
@dynamic sourceType, allowsEditing, allowsImageEditing, showsCameraControls,
    showsHelpOnFail, cameraMode, takesPicture, maxScanDimension;

+ (BOOL) isSourceTypeAvailable: (UIImagePickerControllerSourceType) sourceType
{
    if(sourceType != UIImagePickerControllerSourceTypeCamera)
        return(NO);
    return(TARGET_IPHONE_SIMULATOR ||
           [UIImagePickerController isSourceTypeAvailable: sourceType]);
}

+ (BOOL) isCameraDeviceAvailable: (UIImagePickerControllerCameraDevice) camera
{
    return(TARGET_IPHONE_SIMULATOR ||
           [UIImagePickerController isCameraDeviceAvailable: camera]);
}

+ (BOOL) isFlashAvailableForCameraDevice: (UIImagePickerControllerCameraDevice) camera
{
    return(TARGET_IPHONE_SIMULATOR ||
           [UIImagePickerController isFlashAvailableForCameraDevice: camera]);
}

+ (NSArray*) availableCaptureModesForCameraDevice: (UIImagePickerControllerCameraDevice) camera
{
    if(![self isCameraDeviceAvailable: camera])
        return([NSArray array]);

    // current reader only supports automatic detection
    return([NSArray arrayWithObject:
               [NSNumber numberWithInteger:
                   UIImagePickerControllerCameraCaptureModeVideo]]);
}

- (void) _init
{
    supportedOrientationsMask =
        ZBarOrientationMask(UIInterfaceOrientationPortrait);
    showsZBarControls = tracksSymbols = enableCache = YES;
    scanCrop = CGRectMake(0, 0, 1, 1);
    cameraViewTransform = CGAffineTransformIdentity;

    cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto;
    videoQuality = UIImagePickerControllerQualityType640x480;
    AVCaptureDevice *device = nil;
#if !TARGET_IPHONE_SIMULATOR
    device = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
#endif
    if(device)
        cameraDevice = UICameraForAVPosition(device.position);
    else
        cameraDevice = UIImagePickerControllerCameraDeviceRear;

    // create our own scanner to store configuration,
    // independent of whether view is loaded
    scanner = [ZBarImageScanner new];
    [scanner setSymbology: 0
             config: ZBAR_CFG_X_DENSITY
             to: 3];
    [scanner setSymbology: 0
             config: ZBAR_CFG_Y_DENSITY
             to: 3];
}

- (id) init
{
    if(!TARGET_IPHONE_SIMULATOR &&
       !NSClassFromString(@"AVCaptureSession")) {
        // fallback to old interface
        zlog(@"Falling back to ZBarReaderController");
        [self release];
        return((id)[ZBarReaderController new]);
    }

    self = [super init];
    if(!self)
        return(nil);

    self.wantsFullScreenLayout = YES;
    [self _init];
    return(self);
}

- (id) initWithCoder: (NSCoder*) decoder
{
    self = [super initWithCoder: decoder];
    if(!self)
        return(nil);

    [self _init];
    return(self);
}

- (void) cleanup
{
    [cameraOverlayView removeFromSuperview];
    cameraSim.readerView = nil;
    [cameraSim release];
    cameraSim = nil;
    readerView.readerDelegate = nil;
    [readerView release];
    readerView = nil;
    [controls release];
    controls = nil;
    [shutter release];
    shutter = nil;
}

- (void) dealloc
{
    [self cleanup];
    [cameraOverlayView release];
    cameraOverlayView = nil;
    [scanner release];
    scanner = nil;
    [super dealloc];
}

- (void) initControls
{
    if(!showsZBarControls && controls) {
        [controls removeFromSuperview];
        [controls release];
        controls = nil;
    }
    if(!showsZBarControls)
        return;

    UIView *view = self.view;
    if(controls) {
        assert(controls.superview == view);
        [view bringSubviewToFront: controls];
        return;
    }

    CGRect r = view.bounds;
    r.origin.y = r.size.height - 44;
    r.size.height = 44;
    controls = [[UIView alloc]
                   initWithFrame: r];
    controls.autoresizingMask =
        UIViewAutoresizingFlexibleWidth |
        UIViewAutoresizingFlexibleTopMargin;
    controls.backgroundColor = [UIColor blackColor];

    UIToolbar *toolbar =
        [UIToolbar new];
    r.origin.y = 0;
    toolbar.frame = r;
    toolbar.barStyle = UIBarStyleBlackOpaque;
    toolbar.autoresizingMask =
        UIViewAutoresizingFlexibleWidth |
        UIViewAutoresizingFlexibleHeight;

    UIButton *info =
        [UIButton buttonWithType: UIButtonTypeInfoLight];
    CGRect frame = info.frame;
    frame.size.height = 44;
    info.frame = frame;
    [info addTarget: self
          action: @selector(info)
          forControlEvents: UIControlEventTouchUpInside];

    toolbar.items =
        [NSArray arrayWithObjects:
            [[[UIBarButtonItem alloc]
                 initWithBarButtonSystemItem: UIBarButtonSystemItemCancel
                 target: self
                 action: @selector(cancel)]
                autorelease],
            [[[UIBarButtonItem alloc]
                 initWithBarButtonSystemItem: UIBarButtonSystemItemFlexibleSpace
                 target: nil
                 action: nil]
                autorelease],
            [[[UIBarButtonItem alloc]
                 initWithCustomView: info]
                autorelease],
            nil];
    [controls addSubview: toolbar];
    toolbar.autoresizingMask =
        UIViewAutoresizingFlexibleWidth |
        UIViewAutoresizingFlexibleHeight;
    [toolbar release];

    [view addSubview: controls];

    if (@available(iOS 11, *)) {
        UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
        controls.translatesAutoresizingMaskIntoConstraints = NO;
        //toolbar.translatesAutoresizingMaskIntoConstraints = NO;
        [NSLayoutConstraint activateConstraints:@[
                                                   [safe.trailingAnchor constraintEqualToAnchor:controls.trailingAnchor],
                                                   [controls.leadingAnchor constraintEqualToAnchor:safe.leadingAnchor],
                                                   [safe.bottomAnchor constraintEqualToAnchor:controls.bottomAnchor]
                                                  ]];
        [NSLayoutConstraint
        constraintWithItem:controls
        attribute:NSLayoutAttributeHeight
        relatedBy:NSLayoutRelationEqual
        toItem:nil
        attribute:NSLayoutAttributeHeight
        multiplier:1.0
        constant:44.0].active = YES;
    }
}

- (void) initVideoQuality
{
    if(!readerView) {
        assert(0);
        return;
    }

    AVCaptureSession *session = readerView.session;
    NSString *preset = AVSessionPresetForUIVideoQuality(videoQuality);
    if(session && preset && [session canSetSessionPreset: preset]) {
        zlog(@"set session preset=%@", preset);
        session.sessionPreset = preset;
    }
    else
        zlog(@"unable to set session preset=%@", preset);
}

- (void) loadView
{
    self.view = [[UIView alloc]
                    initWithFrame: CGRectMake(0, 0, 320, 480)];
}

- (void) viewDidLoad
{
    [super viewDidLoad];
    UIView *view = self.view;
    view.backgroundColor = [UIColor blackColor];
    view.autoresizingMask =
        UIViewAutoresizingFlexibleWidth |
        UIViewAutoresizingFlexibleHeight;

    readerView = [[ZBarReaderView alloc]
                     initWithImageScanner: scanner];
    CGRect bounds = view.bounds;
    CGRect r = bounds;
    NSUInteger autoresize =
        UIViewAutoresizingFlexibleWidth |
        UIViewAutoresizingFlexibleHeight;

    if(showsZBarControls ||
       self.parentViewController.modalViewController == self)
    {
        autoresize |= UIViewAutoresizingFlexibleBottomMargin;
        r.size.height -= 44;
    }
    readerView.frame = r;
    readerView.autoresizingMask = autoresize;
    AVCaptureDevice *device = AVDeviceForUICamera(cameraDevice);
    if(device && device != readerView.device)
        readerView.device = device;
    readerView.torchMode = AVTorchModeForUIFlashMode(cameraFlashMode);
    [self initVideoQuality];

    readerView.readerDelegate = (id<ZBarReaderViewDelegate>)self;
    readerView.scanCrop = scanCrop;
    readerView.previewTransform = cameraViewTransform;
    readerView.tracksSymbols = tracksSymbols;
    readerView.enableCache = enableCache;
    [view addSubview: readerView];

    shutter = [[UIView alloc]
                  initWithFrame: r];
    shutter.backgroundColor = [UIColor blackColor];
    shutter.opaque = NO;
    shutter.autoresizingMask =
        UIViewAutoresizingFlexibleWidth |
        UIViewAutoresizingFlexibleHeight;
    [view addSubview: shutter];

    if(cameraOverlayView) {
        assert(!cameraOverlayView.superview);
        [cameraOverlayView removeFromSuperview];
        [view addSubview: cameraOverlayView];
    }

    [self initControls];

    if(TARGET_IPHONE_SIMULATOR) {
        cameraSim = [[ZBarCameraSimulator alloc]
                        initWithViewController: self];
        cameraSim.readerView = readerView;
    }
}

- (void) viewDidUnload
{
    [cameraOverlayView removeFromSuperview];
    [self cleanup];
    [super viewDidUnload];
}

- (void) viewWillAppear: (BOOL) animated
{
    zlog(@"willAppear: anim=%d orient=%d",
         animated, self.interfaceOrientation);
    [self initControls];
    [super viewWillAppear: animated];

    [readerView willRotateToInterfaceOrientation: self.interfaceOrientation
                duration: 0];
    [readerView performSelector: @selector(start)
                withObject: nil
                afterDelay: .001];
    shutter.alpha = 1;
    shutter.hidden = NO;

    UIApplication *app = [UIApplication sharedApplication];
    BOOL willHideStatusBar =
        !didHideStatusBar && self.wantsFullScreenLayout && !app.statusBarHidden;
    if(willHideStatusBar)
        [app setStatusBarHidden: YES
             withAnimation: UIStatusBarAnimationFade];
    didHideStatusBar = didHideStatusBar || willHideStatusBar;
}

- (void) dismissModalViewControllerAnimated: (BOOL) animated
{
    if(didHideStatusBar) {
        [[UIApplication sharedApplication]
            setStatusBarHidden: NO
            withAnimation: UIStatusBarAnimationFade];
        didHideStatusBar = NO;
    }
    [super dismissModalViewControllerAnimated: animated];
}

- (void) viewWillDisappear: (BOOL) animated
{
    readerView.captureReader.enableReader = NO;

    if(didHideStatusBar) {
        [[UIApplication sharedApplication]
            setStatusBarHidden: NO
            withAnimation: UIStatusBarAnimationFade];
        didHideStatusBar = NO;
    }

    [super viewWillDisappear: animated];
}

- (void) viewDidDisappear: (BOOL) animated
{
    // stopRunning can take a really long time (>1s observed),
    // so defer until the view transitions are complete
    [readerView stop];
}

- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) orient
{
    return((supportedOrientationsMask >> orient) & 1);
}

- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) orient
                                 duration: (NSTimeInterval) duration
{
    zlog(@"willRotate: orient=%d #%g", orient, duration);
    rotating = YES;
    if(readerView)
        [readerView willRotateToInterfaceOrientation: orient
                    duration: duration];
}

- (void) willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation) orient
                                          duration: (NSTimeInterval) duration
{
    zlog(@"willAnimateRotation: orient=%d #%g", orient, duration);
    if(helpController)
        [helpController willAnimateRotationToInterfaceOrientation: orient
                        duration: duration];
    if(readerView)
        [readerView setNeedsLayout];
}

- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) orient
{
    zlog(@"didRotate(%d): orient=%d", rotating, orient);
    if(!rotating && readerView) {
        // work around UITabBarController bug: willRotate is not called
        // for non-portrait initial interface orientation
        [readerView willRotateToInterfaceOrientation: self.interfaceOrientation
                    duration: 0];
        [readerView setNeedsLayout];
    }
    rotating = NO;
}

- (ZBarReaderView*) readerView
{
    // force view to load
    (void)self.view;
    assert(readerView);
    return(readerView);
}

- (void) setTracksSymbols: (BOOL) track
{
    tracksSymbols = track;
    if(readerView)
        readerView.tracksSymbols = track;
}

- (void) setEnableCache: (BOOL) enable
{
    enableCache = enable;
    if(readerView)
        readerView.enableCache = enable;
}

- (void) setScanCrop: (CGRect) r
{
    scanCrop = r;
    if(readerView)
        readerView.scanCrop = r;
}

- (void) setCameraOverlayView: (UIView*) newview
{
    UIView *oldview = cameraOverlayView;
    [oldview removeFromSuperview];

    cameraOverlayView = [newview retain];
    if([self isViewLoaded] && newview)
        [self.view addSubview: newview];

    [oldview release];
}

- (void) setCameraViewTransform: (CGAffineTransform) xfrm
{
    cameraViewTransform = xfrm;
    if(readerView)
        readerView.previewTransform = xfrm;
}

- (void) cancel
{
    if(!readerDelegate)
        return;
    SEL cb = @selector(imagePickerControllerDidCancel:);
    if([readerDelegate respondsToSelector: cb])
        [readerDelegate
            imagePickerControllerDidCancel: (UIImagePickerController*)self];
    else
        [self dismissViewControllerAnimated:YES completion:nil];
}

- (void) info
{
    [self showHelpWithReason: @"INFO"];
}

- (void) showHelpWithReason: (NSString*) reason
{
    if(helpController)
        return;
    helpController = [[ZBarHelpController alloc]
                         initWithReason: reason];
    helpController.delegate = (id<ZBarHelpDelegate>)self;
    [self presentViewController:helpController animated:YES completion:nil];
}

- (void) takePicture
{
    if(TARGET_IPHONE_SIMULATOR) {
        [cameraSim takePicture];
        // FIXME return selected image
    }
    else if(readerView)
        [readerView.captureReader captureFrame];
}

- (void) setCameraDevice: (UIImagePickerControllerCameraDevice) camera
{
    cameraDevice = camera;
    if(readerView) {
        AVCaptureDevice *device = AVDeviceForUICamera(camera);
        if(device)
            readerView.device = device;
    }
}

- (void) setCameraFlashMode: (UIImagePickerControllerCameraFlashMode) mode
{
    cameraFlashMode = mode;
    if(readerView)
        readerView.torchMode = AVTorchModeForUIFlashMode(mode);
}

- (UIImagePickerControllerCameraCaptureMode) cameraCaptureMode
{
    return(UIImagePickerControllerCameraCaptureModeVideo);
}

- (void) setCameraCaptureMode: (UIImagePickerControllerCameraCaptureMode) mode
{
    NSAssert2(mode == UIImagePickerControllerCameraCaptureModeVideo,
              @"attempt to set unsupported value (%d)"
              @" for %@ property", mode, @"cameraCaptureMode");
}

- (void) setVideoQuality: (UIImagePickerControllerQualityType) quality
{
    videoQuality = quality;
    if(readerView)
        [self initVideoQuality];
}


// ZBarHelpDelegate

- (void) helpControllerDidFinish: (ZBarHelpController*) help
{

    [UIView beginAnimations: @"ZBarHelp"
          context: NULL];
    [UIView setAnimationDelegate: self];
    [UIView setAnimationDidStopSelector: @selector(removeHelp:done:context:)];
    [UIView commitAnimations];
}

- (void) removeHelp: (NSString*) tag
               done: (NSNumber*) done
            context: (void*) ctx
{
    if([tag isEqualToString: @"ZBarHelp"] && helpController) {
        [helpController release];
        helpController = nil;
    }
}


// ZBarReaderViewDelegate

- (void) readerView: (ZBarReaderView*) readerView
     didReadSymbols: (ZBarSymbolSet*) syms
          fromImage: (UIImage*) image
{
    [readerDelegate
        imagePickerController: (UIImagePickerController*)self
        didFinishPickingMediaWithInfo:
            [NSDictionary dictionaryWithObjectsAndKeys:
                image, UIImagePickerControllerOriginalImage,
                syms, ZBarReaderControllerResults,
                nil]];
}

- (void) readerViewDidStart: (ZBarReaderView*) readerView
{
    if(!shutter.hidden)
        [UIView animateWithDuration: .25
                animations: ^{
                    shutter.alpha = 0;
                }
                completion: ^(BOOL finished) {
                    shutter.hidden = YES;
                }];
}


// "deprecated" properties

#define DEPRECATED_PROPERTY(getter, setter, type, val, ignore) \
    - (type) getter                                    \
    {                                                  \
        return(val);                                   \
    }                                                  \
    - (void) setter: (type) v                          \
    {                                                  \
        NSAssert2(ignore || v == val,                  \
                  @"attempt to set unsupported value (%d)" \
                  @" for %@ property", val, @#getter); \
    }

DEPRECATED_PROPERTY(sourceType, setSourceType, UIImagePickerControllerSourceType, UIImagePickerControllerSourceTypeCamera, NO)
DEPRECATED_PROPERTY(allowsEditing, setAllowsEditing, BOOL, NO, NO)
DEPRECATED_PROPERTY(allowsImageEditing, setAllowsImageEditing, BOOL, NO, NO)
DEPRECATED_PROPERTY(showsCameraControls, setShowsCameraControls, BOOL, NO, NO)
DEPRECATED_PROPERTY(showsHelpOnFail, setShowsHelpOnFail, BOOL, NO, YES)
DEPRECATED_PROPERTY(cameraMode, setCameraMode, ZBarReaderControllerCameraMode, ZBarReaderControllerCameraModeSampling, NO)
DEPRECATED_PROPERTY(takesPicture, setTakesPicture, BOOL, NO, NO)
DEPRECATED_PROPERTY(maxScanDimension, setMaxScanDimension, NSInteger, 640, YES)

@end
