/* HBQueueController

    This file is part of the HandBrake source code.
    Homepage: <http://handbrake.fr/>.
    It may be used under the terms of the GNU General Public License. */

#import "HBQueueController.h"

#import "HBAppDelegate.h"

#import "HBQueue.h"
#import "HBQueueWorker.h"
#import "HBQueueTableViewController.h"
#import "HBQueueDetailsViewController.h"
#import "HBQueueInfoViewController.h"
#import "HBQueueMultiSelectionViewController.h"

#import "HBQueueToolbarDelegate.h"

#import "HBPreferencesKeys.h"
#import "NSArray+HBAdditions.h"

@import HandBrakeKit;
@import QuickLookUI;
@import UserNotifications;

@interface HBQueueController () <NSToolbarItemValidation, NSMenuItemValidation, NSUserNotificationCenterDelegate, UNUserNotificationCenterDelegate, HBQueueTableViewControllerDelegate, HBQueueDetailsViewControllerDelegate>

@property (nonatomic) NSSplitViewController *splitViewController;
@property (nonatomic) HBQueueTableViewController *tableViewController;
@property (nonatomic) NSViewController *containerViewController;
@property (nonatomic) HBQueueInfoViewController *infoViewController;
@property (nonatomic) HBQueueMultiSelectionViewController *multiSelectionViewController;

@property (nonatomic) HBQueueToolbarDelegate *toolbarDelegate;

@property (nonatomic) IBOutlet NSToolbarItem *ripToolbarItem;
@property (nonatomic) IBOutlet NSToolbarItem *pauseToolbarItem;

@property (nonatomic, readonly) dispatch_queue_t sendQueue;

@end

@interface HBQueueController (TouchBar) <NSTouchBarProvider, NSTouchBarDelegate>
- (void)_touchBar_updateButtonsState;
- (void)_touchBar_validateUserInterfaceItems;
- (IBAction)_touchBar_toggleStartCancel:(id)sender;
@end

@implementation HBQueueController

- (instancetype)initWithQueue:(HBQueue *)queue
{
    NSParameterAssert(queue);

    if (self = [super initWithWindowNibName:@"Queue"])
    {
        _queue = queue;
        _sendQueue = dispatch_queue_create("fr.handbrake.SendToQueue",
                                           dispatch_queue_attr_make_with_autorelease_frequency(DISPATCH_QUEUE_SERIAL,
                                                                                               DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM));

        if (@available(macOS 10.15, *))
        {
            UNUserNotificationCenter *center = UNUserNotificationCenter.currentNotificationCenter;
            center.delegate = self;

            UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:HBQueueItemNotificationShowAction
                                                                                title:NSLocalizedString(@"Show", @"Notification -> Show in Finder")
                                                                              options:UNNotificationActionOptionForeground];
            UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:HBQueueItemNotificationShowCategory
                                                                                      actions:@[action]
                                                                            intentIdentifiers:@[]
                                                                                      options:0];
            [center setNotificationCategories:[NSSet setWithObject:category]];
            [center requestAuthorizationWithOptions:UNAuthorizationOptionSound | UNAuthorizationOptionAlert
                                  completionHandler:^(BOOL granted, NSError * _Nullable error) {}];
        }
        else
        {
            NSUserNotificationCenter.defaultUserNotificationCenter.delegate = self;
        }

        [NSNotificationCenter.defaultCenter addObserverForName:HBQueueLowSpaceAlertNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
            [self queueLowDiskSpaceAlert];
        }];

        [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
            [self queueCompletedAlerts];
        }];

        [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
            // Run the per item notification and actions
            HBQueueJobItem *item = note.userInfo[HBQueueItemNotificationItemKey];
            if (item.state == HBQueueItemStateCompleted)
            {
                [self sendToExternalApp:item];
            }

            if (item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateFailed)
            {
                [self itemCompletedAlerts:item];
            }
        }];
    }

    return self;
}

- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window
{
    return _queue.undoManager;
}

