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

#import "IoTMultichannelState.h"
#import "IoTResourceAttributes.h"
#import "IoTPlayer.h"

@implementation IoTMultichannelChannelDevice

- (instancetype)initWithAttr:(IoTGroupPlayersAttr *)attr
                      volume:(double)volume
                       muted:(BOOL)muted
                   connected:(BOOL)connected
                   removable:(BOOL)removable
{
    self = [super init];
    if (self) {
        self->_playerID = [attr.deviceId copy];
        self->_displayName = [attr.displayName copy];
        self->_volume = volume;
        self->_muted = muted;
        self->_removable = removable;
        self->_connected = connected;
    }
    return self;
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"playerID: %@, name: %@, connected: %d, removable: %d",
            self.playerID, self.displayName, self.connected, self.removable];
}

@end


@interface IoTMultichannelState ()

@property (nonatomic,strong, readonly) dispatch_queue_t syncQueue;

@property (nonatomic, assign) BOOL isSoundbar;
@property (nonatomic, assign) BOOL isSatellite;
@property (nonatomic, strong) NSDictionary<NSNumber *,NSMutableArray<IoTMultichannelChannelDevice *> *> *channelMap;

@property (nonatomic, copy) IoTMultichannelGroupMainAttr *mainAttr;
@property (nonatomic, copy) IoTMultichannelVolumeControlAttr *volumeAttr;

@end

@implementation IoTMultichannelState

- (instancetype)initWithStateSyncQueue:(dispatch_queue_t)syncQueue
{
    self = [super init];
    if (self) {
        self->_syncQueue = syncQueue;
        self->_isSoundbar = NO;
        self->_isSatellite = NO;
        self->_channelMap = @{};
    }
    return self;
}

- (IoTHomeTheaterChannel)homeTheaterChannelForAttrChannelMap:(AttrChannelMapNumber)attrChannelMapNumber
{
    IoTHomeTheaterChannel channel = IoTHomeTheaterChannelNone;
    switch (attrChannelMapNumber) {

        case AttrChannelMapNumberNone:
            channel = IoTHomeTheaterChannelNone;
            break;
        case AttrChannelMapNumberFrontLeft:
            channel = IoTHomeTheaterChannelLeft;
            break;
        case AttrChannelMapNumberFrontRight:
            channel = IoTHomeTheaterChannelRight;
            break;
        case AttrChannelMapNumberFrontCenter:
            channel = IoTHomeTheaterChannelCenter;
            break;
        case AttrChannelMapNumberSubwoofer:
            channel = IoTHomeTheaterChannelSubwoofer;
            break;
        case AttrChannelMapNumberSideLeft:
            channel = IoTHomeTheaterChannelLeftSurround;
            break;
        case AttrChannelMapNumberSideRight:
            channel = IoTHomeTheaterChannelRightSurround;
            break;
        case AttrChannelMapNumberBackLeft:
            channel = IoTHomeTheaterChannelLeftRearSurround;
            break;
        case AttrChannelMapNumberBackRight:
            channel = IoTHomeTheaterChannelRightRearSurround;
            break;
        case AttrChannelMapNumberTopFrontLeft:
            channel = IoTHomeTheaterChannelLeftUpfiringSurround;
            break;
        case AttrChannelMapNumberTopFrontRight:
            channel = IoTHomeTheaterChannelRightUpfiringSurround;
            break;
        case AttrChannelMapNumberTopBackLeft:
            channel = IoTHomeTheaterChannelLeftRearUpfiringSurround;
            break;
        case AttrChannelMapNumberTopBackRight:
            channel = IoTHomeTheaterChannelRightRearUpfiringSurround;
            break;
    }
    return channel;
}

- (AttrChannelMapNumber)attrChannelMapForHomeTheaterChannel:(IoTHomeTheaterChannel)homeTheaterChannel
{
    AttrChannelMapNumber channel = AttrChannelMapNumberNone;
    switch (homeTheaterChannel) {
        case IoTHomeTheaterChannelNone:
            channel = AttrChannelMapNumberNone;
            break;
        case IoTHomeTheaterChannelSubwoofer:
            channel = AttrChannelMapNumberSubwoofer;
            break;
        case IoTHomeTheaterChannelLeft:
            channel = AttrChannelMapNumberFrontLeft;
            break;
        case IoTHomeTheaterChannelRight:
            channel = AttrChannelMapNumberFrontRight;
            break;
        case IoTHomeTheaterChannelCenter:
            channel = AttrChannelMapNumberFrontCenter;
            break;
        case IoTHomeTheaterChannelLeftSurround:
            channel = AttrChannelMapNumberSideLeft;
            break;
        case IoTHomeTheaterChannelRightSurround:
            channel = AttrChannelMapNumberSideRight;
            break;
        case IoTHomeTheaterChannelLeftRearSurround:
            channel = AttrChannelMapNumberBackLeft;
            break;
        case IoTHomeTheaterChannelRightRearSurround:
            channel = AttrChannelMapNumberBackRight;
            break;
        case IoTHomeTheaterChannelLeftUpfiringSurround:
            channel = AttrChannelMapNumberTopFrontLeft;
            break;
        case IoTHomeTheaterChannelRightUpfiringSurround:
            channel = AttrChannelMapNumberTopFrontRight;
            break;
        case IoTHomeTheaterChannelLeftRearUpfiringSurround:
            channel = AttrChannelMapNumberTopBackLeft;
            break;
        case IoTHomeTheaterChannelRightRearUpfiringSurround:
            channel = AttrChannelMapNumberTopBackRight;
            break;

    }
    return channel;
}

