// Copyright 2014 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 'dart:ui';

import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  final RendererBinding binding = RenderingFlutterBinding.ensureInitialized();

  test('Adding/removing renderviews updates renderViews getter', () {
    final FlutterView flutterView = FakeFlutterView();
    final RenderView view = RenderView(view: flutterView);

    expect(binding.renderViews, isEmpty);
    binding.addRenderView(view);
    expect(binding.renderViews, contains(view));
    expect(view.configuration.devicePixelRatio, flutterView.devicePixelRatio);
    expect(view.configuration.size, flutterView.physicalSize / flutterView.devicePixelRatio);

    binding.removeRenderView(view);
    expect(binding.renderViews, isEmpty);
  });

  test('illegal add/remove renderviews', () {
    final FlutterView flutterView = FakeFlutterView();
    final RenderView view1 = RenderView(view: flutterView);
    final RenderView view2 = RenderView(view: flutterView);
    final RenderView view3 = RenderView(view: FakeFlutterView(viewId: 200));

    expect(binding.renderViews, isEmpty);
    binding.addRenderView(view1);
    expect(binding.renderViews, contains(view1));

    expect(() => binding.addRenderView(view1), throwsAssertionError);
    expect(() => binding.addRenderView(view2), throwsAssertionError);
    expect(() => binding.removeRenderView(view2), throwsAssertionError);
    expect(() => binding.removeRenderView(view3), throwsAssertionError);

    expect(binding.renderViews, contains(view1));
    binding.removeRenderView(view1);
    expect(binding.renderViews, isEmpty);
    expect(() => binding.removeRenderView(view1), throwsAssertionError);
    expect(() => binding.removeRenderView(view2), throwsAssertionError);
  });

  test('changing metrics updates configuration', () {
    final FakeFlutterView flutterView = FakeFlutterView();
    final RenderView view = RenderView(view: flutterView);
    binding.addRenderView(view);
    expect(view.configuration.devicePixelRatio, 2.5);
    expect(view.configuration.size, const Size(160.0, 240.0));

    flutterView.devicePixelRatio = 3.0;
    flutterView.physicalSize = const Size(300, 300);
    binding.handleMetricsChanged();
    expect(view.configuration.devicePixelRatio, 3.0);
    expect(view.configuration.size, const Size(100.0, 100.0));

    binding.removeRenderView(view);
  });

  test('semantics actions are performed on the right view', () {
    final FakeFlutterView flutterView1 = FakeFlutterView(viewId: 1);
    final FakeFlutterView flutterView2 = FakeFlutterView(viewId: 2);
    final RenderView renderView1 = RenderView(view: flutterView1);
    final RenderView renderView2 = RenderView(view: flutterView2);
    final PipelineOwnerSpy owner1 = PipelineOwnerSpy()
      ..rootNode = renderView1;
    final PipelineOwnerSpy owner2 = PipelineOwnerSpy()
      ..rootNode = renderView2;

    binding.addRenderView(renderView1);
    binding.addRenderView(renderView2);

    binding.performSemanticsAction(
      const SemanticsActionEvent(type: SemanticsAction.copy, viewId: 1, nodeId: 11),
    );
    expect(owner1.semanticsOwner.performedActions.single, (11, SemanticsAction.copy, null));
    expect(owner2.semanticsOwner.performedActions, isEmpty);
    owner1.semanticsOwner.performedActions.clear();

    binding.performSemanticsAction(
      const SemanticsActionEvent(type: SemanticsAction.tap, viewId: 2, nodeId: 22),
    );
    expect(owner1.semanticsOwner.performedActions, isEmpty);
    expect(owner2.semanticsOwner.performedActions.single, (22, SemanticsAction.tap, null));
    owner2.semanticsOwner.performedActions.clear();

    binding.performSemanticsAction(
      const SemanticsActionEvent(type: SemanticsAction.tap, viewId: 3, nodeId: 22),
    );
    expect(owner1.semanticsOwner.performedActions, isEmpty);
    expect(owner2.semanticsOwner.performedActions, isEmpty);

    binding.removeRenderView(renderView1);
    binding.removeRenderView(renderView2);
  });

  test('all registered renderviews are asked to composite frame', () {
    final FakeFlutterView flutterView1 = FakeFlutterView(viewId: 1);
    final FakeFlutterView flutterView2 = FakeFlutterView(viewId: 2);
    final RenderView renderView1 = RenderView(view: flutterView1);
    final RenderView renderView2 = RenderView(view: flutterView2);
    final PipelineOwner owner1 = PipelineOwner()..rootNode = renderView1;
    final PipelineOwner owner2 = PipelineOwner()..rootNode = renderView2;
    binding.rootPipelineOwner.adoptChild(owner1);
    binding.rootPipelineOwner.adoptChild(owner2);
    binding.addRenderView(renderView1);
    binding.addRenderView(renderView2);
    renderView1.prepareInitialFrame();
    renderView2.prepareInitialFrame();

    expect(flutterView1.renderedScenes, isEmpty);
    expect(flutterView2.renderedScenes, isEmpty);

    binding.handleBeginFrame(Duration.zero);
    binding.handleDrawFrame();

    expect(flutterView1.renderedScenes, hasLength(1));
    expect(flutterView2.renderedScenes, hasLength(1));

    binding.removeRenderView(renderView1);

    binding.handleBeginFrame(Duration.zero);
    binding.handleDrawFrame();

    expect(flutterView1.renderedScenes, hasLength(1));
    expect(flutterView2.renderedScenes, hasLength(2));

    binding.removeRenderView(renderView2);

    binding.handleBeginFrame(Duration.zero);
    binding.handleDrawFrame();

    expect(flutterView1.renderedScenes, hasLength(1));
    expect(flutterView2.renderedScenes, hasLength(2));
  });

  test('hit-testing reaches the right view', () {
    final FakeFlutterView flutterView1 = FakeFlutterView(viewId: 1);
    final FakeFlutterView flutterView2 = FakeFlutterView(viewId: 2);
    final RenderView renderView1 = RenderView(view: flutterView1);
    final RenderView renderView2 = RenderView(view: flutterView2);
    binding.addRenderView(renderView1);
    binding.addRenderView(renderView2);

    HitTestResult result = HitTestResult();
    binding.hitTestInView(result, Offset.zero, 1);
    expect(result.path, hasLength(2));
    expect(result.path.first.target, renderView1);
    expect(result.path.last.target, binding);

    result = HitTestResult();
    binding.hitTestInView(result, Offset.zero, 2);
    expect(result.path, hasLength(2));
    expect(result.path.first.target, renderView2);
    expect(result.path.last.target, binding);

    result = HitTestResult();
    binding.hitTestInView(result, Offset.zero, 3);
    expect(result.path.single.target, binding);

    binding.removeRenderView(renderView1);
    binding.removeRenderView(renderView2);
  });
}

class FakeFlutterView extends Fake implements FlutterView  {
  FakeFlutterView({
    this.viewId = 100,
    this.devicePixelRatio = 2.5,
    this.physicalSize = const Size(400,600),
    this.padding = FakeViewPadding.zero,
  });

  @override
  final int viewId;
  @override
  double devicePixelRatio;
  @override
  Size physicalSize;
  @override
  ViewPadding padding;

  List<Scene> renderedScenes = <Scene>[];

  @override
  void render(Scene scene) {
    renderedScenes.add(scene);
  }
}

class PipelineOwnerSpy extends PipelineOwner {
  @override
  final SemanticsOwnerSpy semanticsOwner = SemanticsOwnerSpy();
}

class SemanticsOwnerSpy extends Fake implements SemanticsOwner {
  final List<(int, SemanticsAction, Object?)> performedActions = <(int, SemanticsAction, Object?)>[];

  @override
  void performAction(int id, SemanticsAction action, [ Object? args ]) {
    performedActions.add((id, action, args));
  }
}