//
// © 2018-2020 Qualcomm Technologies, Inc. and/or its subsidiaries. All rights reserved.
//

#import "IoTPlayer+CategoriesHeader.h"

@implementation IoTPlayer(Playlist)

- (int)indexPlaying
{
    // Work around bug
    if ((self.state.playlistState.size == 0) ||
        (self.activeInputSelector.type != IoTInputOutputSelectorTypeNone)) {
        return -1;
    }

    IoTQueuedItemAttr *attr = [self.state.mediaPlayerAttr.playState.queuedItems firstObject];
    return attr ? attr.index : -1;
}

- (IoTMediaItem *)currentItem
{
    IoTQueuedItemAttr *attr = [self.state.mediaPlayerAttr.playState.queuedItems firstObject];

    BOOL alwaysShowSource = (self.activeInputSelector.type != IoTInputOutputSelectorTypeNone) &&
    	(self.activeInputSelector.type != IoTInputOutputSelectorTypeBluetooth);

    BOOL showBluetoothSource = (self.activeInputSelector.type == IoTInputOutputSelectorTypeBluetooth) &&
    	(self.playState == IoTPlayStateStopped);
    if (alwaysShowSource || showBluetoothSource) {
        IoTMediaItem *mediaItem = [[IoTMediaItem alloc] init];
        mediaItem.title = [self.activeInputSelector.name uppercaseString];
        mediaItem.artist = @" ";
        return mediaItem;
    }

    // Work around bug
    if (self.state.playlistState.size == 0 && (attr.index >= 0)) {
        // If we have an empty playlist and an index >= 0 then then the state is wrong.
        return [[IoTMediaItem alloc] init];
    }

    if (self.state.playlistState.size == 0 && (self.playState == IoTPlayStateStopped)) {
        // If we have an empty playlist and the state goes to stopped with an -1 index we are either
        // transitioning songs from another source or we genuinely are stopped. In the later case we
        // need to return empty as otherwise stale meta data may be presented indefinitely.
        return [[IoTMediaItem alloc] init];
    }

	return attr ? [[IoTMediaItem alloc] initWithAttr:attr.playItem] : [[IoTMediaItem alloc] init];
}

- (BOOL)isLoopModeEnabled
{
    RETURN_GETTER(BOOL, mediaPlayerAttr, enabledControls.loopMode, NO)
}

- (BOOL)isShuffleModeEnabled
{
    RETURN_GETTER(BOOL, mediaPlayerAttr, enabledControls.shuffleMode, NO)
}

-(IoTLoopMode)loopMode
{
    RETURN_GETTER(IoTLoopMode, mediaPlayerAttr, loopMode, IoTLoopModeNone)
}

- (IoTShuffleMode)shuffleMode
{
    RETURN_GETTER(IoTShuffleMode, mediaPlayerAttr, shuffleMode, IoTShuffleModeLinear)
}

- (void)setShuffleMode:(IoTShuffleMode)mode
            completion:(void(^)(BOOL, NSError *))completion
       completionQueue:(dispatch_queue_t)queue
{
	SETTER(mediaPlayerAttr,shuffleMode,mode,postMediaPlayerWith)
}

- (void)setLoopMode:(IoTLoopMode)mode
         completion:(void(^)(BOOL, NSError *))completion
    completionQueue:(dispatch_queue_t)queue
{
    SETTER(mediaPlayerAttr,loopMode,mode,postMediaPlayerWith)
}

- (BOOL)isPlaylistInterfaceSupported
{
    BOOL ret = self.state.playlistState.playlistSupported;
    return ret;
}

- (NSString *)playlistSnapshotID
{
    if (![self isPlaylistInterfaceSupported]) {
        return @"";
    }

    return self.state.playlistState.snapshotID;
}

- (NSString *)playlistControllerType
{
    if (![self isPlaylistInterfaceSupported]) {
        return @"";
    }

    return self.state.playlistState.ownerInfo;
}