- (void)windowDidLoad
{
    self.window.tabbingMode = NSWindowTabbingModeDisallowed;

    if (@available (macOS 11, *))
    {
        self.window.toolbarStyle = NSWindowToolbarStyleUnified;
        self.window.titlebarSeparatorStyle = NSTitlebarSeparatorStyleLine;
    }

    // Set up toolbar
    self.toolbarDelegate = [[HBQueueToolbarDelegate alloc] initWithTarget:self];

    NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"HBQueueWindowToolbar2"];
    toolbar.delegate = self.toolbarDelegate;
    toolbar.allowsUserCustomization = YES;
    toolbar.autosavesConfiguration = YES;
    toolbar.displayMode = NSToolbarDisplayModeIconAndLabel;
    self.window.toolbar = toolbar;

    // Set up the child view controllers
    _splitViewController = [[NSSplitViewController alloc] init];
    _splitViewController.view.wantsLayer = YES;
    [_splitViewController.view setFrameSize:NSMakeSize(780, 500)];
    _splitViewController.splitView.vertical = YES;

    _tableViewController = [[HBQueueTableViewController alloc] initWithQueue:self.queue delegate:self];
    _containerViewController = [[HBQueueDetailsViewController alloc] init];
    _infoViewController = [[HBQueueInfoViewController alloc] initWithDelegate:self];
    _multiSelectionViewController = [[HBQueueMultiSelectionViewController alloc] init];

    NSSplitViewItem *tableItem = [NSSplitViewItem splitViewItemWithViewController:_tableViewController];
    tableItem.minimumThickness = 160;

    [_splitViewController addSplitViewItem:tableItem];

    NSSplitViewItem *detailsItem = [NSSplitViewItem splitViewItemWithViewController:_containerViewController];
    detailsItem.canCollapse = YES;
    detailsItem.minimumThickness = 240;

    [_splitViewController addSplitViewItem:detailsItem];

    _splitViewController.splitView.autosaveName = @"HBQueueSplitViewAutosave";
    _splitViewController.splitView.identifier = @"HBQueueSplitViewIdentifier";

    self.window.contentViewController = _splitViewController;
    self.window.frameAutosaveName = @"HBQueueWindowFrameAutosave";

    // Set up observers
    [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidChangeStateNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
        [self updateUI];
    }];

    [self updateUI];
    [self tableViewDidSelectItemsAtIndexes:[NSIndexSet indexSet]];
}

- (void)updateUI
{
    [self.toolbarDelegate updateToolbarButtonsState:self.queue toolbar:self.window.toolbar];
    [self.window.toolbar validateVisibleItems];

    [self _touchBar_updateButtonsState];
    [self _touchBar_validateUserInterfaceItems];

    NSString *subtitle;
    if (self.queue.pendingItemsCount == 0)
    {
        self.window.title = NSLocalizedString(@"Queue", @"Queue window title");
        subtitle = NSLocalizedString(@"No encode pending", @"Queue status");
    }
    else
    {
        if (self.queue.pendingItemsCount == 1)
        {
            subtitle = [NSString stringWithFormat: NSLocalizedString(@"%lu encode pending", @"Queue status"), (unsigned long)self.queue.pendingItemsCount];
        }
        else
        {
            subtitle = [NSString stringWithFormat: NSLocalizedString(@"%lu encodes pending", @"Queue status"), (unsigned long)self.queue.pendingItemsCount];
        }

        if (@available(macOS 11, *)) {} else
        {
            self.window.title = [NSString stringWithFormat: NSLocalizedString(@"Queue (%@)", @"Queue window title"), subtitle];
        }
    }

    if (@available(macOS 11, *))
    {
        self.window.subtitle = subtitle;
    }
}

