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

#import <XCTest/XCTest.h>
#import "TestCategories.h"
#import "IoTPlayer+Private.h"
#import "IoTAllPlayController.h"
#import "IoTServiceDiscoveryResult.h"
#import "MockTransport.h"
#import "IoTGlobals.h"
#import "IoTWrappedRepresentation.h"
#import "IoTMediaItem.h"
#import "IoTProperties.h"

#define LOCALHOST @"127.0.0.1"
#define DEVICE_ID @"0"
#define DEVICE_NAME @"MOCKED DEVICE"

#define MEDIA_PLAYER_URI @"/qti/allplay/mediaPlayer"
#define PLAYLIST_URI @"/qti/allplay/playlist"
#define PLAYLIST_GET_RANGE_URI @"/qti/allplay/playlist/playlistGetRange"



@interface IoTPlayerTests : XCTestCase

@property (nonatomic, strong) dispatch_queue_t syncQueue;
@property (nonatomic, strong) MockTransport *transport;
@property (nonatomic, strong) IoTServiceDiscoveryResult *discoveredService;
@property (nonatomic, strong) IoTPlayer *player;
@property (nonatomic, strong) IoTAllPlayController *allPlayController;

@end

@implementation IoTPlayerTests

- (void)setUp {
    [super setUp];
    self.discoveredService = [IoTServiceDiscoveryResult resultWithIdentifier:DEVICE_ID
                                                                        name:DEVICE_NAME
                                                                        host:LOCALHOST
                                                                        type:ALLPLAY_DEVICE_TYPE];
    self.transport = [[MockTransport alloc] init];
    self.syncQueue = dispatch_queue_create("com.qualcomm.qti.IoTPlayerTests", DISPATCH_QUEUE_CONCURRENT);
    self.allPlayController = [[IoTAllPlayController alloc] initWithServiceInfo:self.discoveredService
                                                                stateSyncQueue:self.syncQueue
                                                                     transport:self.transport];
    
    self.player = [[IoTPlayer alloc] initWithAllPlayController:self.allPlayController
                                               applicationName:@""
                                                stateSyncQueue:self.syncQueue];
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testPlayerNotifiesWhenCompletedUpdate {
    XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Wait for Player to Complete Update"];

    [self.allPlayController createResourceClientsForHost:LOCALHOST];
    [self.player updateWithCompletion:^(BOOL success){
        [expectation fulfill];
    } completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)];
    [self waitForExpectations:@[expectation] timeout:2];
}

