/*
 Copyright 2015 OpenMarket Ltd
 Copyright 2017 Vector Creations Ltd
 Copyright 2018 New Vector 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 "MXKRoomMemberListViewController.h"

#import "MXKRoomMemberTableViewCell.h"

#import "MXKConstants.h"

#import "NSBundle+MatrixKit.h"

#import "MXKSwiftHeader.h"

@interface MXKRoomMemberListViewController ()
{
    /**
     The data source providing UITableViewCells
     */
    MXKRoomMemberListDataSource *dataSource;
    
    /**
     Timer used to update members presence
     */
    NSTimer* presenceUpdateTimer;
    
    /**
     Optional bar buttons
     */
    UIBarButtonItem *searchBarButton;
    UIBarButtonItem *addBarButton;
    
    /**
     The current displayed alert (if any).
     */
    UIAlertController *currentAlert;
    
    /**
     Search bar
     */
    BOOL ignoreSearchRequest;
    
    /**
     Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
     */
    id leaveRoomNotificationObserver;
}

@end

@implementation MXKRoomMemberListViewController
@synthesize dataSource;

#pragma mark - Class methods

+ (UINib *)nib
{
    return [UINib nibWithNibName:NSStringFromClass([MXKRoomMemberListViewController class])
                          bundle:[NSBundle bundleForClass:[MXKRoomMemberListViewController class]]];
}

+ (instancetype)roomMemberListViewController
{
    return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKRoomMemberListViewController class])
                                          bundle:[NSBundle bundleForClass:[MXKRoomMemberListViewController class]]];
}


#pragma mark -

- (void)finalizeInit
{
    [super finalizeInit];
    
    // Enable both bar button by default.
    _enableMemberInvitation = YES;
    _enableMemberSearch = YES;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // Check whether the view controller has been pushed via storyboard
    if (!self.membersTableView)
    {
        // Instantiate view controller objects
        [[[self class] nib] instantiateWithOwner:self options:nil];
    }
    
    // Adjust Top and Bottom constraints to take into account potential navBar and tabBar.
    [NSLayoutConstraint deactivateConstraints:@[_membersSearchBarTopConstraint, _membersTableViewBottomConstraint]];
    
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdeprecated"
    _membersSearchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide
                                                                  attribute:NSLayoutAttributeBottom
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:self.membersSearchBar
                                                                  attribute:NSLayoutAttributeTop
                                                                 multiplier:1.0f
                                                                   constant:0.0f];
    
    _membersTableViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide
                                                                     attribute:NSLayoutAttributeTop
                                                                     relatedBy:NSLayoutRelationEqual
                                                                        toItem:self.membersTableView
                                                                     attribute:NSLayoutAttributeBottom
                                                                    multiplier:1.0f
                                                                      constant:0.0f];
    #pragma clang diagnostic pop
    
    [NSLayoutConstraint activateConstraints:@[_membersSearchBarTopConstraint, _membersTableViewBottomConstraint]];
    
    // Hide search bar by default
    self.membersSearchBar.hidden = YES;
    self.membersSearchBarHeightConstraint.constant = 0;
    [self.view setNeedsUpdateConstraints];
    
    searchBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)];
    addBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(inviteNewMember:)];
    
    // Refresh bar button display.
    [self refreshUIBarButtons];
    
    // Add an accessory view to the search bar in order to retrieve keyboard view.
    self.membersSearchBar.inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
    
    // Finalize table view configuration
    self.membersTableView.delegate = self;
    self.membersTableView.dataSource = dataSource; // Note datasource may be nil here.
    
    // Set up default table view cell class
    [self.membersTableView registerNib:MXKRoomMemberTableViewCell.nib forCellReuseIdentifier:MXKRoomMemberTableViewCell.defaultReuseIdentifier];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    // Check whether the user still belongs to the room's members.
    if (self.dataSource && [self.mainSession roomWithRoomId:self.dataSource.roomId])
    {
        [self refreshUIBarButtons];
        
        // Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
        leaveRoomNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionWillLeaveRoomNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif)
        {
            
            // Check whether the user will leave the room related to the displayed member list
            if (notif.object == self.mainSession)
            {
                NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
                if (roomId && [roomId isEqualToString:self.dataSource.roomId])
                {
                    // We remove the current view controller.
                    [self withdrawViewControllerAnimated:YES completion:nil];
                }
            }
        }];
    }
    else
    {
        // We remove the current view controller.
        [self withdrawViewControllerAnimated:YES completion:nil];
    }
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    // Restore search mechanism (if enabled)
    ignoreSearchRequest = NO;
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    
    // The user may still press search button whereas the view disappears
    ignoreSearchRequest = YES;
    
    if (leaveRoomNotificationObserver)
    {
        [[NSNotificationCenter defaultCenter] removeObserver:leaveRoomNotificationObserver];
        leaveRoomNotificationObserver = nil;
    }
    
    // Leave potential search session
    if (!self.membersSearchBar.isHidden)
    {
        [self searchBarCancelButtonClicked:self.membersSearchBar];
    }
}

