/*  HBQTKitPlayer.m $

 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 "HBQTKitPlayer.h"

#if __HB_QTKIT_PLAYER_AVAILABLE

#import <QTKit/QTKit.h>

@import HandBrakeKit;

@interface QTTrack (HBAdditions)
- (id)isoLanguageCodeAsString;
@end

typedef void (^HBPeriodicObverser)(NSTimeInterval time);
typedef void (^HBRateObverser)(void);
typedef void (^HBPlayableObverser)(void);

@interface HBQTKitPlayerPeriodicObserver : NSObject

@property (nonatomic, copy) HBPeriodicObverser block;

- (void)postNotification:(NSTimeInterval)time;

@end

@implementation HBQTKitPlayerPeriodicObserver

- (void)postNotification:(NSTimeInterval)time
{
    self.block(time);
}

@end

@interface HBQTKitPlayerRateObserver : NSObject

@property (nonatomic, copy) HBRateObverser block;

- (void)postNotification;

@end

@implementation HBQTKitPlayerRateObserver

- (void)postNotification;
{
    self.block();
}

@end

@interface HBQTKitPlayer ()

@property (nonatomic, strong) QTMovie *movie;
@property (nonatomic, strong) NSTimer *timer;

@property (nonatomic, readwrite, getter=isPlayable) BOOL playable;
@property (nonatomic, readwrite, getter=isLoaded) BOOL loaded;

@property (nonatomic, strong) NSMutableSet<HBQTKitPlayerPeriodicObserver *> *periodicObservers;
@property (nonatomic, strong) NSMutableSet<HBQTKitPlayerRateObserver *> *rateObservers;

@property (nonatomic, strong) NSMutableSet<HBPlayableObverser> *playableObservers;

@end

@implementation HBQTKitPlayer

- (instancetype)initWithURL:(NSURL *)url
{
    self = [super init];

    if (self)
    {
        NSError *outError;
        NSDictionary *attributes = @{ QTMovieURLAttribute: url,
                                      QTMovieAskUnresolvedDataRefsAttribute: @NO,
                                      QTMovieOpenForPlaybackAttribute: @YES,
                                      QTMovieIsSteppableAttribute: @YES,
                                      QTMovieOpenAsyncRequiredAttribute: @NO,
                                      QTMovieOpenAsyncOKAttribute: @NO,
                                      QTMovieApertureModeAttribute: QTMovieApertureModeClean };

        _movie = [[QTMovie alloc] initWithAttributes:attributes error:&outError];
        
        if (_movie)
        {
            _movie.delegate = self;

            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(_movieRateDidChange:)
                                                         name:QTMovieRateDidChangeNotification
                                                       object:_movie];

            _layer = [QTMovieLayer layerWithMovie:_movie];
        }

        _periodicObservers = [NSMutableSet set];
        _rateObservers = [NSMutableSet set];
        _playableObservers = [NSMutableSet set];

        // Can't open things async
        // because of 23414 QTKit bugs.
        if (_movie && _layer)
        {
            self.playable = YES;
            [self _enableSubtitles];
        }
        else
        {
            self.playable = NO;
        }

        self.loaded = YES;
    }

    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [self _stopMovieTimer];
}

- (void)setLoaded:(BOOL)loaded
{
    _loaded = loaded;

    for (HBPlayableObverser block in self.playableObservers)
    {
        block();
    }
    [self.playableObservers removeAllObjects];
}

- (void)_movieRateDidChange:(NSNotification *)notification
{
    for (HBQTKitPlayerRateObserver *observer in self.rateObservers)
    {
        [observer postNotification];
    }
}

- (void)_enableSubtitles
{
    // Get and enable subtitles
    NSArray<QTTrack *> *subtitlesArray = [self.movie tracksOfMediaType:QTMediaTypeSubtitle];
    if (subtitlesArray.count)
    {
        // enable the first tx3g subtitle track
        [subtitlesArray.firstObject setEnabled:YES];
    }
    else
    {
        // Perian subtitles
        subtitlesArray = [self.movie tracksOfMediaType:QTMediaTypeVideo];
        if (subtitlesArray.count >= 2)
        {
            // track 0 should be video, other video tracks should
            // be subtitles; force-enable the first subs track
            [subtitlesArray[1] setEnabled:YES];
        }
    }
}

- (void)_startMovieTimer
{
    if (!self.timer)
    {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.09 target:self
                                                         selector:@selector(_timerFired:)
                                                         userInfo:nil repeats:YES];
    }
}

- (void)_stopMovieTimer
{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)_timerFired:(NSTimer *)timer
{
    for (HBQTKitPlayerPeriodicObserver *observer in self.periodicObservers)
    {
        [observer postNotification:self.currentTime];
    }
}

#pragma mark Public properties

- (NSArray<HBPlayerTrack *> *)tracksWithMediaType:(NSString *)mediaType
{
    NSMutableArray *result = [NSMutableArray array];
    NSArray<QTTrack *> *tracks = [self.movie tracksOfMediaType:mediaType];
    for (QTTrack *track in tracks)
    {
        NSNumber *trackID = [track attributeForKey:QTTrackIDAttribute];
        NSString *name = NSLocalizedString(@"Unknown", nil);

        if ([track respondsToSelector:@selector(isoLanguageCodeAsString)])
        {
            NSString *language = [track isoLanguageCodeAsString];
            name = [HBUtilities languageCodeForIso6392Code:language];
        }
        BOOL enabled = [[track attributeForKey:QTTrackEnabledAttribute] boolValue];

        HBPlayerTrack *playerTrack = [[HBPlayerTrack alloc] initWithTrackName:name object:trackID enabled:enabled];

        [result addObject:playerTrack];
    }
    return result;
}

@synthesize audioTracks = _audioTracks;

- (NSArray<HBPlayerTrack *> *)audioTracks
{
    if (_audioTracks == nil)
    {
        _audioTracks = [self tracksWithMediaType:QTMediaTypeSound];
    }
    return _audioTracks;
}

@synthesize subtitlesTracks = _subtitlesTracks;

- (NSArray<HBPlayerTrack *> *)subtitlesTracks
{
    if (_subtitlesTracks == nil)
    {
        _subtitlesTracks = [self tracksWithMediaType:QTMediaTypeSubtitle];
    }
    return _subtitlesTracks;
}

- (void)_enableTrack:(HBPlayerTrack *)playerTrack mediaType:(NSString *)mediaType
{
    NSArray<QTTrack *> *tracks = [self.movie tracksOfMediaType:mediaType];
    for (QTTrack *track in tracks)
    {
        NSNumber *trackID = [track attributeForKey:QTTrackIDAttribute];

        if ([trackID isEqualTo:playerTrack.representedObject])
        {
            [track setEnabled:YES];
        }
        else
        {
            [track setEnabled:NO];
        }
    }
}

- (void)enableAudioTrack:(HBPlayerTrack *)playerTrack
{
    for (HBPlayerTrack *track in self.audioTracks)
    {
        track.enabled = NO;
    }
    playerTrack.enabled = YES;
    [self _enableTrack:playerTrack mediaType:QTMediaTypeSound];
}

- (void)enableSubtitlesTrack:(HBPlayerTrack *)playerTrack
{
    for (HBPlayerTrack *track in self.subtitlesTracks)
    {
        track.enabled = NO;
    }
    playerTrack.enabled = YES;
    [self _enableTrack:playerTrack mediaType:QTMediaTypeSubtitle];
}

- (NSTimeInterval)duration
{
    QTTime duration = [self.movie duration];
    return (double)duration.timeValue / (double)duration.timeScale;;
}

- (NSTimeInterval)currentTime
{
    QTTime time = [self.movie currentTime];
    return (double)time.timeValue / (double)time.timeScale;;
}

- (void)setCurrentTime:(NSTimeInterval)value
{
    long timeScale = [[self.movie attributeForKey:QTMovieTimeScaleAttribute] longValue];
    [self.movie setCurrentTime:QTMakeTime((long long)value * timeScale, timeScale)];
    [self _timerFired:nil];
}

- (void)setRate:(float)rate
{
    self.movie.rate = rate;
}

- (float)rate
{
    return self.movie.rate;
}

- (float)volume
{
    return self.movie.volume;
}

- (void)setVolume:(float)volume
{
    self.movie.volume = volume;
}

#pragma mark public methods

- (void)loadPlayableValueAsynchronouslyWithCompletionHandler:(nullable void (^)(void))handler;
{
    if (self.isLoaded)
    {
        handler();
    }
    else
    {
        [self.playableObservers addObject:handler];
    }
}

- (id)addPeriodicTimeObserverUsingBlock:(void (^)(NSTimeInterval time))block
{
    HBQTKitPlayerPeriodicObserver *observer = [[HBQTKitPlayerPeriodicObserver alloc] init];
    observer.block = block;
    [self.periodicObservers addObject:observer];

    [self _startMovieTimer];

    return observer;
}

- (void)removeTimeObserver:(id)observer
{
    [self.periodicObservers removeObject:observer];
}

- (id)addRateObserverUsingBlock:(void (^)(void))block
{
    HBQTKitPlayerRateObserver *observer = [[HBQTKitPlayerRateObserver alloc] init];
    observer.block = block;
    [self.rateObservers addObject:observer];

    return observer;
}

- (void)removeRateObserver:(id)observer
{
    [self.rateObservers removeObject:observer];
}

- (void)play
{
    [self.movie play];
    [self _startMovieTimer];
}

- (void)pause
{
    [self.movie stop];
    [self _stopMovieTimer];
}

- (void)gotoBeginning
{
    [self.movie gotoBeginning];
    [self _timerFired:nil];
}

- (void)gotoEnd
{
    [self.movie gotoEnd];
    [self _timerFired:nil];
}

- (void)stepForward
{
    [self.movie stepForward];
    [self _timerFired:nil];
}

- (void)stepBackward
{
    [self.movie stepBackward];
    [self _timerFired:nil];
}

@end

#endif