- (void)testPlayerReturnsExpectedMediaPlayerValues {
    XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Wait for Player to Complete Update"];
    makeWeak(self)
    self.transport.dispatcherGetCallback = ^(MockDispatcher *dispatcher, IoTQueryParams params, IoTTransportDispatcherCompletion callback, dispatch_queue_t queue) {
        makeStrongAndReturnIfNil(self)
        if ([dispatcher.uri isEqualToString:MEDIA_PLAYER_URI]) {
            IoTMediaPlayerAttr *mediaPlayerAttr = [[IoTMediaPlayerAttr alloc] initWithStateSyncQueue:self.syncQueue];
            IoTEnabledControlsAttr *enabledControls = [[IoTEnabledControlsAttr alloc] initWithStateSyncQueue:self.syncQueue];
            enabledControls.loopMode = YES;
            enabledControls.next = NO;
            enabledControls.pause = YES;
            enabledControls.previous = NO;
            enabledControls.seek = YES;
            enabledControls.shuffleMode = NO;

            IoTPlayStateAttr *playState = [[IoTPlayStateAttr alloc] initWithStateSyncQueue:self.syncQueue];
            playState.playState = IoTPlayStatePaused;
            playState.positionMsecs = 100;

/*            IoTQueuedItemAttr *firstItem = [[IoTQueuedItemAttr alloc] initWithStateSyncQueue:self.syncQueue];
            IoTPlayItemAttr *firstPlayItem = [[IoTPlayItemAttr alloc] initWithStateSyncQueue:self.syncQueue];
            firstPlayItem.title = @"ITEM0";
            firstItem.index = 0;
            firstItem.playItem = firstPlayItem;

            IoTQueuedItemAttr *secondItem = [[IoTQueuedItemAttr alloc] initWithStateSyncQueue:self.syncQueue];
            IoTPlayItemAttr *secondPlayItem = [[IoTPlayItemAttr alloc] initWithStateSyncQueue:self.syncQueue];
            secondPlayItem.title = @"ITEM1";
            secondItem.index = 1;
            secondItem.playItem = secondPlayItem;
            NSArray <IoTQueuedItemAttr *> *queued = @[firstItem,secondItem];
            playState.queuedItems = queued;
*/
            mediaPlayerAttr.capabilities = @[@"audio"];
            mediaPlayerAttr.displayName = DEVICE_NAME;
            mediaPlayerAttr.enabledControls = enabledControls;
            mediaPlayerAttr.loopMode = IoTLoopModeOne;
            mediaPlayerAttr.playState = playState;
            mediaPlayerAttr.shuffleMode = IoTShuffleModeShuffle;
            IoTWrappedRepresentation *wrapped = [mediaPlayerAttr packAll];
            dispatch_async(queue, ^{
                callback(wrapped, 0);
            });
        } else {
            dispatch_async(queue, ^{
                callback([IoTWrappedRepresentation emptyWrapper], 0);
            });
        }
    };

    [self.allPlayController createResourceClientsForHost:LOCALHOST];

    [self.player updateWithCompletion:^(BOOL success){
        makeStrongAndReturnIfNil(self)
        XCTAssertEqualObjects(self.player.displayName, DEVICE_NAME, @"displayName not correct");
        XCTAssertTrue(self.player.isAudioSupported, @"Audio is supported");
        XCTAssertFalse(self.player.isVideoSupported, @"Video is not supported");
        XCTAssertFalse(self.player.isPhotoSupported, @"Photo is not supported");
        XCTAssertEqual(self.player.loopMode, IoTLoopModeOne, @"Incorrect loop mode");
        XCTAssertEqual(self.player.shuffleMode, IoTShuffleModeShuffle, @"Incorrect Shuffle Mode");

        XCTAssertTrue(self.player.isLoopModeEnabled, @"Loop mode should be enabled");
        XCTAssertTrue(self.player.isPauseEnabled, @"Pause should be enabled");
        XCTAssertTrue(self.player.isSeekEnabled, @"Seek should be enabled");
        XCTAssertFalse(self.player.isNextEnabled, @"Next should not be enabled");
        XCTAssertFalse(self.player.isPreviousEnabled, @"Previous should not be enabled");
        XCTAssertFalse(self.player.isShuffleModeEnabled, @"Shuffle should not be enabled");

        XCTAssertEqual(self.player.playState, IoTPlayStatePaused, @"Player should be paused");
        XCTAssertEqual(self.player.playerPosition, 100, @"Position should be 100");

        IoTMediaItem *current = self.player.currentItem;
        XCTAssertNotNil(current, @"currentitem should not be nil");
 //       XCTAssertFalse(current.isEmpty, @"currentItem should not be empty");
 //       XCTAssertEqualObjects(current.title, @"ITEM0", @"currentItem title incorrect");

        [expectation fulfill];
    } completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)];

    [self waitForExpectations:@[expectation] timeout:2];
}

- (void)testPlayerPostsMediaPlayerValuesChanges {
    XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Wait for Player to Complete Update"];
    makeWeak(self)

    __block int remaining = 2;
    self.transport.dispatcherPostCallback = ^(MockDispatcher *dispatcher, IoTQueryParams params, IoTWrappedRepresentation *wrapped, IoTTransportDispatcherCompletion callback, dispatch_queue_t queue) {
        makeStrongAndReturnIfNil(self)
        
        if ([dispatcher.uri isEqualToString:MEDIA_PLAYER_URI]) {
            NSLog(@"Posted");
            remaining --;
            if (remaining == 1) {
                XCTAssertEqualObjects([wrapped stringValueForKey:[NSString stringWithUTF8String:kShuffleMode]], @"linear", @"Shuffle mode not set as expected");
            } else if (remaining <= 0) {
                XCTAssertEqualObjects([wrapped stringValueForKey:[NSString stringWithUTF8String:kLoopMode]], @"one", @"Loop mode not set as expected");
                [expectation fulfill];
            }
        } else {
            dispatch_async(queue, ^{
                callback([IoTWrappedRepresentation emptyWrapper], 0);
            });
        }
    };

    [self.allPlayController createResourceClientsForHost:LOCALHOST];

    [self.player updateWithCompletion:^(BOOL success){
        makeStrongAndReturnIfNil(self)

        [self.player setShuffleMode:IoTShuffleModeLinear
                         completion:nil
                    completionQueue:nil];

        [self.player setLoopMode:IoTLoopModeOne
                            completion:nil
                       completionQueue:nil];

    } completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)];

    [self waitForExpectations:@[expectation] timeout:2];
}

