// 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 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'dart:io';
import 'dart:ui';
import 'dart:zircon';

import 'package:args/args.dart';
import 'package:vector_math/vector_math_64.dart' as vector_math_64;

final _argsCsvFilePath = '/config/data/args.csv';

void main(List<String> args) async {
  print('Launching embedding-flutter-view');

  args = args + _GetArgsFromConfigFile();
  final parser = ArgParser()
    ..addFlag('showOverlay', defaultsTo: false)
    ..addFlag('focusable', defaultsTo: true);

  final arguments = parser.parse(args);
  for (final option in arguments.options) {
    print('embedding-flutter-view args: $option: ${arguments[option]}');
  }

  TestApp app = TestApp(
    ChildView(await _launchChildView()),
    showOverlay: arguments['showOverlay'],
    focusable: arguments['focusable'],
  );

  app.run();
}

class TestApp {
  static const _black = Color.fromARGB(255, 0, 0, 0);
  static const _blue = Color.fromARGB(255, 0, 0, 255);

  final ChildView childView;
  final bool showOverlay;
  final bool focusable;

  Color _backgroundColor = _blue;

  TestApp(
    this.childView,
    {this.showOverlay = false,
    this.focusable = true}) {
  }

  void run() {
    childView.create(focusable, (ByteData reply) {
        // Set up window callbacks.
        window.onPointerDataPacket = (PointerDataPacket packet) {
          this.pointerDataPacket(packet);
        };
        window.onMetricsChanged = () {
          window.scheduleFrame();
        };
        window.onBeginFrame = (Duration duration) {
          this.beginFrame(duration);
        };

        // The child view should be attached to Scenic now.
        // Ready to build the scene.
        window.scheduleFrame();
      });
  }

  void beginFrame(Duration duration) {
    // Convert physical screen size of device to values
    final pixelRatio = window.devicePixelRatio;
    final size = window.physicalSize / pixelRatio;
    final physicalBounds = Offset.zero & window.physicalSize;
    final windowBounds = Offset.zero & size;
    // Set up a Canvas that uses the screen size
    final recorder = PictureRecorder();
    final canvas = Canvas(recorder, physicalBounds);
    canvas.scale(pixelRatio);
    // Draw something
    final paint = Paint()..color = this._backgroundColor;
    canvas.drawRect(windowBounds, paint);
    final picture = recorder.endRecording();
    // Build the scene
    final sceneBuilder = SceneBuilder()
      ..pushClipRect(physicalBounds)
      ..addPicture(Offset.zero, picture);

    final childPhysicalSize = window.physicalSize * 0.25;
    // Alignment.center
    final windowCenter = size.center(Offset.zero);
    final windowPhysicalCenter = window.physicalSize.center(Offset.zero);
    final childPhysicalOffset = windowPhysicalCenter - childPhysicalSize.center(Offset.zero);

    sceneBuilder
      ..pushTransform(
        vector_math_64.Matrix4.translationValues(childPhysicalOffset.dx,
                                                 childPhysicalOffset.dy,
                                                 0.0).storage)
      ..addPlatformView(childView.viewId,
                        width: childPhysicalSize.width,
                        height: childPhysicalSize.height)
      ..pop();

    if (showOverlay) {
      final containerSize = size * 0.5;
      // Alignment.center
      final containerOffset = windowCenter - containerSize.center(Offset.zero);

      final overlaySize = containerSize * 0.5;
      // Alignment.topRight
      final overlayOffset = Offset(
        containerOffset.dx + containerSize.width - overlaySize.width,
        containerOffset.dy);
      final overlayPhysicalSize = overlaySize * pixelRatio;
      final overlayPhysicalOffset = overlayOffset * pixelRatio;
      final overlayPhysicalBounds = overlayPhysicalOffset & overlayPhysicalSize;

      final recorder = PictureRecorder();
      final overlayCullRect = Offset.zero & overlayPhysicalSize; // in canvas physical coordinates
      final canvas = Canvas(recorder, overlayCullRect);
      canvas.scale(pixelRatio);

      final paint = Paint()..color = Color.fromARGB(255, 0, 255, 0);
      canvas.drawRect(Offset.zero & overlaySize, paint);

      final overlayPicture = recorder.endRecording();
      sceneBuilder
        ..pushClipRect(overlayPhysicalBounds) // in window physical coordinates
        ..addPicture(overlayPhysicalOffset, overlayPicture)
        ..pop();
    }

    sceneBuilder.pop();
    window.render(sceneBuilder.build());
  }

  void pointerDataPacket(PointerDataPacket packet) async {
    int nowNanos = System.clockGetMonotonic();

    for (PointerData data in packet.data) {
      print('embedding-flutter-view received tap: ${data.toStringFull()}');

      if (data.change == PointerChange.down) {
        this._backgroundColor = _black;
      }

      if (data.change == PointerChange.down || data.change == PointerChange.move) {
        _reportTouchInput(
          localX: data.physicalX,
          localY: data.physicalY,
          timeReceived: nowNanos,
        );
      }
    }

    window.scheduleFrame();
  }

  void _reportTouchInput({double localX, double localY, int timeReceived}) {
    print('embedding-flutter-view reporting touch input to TouchInputListener');
    final message = utf8.encode(json.encode({
      'method': 'TouchInputListener.ReportTouchInput',
      'local_x': localX,
      'local_y': localY,
      'time_received': timeReceived,
      'component_name': 'embedding-flutter-view',
    })).buffer.asByteData();
    PlatformDispatcher.instance.sendPlatformMessage('fuchsia/input_test', message, null);
  }
}

class ChildView {
  final int viewId;

  ChildView(this.viewId);

  void create(
    bool focusable,
    PlatformMessageResponseCallback callback) {
    // Construct the dart:ui platform message to create the view, and when the
    // return callback is invoked, build the scene. At that point, it is safe
    // to embed the child view in the scene.
    final viewOcclusionHint = Rect.zero;
    final Map<String, dynamic> args = <String, dynamic>{
      'viewId': viewId,
      // Flatland doesn't support disabling hit testing.
      'hitTestable': true,
      'focusable': focusable,
      'viewOcclusionHintLTRB': <double>[
        viewOcclusionHint.left,
        viewOcclusionHint.top,
        viewOcclusionHint.right,
        viewOcclusionHint.bottom
      ],
    };

    final ByteData createViewMessage = utf8.encode(
      json.encode(<String, Object>{
        'method': 'View.create',
        'args': args,
      })
    ).buffer.asByteData();

    final platformViewsChannel = 'flutter/platform_views';

    PlatformDispatcher.instance.sendPlatformMessage(
      platformViewsChannel,
      createViewMessage,
      callback);
  }
}

Future<int> _launchChildView() async {
  final message = Int8List.fromList([0x31]);
  final completer = new Completer<ByteData>();
  PlatformDispatcher.instance.sendPlatformMessage(
      'fuchsia/child_view', ByteData.sublistView(message), (ByteData reply) {
    completer.complete(reply);
  });

  return int.parse(
      ascii.decode(((await completer.future).buffer.asUint8List())));
}


List<String> _GetArgsFromConfigFile() {
  List<String> args;
  final f = File(_argsCsvFilePath);
  if (!f.existsSync()) {
    return List.empty();
  }
  final fileContentCsv = f.readAsStringSync();
  args = fileContentCsv.split('\n');
  return args;
}
