/*****************************************************************************
 * VLCStatusBarIcon.m: Status bar icon controller/delegate
 *****************************************************************************
 * Copyright (C) 2016 VLC authors and VideoLAN
 * $Id$
 *
 * Authors: Goran Dokic <vlc at 8hz dot com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#import "VLCStatusBarIcon.h"

#import "VLCMainMenu.h"
#import "VLCMain.h"

#import <vlc_common.h>
#import <vlc_playlist.h>
#import <vlc_input.h>

#import "CompatibilityFixes.h"
#import "VLCCoreInteraction.h"
#import "VLCStringUtility.h"

#import "VLCApplication.h"

@interface VLCStatusBarIcon ()
{
    NSMenuItem *_vlcStatusBarMenuItem;

    /* Outlets for Now Playing labels */
    IBOutlet NSTextField *titleField;
    IBOutlet NSTextField *artistField;
    IBOutlet NSTextField *albumField;
    IBOutlet NSTextField *progressField;
    IBOutlet NSTextField *separatorField;
    IBOutlet NSTextField *totalField;
    IBOutlet NSImageView *coverImageView;

    /* Outlets for player controls */
    IBOutlet NSButton *backwardsButton;
    IBOutlet NSButton *playPauseButton;
    IBOutlet NSButton *forwardButton;
    IBOutlet NSButton *randButton;

    /* Outlets for menu items */
    IBOutlet NSMenuItem *pathActionItem;
    IBOutlet NSMenuItem *showMainWindowItem;
    IBOutlet NSMenuItem *quitItem;

    BOOL isStopped;
    BOOL showTimeElapsed;
    NSString *_currentPlaybackUrl;
}
@end

#pragma mark -
#pragma mark Implementation

@implementation VLCStatusBarIcon

#pragma mark -
#pragma mark Init