#pragma mark Toolbar

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
    SEL action = menuItem.action;

    if (action == @selector(toggleStartCancel:))
    {
        if (self.queue.isEncoding)
        {
            menuItem.title = NSLocalizedString(@"Stop Encoding", @"Queue -> start/stop menu");
            return YES;
        }
        else
        {
            menuItem.title = NSLocalizedString(@"Start Encoding", @"Queue -> start/stop menu");
            return self.queue.canEncode;
        }
    }
    else if (action == @selector(togglePauseResume:))
    {
        if (self.queue.canPause)
        {
            menuItem.title = NSLocalizedString(@"Pause Encoding", @"Queue -> pause/resume menu");
        }
        else
        {
            menuItem.title = NSLocalizedString(@"Resume Encoding", @"Queue -> pause/resume men");
        }
        return self.queue.canPause || self.queue.canResume;
    }
    else if (action == @selector(toggleDetails:))
    {
        NSSplitViewItem *detailsItem = self.splitViewController.splitViewItems[1];
        if (detailsItem.isCollapsed)
        {
            menuItem.title = NSLocalizedString(@"Show Sidebar", @"Queue -> sidebar menu");
        }
        else
        {
            menuItem.title = NSLocalizedString(@"Hide Sidebar", @"Queue -> sidebar menu");
        }
        return YES;
    }
    else if (action == @selector(removeAll:) || action == @selector(resetAll:))
    {
        return self.queue.items.count > 0;
    }
    else if (action == @selector(resetFailed:))
    {
        return self.queue.failedItemsCount > 0;
    }
    else if (action == @selector(removeCompleted:))
    {
        return self.queue.completedItemsCount > 0;
    }

    return YES;
}

- (BOOL)validateUserIterfaceItemForAction:(SEL)action
{
    if (action == @selector(toggleStartCancel:) || action == @selector(_touchBar_toggleStartCancel:))
    {
        return self.queue.isEncoding || self.queue.canEncode;
    }
    else if (action == @selector(togglePauseResume:))
    {
        return self.queue.canPause || self.queue.canResume;
    }
    else if (action == @selector(toggleDetails:) ||
        action == @selector(toggleQuickLook:))
    {
        return YES;
    }

    return NO;
}

- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
{
    return [self validateUserIterfaceItemForAction:theItem.action];
}

#pragma mark - Private queue editing methods

/**
 * Delete encodes from the queue window and accompanying array
 * Also handling first cancelling the encode if in fact its currently encoding.
 */