- (void)updateWithMainAttr:(nullable IoTMultichannelGroupMainAttr *)mainAttr
                volumeAttr:(nullable IoTMultichannelVolumeControlAttr *)volumeAttr
       availabilityChanged:(nullable IoTMultichannelChanged)availabilityChanged
             volumeChanged:(nullable IoTMultichannelChanged)volumeChanged
               changeQueue:(nullable dispatch_queue_t)changeQueue
{
    if (!mainAttr) {
        self.isSoundbar = NO;
        self.channelMap = @{};
        return;
    } else {
        self.isSoundbar = YES;
    }

    // Process Channel Map

    NSMutableDictionary<NSNumber *, NSMutableArray<IoTMultichannelChannelDevice *> *> *newChannelMap = [NSMutableDictionary dictionary];
    NSLog (@"Soundbar %@ with %lu players", self.playerID, (unsigned long)mainAttr.group.count);
    for (IoTGroupPlayersAttr *player in mainAttr.group) {
        NSLog (@"player: %@ has %lu channnels", player.deviceId, (unsigned long)player.channelMap.count);
        BOOL builtIn = [player.deviceId isEqualToString:self.playerID];

        // Find volume if we have it

        __block double volume = 0.0;
        __block BOOL muted = NO;
        [volumeAttr.members enumerateObjectsUsingBlock:^(IoTMultichannelMembersGetAttr * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if ([obj.deviceId isEqualToString:player.deviceId]) {
                volume = obj.volume;
                muted = obj.mute;
                *stop = YES;
            }
        }];

        IoTMultichannelChannelDevice *channelInfo = [[IoTMultichannelChannelDevice alloc] initWithAttr:player
                                                                                                volume:volume
                                                                                                 muted:muted
                                                                                             connected:builtIn || player.connected
                                                                                             removable:!builtIn];
        for (NSNumber *channel in player.channelMap) {
            IoTHomeTheaterChannel htChannel = [self homeTheaterChannelForAttrChannelMap:[channel intValue]];
            if (htChannel != IoTHomeTheaterChannelNone) {
                NSMutableArray<IoTMultichannelChannelDevice *> *channelEntry = newChannelMap[@(htChannel)] ?: [NSMutableArray array];
                [channelEntry addObject:channelInfo];
                newChannelMap[@(htChannel)] = channelEntry;
            }
        }
    }

    NSDictionary<NSNumber *, NSMutableArray<IoTMultichannelChannelDevice *> *> *oldChannelMap = self.channelMap;
    self.channelMap = newChannelMap;

    // Figure out diffs

    BOOL canSendVolumeChanged = volumeChanged && changeQueue;
    BOOL canSendAvailabilityChanged = availabilityChanged && changeQueue;

    if (!canSendAvailabilityChanged && !canSendVolumeChanged) {
        return;
    }

    NSSet<NSNumber *> *oldChannels = [NSSet setWithArray:[oldChannelMap allKeys]];
    NSSet<NSNumber *> *newChannels = [NSSet setWithArray:[newChannelMap allKeys]];

    NSMutableSet *addedChannels = [newChannels mutableCopy];
    [addedChannels minusSet:oldChannels];
    for (NSNumber *channel in addedChannels) {
        // Availability has clearly changed. We send volume as well
        if (canSendAvailabilityChanged) {
            dispatch_async(changeQueue, ^{
                availabilityChanged([channel intValue]);
            });
        }

        if (canSendVolumeChanged) {
            dispatch_async(changeQueue, ^{
                volumeChanged([channel intValue]);
            });
        }
    }

    NSMutableSet *removedChannels = [oldChannels mutableCopy];
    [removedChannels minusSet:newChannels];
    for (NSNumber *channel in removedChannels) {
        // Availability has clearly changed.
        if (canSendAvailabilityChanged) {
            dispatch_async(changeQueue, ^{
                availabilityChanged([channel intValue]);
            });
        }
    }

    NSMutableSet *stillPresentChannels = [oldChannels mutableCopy];
    [stillPresentChannels intersectSet:newChannels];
    for (NSNumber *channel in stillPresentChannels) {
        // We need to compare old with new
        if (canSendAvailabilityChanged) {
            BOOL needToSendAvailability = NO;
            if ([oldChannelMap[channel] count] != [newChannelMap[channel] count]) {
                needToSendAvailability = YES;
            } else {
                for (int i = 0; i < [oldChannelMap[channel] count]; i++) {
                    if ([oldChannelMap[channel][i] connected] != [newChannelMap[channel][i] connected]) {
                        needToSendAvailability = YES;
                    }
                }
            }
            if (needToSendAvailability) {
                dispatch_async(changeQueue, ^{
                    availabilityChanged([channel intValue]);
                });
            }
        }

        if (canSendVolumeChanged) {
            BOOL needToSendVolume = NO;
            for (int i = 0; i < [oldChannelMap[channel] count]; i++) {
                if (fabs([oldChannelMap[channel][i] volume] - [newChannelMap[channel][i] volume]) > 0.01) {
                    needToSendVolume = YES;
                }

                if ([oldChannelMap[channel][i] muted] != [newChannelMap[channel][i] muted]) {
                    needToSendVolume = YES;
                }

                if (needToSendVolume) {
                    dispatch_async(changeQueue, ^{
                        volumeChanged([channel intValue]);
                    });
                }
            }
        }
    }
}

