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

#include "ui/compositor/test/test_compositor_host.h"

#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>

#include <memory>

#include "base/apple/scoped_nsautorelease_pool.h"
#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/stack_allocated.h"
#include "base/task/single_thread_task_runner.h"
#include "components/viz/common/surfaces/local_surface_id.h"
#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
#include "ui/accelerated_widget_mac/accelerated_widget_mac.h"
#include "ui/compositor/compositor.h"
#include "ui/gfx/geometry/rect.h"

// AcceleratedTestView provides an NSView class that delegates drawing to a
// ui::Compositor delegate, setting up the NSOpenGLContext as required.
@interface AcceleratedTestView : NSView {
  raw_ptr<ui::Compositor> _compositor;
}
// Designated initializer.
- (instancetype)init;
- (void)setCompositor:(ui::Compositor*)compositor;
@end

@implementation AcceleratedTestView
- (instancetype)init {
  // The frame will be resized when reparented into the window's view hierarchy.
  if ((self = [super initWithFrame:NSZeroRect])) {
    self.wantsLayer = YES;
  }
  return self;
}

- (void)setCompositor:(ui::Compositor*)compositor {
  _compositor = compositor;
}

- (void)drawRect:(NSRect)rect {
  DCHECK(_compositor) << "Drawing with no compositor set.";
  _compositor->ScheduleFullRedraw();
}
@end

namespace ui {

// Tests that use Objective-C memory semantics need to have a top-level
// autoreleasepool set up and initialized prior to execution and drained upon
// exit.  The tests will leak otherwise.
class FoundationHost {
 public:
  FoundationHost(const FoundationHost&) = delete;
  FoundationHost& operator=(const FoundationHost&) = delete;

 protected:
  FoundationHost() = default;
  virtual ~FoundationHost() = default;

 private:
  STACK_ALLOCATED_IGNORE("https://crbug.com/1424190")
  base::apple::ScopedNSAutoreleasePool pool_;
};

// Tests that use the AppKit framework need to have the NSApplication
// initialized prior to doing anything with display objects such as windows,
// views, or controls.
class AppKitHost : public FoundationHost {
 public:
  AppKitHost(const AppKitHost&) = delete;
  AppKitHost& operator=(const AppKitHost&) = delete;

 protected:
  AppKitHost() { [NSApplication sharedApplication]; }
  ~AppKitHost() override = default;
};

class TestAcceleratedWidgetMacNSView : public AcceleratedWidgetMacNSView {
 public:
  explicit TestAcceleratedWidgetMacNSView(NSView* view) : view_(view) {}

  TestAcceleratedWidgetMacNSView(const TestAcceleratedWidgetMacNSView&) =
      delete;
  TestAcceleratedWidgetMacNSView& operator=(
      const TestAcceleratedWidgetMacNSView&) = delete;

  virtual ~TestAcceleratedWidgetMacNSView() = default;

  // AcceleratedWidgetMacNSView
  void AcceleratedWidgetCALayerParamsUpdated(gfx::CALayerParams) override {}

 private:
  NSView* __strong view_ [[maybe_unused]];
};

// TestCompositorHostMac provides a window surface and a coordinated compositor
// for use in the compositor unit tests.
class TestCompositorHostMac : public TestCompositorHost, public AppKitHost {
 public:
  TestCompositorHostMac(const gfx::Rect& bounds,
                        ui::ContextFactory* context_factory);

  TestCompositorHostMac(const TestCompositorHostMac&) = delete;
  TestCompositorHostMac& operator=(const TestCompositorHostMac&) = delete;

  ~TestCompositorHostMac() override;

 private:
  // TestCompositorHost:
  void Show() override;
  ui::Compositor* GetCompositor() override;

  gfx::Rect bounds_;

  ui::Compositor compositor_;
  ui::AcceleratedWidgetMac accelerated_widget_;
  std::unique_ptr<TestAcceleratedWidgetMacNSView>
      test_accelerated_widget_nsview_;

  NSWindow* __strong window_;
  viz::ParentLocalSurfaceIdAllocator allocator_;
};

TestCompositorHostMac::TestCompositorHostMac(
    const gfx::Rect& bounds,
    ui::ContextFactory* context_factory)
    : bounds_(bounds),
      compositor_(context_factory->AllocateFrameSinkId(),
                  context_factory,
                  base::SingleThreadTaskRunner::GetCurrentDefault(),
                  /*enable_pixel_canvas=*/false) {}

TestCompositorHostMac::~TestCompositorHostMac() {
  accelerated_widget_.ResetNSView();

  // Release reference to |compositor_|.  Important because the |compositor_|
  // holds |this| as its delegate, so that reference must be removed here.
  [window_.contentView setCompositor:nullptr];
  window_.contentView = [[NSView alloc] initWithFrame:NSZeroRect];

  [window_ orderOut:nil];
  [window_ close];
}

void TestCompositorHostMac::Show() {
  DCHECK(!window_);
  window_ = [[NSWindow alloc]
      initWithContentRect:NSMakeRect(bounds_.x(), bounds_.y(), bounds_.width(),
                                     bounds_.height())
                styleMask:NSWindowStyleMaskBorderless
                  backing:NSBackingStoreBuffered
                    defer:NO];
  window_.releasedWhenClosed = NO;
  AcceleratedTestView* view = [[AcceleratedTestView alloc] init];
  test_accelerated_widget_nsview_ =
      std::make_unique<TestAcceleratedWidgetMacNSView>(view);
  allocator_.GenerateId();
  accelerated_widget_.SetNSView(test_accelerated_widget_nsview_.get());
  compositor_.SetAcceleratedWidget(accelerated_widget_.accelerated_widget());
  compositor_.SetScaleAndSize(1.0f, bounds_.size(),
                              allocator_.GetCurrentLocalSurfaceId());
  [view setCompositor:&compositor_];
  window_.contentView = view;
  [window_ orderFront:nil];
}

ui::Compositor* TestCompositorHostMac::GetCompositor() {
  return &compositor_;
}

// static
TestCompositorHost* TestCompositorHost::Create(
    const gfx::Rect& bounds,
    ui::ContextFactory* context_factory) {
  return new TestCompositorHostMac(bounds, context_factory);
}

}  // namespace ui