- (void)dealloc
{
    self.membersSearchBar.inputAccessoryView = nil;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    
    // Dispose of any resources that can be recreated.
}

#pragma mark - Override MXKTableViewController

- (void)onKeyboardShowAnimationComplete
{
    // Report the keyboard view in order to track keyboard frame changes
    self.keyboardView = _membersSearchBar.inputAccessoryView.superview;
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
- (void)setKeyboardHeight:(CGFloat)keyboardHeight
{
    // Deduce the bottom constraint for the table view (Don't forget the potential tabBar)
    CGFloat tableViewBottomConst = keyboardHeight - self.bottomLayoutGuide.length;
    // Check whether the keyboard is over the tabBar
    if (tableViewBottomConst < 0)
    {
        tableViewBottomConst = 0;
    }
    
    // Update constraints
    _membersTableViewBottomConstraint.constant = tableViewBottomConst;
    
    // Force layout immediately to take into account new constraint
    [self.view layoutIfNeeded];
}
#pragma clang diagnostic pop

- (void)destroy
{
    if (presenceUpdateTimer)
    {
        [presenceUpdateTimer invalidate];
        presenceUpdateTimer = nil;
    }
    
    self.membersTableView.dataSource = nil;
    self.membersTableView.delegate = nil;
    self.membersTableView = nil;
    dataSource.delegate = nil;
    dataSource = nil;
    
    if (currentAlert)
    {
        [currentAlert dismissViewControllerAnimated:NO completion:nil];
        currentAlert = nil;
    }
    
    searchBarButton = nil;
    addBarButton = nil;
    
    _delegate = nil;
    
    [super destroy];
}

#pragma mark - Internal methods

- (void)updateMembersActivityInfo
{
    for (id memberCell in self.membersTableView.visibleCells)
    {
        if ([memberCell respondsToSelector:@selector(updateActivityInfo)])
        {
            [memberCell updateActivityInfo];
        }
    }
}

#pragma mark - UIBarButton handling

- (void)setEnableMemberSearch:(BOOL)enableMemberSearch
{
    _enableMemberSearch = enableMemberSearch;
    [self refreshUIBarButtons];
}

- (void)setEnableMemberInvitation:(BOOL)enableMemberInvitation
{
    _enableMemberInvitation = enableMemberInvitation;
    [self refreshUIBarButtons];
}

- (void)refreshUIBarButtons
{
    MXRoom *mxRoom = [self.mainSession roomWithRoomId:dataSource.roomId];

    MXWeakify(self);
    [mxRoom state:^(MXRoomState *roomState) {
        MXStrongifyAndReturnIfNil(self);

        BOOL showInvitationOption = self.enableMemberInvitation;

        if (showInvitationOption && self->dataSource)
        {
            // Check conditions to be able to invite someone
            NSInteger oneSelfPowerLevel = [roomState.powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId];
            if (oneSelfPowerLevel < [roomState.powerLevels invite])
            {
                showInvitationOption = NO;
            }
        }

        if (showInvitationOption)
        {
            if (self.enableMemberSearch)
            {
                self.navigationItem.rightBarButtonItems = @[self->searchBarButton, self->addBarButton];
            }
            else
            {
                self.navigationItem.rightBarButtonItems = @[self->addBarButton];
            }
        }
        else if (self.enableMemberSearch)
        {
            self.navigationItem.rightBarButtonItems = @[self->searchBarButton];
        }
        else
        {
            self.navigationItem.rightBarButtonItems = nil;
        }
    }];
}

#pragma mark -
- (void)displayList:(MXKRoomMemberListDataSource *)listDataSource
{
    if (dataSource)
    {
        dataSource.delegate = nil;
        dataSource = nil;
        [self removeMatrixSession:self.mainSession];
    }
    
    dataSource = listDataSource;
    dataSource.delegate = self;
    
    // Report the matrix session at view controller level to update UI according to session state
    [self addMatrixSession:dataSource.mxSession];
    
    if (self.membersTableView)
    {
        // Set up table data source
        self.membersTableView.dataSource = dataSource;
    }
}

- (void)scrollToTop:(BOOL)animated
{
    [self.membersTableView setContentOffset:CGPointMake(-self.membersTableView.adjustedContentInset.left, -self.membersTableView.adjustedContentInset.top) animated:animated];
}

#pragma mark - MXKDataSourceDelegate

- (Class<MXKCellRendering>)cellViewClassForCellData:(MXKCellData*)cellData
{
    // Return the default member table view cell
    return MXKRoomMemberTableViewCell.class;
}

- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData
{
    // Consider the default member table view cell
    return MXKRoomMemberTableViewCell.defaultReuseIdentifier;
}

- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes
{
    if (presenceUpdateTimer)
    {
        [presenceUpdateTimer invalidate];
        presenceUpdateTimer = nil;
    }
    
    // For now, do a simple full reload
    [self.membersTableView reloadData];
    
    if (shouldScrollToTopOnRefresh)
    {
        [self scrollToTop:NO];
        shouldScrollToTopOnRefresh = NO;
    }
    
    // Place a timer to update members's activity information
    presenceUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(updateMembersActivityInfo) userInfo:self repeats:YES];
}