- (NSString *)playlistUserData
{
    if (![self isPlaylistInterfaceSupported]) {
        return @"";
    }

    return self.state.playlistState.userData;
}

- (uint)playlistSize
{
    if (![self isPlaylistInterfaceSupported]) {
        return 0;
    }

    return self.state.playlistState.size;
}

- (void)getItemsInRange:(NSRange)range
             completion:(void(^)(BOOL, NSArray<IoTMediaItem *> *,NSError *))completion
        completionQueue:(dispatch_queue_t)queue
{
    BOOL canDispatchCompletion = completion && queue;
    if (![self isPlaylistInterfaceSupported]) {
        if (canDispatchCompletion) {
            dispatch_async(queue, ^{
                completion(NO, @[], [NSError errorWithIoTError:IoTErrorNotSupported description:nil]);
            });
        }
        return;
    }

    if (range.length == 0) {
        if (canDispatchCompletion) {
            dispatch_async(queue, ^{
                completion(YES, @[], nil);
            });
        }
        return;
    }

    IoTPlaylistGetRangeInAttr *getAttr = [[IoTPlaylistGetRangeInAttr alloc] initWithStateSyncQueue:self.stateSyncQueue];

    getAttr.snapshotId = self.state.playlistState.snapshotID;
    getAttr.count = (int)range.length;
    getAttr.start = (int)range.location;
    NSLog (@"**** Requesting Range: %d length %d",getAttr.start, getAttr.count);

    BOOL success = [self.allPlayController postPlaylistGetRangeWith:getAttr
                                                        completion:^(IoTPlaylistGetRangeOutAttr *result, BOOL success) {
                                                            if (canDispatchCompletion) {
                                                                dispatch_async(queue, ^{
                                                                    if (success) {
                                                                        NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:result.itemsInRange.count];
                                                                        NSLog(@"**** Got %lu items", (unsigned long)result.itemsInRange.count);
                                                                        for (IoTPlayItemAttr *playItem in result.itemsInRange) {
                                                                            IoTMediaItem *item = [[IoTMediaItem alloc] initWithAttr:playItem];
                                                                            NSLog(@"Got item: %@", item.title);
                                                                            [items addObject:item];
                                                                        }
                                                                        completion(YES, [items copy], nil);
                                                                    } else {
                                                                        completion(NO, @[], [NSError errorWithIoTError:IoTErrorRequest description:nil]);
                                                                    }
                                                                });
                                                            }
                                                        } completionQueue:self.backgroundQueue];

    if (!success && canDispatchCompletion) {
        dispatch_async(queue, ^{
            completion(NO, @[], [NSError errorWithIoTError:IoTErrorRequest description:nil]);
        });
    }
}

- (void)addMediaItem:(IoTMediaItem *)mediaItem
             atIndex:(int)index
                play:(BOOL)play
            userData:(NSString *)userData
          completion:(void(^)(BOOL, NSError *))completion
     completionQueue:(dispatch_queue_t)queue
{
    [self addMediaItems:@[mediaItem] atIndex:index play:play userData:userData completion:completion completionQueue:queue];
}

