// Copyright 2013 The Flutter 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 <XCTest/XCTest.h>

#import "flutter/shell/platform/darwin/ios/platform_message_handler_ios.h"

#import "flutter/common/task_runners.h"
#import "flutter/fml/message_loop.h"
#import "flutter/fml/thread.h"
#import "flutter/lib/ui/window/platform_message.h"
#import "flutter/lib/ui/window/platform_message_response.h"
#import "flutter/shell/common/thread_host.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"

FLUTTER_ASSERT_NOT_ARC

namespace {
using namespace flutter;
fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
  auto thread = std::make_unique<fml::Thread>(name);
  auto runner = thread->GetTaskRunner();
  return runner;
}

fml::RefPtr<fml::TaskRunner> GetCurrentTaskRunner() {
  fml::MessageLoop::EnsureInitializedForCurrentThread();
  return fml::MessageLoop::GetCurrent().GetTaskRunner();
}

class MockPlatformMessageResponse : public PlatformMessageResponse {
 public:
  static fml::RefPtr<MockPlatformMessageResponse> Create() {
    return fml::AdoptRef(new MockPlatformMessageResponse());
  }
  void Complete(std::unique_ptr<fml::Mapping> data) override { is_complete_ = true; }
  void CompleteEmpty() override { is_complete_ = true; }
};
}  // namespace

@interface PlatformMessageHandlerIosTest : XCTestCase
@end

@implementation PlatformMessageHandlerIosTest
- (void)testCreate {
  TaskRunners task_runners("test", GetCurrentTaskRunner(), CreateNewThread("raster"),
                           CreateNewThread("ui"), CreateNewThread("io"));
  auto handler = std::make_unique<PlatformMessageHandlerIos>(task_runners.GetPlatformTaskRunner());
  XCTAssertTrue(handler);
}

- (void)testSetAndCallHandler {
  ThreadHost thread_host("io.flutter.test." + std::string(self.name.UTF8String),
                         ThreadHost::Type::RASTER | ThreadHost::Type::IO | ThreadHost::Type::UI);
  TaskRunners task_runners(
      "test", GetCurrentTaskRunner(), thread_host.raster_thread->GetTaskRunner(),
      thread_host.ui_thread->GetTaskRunner(), thread_host.io_thread->GetTaskRunner());

  auto handler = std::make_unique<PlatformMessageHandlerIos>(task_runners.GetPlatformTaskRunner());
  std::string channel = "foo";
  XCTestExpectation* didCallReply = [self expectationWithDescription:@"didCallReply"];
  handler->SetMessageHandler(
      channel,
      ^(NSData* _Nullable data, FlutterBinaryReply _Nonnull reply) {
        reply(nil);
        [didCallReply fulfill];
      },
      nil);
  auto response = MockPlatformMessageResponse::Create();
  task_runners.GetUITaskRunner()->PostTask([channel, response, &handler] {
    auto platform_message = std::make_unique<flutter::PlatformMessage>(channel, response);
    handler->HandlePlatformMessage(std::move(platform_message));
  });
  [self waitForExpectationsWithTimeout:1.0 handler:nil];
  XCTAssertTrue(response->is_complete());
}

- (void)testSetClearAndCallHandler {
  ThreadHost thread_host("io.flutter.test." + std::string(self.name.UTF8String),
                         ThreadHost::Type::RASTER | ThreadHost::Type::IO | ThreadHost::Type::UI);
  TaskRunners task_runners(
      "test", GetCurrentTaskRunner(), thread_host.raster_thread->GetTaskRunner(),
      thread_host.ui_thread->GetTaskRunner(), thread_host.io_thread->GetTaskRunner());

  auto handler = std::make_unique<PlatformMessageHandlerIos>(task_runners.GetPlatformTaskRunner());
  std::string channel = "foo";
  XCTestExpectation* didCallMessage = [self expectationWithDescription:@"didCallMessage"];
  handler->SetMessageHandler(
      channel,
      ^(NSData* _Nullable data, FlutterBinaryReply _Nonnull reply) {
        XCTFail(@"This shouldn't be called");
        reply(nil);
      },
      nil);
  handler->SetMessageHandler(channel, nil, nil);
  auto response = MockPlatformMessageResponse::Create();
  task_runners.GetUITaskRunner()->PostTask([channel, response, &handler, &didCallMessage] {
    auto platform_message = std::make_unique<flutter::PlatformMessage>(channel, response);
    handler->HandlePlatformMessage(std::move(platform_message));
    [didCallMessage fulfill];
  });
  [self waitForExpectationsWithTimeout:1.0 handler:nil];
  XCTAssertTrue(response->is_complete());
}

- (void)testSetAndCallHandlerTaskQueue {
  ThreadHost thread_host("io.flutter.test." + std::string(self.name.UTF8String),
                         ThreadHost::Type::RASTER | ThreadHost::Type::IO | ThreadHost::Type::UI);
  TaskRunners task_runners(
      "test", GetCurrentTaskRunner(), thread_host.raster_thread->GetTaskRunner(),
      thread_host.ui_thread->GetTaskRunner(), thread_host.io_thread->GetTaskRunner());

  auto handler = std::make_unique<PlatformMessageHandlerIos>(task_runners.GetPlatformTaskRunner());
  std::string channel = "foo";
  XCTestExpectation* didCallReply = [self expectationWithDescription:@"didCallReply"];
  NSObject<FlutterTaskQueue>* taskQueue = PlatformMessageHandlerIos::MakeBackgroundTaskQueue();
  handler->SetMessageHandler(
      channel,
      ^(NSData* _Nullable data, FlutterBinaryReply _Nonnull reply) {
        XCTAssertFalse([NSThread isMainThread]);
        reply(nil);
        [didCallReply fulfill];
      },
      taskQueue);
  auto response = MockPlatformMessageResponse::Create();
  task_runners.GetUITaskRunner()->PostTask([channel, response, &handler] {
    auto platform_message = std::make_unique<flutter::PlatformMessage>(channel, response);
    handler->HandlePlatformMessage(std::move(platform_message));
  });
  [self waitForExpectationsWithTimeout:1.0 handler:nil];
  XCTAssertTrue(response->is_complete());
}
@end
