/*
 Copyright 2015 OpenMarket Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

#import "MXKRoomBubbleCellDataWithAppendingMode.h"

#import "GeneratedInterface-Swift.h"

static NSAttributedString *messageSeparator = nil;

@implementation MXKRoomBubbleCellDataWithAppendingMode

#pragma mark - MXKRoomBubbleCellDataStoring

- (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomState andRoomDataSource:(MXKRoomDataSource *)roomDataSource
{
    self = [super initWithEvent:event andRoomState:roomState andRoomDataSource:roomDataSource];
    if (self)
    {
        // Set default settings
        self.maxComponentCount = 10;
    }
    
    return self;
}

- (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState
{
    // We group together text messages from the same user (attachments are not merged).
    if ([event.sender isEqualToString:self.senderId] && (self.attachment == nil) && (self.bubbleComponents.count < self.maxComponentCount))
    {
        // Attachments (image, video, sticker ...) cannot be added here
        if ([roomDataSource.eventFormatter isSupportedAttachment:event])
        {
            return NO;
        }
        
        // Check sender information
        // If `roomScreenUseOnlyLatestUserAvatarAndName`is enabled, the avatar and name are
        // displayed from the latest room state perspective rather than the historical.
        MXRoomState *latestRoomState = roomDataSource.roomState;
        MXRoomState *displayRoomState = RiotSettings.shared.roomScreenUseOnlyLatestUserAvatarAndName ? latestRoomState : roomState;
        NSString *eventSenderName = [roomDataSource.eventFormatter senderDisplayNameForEvent:event withRoomState:displayRoomState];
        NSString *eventSenderAvatar = [roomDataSource.eventFormatter senderAvatarUrlForEvent:event withRoomState:displayRoomState];
        if ((self.senderDisplayName || eventSenderName) &&
            ([self.senderDisplayName isEqualToString:eventSenderName] == NO))
        {
            return NO;
        }
        if ((self.senderAvatarUrl || eventSenderAvatar) &&
            ([self.senderAvatarUrl isEqualToString:eventSenderAvatar] == NO))
        {
            return NO;
        }
        
        // Take into account here the rendered bubbles pagination
        if (roomDataSource.bubblesPagination == MXKRoomDataSourceBubblesPaginationPerDay)
        {
            // Event must be sent the same day than the existing bubble.
            NSString *bubbleDateString = [roomDataSource.eventFormatter dateStringFromDate:self.date withTime:NO];
            NSString *eventDateString = [roomDataSource.eventFormatter dateStringFromEvent:event withTime:NO];
            if (bubbleDateString && eventDateString && ![bubbleDateString isEqualToString:eventDateString])
            {
                return NO;
            }
        }
        
        // Create new message component
        MXKRoomBubbleComponent *addedComponent = [[MXKRoomBubbleComponent alloc] initWithEvent:event
                                                                                     roomState:roomState
                                                                            andLatestRoomState:roomDataSource.roomState
                                                                                eventFormatter:roomDataSource.eventFormatter
                                                                                       session:self.mxSession];
        if (addedComponent)
        {
            [self addComponent:addedComponent];
        }
        // else the event is ignored, we consider it as handled
        return YES;
    }
    return NO;
}

- (BOOL)mergeWithBubbleCellData:(id<MXKRoomBubbleCellDataStoring>)bubbleCellData
{
    if ([self hasSameSenderAsBubbleCellData:bubbleCellData])
    {
        MXKRoomBubbleCellData *cellData = (MXKRoomBubbleCellData*)bubbleCellData;
        // Only text messages are merged (Attachments are not merged).
        if ((self.attachment == nil) && (cellData.attachment == nil))
        {
            // Take into account here the rendered bubbles pagination
            if (roomDataSource.bubblesPagination == MXKRoomDataSourceBubblesPaginationPerDay)
            {
                // bubble components must be sent the same day than self.
                NSString *selfDateString = [roomDataSource.eventFormatter dateStringFromDate:self.date withTime:NO];
                NSString *bubbleDateString = [roomDataSource.eventFormatter dateStringFromDate:bubbleCellData.date withTime:NO];
                if (![bubbleDateString isEqualToString:selfDateString])
                {
                    return NO;
                }
            }
            
            // Add all components of the provided message
            for (MXKRoomBubbleComponent* component in cellData.bubbleComponents)
            {
                [self addComponent:component];
            }
            return YES;
        }
    }
    return NO;
}

- (NSAttributedString*)attributedTextMessageWithHighlightedEvent:(NSString*)eventId tintColor:(UIColor*)tintColor
{
    // Create attributed string
    NSMutableAttributedString *customAttributedTextMsg;
    NSAttributedString *componentString;
    
    @synchronized(bubbleComponents)
    {
        for (MXKRoomBubbleComponent* component in bubbleComponents)
        {
            componentString = component.attributedTextMessage;
            
            if (componentString)
            {
                if ([component.event.eventId isEqualToString:eventId])
                {
                    NSMutableAttributedString *customComponentString = [[NSMutableAttributedString alloc] initWithAttributedString:componentString];
                    UIColor *color = tintColor ? tintColor : [UIColor lightGrayColor];
                    [customComponentString addAttribute:NSBackgroundColorAttributeName value:color range:NSMakeRange(0, customComponentString.length)];
                    componentString = customComponentString;
                }
                
                if (!customAttributedTextMsg)
                {
                    customAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:componentString];
                }
                else
                {
                    // Append attributed text
                    [customAttributedTextMsg appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
                    [customAttributedTextMsg appendAttributedString:componentString];
                }
            }
        }
    }

    return customAttributedTextMsg;
}

#pragma mark -

- (void)prepareBubbleComponentsPosition
{
    // Set position of the first component
    [super prepareBubbleComponentsPosition];
    
    @synchronized(bubbleComponents)
    {
        // Check whether the position of other components need to be refreshed
        if (!self.attachment && shouldUpdateComponentsPosition && bubbleComponents.count > 1)
        {
            // Init attributed string with the first text component not nil.
            MXKRoomBubbleComponent *component = bubbleComponents.firstObject;
            CGFloat positionY = component.position.y;
            NSMutableAttributedString *attributedString;
            NSUInteger index = 0;
            
            for (; index < bubbleComponents.count; index++)
            {
                component = [bubbleComponents objectAtIndex:index];
                
                component.position = CGPointMake(0, positionY);
                
                if (component.attributedTextMessage)
                {
                    attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:component.attributedTextMessage];
                    [attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
                    break;
                }
            }
            
            for (index++; index < bubbleComponents.count; index++)
            {
                // Append the next text component
                component = [bubbleComponents objectAtIndex:index];
                
                if (component.attributedTextMessage)
                {
                    [attributedString appendAttributedString:component.attributedTextMessage];
                    
                    // Compute the height of the resulting string
                    CGFloat cumulatedHeight = [self rawTextHeight:attributedString];
                    
                    // Deduce the position of the beginning of this component
                    CGFloat positionY = MXKROOMBUBBLECELLDATA_TEXTVIEW_DEFAULT_VERTICAL_INSET + (cumulatedHeight - [self rawTextHeight:component.attributedTextMessage]);
                    
                    component.position = CGPointMake(0, positionY);
                    
                    [attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
                }
                else
                {
                    // Apply the current vertical position on this empty component.
                    component.position = CGPointMake(0, positionY);
                }
            }
        }
    }
    
    shouldUpdateComponentsPosition = NO;
}

#pragma mark -

- (NSString*)textMessage
{
    NSString *rawText = nil;
    
    if (self.attributedTextMessage)
    {
        // Append all components text message
        NSMutableString *currentTextMsg;
        @synchronized(bubbleComponents)
        {
            for (MXKRoomBubbleComponent* component in bubbleComponents)
            {
                if (component.textMessage == nil)
                {
                    continue;
                }
                if (!currentTextMsg)
                {
                    currentTextMsg = [NSMutableString stringWithString:component.textMessage];
                }
                else
                {
                    // Append text message
                    [currentTextMsg appendString:@"\n"];
                    [currentTextMsg appendString:component.textMessage];
                }
            }
        }
        rawText = currentTextMsg;
    }
    
    return rawText;
}

- (void)setAttributedTextMessage:(NSAttributedString *)inAttributedTextMessage
{
    super.attributedTextMessage = inAttributedTextMessage;

    // Position of each components should be computed again
    shouldUpdateComponentsPosition = YES;
}

- (NSAttributedString*)attributedTextMessage
{
    @synchronized(bubbleComponents)
    {
        if (self.hasAttributedTextMessage && !attributedTextMessage.length)
        {
            // Create attributed string
            NSMutableAttributedString *currentAttributedTextMsg;
            
            for (MXKRoomBubbleComponent* component in bubbleComponents)
            {
                if (component.attributedTextMessage)
                {
                    if (!currentAttributedTextMsg)
                    {
                        currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:component.attributedTextMessage];
                    }
                    else
                    {
                        // Append attributed text
                        [currentAttributedTextMsg appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
                        [currentAttributedTextMsg appendAttributedString:component.attributedTextMessage];
                    }
                }
            }
            self.attributedTextMessage = currentAttributedTextMsg;
        }
    }
    
    return attributedTextMessage;
}

- (void)setMaxTextViewWidth:(CGFloat)inMaxTextViewWidth
{
    CGFloat previousMaxWidth = self.maxTextViewWidth;
    
    [super setMaxTextViewWidth:inMaxTextViewWidth];
    
    // Check change
    if (previousMaxWidth != self.maxTextViewWidth)
    {
        // Position of each components should be computed again
        shouldUpdateComponentsPosition = YES;
    }
}

#pragma mark -

+ (NSAttributedString *)messageSeparator
{
    @synchronized(self)
    {
        if(messageSeparator == nil)
        {
            messageSeparator = [[NSAttributedString alloc] initWithString:@"\n\n" attributes:@{NSForegroundColorAttributeName : [UIColor blackColor],
                                                                                               NSFontAttributeName: [UIFont systemFontOfSize:4]}];
        }
    }
    return messageSeparator;
}

#pragma mark - Privates

- (void)addComponent:(MXKRoomBubbleComponent*)addedComponent
{
    @synchronized(bubbleComponents)
    {
        // Check date of existing components to insert this new one
        NSUInteger index = bubbleComponents.count;
        
        // Component without date is added at the end by default
        if (addedComponent.date)
        {
            while (index)
            {
                MXKRoomBubbleComponent *msgComponent = [bubbleComponents objectAtIndex:(--index)];
                if (msgComponent.date && [msgComponent.date compare:addedComponent.date] != NSOrderedDescending)
                {
                    // New component will be inserted here
                    index ++;
                    break;
                }
            }
        }
        
        // Insert new component
        [bubbleComponents insertObject:addedComponent atIndex:index];
        
        // Indicate that the data's text message layout should be recomputed.
        [self invalidateTextLayout];
    }
}

@end
