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

#import <XCTest/XCTest.h>
#import "TestCategories.h"
#import "IoTClient+Private.h"
#import "IoTPlayer+Private.h"
#import "IoTPlayerGroup+Private.h"
#import "IoTAppListenerProtocol.h"
#import "IoTServiceDiscoveryResult.h"
#import "IoTWrappedRepresentation.h"
#import "MockTransport.h"
#import "IoTGlobals.h"
#import "IoTAllPlayController.h"
#import "IoTResourceAttributes.h"


#define LOCALHOST @"127.0.0.1"
#define DEVICE_ID @"0"
#define DEVICE_NAME @"MOCKED DEVICE"
#define URI_PREFIX @"/qti/allplay"
#define MEDIA_PLAYER_RESOURCE @"qti.allplay.r.mediaPlayer"
#define MEDIA_PLAYER_URI @"/qti/allplay/mediaPlayer"

@interface IoTPlayerGroupTests : XCTestCase <IoTAppListenerProtocol>

@property (nonatomic, strong) dispatch_queue_t syncQueue;

@property (nonatomic, strong) IoTClient *client;
@property (nonatomic, strong) MockTransport *transport;
@property (nonatomic, strong) IoTServiceDiscoveryResult *discoveredService;

@property (nonatomic, strong) XCTestExpectation *playerGroupExpectation;

@end

@implementation IoTPlayerGroupTests

- (void)setUp
{
    [super setUp];
    self.syncQueue = dispatch_queue_create("com.qualcomm.qti.IoTPlayerGroupTests", DISPATCH_QUEUE_CONCURRENT);
    self.discoveredService = [IoTServiceDiscoveryResult
                              resultWithIdentifier:DEVICE_ID
                              name:DEVICE_NAME
                              host:LOCALHOST
                              type:ALLPLAY_DEVICE_TYPE];
    self.transport = [[MockTransport alloc] init];
    self.client = [[IoTClient alloc] initWithApplicationName:@"IoTPlayerGroupTests"
                                                   transport:self.transport];
    self.client.delegate = self;

    self.playerGroupExpectation = [[XCTestExpectation alloc] initWithDescription:@"Wait for Player Group Creation"];
    makeWeak(self)
    self.transport.serviceScanCallback = ^(NSSet<NSString *> *types, IoTTransportServiceDiscoveredCallback callback, dispatch_queue_t queue) {
        makeStrongAndReturnIfNil(self)
        dispatch_async(queue, ^{
            callback(self.discoveredService);
        });
    };

    self.transport.resourceScanCallback = ^(NSString *host, NSString *uriPrefix, IoTTransportResourceDiscoveredCallback callback, dispatch_queue_t queue) {
        makeStrongAndReturnIfNil(self)
        dispatch_async(queue, ^{
            callback(MEDIA_PLAYER_RESOURCE);
        });
    };

    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 setLoopMode:YES];
            [enabledControls setNext:NO];
            [enabledControls setPause:YES];
            [enabledControls setPrevious:NO];
            [enabledControls setSeek:YES];
            [enabledControls setShuffleMode:NO];

            IoTPlayStateAttr *playStateAttr = [[IoTPlayStateAttr alloc] initWithStateSyncQueue:self.syncQueue];
            playStateAttr.playState = IoTPlayStatePaused;
            playStateAttr.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];
            playStateAttr.queuedItems = queued;

            mediaPlayerAttr.capabilities = @[@"audio"];
            mediaPlayerAttr.displayName = DEVICE_NAME;
            mediaPlayerAttr.enabledControls = enabledControls;
            mediaPlayerAttr.loopMode = IoTLoopModeOne;
            mediaPlayerAttr.playState = playStateAttr;
            mediaPlayerAttr.shuffleMode = IoTShuffleModeShuffle;
            IoTWrappedRepresentation *wrapped = [mediaPlayerAttr packAll];
            dispatch_async(queue, ^{
                callback(wrapped, 0);
            });
        } else {
            dispatch_async(queue, ^{
                callback([IoTWrappedRepresentation emptyWrapper], 0);
            });
        }
    };
}

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

- (void)testClientCreatesPlayerGroupWithExpectedPlayer
{
    [self.client start];
    [self waitForExpectations:@[self.playerGroupExpectation] timeout:5];

    IoTPlayerGroup *group = [[self.client getPlayerGroups] firstObject];
    XCTAssertNotNil(group, @"Group should not be nil");
    NSArray<IoTPlayerGroupMember *> *members = [group members];
    IoTPlayerGroupMember *firstMember = [members firstObject];
    XCTAssertEqual([members count], 1, @"There should be one player");
    XCTAssertEqualObjects([group sourcePlayerID], firstMember.playerID, @"The lead player and the only player should be the same");
    XCTAssertEqualObjects(firstMember.displayName, DEVICE_NAME);
}

- (void)testPlayerGroupHasExpectedValues
{
    [self.client start];
    [self waitForExpectations:@[self.playerGroupExpectation] timeout:5];

    IoTPlayerGroup *group = [[self.client getPlayerGroups] firstObject];
    XCTAssertNotNil(group, @"Group should not be nil");

    XCTAssertEqualObjects(group.displayName, DEVICE_NAME, @"displayName not correct");
    XCTAssertEqual(group.loopMode, IoTLoopModeOne, @"Incorrect loop mode");
    XCTAssertEqual(group.shuffleMode, IoTShuffleModeShuffle, @"Incorrect Shuffle Mode");

    XCTAssertTrue(group.isLoopModeEnabled, @"Loop mode should be enabled");
    XCTAssertTrue(group.isPauseEnabled, @"Pause should be enabled");
    XCTAssertTrue(group.isSeekEnabled, @"Seek should be enabled");
    XCTAssertFalse(group.isNextEnabled, @"Next should not be enabled");
    XCTAssertFalse(group.isPreviousEnabled, @"Previous should not be enabled");
    XCTAssertFalse(group.isShuffleModeEnabled, @"Shuffle should not be enabled");
    XCTAssertEqual(group.playState, IoTPlayStatePaused, @"Player should be paused");
    XCTAssertEqual(group.playerPosition, 100, @"Position should be 100");
}

- (void)playerGroupAdded:(IoTPlayerGroup *)group
{
    XCTAssertEqual([[self.client getPlayerGroups] count], 1, @"Should be one group");
    [self.playerGroupExpectation fulfill];
}

@end