- (void)addMediaItems:(NSArray *)mediaItems
              atIndex:(int)index
                 play:(BOOL)play
             userData:(NSString *)userData
           completion:(void(^)(BOOL, NSError *))completion
      completionQueue:(dispatch_queue_t)queue
{
    BOOL canDispatchCompletion = completion && queue;
    if (![self isPlaylistInterfaceSupported]) {
        if (canDispatchCompletion) {
            dispatch_async(queue, ^{
                completion(NO, [NSError errorWithIoTError:IoTErrorNotSupported description:nil]);
            });
        }
        return;
    }

    if (index > [self playlistSize]) {
        index = [self playlistSize];
    }

    // Firstly update the playlist data if needed

    BOOL alreadyOwned = ([self.state.playlistState.userData isEqualToString:userData] &&
                         [self.state.playlistState.ownerInfo isEqualToString:self.applicationName]);

    makeWeak(self)
    void(^performUploadBlock)(BOOL) = ^(BOOL success){
        makeStrongAndReturnIfNil(self)

        self.state.playlistState.userData = userData;
        self.state.playlistState.ownerInfo = self.applicationName;

        NSMutableArray *transformedItems = [[NSMutableArray alloc] initWithCapacity:mediaItems.count];
        [mediaItems enumerateObjectsUsingBlock:^(IoTMediaItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            IoTPlayItemAttr *attr = [[IoTPlayItemAttr alloc] initWithStateSyncQueue:self.stateSyncQueue];
            [obj copyToAttr:attr];
            [transformedItems addObject:attr];
        }];

        NSLog (@"**** We have %lu transformed objects", (unsigned long)transformedItems.count);
        __block BOOL firstAddition = YES;
        __block int currentIndex = index;
        __block void(^pushItemsBlock)(void); // We need __block as block is recursive.
        __block void(^pushItemsBlockInside)(void); // We need __block as block is recursive.

        pushItemsBlock = [^{
            IoTPlaylistInsertInAttr *insertAttr = [[IoTPlaylistInsertInAttr alloc] initWithStateSyncQueue:self.stateSyncQueue];
            NSLog (@"**** Attempt to insert %lu items at position %d", (unsigned long)transformedItems.count, index);
            insertAttr.playlistItems = transformedItems;
            insertAttr.position = index;
            insertAttr.snapshotId = self.state.playlistState.snapshotID;

            BOOL success = [self.allPlayController postPlaylistInsertWith:insertAttr
                                                               completion:^(IoTPlaylistInsertOutAttr * attr, BOOL success) {
                                                                   makeStrongAndReturnIfNil(self)
                                                                   if (success) {

                                                                       self.state.playlistState.size = self.state.playlistState.size + attr.count;
                                                                       self.state.playlistState.snapshotID = attr.snapshotId;


                                                                       if (firstAddition && play) {
                                                                           [self playItemAtIndex:index completion:nil completionQueue:nil];
                                                                           firstAddition = NO;
                                                                       }
                                                                       NSLog(@"**** Inserted: %d", attr.count);
                                                                       currentIndex += attr.count;

                                                                       if ((attr.count == transformedItems.count) || (attr.truncated)) {
                                                                           if (canDispatchCompletion) {
                                                                               dispatch_async(queue, ^{
                                                                                   completion(YES, nil);
                                                                               });
                                                                           }
                                                                           return;
                                                                       }

                                                                       [transformedItems removeObjectsInRange:NSMakeRange(0, attr.count)]; // trim the used items
                                                                       pushItemsBlockInside();
                                                                   } else {
                                                                       [self getPlaylistSizeAndSnapshotIDWithCompletion:^(BOOL success) {
                                                                           if (canDispatchCompletion) {
                                                                               dispatch_async(queue, ^{
                                                                                   // Earlier setp failed so always a failure.
                                                                                   completion(NO, [NSError errorWithIoTError:IoTErrorRequest description:nil]);
                                                                               });
                                                                           }
                                                                       } completionQueue:self.backgroundQueue];
                                                                   }
                                                               } completionQueue:self.backgroundQueue];
            if (!success && canDispatchCompletion) {
                dispatch_async(queue, ^{
                    completion(NO, [NSError errorWithIoTError:IoTErrorRequest description:nil]);
                });
            }
        } copy]; // The copy is required due to stack vs heap shenanigans.. http://ddeville.me/2011/10/recursive-blocks-objc

        pushItemsBlockInside = pushItemsBlock;
        pushItemsBlock();
    };

    if (!alreadyOwned) {
        IoTPlaylistAttr *attr = [[IoTPlaylistAttr alloc] initWithStateSyncQueue:self.stateSyncQueue];
        attr.userData = userData;
        attr.ownerInfo = self.applicationName;
        BOOL success = [self.allPlayController postPlaylistWith:attr
                                                     completion:performUploadBlock
                                                completionQueue:self.backgroundQueue];

        if (!success && canDispatchCompletion) {
            dispatch_async(queue, ^{
                completion(NO, [NSError errorWithIoTError:IoTErrorRequest description:nil]);
            });
        }
    } else {
        NSLog (@"Not setting owner");
        dispatch_async(self.backgroundQueue, ^{
            performUploadBlock(YES);
        });
    }
}

