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

#import "image_copier.h"

#include <MobileCoreServices/MobileCoreServices.h>

#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#import "base/strings/sys_string_conversions.h"
#include "base/task/post_task.h"
#include "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
#import "ios/chrome/browser/ui/image_util/image_util.h"
#import "ios/chrome/browser/web/image_fetch_tab_helper.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ios/web/public/thread/web_task_traits.h"
#include "ios/web/public/thread/web_thread.h"
#include "ui/base/l10n/l10n_util.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

namespace {
// Name of the UMA ContextMenu.iOS.CopyImage histogram.
const char kUmaContextMenuCopyImage[] = "ContextMenu.iOS.CopyImage";
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// Enum for the ContextMenu.iOS.CopyImage UMA histogram to report
// the results of Copy Image.
enum class ContextMenuCopyImage {
  // Copy Image is called.
  kInvoked = 0,
  // Image data is fetched.
  kImageFetched = 1,
  // Image data is fetched, and Copy Image is not canceled by user.
  kTryCopyImage = 2,
  // Fetched image data is valid and copied to pasteboard.
  kImageCopied = 3,
  // Fetching image data takes too long, a waiting alert popped up.
  kAlertPopUp = 4,
  // Copy Image is canceled by user from the alert.
  kCanceled = 5,
  kMaxValue = kCanceled,
};
// Time Period between "Copy Image" is clicked and "Copying..." alert is
// launched.
const int kAlertDelayInMs = 300;
// A speical id indicates that last copy is finished or canceled and next
// copy has not started.
const int kNoActiveCopy = 0;
}

@interface ImageCopier ()
// Base view controller for the alerts.
@property(nonatomic, weak) UIViewController* baseViewController;
// The browser.
@property(nonatomic, assign) Browser* browser;
// Alert coordinator to give feedback to the user.
@property(nonatomic, strong) AlertCoordinator* alertCoordinator;
// A counter which generates one ID for each call on
// CopyImageAtURL:referrer:webState.
@property(nonatomic) int idGenerator;
// ID of current active copy. A copy is active after
// CopyImageAtURL:referrer:webState is called, and before user cancels the
// copy or the copy finishes.
@property(nonatomic) int activeID;

@end

@implementation ImageCopier

@synthesize alertCoordinator = _alertCoordinator;
@synthesize baseViewController = _baseViewController;
@synthesize idGenerator = _idGenerator;
@synthesize activeID = _activeID;
@synthesize browser = _browser;

- (instancetype)initWithBaseViewController:(UIViewController*)baseViewController
                                   browser:(Browser*)browser {
  self = [super init];
  if (self) {
    self.idGenerator = 1;
    self.activeID = kNoActiveCopy;
    self.baseViewController = baseViewController;
    self.browser = browser;
  }
  return self;
}

- (void)copyImageAtURL:(const GURL&)url
              referrer:(const web::Referrer&)referrer
              webState:(web::WebState*)webState {
  __weak ImageCopier* weakSelf = self;

  // |idGenerator| is initiated to 1 and incremented by 2, so it will always be
  // odd number and won't collides with |kNoActiveCopy| or nil.activeID, which
  // are 0s.
  self.idGenerator += 2;
  self.activeID = self.idGenerator;
  // This var is to be captured by blocks in ImageFetchTabHelper::GetImageData
  // and web::WebThread::PostDelayedTask. When a block is invoked, it uses the
  // captured |callbackID| to check if the copy from where it's started is still
  // alive or has been finished/canceled.
  int callbackID = self.idGenerator;

  ImageFetchTabHelper* tabHelper = ImageFetchTabHelper::FromWebState(webState);
  DCHECK(tabHelper);
  NSString* urlStr = base::SysUTF8ToNSString(url.spec());
  tabHelper->GetImageData(url, referrer, ^(NSData* data) {
    // Check that the copy has not been canceled.
    if (callbackID == weakSelf.activeID) {
      [weakSelf.alertCoordinator stop];
      weakSelf.activeID = kNoActiveCopy;

      // Copy image url and data to pasteboard.
      NSMutableDictionary* item =
          [NSMutableDictionary dictionaryWithCapacity:3];
      [item setValue:urlStr forKey:(__bridge NSString*)kUTTypeText];
      [item setValue:[NSURL URLWithString:urlStr]
              forKey:(__bridge NSString*)kUTTypeURL];
      NSString* uti = GetImageUTIFromData(data);
      if (uti) {
        [item setValue:data forKey:uti];
        [weakSelf recordCopyImageUMA:ContextMenuCopyImage::kImageCopied];
      }
      UIPasteboard.generalPasteboard.items =
          [NSMutableArray arrayWithObject:item];
      [weakSelf recordCopyImageUMA:ContextMenuCopyImage::kTryCopyImage];
    }
    [weakSelf recordCopyImageUMA:ContextMenuCopyImage::kImageFetched];
  });

  // Dismiss current alert.
  [self.alertCoordinator stop];
  self.alertCoordinator = [[AlertCoordinator alloc]
      initWithBaseViewController:self.baseViewController
                         browser:self.browser
                           title:l10n_util::GetNSStringWithFixup(
                                     IDS_IOS_CONTENT_COPYIMAGE_ALERT_COPYING)
                         message:nil];
  [self.alertCoordinator
      addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_CANCEL)
                action:^() {
                  // Cancels current copy and closes the alert.
                  weakSelf.activeID = kNoActiveCopy;
                  [weakSelf.alertCoordinator stop];
                  [weakSelf recordCopyImageUMA:ContextMenuCopyImage::kCanceled];
                }
                 style:UIAlertActionStyleCancel];

  // Delays launching alert by |kAlertDelayInMs|.
  base::PostDelayedTask(
      FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
        // Checks that the copy has not finished yet.
        if (callbackID == weakSelf.activeID) {
          [weakSelf.alertCoordinator start];
          [weakSelf recordCopyImageUMA:ContextMenuCopyImage::kAlertPopUp];
        }
      }),
      base::TimeDelta::FromMilliseconds(kAlertDelayInMs));

  [self recordCopyImageUMA:ContextMenuCopyImage::kInvoked];
}

- (void)recordCopyImageUMA:(ContextMenuCopyImage)UMAEnum {
  UMA_HISTOGRAM_ENUMERATION(kUmaContextMenuCopyImage, UMAEnum);
}

@end