#pragma mark - UITableView delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [dataSource cellHeightAtIndex:indexPath.row];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (_delegate)
    {
        id<MXKRoomMemberCellDataStoring> cellData = [dataSource cellDataAtIndex:indexPath.row];
        
        [_delegate roomMemberListViewController:self didSelectMember:cellData.roomMember];
    }
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 0;
}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
{
    // Release here resources, and restore reusable cells
    if ([cell respondsToSelector:@selector(didEndDisplay)])
    {
        [(id<MXKCellRendering>)cell didEndDisplay];
    }
}

#pragma mark - UISearchBarDelegate

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    // Apply filter
    shouldScrollToTopOnRefresh = YES;
    if (searchText.length)
    {
        [self.dataSource searchWithPatterns:@[searchText]];
    }
    else
    {
        [self.dataSource searchWithPatterns:nil];
    }
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
    // "Done" key has been pressed
    [searchBar resignFirstResponder];
}

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
    // Leave search
    [searchBar resignFirstResponder];
    
    self.membersSearchBar.hidden = YES;
    self.membersSearchBarHeightConstraint.constant = 0;
    [self.view setNeedsUpdateConstraints];
    
    self.membersSearchBar.text = nil;
    
    // Refresh display
    shouldScrollToTopOnRefresh = YES;
    [self.dataSource searchWithPatterns:nil];
}

#pragma mark - Actions

- (void)search:(id)sender
{
    // The user may have pressed search button whereas the view controller was disappearing
    if (ignoreSearchRequest)
    {
        return;
    }
    
    if (self.membersSearchBar.isHidden)
    {
        // Check whether there are data in which search
        if ([self.dataSource tableView:self.membersTableView numberOfRowsInSection:0])
        {
            self.membersSearchBar.hidden = NO;
            self.membersSearchBarHeightConstraint.constant = 44;
            [self.view setNeedsUpdateConstraints];
            
            // Create search bar
            [self.membersSearchBar becomeFirstResponder];
        }
    }
    else
    {
        [self searchBarCancelButtonClicked: self.membersSearchBar];
    }
}

- (void)inviteNewMember:(id)sender
{
    __weak typeof(self) weakSelf = self;
    
    if (currentAlert)
    {
        [currentAlert dismissViewControllerAnimated:NO completion:nil];
    }
    
    // Ask for userId to invite
    currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n userIdTitle] message:nil preferredStyle:UIAlertControllerStyleAlert];
    
    
    [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
                                                     style:UIAlertActionStyleDefault
                                                   handler:^(UIAlertAction * action) {
                                                       
                                                       if (weakSelf)
                                                       {
                                                           typeof(self) self = weakSelf;
                                                           self->currentAlert = nil;
                                                       }
                                                       
                                                   }]];
    
    
    [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField)
    {
        textField.secureTextEntry = NO;
        textField.placeholder = [VectorL10n userIdPlaceholder];
    }];
    
    [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n invite]
                                                     style:UIAlertActionStyleDefault
                                                   handler:^(UIAlertAction * action) {
                                                       
                                                       if (weakSelf)
                                                       {
                                                           typeof(self) self = weakSelf;
                                                           
                                                           NSString *userId = [self->currentAlert textFields].firstObject.text;
                                                           
                                                           self->currentAlert = nil;
                                                           
                                                           if (userId.length)
                                                           {
                                                               MXRoom *mxRoom = [self.mainSession roomWithRoomId:self.dataSource.roomId];
                                                               if (mxRoom)
                                                               {
                                                                   [mxRoom inviteUser:userId success:^{
                                                                       
                                                                   } failure:^(NSError *error) {
                                                                       
                                                                       MXLogDebug(@"[MXKRoomVC] Invite %@ failed", userId);
                                                                       // Notify MatrixKit user
                                                                       NSString *myUserId = self.mainSession.myUser.userId;
                                                                       [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
                                                                       
                                                                   }];
                                                               }
                                                           }
                                                       }
                                                       
                                                   }]];
    
    [self presentViewController:currentAlert animated:YES completion:nil];
}

@end