- (void)removeMediaItemsInRange:(NSRange)range
                     completion:(void(^)(BOOL, NSError *))completion
                completionQueue:(dispatch_queue_t)queue
{
    IoTPlaylistDeleteInAttr *deleteAttr = [[IoTPlaylistDeleteInAttr alloc] initWithStateSyncQueue:self.stateSyncQueue];
    deleteAttr.start = (int)range.location;
    deleteAttr.count = (int)range.length;
    deleteAttr.snapshotId = self.state.playlistState.snapshotID;

    BOOL success = [self.allPlayController postPlaylistDeleteWith:deleteAttr
                                                      completion:^(IoTPlaylistDeleteOutAttr *result, BOOL success) {
                                                          // The result only has ID not size
                                                          [self getPlaylistSizeAndSnapshotIDWithCompletion:^(BOOL success2) {
                                                              if (completion && queue) {
                                                                  BOOL overallSuccess = success && success2;
                                                                  dispatch_async(queue, ^{
                                                                      completion(overallSuccess, overallSuccess ? nil : [NSError errorWithIoTError:IoTErrorRequest description:nil]);
                                                                  });
                                                              }
                                                          } completionQueue:self.backgroundQueue];
                                                      } completionQueue:self.backgroundQueue];

    if (!success && completion && queue) {
        dispatch_async(queue, ^{
            completion(NO, [NSError errorWithIoTError:IoTErrorRequest description:nil]);
        });
    }
}

- (void)moveMediaItemsInRange:(NSRange)range
                     position:(int)position
                   completion:(void(^)(BOOL, NSError *))completion
              completionQueue:(dispatch_queue_t)queue
{
    IoTPlaylistMoveInAttr *moveAttr = [[IoTPlaylistMoveInAttr alloc] initWithStateSyncQueue:self.stateSyncQueue];
    moveAttr.start = (int)range.location;
    moveAttr.count = (int)range.length;
    moveAttr.position = position;
    moveAttr.snapshotId = self.state.playlistState.snapshotID;

    BOOL success = [self.allPlayController postPlaylistMoveWith:moveAttr
                                                    completion:^(IoTPlaylistMoveOutAttr *result, BOOL success) {
                                                        // The result only has ID not size
                                                        if (success) {
                                                            self.state.playlistState.snapshotID = result.snapshotId;
                                                            if (completion && queue) {
                                                                dispatch_async(queue, ^{
                                                                    completion(YES, nil);
                                                                });
                                                            }
                                                        } else {
                                                            [self getPlaylistSizeAndSnapshotIDWithCompletion:^(BOOL success) {
                                                                if (completion && queue) {
                                                                    dispatch_async(queue, ^{
                                                                        // Failed earlier so ignore success here.
                                                                        completion(NO, [NSError errorWithIoTError:IoTErrorRequest description:nil]);
                                                                    });
                                                                }
                                                            } completionQueue:self.backgroundQueue];
                                                        }
                                                    } completionQueue:self.backgroundQueue];

    if (!success && completion && queue) {
        dispatch_async(queue, ^{
            completion(NO, [NSError errorWithIoTError:IoTErrorRequest description:nil]);
        });
    }
}

- (void)clearWithCompletion:(void(^)(BOOL, NSError *))completion
            completionQueue:(dispatch_queue_t)queue
{
    int size = self.state.playlistState.size;
    [self removeMediaItemsInRange:NSMakeRange(0, size) completion:completion completionQueue:queue];
}

@end