- (instancetype)init
{
    self = [super init];

    if (self) {
        msg_Dbg(getIntf(), "Loading VLCStatusBarIcon");
        [NSBundle loadNibNamed:@"VLCStatusBarIconMainMenu" owner:self];
    }

    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];

    [_controlsView setAutoresizingMask:NSViewWidthSizable];
    [_playbackInfoView setAutoresizingMask:NSViewWidthSizable];

    [self configurationChanged:nil];

    // Set Accessibility Attributes for Image Buttons
    [backwardsButton.cell accessibilitySetOverrideValue:_NS("Go to previous item")
                                           forAttribute:NSAccessibilityDescriptionAttribute];

    [playPauseButton.cell accessibilitySetOverrideValue:_NS("Toggle Play/Pause")
                                           forAttribute:NSAccessibilityDescriptionAttribute];

    [forwardButton.cell accessibilitySetOverrideValue:_NS("Go to next item")
                                         forAttribute:NSAccessibilityDescriptionAttribute];

    [randButton.cell accessibilitySetOverrideValue:_NS("Toggle random order playback")
                                      forAttribute:NSAccessibilityDescriptionAttribute];


    // Populate menu items with localized strings
    [showMainWindowItem setTitle:_NS("Show Main Window")];
    [pathActionItem setTitle:_NS("Path/URL Action")];
    [quitItem setTitle:_NS("Quit")];

    showTimeElapsed = YES;

    // Set our selves up as delegate, to receive menuNeedsUpdate messages, so
    // we can update our menu as needed/before it's drawn
    [_vlcStatusBarIconMenu setDelegate:self];

    // Register notifications
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(updateNowPlayingInfo)
                                                 name:VLCInputChangedNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(configurationChanged:)
                                                 name:VLCConfigurationChangedNotification
                                               object:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if ([keyPath isEqualToString: NSStringFromSelector(@selector(isVisible))]) {
        bool isVisible = [[change objectForKey:NSKeyValueChangeNewKey] boolValue];

        // Sync status bar visibility with VLC setting
        msg_Dbg(getIntf(), "Status bar icon visibility changed to %i", isVisible);
        config_PutInt(getIntf(), "macosx-statusicon", isVisible ? 1 : 0);
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (void)configurationChanged:(id)obj
{
    if (var_InheritBool(getIntf(), "macosx-statusicon"))
        [self enableMenuIcon];
    else
        [self disableStatusItem];
}

/* Enables the Status Bar Item and initializes it's image
 * and context menu
 */
- (void)enableMenuIcon
{
    if (!self.statusItem) {
        // Init the status item
        self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
        [self.statusItem setHighlightMode:YES];
        [self.statusItem setEnabled:YES];

        // Set the status item image
        NSImage *menuIcon = [NSImage imageNamed:@"VLCStatusBarIcon"];
        [menuIcon setTemplate:YES];
        [self.statusItem setImage:menuIcon];

        // Attach pull-down menu
        [self.statusItem setMenu:_vlcStatusBarIconMenu];

        // Visibility is 10.12+
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        if (OSX_SIERRA_AND_HIGHER) {
            [self.statusItem setBehavior:NSStatusItemBehaviorRemovalAllowed];
            [self.statusItem setAutosaveName:@"statusBarItem"];
            [self.statusItem addObserver:self forKeyPath:NSStringFromSelector(@selector(isVisible))
                                 options:NSKeyValueObservingOptionNew context:NULL];
        }
    }

    if (OSX_SIERRA_AND_HIGHER) {
        // Sync VLC setting with status bar visibility setting (10.12 runtime only)
        [self.statusItem setVisible:YES];
    }
}

- (void)disableStatusItem
{
    if (!self.statusItem)
        return;

    // Lets keep alive the object in Sierra, and destroy it in older OS versions
    if (OSX_SIERRA_AND_HIGHER) {
        self.statusItem.visible = NO;
    } else {
        [[NSStatusBar systemStatusBar] removeStatusItem:self.statusItem];
        self.statusItem = nil;
    }
#pragma clang diagnostic pop
}

- (void)dealloc
{
    if (self.statusItem && [self.statusItem respondsToSelector:@selector(isVisible)]) {
        [self.statusItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(isVisible)) context:NULL];
    }

    // Cleanup
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark -
#pragma mark Event callback functions

/* Menu update delegate
 * Called before menu is opened/displayed
 */
- (void)menuNeedsUpdate:(NSMenu *)menu
{
    [self updateMetadata];
    [self updateMenuItemRandom];
    [self updateDynamicMenuItemText];
}

/* This is called whenever the playback status for VLC changes and here
 * we can update our information in the menu/view
 */
- (void) updateNowPlayingInfo
{
    [self updateMetadata];
    [self updateProgress];
    [self updateDynamicMenuItemText];
}

/* Callback to update current playback time
 * Called by InputManager
 */
- (void)updateProgress
{
    input_thread_t *input = pl_CurrentInput(getIntf());

    if (input) {
        NSString *elapsedTime;
        NSString *remainingTime;
        NSString *totalTime;

        /* Get elapsed and remaining time */
        elapsedTime = [[VLCStringUtility sharedInstance] getCurrentTimeAsString:input negative:NO];
        remainingTime = [[VLCStringUtility sharedInstance] getCurrentTimeAsString:input negative:YES];

        /* Check item duration */
        vlc_tick_t dur = input_item_GetDuration(input_GetItem(input));

        if (dur == -1) {
            /* Unknown duration, possibly due to buffering */
            [progressField setStringValue:@"--:--"];
            [totalField setStringValue:@"--:--"];
        } else if (dur == 0) {
            /* Infinite duration */
            [progressField setStringValue:elapsedTime];
            [totalField setStringValue:@"∞"];
        } else {
            /* Not unknown, update displayed duration */
            totalTime = [[VLCStringUtility sharedInstance] stringForTime:(dur/1000000)];
            [progressField setStringValue:(showTimeElapsed) ? elapsedTime : remainingTime];
            [totalField setStringValue:totalTime];
        }
        [self setStoppedStatus:NO];
        vlc_object_release(input);
    } else {
        /* Nothing playing */
        [progressField setStringValue:@"--:--"];
        [totalField setStringValue:@"--:--"];
        [self setStoppedStatus:YES];
    }
}


#pragma mark -
#pragma mark Update functions

/* Updates the Metadata for the currently
 * playing item or resets it if nothing is playing
 */
- (void)updateMetadata
{
    NSImage         *coverArtImage;
    NSString        *title;
    NSString        *nowPlaying;
    NSString        *artist;
    NSString        *album;
    input_thread_t  *input = pl_CurrentInput(getIntf());
    input_item_t    *item  = NULL;

    // Update play/pause status
    switch ([self getPlaylistPlayStatus]) {
        case PLAYLIST_RUNNING:
            [self setStoppedStatus:NO];
            [self setProgressTimeEnabled:YES];
            [pathActionItem setEnabled:YES];
            _currentPlaybackUrl = [[[VLCCoreInteraction sharedInstance]
                                    URLOfCurrentPlaylistItem] absoluteString];
            break;
        case PLAYLIST_STOPPED:
            [self setStoppedStatus:YES];
            [self setProgressTimeEnabled:NO];
            [pathActionItem setEnabled:NO];
            _currentPlaybackUrl = nil;
            break;
        case PLAYLIST_PAUSED:
            [self setStoppedStatus:NO];
            [self setProgressTimeEnabled:YES];
            [pathActionItem setEnabled:YES];
            _currentPlaybackUrl = [[[VLCCoreInteraction sharedInstance]
                                    URLOfCurrentPlaylistItem] absoluteString];
            [playPauseButton setState:NSOffState];
        default:
            break;
    }

    if (input) {
        item = input_GetItem(input);
    }

    if (item) {
        /* Something is playing */
        static char *tmp_cstr = NULL;

        // Get Coverart
        tmp_cstr = input_item_GetArtworkURL(item);
        if (tmp_cstr) {
            NSString *tempStr = toNSStr(tmp_cstr);
            if (![tempStr hasPrefix:@"attachment://"]) {
                coverArtImage = [[NSImage alloc]
                                 initWithContentsOfURL:[NSURL URLWithString:tempStr]];
            }
            FREENULL(tmp_cstr);
        }

        // Get Titel
        tmp_cstr = input_item_GetTitleFbName(item);
        if (tmp_cstr) {
            title = toNSStr(tmp_cstr);
            FREENULL(tmp_cstr);
        }

        // Get Now Playing
        tmp_cstr = input_item_GetNowPlaying(item);
        if (tmp_cstr) {
            nowPlaying = toNSStr(tmp_cstr);
            FREENULL(tmp_cstr);
        }

        // Get author
        tmp_cstr = input_item_GetArtist(item);
        if (tmp_cstr) {
            artist = toNSStr(tmp_cstr);
            FREENULL(tmp_cstr);
        }

        // Get album
        tmp_cstr = input_item_GetAlbum(item);
        if (tmp_cstr) {
            album = toNSStr(tmp_cstr);
            FREENULL(tmp_cstr);
        }
    } else {
        /* Nothing playing */
        title = _NS("VLC media player");
        artist = _NS("Nothing playing");
    }

    // Set fallback coverart
    if (!coverArtImage) {
        coverArtImage = [NSImage imageNamed:@"noart.png"];
    }

    // Hack to show now playing for streams (ICY)
    if (nowPlaying && !artist) {
        artist = nowPlaying;
    }

    // Set the metadata in the UI
    [self setMetadataTitle:title artist:artist album:album andCover:coverArtImage];

    // Cleanup
    if (input)
        vlc_object_release(input);
}



// Update dynamic copy/open menu item status
- (void)updateDynamicMenuItemText
{
    if (!_currentPlaybackUrl) {
        [pathActionItem setTitle:_NS("Path/URL Action")];
        return;
    }

    NSURL *itemURI = [NSURL URLWithString:_currentPlaybackUrl];

    if ([itemURI.scheme isEqualToString:@"file"]) {
        [pathActionItem setTitle:_NS("Select File In Finder")];
    } else {
        [pathActionItem setTitle:_NS("Copy URL to clipboard")];
    }
}

// Update the random menu item status
- (void)updateMenuItemRandom
{
    // Get current random status
    bool random;
    playlist_t *playlist = pl_Get(getIntf());
    random = var_GetBool(playlist, "random");

    [randButton setState:(random) ? NSOnState : NSOffState];
}



#pragma mark -
#pragma mark Utility functions

/* Update the UI to the specified metadata
 * Any of the values can be nil and will be replaced with empty strings
 * or no cover Image at all
 */
- (void)setMetadataTitle:(NSString *)title
                  artist:(NSString *)artist
                   album:(NSString *)album
                andCover:(NSImage *)cover
{
    [titleField setStringValue:(title) ? title : @""];
    [artistField setStringValue:(artist) ? artist : @""];
    [albumField setStringValue:(album) ? album : @""];
    [coverImageView setImage:cover];
}

// Set the play/pause menu item status
- (void)setStoppedStatus:(BOOL)stopped
{
    isStopped = stopped;
    if (stopped) {
        [playPauseButton setState:NSOffState];
    } else {
        [playPauseButton setState:NSOnState];
    }
}

- (void)setProgressTimeEnabled:(BOOL)enabled
{
    [progressField setEnabled:enabled];
    [separatorField setEnabled:enabled];
    [totalField setEnabled:enabled];
}


/* Returns VLC playlist status
 * Check for constants:
 *   PLAYLIST_RUNNING, PLAYLIST_STOPPED, PLAYLIST_PAUSED
 */
- (int)getPlaylistPlayStatus
{
    int res;
    playlist_t *p_playlist = pl_Get(getIntf());

    PL_LOCK;
    res = playlist_Status( p_playlist );
    PL_UNLOCK;

    return res;
}


#pragma mark -
#pragma mark Menu item Actions

/* Action: Select the currently playing file in Finder
 *         or in case of a network stream, copy the URL
 */
- (IBAction)copyOrOpenCurrentPlaybackItem:(id)sender
{
    // If nothing playing, there is nothing to do
    if (!_currentPlaybackUrl) {
        return;
    }

    // Check if path or URL
    NSURL *itemURI = [NSURL URLWithString:_currentPlaybackUrl];

    if ([itemURI.scheme isEqualToString:@"file"]) {
        // Local file, open in Finder
        [[NSWorkspace sharedWorkspace] selectFile:itemURI.path
                         inFileViewerRootedAtPath:itemURI.path];
    } else {
        // URL, copy to pasteboard
        NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
        [pasteboard clearContents];
        [pasteboard setString:_currentPlaybackUrl forType:NSPasteboardTypeString];
    }
}

// Action: Show VLC main window
- (IBAction)restoreMainWindow:(id)sender
{
    [[VLCApplication sharedApplication] activateIgnoringOtherApps:YES];
    [[[VLCMain sharedInstance] mainWindow] makeKeyAndOrderFront:sender];
}

// Action: Toggle Play / Pause
- (IBAction)statusBarIconTogglePlayPause:(id)sender
{
    [[VLCCoreInteraction sharedInstance] playOrPause];
}

// Action: Stop playback
- (IBAction)statusBarIconStop:(id)sender
{
    [[VLCCoreInteraction sharedInstance] stop];
}

// Action: Go to next track
- (IBAction)statusBarIconNext:(id)sender
{
    [[VLCCoreInteraction sharedInstance] next];
}

// Action: Go to previous track
- (IBAction)statusBarIconPrevious:(id)sender
{
    [[VLCCoreInteraction sharedInstance] previous];
}

// Action: Toggle random playback (shuffle)
- (IBAction)statusBarIconToggleRandom:(id)sender
{
    [[VLCCoreInteraction sharedInstance] shuffle];
}

// Action: Toggle between elapsed and remaining time
- (IBAction)toggelProgressTime:(id)sender
{
    showTimeElapsed = (!showTimeElapsed);
}

// Action: Quit VLC
- (IBAction)quitAction:(id)sender
{
    [[NSApplication sharedApplication] terminate:nil];
}

@end
