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

#import "ios/web_view/public/cwv_user_content_controller.h"

#import "base/apple/foundation_util.h"
#import "base/json/json_writer.h"
#import "base/strings/sys_string_conversions.h"
#import "ios/web_view/internal/cwv_user_content_controller_internal.h"
#import "ios/web_view/internal/cwv_web_view_configuration_internal.h"
#import "ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.h"
#import "ios/web_view/internal/web_view_browser_state.h"
#import "ios/web_view/internal/web_view_message_handler_java_script_feature.h"
#import "ios/web_view/public/cwv_user_script.h"

namespace {

// Converts base::DictValue to NSDictionary.
NSDictionary* NSDictionaryFromDictValue(const base::DictValue& value) {
  std::string json;
  const bool success = base::JSONWriter::Write(value, &json);
  DCHECK(success) << "Failed to convert base::Value to JSON";

  NSData* json_data = [NSData dataWithBytes:json.c_str() length:json.length()];
  NSDictionary* ns_dictionary = base::apple::ObjCCastStrict<NSDictionary>(
      [NSJSONSerialization JSONObjectWithData:json_data
                                      options:kNilOptions
                                        error:nil]);
  DCHECK(ns_dictionary) << "Failed to convert JSON to NSDictionary";
  return ns_dictionary;
}

}  // namespace

@interface CWVUserContentController ()
@property(weak, nonatomic) CWVWebViewConfiguration* configuration;
@end

@implementation CWVUserContentController {
  NSMutableArray<CWVUserScript*>* _userScripts;
}

@synthesize configuration = _configuration;

- (nonnull instancetype)initWithConfiguration:
    (nonnull __weak CWVWebViewConfiguration*)configuration {
  self = [super init];
  if (self) {
    _configuration = configuration;
    _userScripts = [[NSMutableArray alloc] init];
  }
  return self;
}

- (void)addUserScript:(nonnull CWVUserScript*)userScript {
  [_userScripts addObject:userScript];
  [self updatePageScripts];
}

- (void)removeAllUserScripts {
  [_userScripts removeAllObjects];
  [self updatePageScripts];
}

- (nonnull NSArray<CWVUserScript*>*)userScripts {
  return _userScripts;
}

// Updates the early page script associated with the BrowserState with the
// content of _userScripts.
- (void)updatePageScripts {
  NSMutableString* joinedAllFramesScript = [[NSMutableString alloc] init];
  NSMutableString* joinedMainFrameScript = [[NSMutableString alloc] init];
  NSMutableString* joinedAllFramesDocEndScript = [[NSMutableString alloc] init];
  NSMutableString* joinedMainFrameDocEndScript = [[NSMutableString alloc] init];
  for (CWVUserScript* script in _userScripts) {
    // Inserts "\n" between scripts to make it safer to join multiple scripts,
    // in case the first script doesn't end with ";" or "\n".
    if (script.injectionTime == CWVUserScriptInjectionTimeAtDocumentEnd) {
      if (script.isForMainFrameOnly) {
        [joinedMainFrameDocEndScript appendString:script.source];
        [joinedMainFrameDocEndScript appendString:@"\n"];
      } else {
        [joinedAllFramesDocEndScript appendString:script.source];
        [joinedAllFramesDocEndScript appendString:@"\n"];
      }
    } else if (script.isForMainFrameOnly) {
      [joinedMainFrameScript appendString:script.source];
      [joinedMainFrameScript appendString:@"\n"];
    } else {
      [joinedAllFramesScript appendString:script.source];
      [joinedAllFramesScript appendString:@"\n"];
    }
  }
  WebViewScriptsJavaScriptFeature::FromBrowserState(_configuration.browserState)
      ->SetScripts(base::SysNSStringToUTF8(joinedAllFramesScript),
                   base::SysNSStringToUTF8(joinedMainFrameScript),
                   base::SysNSStringToUTF8(joinedAllFramesDocEndScript),
                   base::SysNSStringToUTF8(joinedMainFrameDocEndScript));
}

- (void)addMessageHandler:(void (^)(NSDictionary* payload))handler
               forCommand:(NSString*)nsCommand {
  DCHECK(handler);
  std::string command = base::SysNSStringToUTF8(nsCommand);
  WebViewMessageHandlerJavaScriptFeature::FromBrowserState(
      _configuration.browserState)
      ->RegisterHandler(command,
                        base::BindRepeating(^(const base::DictValue& payload) {
                          handler(NSDictionaryFromDictValue(payload));
                        }));
}

- (void)removeMessageHandlerForCommand:(NSString*)nsCommand {
  std::string command = base::SysNSStringToUTF8(nsCommand);
  WebViewMessageHandlerJavaScriptFeature::FromBrowserState(
      _configuration.browserState)
      ->UnregisterHandler(command);
}

- (BOOL)isMessageHandlerRegisteredForCommand:(NSString*)nsCommand {
  std::string command = base::SysNSStringToUTF8(nsCommand);
  return WebViewMessageHandlerJavaScriptFeature::FromBrowserState(
             _configuration.browserState)
      ->IsHandlerRegistered(command);
}

@end