- (void)updateWithMainAttr:(nullable IoTMultichannelGroupMainAttr *)mainAttr
availabilityChanged:(nullable IoTMultichannelChanged)availabilityChanged
      volumeChanged:(nullable IoTMultichannelChanged)volumeChanged
               changeQueue:(nullable dispatch_queue_t)changeQueue
{
    self.mainAttr = mainAttr;
    [self updateWithMainAttr:self.mainAttr
                  volumeAttr:self.volumeAttr
         availabilityChanged:availabilityChanged
               volumeChanged:volumeChanged
                 changeQueue:changeQueue];
}

- (void)updateWithMainAttrDiff:(IoTWrappedRepresentation *)mainAttrDiff
           availabilityChanged:(nullable IoTMultichannelChanged)availabilityChanged
volumeChanged:(nullable IoTMultichannelChanged)volumeChanged
                   changeQueue:(nullable dispatch_queue_t)changeQueue
{
    if (!self.mainAttr) {
        self.mainAttr = [[IoTMultichannelGroupMainAttr alloc] initWithStateSyncQueue:self.syncQueue];
    }
    
    [self.mainAttr unpack:mainAttrDiff];
    [self updateWithMainAttr:self.mainAttr
                  volumeAttr:self.volumeAttr
         availabilityChanged:availabilityChanged
               volumeChanged:volumeChanged
                 changeQueue:changeQueue];
}

- (void)updateWithMCVolumeAttr:(nullable IoTMultichannelVolumeControlAttr *)volumeAttr
                 volumeChanged:(nullable IoTMultichannelChanged)volumeChanged
                   changeQueue:(nullable dispatch_queue_t)changeQueue
{
    self.volumeAttr = volumeAttr;
    [self updateWithMainAttr:self.mainAttr
                  volumeAttr:self.volumeAttr
         availabilityChanged:nil
               volumeChanged:volumeChanged
                 changeQueue:changeQueue];
}

- (void)updateWithMCVolumeAttrDiff:(IoTWrappedRepresentation *)volumeAttrDiff
                     volumeChanged:(nullable IoTMultichannelChanged)volumeChanged
                       changeQueue:(nullable dispatch_queue_t)changeQueue
{
    if (!self.volumeAttr) {
        self.volumeAttr = [[IoTMultichannelVolumeControlAttr alloc] initWithStateSyncQueue:self.syncQueue];
    }
    [self.volumeAttr unpack:volumeAttrDiff];
    [self updateWithMCVolumeAttr:self.volumeAttr
                   volumeChanged:volumeChanged
                     changeQueue:changeQueue];
}


- (void)updateWithSatelliteAttr:(nullable IoTMultichannelGroupSatelliteAttr *)satelliteAttr
{
    self.isSatellite = satelliteAttr != nil;
}

- (void)updateWithSatelliteAttrDiff:(IoTWrappedRepresentation *)satelliteAttrDiff
{
    self.isSatellite = YES;
}

- (NSArray<IoTMultichannelChannelDevice *> *)devicesForChannel:(IoTHomeTheaterChannel)channel
{
    return [self.channelMap[@(channel)] copy] ?: [NSArray array];
}

- (NSArray<NSString *> *)playerIDsForAllChannels
{
    NSMutableSet<NSString *> *playerIDs = [NSMutableSet set];
    for (NSArray<IoTMultichannelChannelDevice *> *deviceArray in self.channelMap.allValues) {
        for (IoTMultichannelChannelDevice *device in deviceArray) {
			[playerIDs addObject:device.playerID];
        }
    }
    return [playerIDs allObjects];
}

- (NSArray<NSString *> *)playerIDsForAllChannelsRemovable
{
    NSMutableSet<NSString *> *playerIDs = [NSMutableSet set];
    for (NSArray<IoTMultichannelChannelDevice *> *deviceArray in self.channelMap.allValues) {
        for (IoTMultichannelChannelDevice *device in deviceArray) {
            if (device.removable) {
            	[playerIDs addObject:device.playerID];
            }
        }
    }
    return [playerIDs allObjects];
}

@end