- (void)removeQueueItemsAtIndexes:(NSIndexSet *)indexes
{
    if (indexes.count)
    {
        NSMutableIndexSet *mutableIndexes = [indexes mutableCopy];
        // if this is a currently encoding job, we need to be sure to alert the user,
        // to let them decide to cancel it first, then if they do, we can come back and
        // remove it
        NSIndexSet *workingIndexes = [self.queue.items HB_indexesOfObjectsUsingBlock:^BOOL(HBQueueJobItem *item) {
            return item.state == HBQueueItemStateWorking;
        }];

        NSIndexSet *workingSelectedIndexes = [workingIndexes HB_intersectionWith:indexes];
        [mutableIndexes removeIndexes:workingSelectedIndexes];

        if (workingSelectedIndexes.count)
        {
            NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It?", @"Queue Stop Alert -> stop and remove message")];

            // Which window to attach the sheet to?
            NSWindow *targetWindow = self.window;

            NSAlert *alert = [[NSAlert alloc] init];
            [alert setMessageText:alertTitle];
            [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Stop Alert -> stop and remove informative text")];
            [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Stop Alert -> stop and remove first button")];
            [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Delete", @"Queue Stop Alert -> stop and remove second button")];
            if (@available(macOS 11, *))
            {
                alert.buttons.lastObject.hasDestructiveAction = true;
            }
            [alert setAlertStyle:NSAlertStyleCritical];

            [alert beginSheetModalForWindow:targetWindow completionHandler:^(NSModalResponse returnCode) {
                if (returnCode == NSAlertSecondButtonReturn)
                {
                    [self.queue cancelItemsAtIndexes:workingSelectedIndexes];
                    [self.queue removeItemsAtIndexes:workingSelectedIndexes];
                }
            }];
        }

        // remove the non working items immediately
        [self.queue removeItemsAtIndexes:mutableIndexes];
    }
}

- (void)doEditQueueItem:(HBQueueJobItem *)item
{
    [self.queue prepareItemForEditingAtIndex:[self.queue.items indexOfObject:item]];

    [self.delegate openJob:[item.job copy] completionHandler:^(BOOL result) {
        NSInteger index = [self.queue.items indexOfObject:item];
        if (index != NSNotFound)
        {
            [self.queue resetItemsAtIndexes:[NSIndexSet indexSetWithIndex:index]];
            if (result)
            {
                // Now that source is loaded and settings applied, delete the queue item from the queue
                [self.queue removeItemsAtIndexes:[NSIndexSet indexSetWithIndex:index]];
            }
            else
            {
                NSBeep();
            }
        }
        else
        {
            item.state = HBQueueItemStateReady;
        }
    }];
}

/**
 * Send the selected queue item back to the main window for rescan and possible edit.
 */
- (void)editQueueItem:(HBQueueJobItem *)item
{
    // if this is a currently encoding item, we need to be sure to alert the user,
    // to let them decide to cancel it first, then if they do, we can come back and
    // remove it
    if (item.state == HBQueueItemStateWorking)
    {
        NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Edit It?", @"Queue Edit Alert -> stop and edit message")];

        // Which window to attach the sheet to?
        NSWindow *docWindow = self.window;

        NSAlert *alert = [[NSAlert alloc] init];
        [alert setMessageText:alertTitle];
        [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Edit Alert -> stop and edit informative text")];
        [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Edit Alert -> stop and edit first button")];
        [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Edit", @"Queue Edit Alert -> stop and edit second button")];
        if (@available(macOS 11, *))
        {
            alert.buttons.lastObject.hasDestructiveAction = true;
        }
        [alert setAlertStyle:NSAlertStyleCritical];

        [alert beginSheetModalForWindow:docWindow completionHandler:^(NSModalResponse returnCode) {
            if (returnCode == NSAlertSecondButtonReturn)
            {
                [self doEditQueueItem:item];
            }
        }];
    }
    else if (item.state != HBQueueItemStateWorking && item.state != HBQueueItemStateRescanning)
    {
        [self doEditQueueItem:item];
    }
}

- (void)resetQueueItemsAtIndexes:(NSIndexSet *)indexes
{
    [self.queue resetItemsAtIndexes:indexes];
}

#pragma mark - Encode Done Actions

NSString * const HBQueueItemNotificationPathKey = @"HBQueueItemNotificationPathKey";
NSString * const HBQueueItemNotificationShowAction = @"HBQueueItemNotificationShowAction";
NSString * const HBQueueItemNotificationShowCategory = @"HBQueueItemNotificationShowCategory";

- (void)showInFinder:(NSString *)path
{
    if ([path isKindOfClass:[NSString class]] && path.length)
    {
        NSURL *fileURL = [NSURL fileURLWithPath:path];
        [NSWorkspace.sharedWorkspace activateFileViewerSelectingURLs:@[fileURL]];
    }
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler API_AVAILABLE(macos(10.15))
{
    completionHandler(UNNotificationPresentationOptionSound);
}

- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
    // Show the file in Finder when a done notification is clicked
    NSString *path = notification.userInfo[HBQueueItemNotificationPathKey];
    [self showInFinder:path];
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler API_AVAILABLE(macos(10.14))
{
    NSString *path = response.notification.request.content.userInfo[HBQueueItemNotificationPathKey];
    [self showInFinder:path];
    completionHandler();
}

- (void)showNotificationWithTitle:(NSString *)title description:(NSString *)description url:(NSURL *)fileURL playSound:(BOOL)playSound
{
    if (@available(macOS 10.15, *))
    {
        UNMutableNotificationContent *notification = [[UNMutableNotificationContent alloc] init];
        notification.title = title;
        notification.body = description;
        notification.sound = playSound ? UNNotificationSound.defaultSound : nil;
        if (fileURL)
        {
            notification.categoryIdentifier = HBQueueItemNotificationShowCategory;
            notification.userInfo = @{ HBQueueItemNotificationPathKey: fileURL.path };
        }

        UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:[NSUUID UUID].UUIDString
                                                                              content:notification
                                                                              trigger:nil];
        [UNUserNotificationCenter.currentNotificationCenter addNotificationRequest:request withCompletionHandler:NULL];
    }
    else
    {
        NSUserNotification *notification = [[NSUserNotification alloc] init];
        notification.title = title;
        notification.informativeText = description;
        notification.soundName = playSound ? NSUserNotificationDefaultSoundName : nil;

        if (fileURL)
        {
            notification.hasActionButton = YES;
            notification.actionButtonTitle = NSLocalizedString(@"Show", @"Notification -> Show in Finder");
            notification.userInfo = @{ HBQueueItemNotificationPathKey: fileURL.path };
        }
        else
        {
            notification.hasActionButton = NO;
        }

        [NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification];
    }
}

/**
 *  Sends the URL to the external app
 *  selected in the preferences.
 *
 *  @param job the job of the file to send
 */
- (void)sendToExternalApp:(HBQueueJobItem *)item
{
    // This end of encode action is called as each encode rolls off of the queue
    if ([NSUserDefaults.standardUserDefaults boolForKey:HBSendToAppEnabled] == YES)
    {
        NSURL *destinationFolderURL = item.destinationFolderURL;
        NSString *destinationPath = item.destinationURL.path;

        dispatch_async(_sendQueue, ^{
#ifdef __SANDBOX_ENABLED__
            BOOL accessingSecurityScopedResource = [destinationFolderURL startAccessingSecurityScopedResource];
#endif

            NSWorkspace *workspace = NSWorkspace.sharedWorkspace;
            NSString *app = [workspace fullPathForApplication:[NSUserDefaults.standardUserDefaults objectForKey:HBSendToApp]];

            if (app)
            {
                if (![workspace openFile:destinationPath withApplication:app])
                {
                    [HBUtilities writeToActivityLog:"Failed to send file to: %s", app];
                }
            }
            else
            {
                [HBUtilities writeToActivityLog:"Send file to: app not found"];
            }

#ifdef __SANDBOX_ENABLED__
            if (accessingSecurityScopedResource)
            {
                [destinationFolderURL stopAccessingSecurityScopedResource];
            }
#endif
        });
    }
}

/**
 *  Runs the alert for a single job
 */
- (void)itemCompletedAlerts:(HBQueueJobItem *)item
{
    NSUserDefaults *ud = NSUserDefaults.standardUserDefaults;

    if ([ud boolForKey:HBQueueNotificationWhenJobDone])
    {
        NSString *title;
        NSString *description;
        if (item.state == HBQueueItemStateCompleted)
        {
            title = NSLocalizedString(@"Put down that cocktail…", @"Queue notification alert message");
            description = [NSString stringWithFormat:NSLocalizedString(@"Your encode %@ is done!", @"Queue done notification message"),
                                     item.destinationFileName];

        }
        else
        {
            title = NSLocalizedString(@"Encode failed", @"Queue done notification failed message");
            description = [NSString stringWithFormat:NSLocalizedString(@"Your encode %@ couldn't be completed.", @"Queue done notification message"),
                           item.destinationFileName];
        }

        bool playSound = [ud boolForKey:HBQueueNotificationPlaySound];

        [self showNotificationWithTitle:title
                            description:description
                                    url:item.destinationURL
                              playSound:playSound];
    }

    if ([ud boolForKey:HBQueueAutoClearCompletedItems])
    {
        [self.queue removeCompletedItems];
    }
}

/**
 *  Runs the global queue completed alerts
 */
- (void)queueCompletedAlerts
{
    NSUserDefaults *ud = NSUserDefaults.standardUserDefaults;

    if ([ud boolForKey:HBQueueNotificationWhenDone])
    {
        bool playSound = [ud boolForKey:HBQueueNotificationPlaySound];

        [self showNotificationWithTitle:NSLocalizedString(@"Put down that cocktail…", @"Queue notification alert message")
                            description:NSLocalizedString(@"Your queue is done!", @"Queue done notification message")
                                    url:nil
                              playSound:playSound];
    }

    // If sleep has been selected
    if ([ud integerForKey:HBQueueDoneAction] == HBDoneActionSleep)
    {
        // Sleep
        NSAppleScript *scriptObject = [[NSAppleScript alloc] initWithSource:@"tell application \"System Events\" to sleep"];
        [scriptObject executeAndReturnError:NULL];
    }

    // If Shutdown has been selected
    if ([ud integerForKey:HBQueueDoneAction] == HBDoneActionShutDown)
    {
        // Shut Down
        NSAppleScript *scriptObject = [[NSAppleScript alloc] initWithSource:@"tell application \"System Events\" to shut down"];
        [scriptObject executeAndReturnError:NULL];
    }

    // If Quit HB has been selected
    if ([ud integerForKey:HBQueueDoneAction] == HBDoneActionQuit)
    {
        [NSApp terminate:self];
    }
}

- (void)queueLowDiskSpaceAlert
{
    NSAlert *alert = [[NSAlert alloc] init];
    [alert setMessageText:NSLocalizedString(@"Your destination disk is almost full.", @"Queue -> disk almost full alert message")];
    [alert setInformativeText:NSLocalizedString(@"You need to make more space available on your destination disk.",@"Queue -> disk almost full alert informative text")];
    [NSApp requestUserAttention:NSCriticalRequest];
    [alert runModal];
}

- (void)remindUserOfSleepOrShutdown
{
    NSUserDefaults *ud = NSUserDefaults.standardUserDefaults;

    if ([ud integerForKey:HBQueueDoneAction] == HBDoneActionSleep)
    {
        // Warn that computer will sleep after encoding
        NSBeep();
        [NSApp requestUserAttention:NSCriticalRequest];

        NSAlert *alert = [[NSAlert alloc] init];
        [alert setMessageText:NSLocalizedString(@"The computer will sleep after encoding is done.", @"Queue Done Alert -> sleep message")];
        [alert setInformativeText:NSLocalizedString(@"You have selected to sleep the computer after encoding. To turn off sleeping, go to the HandBrake preferences.", @"Queue Done Alert -> sleep informative text")];
        [alert addButtonWithTitle:NSLocalizedString(@"OK", @"Queue Done Alert -> sleep first button")];
        [alert addButtonWithTitle:NSLocalizedString(@"Preferences…", @"Queue Done Alert -> sleep second button")];

        NSInteger response = [alert runModal];
        if (response == NSAlertSecondButtonReturn)
        {
            [self.delegate showPreferencesWindow:nil];
        }

        [self promptForAppleEventAuthorization];
    }
    else if ([ud integerForKey:HBQueueDoneAction] == HBDoneActionShutDown)
    {
        // Warn that computer will shut down after encoding
        NSBeep();
        [NSApp requestUserAttention:NSCriticalRequest];

        NSAlert *alert = [[NSAlert alloc] init];
        [alert setMessageText:NSLocalizedString(@"The computer will shut down after encoding is done.", @"Queue Done Alert -> shut down message")];
        [alert setInformativeText:NSLocalizedString(@"You have selected to shut down the computer after encoding. To turn off shut down, go to the HandBrake preferences.", @"Queue Done Alert -> shut down informative text")];
        [alert addButtonWithTitle:NSLocalizedString(@"OK", @"Queue Done Alert -> shut down first button")];
        [alert addButtonWithTitle:NSLocalizedString(@"Preferences…", @"Queue Done Alert -> shut down second button")];

        NSInteger response = [alert runModal];
        if (response == NSAlertSecondButtonReturn)
        {
            [self.delegate showPreferencesWindow:nil];
        }

        [self promptForAppleEventAuthorization];
    }
}

- (void)promptForAppleEventAuthorization
{
    HBPrivacyConsentState result = [HBUtilities determinePermissionToAutomateTarget:@"com.apple.systemevents" promptIfNeeded:YES];
    if (result != HBPrivacyConsentStateGranted)
    {
        [HBUtilities writeToActivityLog:"Failed to get permission to automate system events"];
    }
}

#pragma mark - UI Actions

/**
 * Rip: puts up an alert before ultimately calling doRip
 */
- (IBAction)toggleStartCancel:(id)sender
{
    // Rip or Cancel ?
    if (self.queue.isEncoding)
    {
        [self cancelRip:sender];
    }
    // If there are pending items in the queue, then this is a rip the queue
    else if (self.queue.canEncode)
    {
        // We check to see if we need to warn the user that the computer will go to sleep
        // or shut down when encoding is finished
        [self remindUserOfSleepOrShutdown];
        [self.queue start];
    }
}

/**
* Starts or cancels the processing of items depending on the current state
 * Displays an alert asking user if the want to cancel encoding of current item.
 */
- (IBAction)cancelRip:(id)sender
{
    // Which window to attach the sheet to?
    NSWindow *window = self.window;
    if ([sender respondsToSelector:@selector(window)])
    {
        window = [sender window];
    }

    NSAlert *alert = [[NSAlert alloc] init];
    [alert setMessageText:NSLocalizedString(@"You are currently encoding. What would you like to do?", @"Queue Alert -> cancel rip message")];
    [alert setInformativeText:NSLocalizedString(@"Select Continue Encoding to dismiss this dialog without making changes.", @"Queue Alert -> cancel rip informative text")];
    [alert addButtonWithTitle:NSLocalizedString(@"Continue Encoding", @"Queue Alert -> cancel rip first button")];
    [alert addButtonWithTitle:NSLocalizedString(@"Skip Current Job", @"Queue Alert -> cancel rip second button")];
    if (@available(macOS 11, *))
    {
        alert.buttons.lastObject.hasDestructiveAction = YES;
    }
    [alert addButtonWithTitle:NSLocalizedString(@"Stop After Current Job", @"Queue Alert -> cancel rip third button")];
    [alert addButtonWithTitle:NSLocalizedString(@"Stop All", @"Queue Alert -> cancel rip fourth button")];
    if (@available(macOS 11, *))
    {
        alert.buttons.lastObject.hasDestructiveAction = YES;
    }
    [alert setAlertStyle:NSAlertStyleCritical];

    [alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse returnCode) {
        if (returnCode == NSAlertSecondButtonReturn)
        {
            [self.queue cancelCurrentAndContinue];
        }
        else if (returnCode == NSAlertThirdButtonReturn)
        {
            [self.queue finishCurrentAndStop];
        }
        else if (returnCode == NSAlertThirdButtonReturn + 1)
        {
            [self.queue cancelCurrentAndStop];
        }
    }];
}

/**
 * Toggles the pause/resume state of libhb
 */
- (IBAction)togglePauseResume:(id)sender
{
    if (self.queue.canResume)
    {
        [self.queue resume];
    }
    else if (self.queue.canPause)
    {
        [self.queue pause];
    }
}

- (IBAction)toggleDetails:(id)sender
{
    NSSplitViewItem *detailsItem = self.splitViewController.splitViewItems[1];
    detailsItem.animator.collapsed = !detailsItem.isCollapsed;
}

- (IBAction)toggleQuickLook:(id)sender
{
    if (QLPreviewPanel.sharedPreviewPanelExists && QLPreviewPanel.sharedPreviewPanel.isVisible)
    {
        [QLPreviewPanel.sharedPreviewPanel orderOut:sender];
    }
    else
    {
        [QLPreviewPanel.sharedPreviewPanel makeKeyAndOrderFront:sender];
    }
}

#pragma mark - table view controller delegate

- (void)tableViewDidSelectItemsAtIndexes:(NSIndexSet *)indexes
{
    NSUInteger count = indexes.count;

    if (count != 1)
    {
        self.multiSelectionViewController.count = count;
        [self switchToViewController:self.multiSelectionViewController];
    }
    else
    {
        NSArray<id<HBQueueItem>> *items = [self.queue.items objectsAtIndexes:indexes];
        self.infoViewController.item = items.firstObject;
        [self switchToViewController:self.infoViewController];
    }
}

- (void)switchToViewController:(NSViewController *)viewController
{
    NSViewController *firstChild = self.containerViewController.childViewControllers.firstObject;

    if (firstChild != viewController)
    {
        if (firstChild)
        {
            [firstChild.view removeFromSuperviewWithoutNeedingDisplay];
            [firstChild removeFromParentViewController];
        }

        [self.containerViewController addChildViewController:viewController];
        viewController.view.frame = self.containerViewController.view.bounds;
        viewController.view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
        [self.containerViewController.view addSubview:viewController.view];
    }
}

- (void)tableViewEditItem:(id<HBQueueItem>)item
{
    if ([item isKindOfClass:[HBQueueJobItem class]])
    {
        [self editQueueItem:item];
    }
}

- (void)tableViewRemoveItemsAtIndexes:(nonnull NSIndexSet *)indexes
{
    [self removeQueueItemsAtIndexes:indexes];
}

- (void)tableViewResetItemsAtIndexes:(nonnull NSIndexSet *)indexes {
    [self resetQueueItemsAtIndexes:indexes];
}

- (void)detailsViewEditItem:(nonnull id<HBQueueItem>)item
{
    if ([item isKindOfClass:[HBQueueJobItem class]])
    {
        [self editQueueItem:item];
    }
}

- (void)detailsViewResetItem:(nonnull id<HBQueueItem>)item
{
    if ([item isKindOfClass:[HBQueueJobItem class]])
    {
        NSUInteger index = [self.queue.items indexOfObject:item];
        [self resetQueueItemsAtIndexes:[NSIndexSet indexSetWithIndex:index]];
    }
}

- (IBAction)resetAll:(id)sender
{
    [self.queue resetAllItems];
}

- (IBAction)resetFailed:(id)sender
{
    [self.queue resetFailedItems];
}

- (IBAction)removeAll:(id)sender
{
    [self.queue removeNotWorkingItems];
}

- (IBAction)removeCompleted:(id)sender
{
    [self.queue removeCompletedItems];
}

@end

@implementation HBQueueController (TouchBar)

@dynamic touchBar;

static NSTouchBarItemIdentifier HBTouchBarMain = @"fr.handbrake.queueWindowTouchBar";

static NSTouchBarItemIdentifier HBTouchBarRip = @"fr.handbrake.rip";
static NSTouchBarItemIdentifier HBTouchBarPause = @"fr.handbrake.pause";

- (NSTouchBar *)makeTouchBar
{
    NSTouchBar *bar = [[NSTouchBar alloc] init];
    bar.delegate = self;

    bar.defaultItemIdentifiers = @[HBTouchBarRip, HBTouchBarPause];

    bar.customizationIdentifier = HBTouchBarMain;
    bar.customizationAllowedItemIdentifiers = @[HBTouchBarRip, HBTouchBarPause];

    return bar;
}

- (IBAction)_touchBar_toggleStartCancel:(id)sender
{
    [self toggleStartCancel:self];
}

- (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
{
    if ([identifier isEqualTo:HBTouchBarRip])
    {
        NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
        item.customizationLabel = NSLocalizedString(@"Start/Stop Encoding", @"Touch bar");

        NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPlayTemplate] target:self action:@selector(_touchBar_toggleStartCancel:)];

        item.view = button;
        return item;
    }
    else if ([identifier isEqualTo:HBTouchBarPause])
    {
        NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
        item.customizationLabel = NSLocalizedString(@"Pause/Resume Encoding", @"Touch bar");

        NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPauseTemplate] target:self action:@selector(togglePauseResume:)];

        item.view = button;
        return item;
    }

    return nil;
}

- (void)_touchBar_updateButtonsState
{
    NSButton *ripButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarRip] view];
    NSButton *pauseButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarPause] view];

    if (self.queue.isEncoding)
    {
        ripButton.image = [NSImage imageNamed:NSImageNameTouchBarRecordStopTemplate];
    }
    else
    {
        ripButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate];
    }

    if (self.queue.canResume)
    {
        pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate];
    }
    else
    {
        pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate];
    }
}

- (void)_touchBar_validateUserInterfaceItems
{
    for (NSTouchBarItemIdentifier identifier in self.touchBar.itemIdentifiers) {
        NSTouchBarItem *item = [self.touchBar itemForIdentifier:identifier];
        NSView *view = item.view;
        if ([view isKindOfClass:[NSButton class]]) {
            NSButton *button = (NSButton *)view;
            BOOL enabled = [self validateUserIterfaceItemForAction:button.action];
            button.enabled = enabled;
        }
    }
}

@end
