// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "base/strings/sys_string_conversions.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web_view/internal/cwv_back_forward_list_internal.h"
#import "net/base/apple/url_conversions.h"

@implementation CWVBackForwardListItemArray {
  BOOL _isBackList;

  CWVBackForwardListItem* _fastEnumerationItemCache;
}

- (instancetype)initWithBackForwardList:(CWVBackForwardList*)list
                             isBackList:(BOOL)isBackList {
  self = [super init];
  if (self) {
    _list = list;
    _isBackList = isBackList;

    _fastEnumerationItemCache = nil;
  }
  return self;
}

- (NSUInteger)count {
  if (!self.list.navigationManager) {
    return 0;
  }

  int currentIndex = self.list.navigationManager->GetLastCommittedItemIndex();
  if (currentIndex == -1) {
    // If there is no item in the list, currentIndex will be -1 so at that time
    // |self| should be empty array.
    return 0;
  }

  if (_isBackList) {
    return currentIndex;
  }
  // |self| is forwardList.
  int count = self.list.navigationManager->GetItemCount();
  DCHECK(count >= currentIndex + 1);
  return count - currentIndex - 1;
}

- (CWVBackForwardListItem*)objectAtIndexedSubscript:(NSUInteger)index {
  if (index >= self.count) {
    [NSException raise:NSRangeException
                format:@"The index of %@ is out of boundary",
                       _isBackList ? @"backList" : @"forwardList"];
  }

  int internalIndex;
  if (_isBackList) {
    internalIndex = index;
  } else {
    internalIndex =
        self.list.navigationManager->GetLastCommittedItemIndex() + 1 + index;
  }

  web::NavigationItem* item =
      self.list.navigationManager->GetItemAtIndex(internalIndex);
  DCHECK(item);
  // TODO(crbug.com/462520371): This is a defensive check to prevent a crash.
  // The root cause of the null item needs to be investigated.
  if (!item) {
    return nil;
  }

  return [[CWVBackForwardListItem alloc] initWithNavigationItem:item];
}

#pragma mark - NSFastEnumeration

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state
                                  objects:(id __unsafe_unretained[])stackbuf
                                    count:(NSUInteger)len {
  DCHECK_GE(self.count, state->state);
  DCHECK_GE(len, 1UL);
  if (self.count == state->state) {
    return 0;
  }

  // |state| is a pure C-struct so retaining an item in |state| is impossible.
  // So, |_fastEnumerationItemCache| is designed to retain the returned item.
  // It is the caller's responsibility to ensure this is not deallocated during
  // enumeration, so a caller should not use nested for-in statement on the same
  // list, otherwise it will cause a use-after-free bug.
  _fastEnumerationItemCache = [self objectAtIndexedSubscript:state->state];

  // This tells Obj-C runtime that |self| is immutable (because
  // |&state->extra[0]| is constant) during enumeration.
  state->mutationsPtr = &state->extra[0];

  state->itemsPtr = stackbuf;
  state->itemsPtr[0] = _fastEnumerationItemCache;
  ++state->state;
  return 1;
}

@end

@implementation CWVBackForwardList

@synthesize backList = _backList;
@synthesize forwardList = _forwardList;

- (instancetype)initWithNavigationManager:
    (const web::NavigationManager*)navigationManager {
  self = [super init];
  if (self) {
    DCHECK(navigationManager);
    _navigationManager = navigationManager;
    _forwardList =
        [[CWVBackForwardListItemArray alloc] initWithBackForwardList:self
                                                          isBackList:NO];
    _backList =
        [[CWVBackForwardListItemArray alloc] initWithBackForwardList:self
                                                          isBackList:YES];
  }
  return self;
}

- (CWVBackForwardListItem*)currentItem {
  if (!self.navigationManager) {
    return nil;
  }

  web::NavigationItem* item = self.navigationManager->GetLastCommittedItem();
  if (!item) {
    return nil;
  }
  return [[CWVBackForwardListItem alloc] initWithNavigationItem:item];
}

- (CWVBackForwardListItem*)backItem {
  if (self.backList.count == 0) {
    return nil;
  }
  return self.backList[self.backList.count - 1];
}

- (CWVBackForwardListItem*)forwardItem {
  if (self.forwardList.count == 0) {
    return nil;
  }
  return self.forwardList[0];
}

- (CWVBackForwardListItem*)itemAtIndex:(NSInteger)index {
  if (index == 0) {
    return self.currentItem;
  } else if (index < 0) {
    NSInteger internalIndex = self.backList.count + index;
    if (internalIndex < 0) {
      return nil;
    }
    return self.backList[internalIndex];
  } else {
    NSUInteger internalIndex = index - 1;
    if (internalIndex >= self.forwardList.count) {
      return nil;
    }
    return self.forwardList[internalIndex];
  }
}

- (int)internalIndexOfItem:(CWVBackForwardListItem*)item {
  int count = self.navigationManager->GetItemCount();
  for (int i = 0; i < count; i++) {
    if (item.uniqueID ==
        self.navigationManager->GetItemAtIndex(i)->GetUniqueID()) {
      return i;
    }
  }
  return -1;
}

@end