- (void)testPlayerReturnsExpectedPlaylistValues {
    XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Wait for Player to Complete Update"];
    makeWeak(self)
    self.transport.dispatcherGetCallback = ^(MockDispatcher *dispatcher, IoTQueryParams params, IoTTransportDispatcherCompletion callback, dispatch_queue_t queue) {
        makeStrongAndReturnIfNil(self)
        if ([dispatcher.uri isEqualToString:PLAYLIST_URI]) {
            IoTPlaylistAttr *playlistAttr = [[IoTPlaylistAttr alloc] initWithStateSyncQueue:self.syncQueue];
            playlistAttr.userData = @"USERDATA";
            playlistAttr.ownerInfo = @"TESTS";
            IoTWrappedRepresentation *wrapped = [playlistAttr packAll];
            dispatch_async(queue, ^{
                callback(wrapped, 0);
            });
        } else {
            dispatch_async(queue, ^{
                callback([IoTWrappedRepresentation emptyWrapper], 0);
            });
        }
    };

    self.transport.dispatcherPostCallback = ^(MockDispatcher *dispatcher, IoTQueryParams params, IoTWrappedRepresentation *wrapped, IoTTransportDispatcherCompletion callback, dispatch_queue_t queue) {
        makeStrongAndReturnIfNil(self)
		if ([dispatcher.uri isEqualToString:PLAYLIST_GET_RANGE_URI]) {
            IoTPlaylistGetRangeOutAttr *playlistOutAttr = [[IoTPlaylistGetRangeOutAttr alloc] initWithStateSyncQueue:self.syncQueue];
            [playlistOutAttr setLatestSnapshotId:@"VALID"];
            [playlistOutAttr setTotalSize:3];
            IoTPlayItemAttr *item0 = [[IoTPlayItemAttr alloc] initWithStateSyncQueue:self.syncQueue];
            item0.title = @"ITEM0";

            IoTPlayItemAttr *item1 = [[IoTPlayItemAttr alloc] initWithStateSyncQueue:self.syncQueue];
            item1.title = @"ITEM1";

            IoTPlayItemAttr *item2 = [[IoTPlayItemAttr alloc] initWithStateSyncQueue:self.syncQueue];
            item2.title = @"ITEM2";

            [playlistOutAttr setItemsInRange:@[item0, item1, item2]];
            IoTWrappedRepresentation *wrapped = [playlistOutAttr packAll];
            dispatch_async(queue, ^{
                callback(wrapped, 0);
            });
        } else {
            dispatch_async(queue, ^{
                callback([IoTWrappedRepresentation emptyWrapper], 0);
            });
        }
    };

    [self.allPlayController createResourceClientsForHost:LOCALHOST];

    [self.player updateWithCompletion:^(BOOL success){
        makeStrongAndReturnIfNil(self)
        XCTAssertEqualObjects(self.player.playlistUserData, @"USERDATA", @"Playlist User Data incorrect");
        XCTAssertEqual(self.player.playlistSize, 3, @"Playlist should have 3 items");
        XCTAssertEqualObjects(self.player.playlistSnapshotID, @"VALID", "Playlist snapshot Id incorrect");

        [self.player getItemsInRange:NSMakeRange(0, 3)
                          completion:^(BOOL success, NSArray<IoTMediaItem *> *items, NSError *error) {
                              XCTAssertTrue(success, @"success should be true");
                              XCTAssertNil(error, @"Error should be nil");
                              XCTAssertEqual(items.count, 3, @"There should be three items");
                              XCTAssertEqualObjects(items[0].title, @"ITEM0", @"Title incorrect for item");
                              XCTAssertEqualObjects(items[1].title, @"ITEM1", @"Title incorrect for item");
                              XCTAssertEqualObjects(items[2].title, @"ITEM2", @"Title incorrect for item");
                              [expectation fulfill];
                          } completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)];
        
    } completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)];

    [self waitForExpectations:@[expectation] timeout:2];
}

@end
