mirrored_repositories/awesome-flutter/examples/plugins/packages/google_maps_flutter/google_maps_flutter
// 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:typed_data';

import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class FakePlatformGoogleMap {
  FakePlatformGoogleMap(int id, Map<dynamic, dynamic> params)
      : cameraPosition =
            CameraPosition.fromMap(params['initialCameraPosition']),
        channel = MethodChannel(
            'plugins.flutter.io/google_maps_$id', const StandardMethodCodec()) {
    channel.setMockMethodCallHandler(onMethodCall);
    updateOptions(params['options']);
    updateMarkers(params);
    updatePolygons(params);
    updatePolylines(params);
    updateCircles(params);
    updateTileOverlays(Map.castFrom<dynamic, dynamic, String, dynamic>(params));
  }

  MethodChannel channel;

  CameraPosition? cameraPosition;

  bool? compassEnabled;

  bool? mapToolbarEnabled;

  CameraTargetBounds? cameraTargetBounds;

  MapType? mapType;

  MinMaxZoomPreference? minMaxZoomPreference;

  bool? rotateGesturesEnabled;

  bool? scrollGesturesEnabled;

  bool? tiltGesturesEnabled;

  bool? zoomGesturesEnabled;

  bool? zoomControlsEnabled;

  bool? liteModeEnabled;

  bool? trackCameraPosition;

  bool? myLocationEnabled;

  bool? trafficEnabled;

  bool? buildingsEnabled;

  bool? myLocationButtonEnabled;

  List<dynamic>? padding;

  Set<MarkerId> markerIdsToRemove = <MarkerId>{};

  Set<Marker> markersToAdd = <Marker>{};

  Set<Marker> markersToChange = <Marker>{};

  Set<PolygonId> polygonIdsToRemove = <PolygonId>{};

  Set<Polygon> polygonsToAdd = <Polygon>{};

  Set<Polygon> polygonsToChange = <Polygon>{};

  Set<PolylineId> polylineIdsToRemove = <PolylineId>{};

  Set<Polyline> polylinesToAdd = <Polyline>{};

  Set<Polyline> polylinesToChange = <Polyline>{};

  Set<CircleId> circleIdsToRemove = <CircleId>{};

  Set<Circle> circlesToAdd = <Circle>{};

  Set<Circle> circlesToChange = <Circle>{};

  Set<TileOverlayId> tileOverlayIdsToRemove = <TileOverlayId>{};

  Set<TileOverlay> tileOverlaysToAdd = <TileOverlay>{};

  Set<TileOverlay> tileOverlaysToChange = <TileOverlay>{};

  Future<dynamic> onMethodCall(MethodCall call) {
    switch (call.method) {
      case 'map#update':
        updateOptions(call.arguments['options']);
        return Future<void>.sync(() {});
      case 'markers#update':
        updateMarkers(call.arguments);
        return Future<void>.sync(() {});
      case 'polygons#update':
        updatePolygons(call.arguments);
        return Future<void>.sync(() {});
      case 'polylines#update':
        updatePolylines(call.arguments);
        return Future<void>.sync(() {});
      case 'tileOverlays#update':
        updateTileOverlays(
            Map.castFrom<dynamic, dynamic, String, dynamic>(call.arguments));
        return Future<void>.sync(() {});
      case 'circles#update':
        updateCircles(call.arguments);
        return Future<void>.sync(() {});
      default:
        return Future<void>.sync(() {});
    }
  }

  void updateMarkers(Map<dynamic, dynamic>? markerUpdates) {
    if (markerUpdates == null) {
      return;
    }
    markersToAdd = _deserializeMarkers(markerUpdates['markersToAdd']);
    markerIdsToRemove =
        _deserializeMarkerIds(markerUpdates['markerIdsToRemove']);
    markersToChange = _deserializeMarkers(markerUpdates['markersToChange']);
  }

  Set<MarkerId> _deserializeMarkerIds(List<dynamic>? markerIds) {
    if (markerIds == null) {
      return <MarkerId>{};
    }
    return markerIds.map((dynamic markerId) => MarkerId(markerId)).toSet();
  }

  Set<Marker> _deserializeMarkers(dynamic markers) {
    if (markers == null) {
      return <Marker>{};
    }
    final List<dynamic> markersData = markers;
    final Set<Marker> result = <Marker>{};
    for (Map<dynamic, dynamic> markerData
        in markersData.cast<Map<dynamic, dynamic>>()) {
      final String markerId = markerData['markerId'];
      final double alpha = markerData['alpha'];
      final bool draggable = markerData['draggable'];
      final bool visible = markerData['visible'];

      final dynamic infoWindowData = markerData['infoWindow'];
      InfoWindow infoWindow = InfoWindow.noText;
      if (infoWindowData != null) {
        final Map<dynamic, dynamic> infoWindowMap = infoWindowData;
        infoWindow = InfoWindow(
          title: infoWindowMap['title'],
          snippet: infoWindowMap['snippet'],
        );
      }

      result.add(Marker(
        markerId: MarkerId(markerId),
        draggable: draggable,
        visible: visible,
        infoWindow: infoWindow,
        alpha: alpha,
      ));
    }

    return result;
  }

  void updatePolygons(Map<dynamic, dynamic>? polygonUpdates) {
    if (polygonUpdates == null) {
      return;
    }
    polygonsToAdd = _deserializePolygons(polygonUpdates['polygonsToAdd']);
    polygonIdsToRemove =
        _deserializePolygonIds(polygonUpdates['polygonIdsToRemove']);
    polygonsToChange = _deserializePolygons(polygonUpdates['polygonsToChange']);
  }

  Set<PolygonId> _deserializePolygonIds(List<dynamic>? polygonIds) {
    if (polygonIds == null) {
      return <PolygonId>{};
    }
    return polygonIds.map((dynamic polygonId) => PolygonId(polygonId)).toSet();
  }

  Set<Polygon> _deserializePolygons(dynamic polygons) {
    if (polygons == null) {
      return <Polygon>{};
    }
    final List<dynamic> polygonsData = polygons;
    final Set<Polygon> result = <Polygon>{};
    for (Map<dynamic, dynamic> polygonData
        in polygonsData.cast<Map<dynamic, dynamic>>()) {
      final String polygonId = polygonData['polygonId'];
      final bool visible = polygonData['visible'];
      final bool geodesic = polygonData['geodesic'];
      final List<LatLng> points = _deserializePoints(polygonData['points']);
      final List<List<LatLng>> holes = _deserializeHoles(polygonData['holes']);

      result.add(Polygon(
        polygonId: PolygonId(polygonId),
        visible: visible,
        geodesic: geodesic,
        points: points,
        holes: holes,
      ));
    }

    return result;
  }

  List<LatLng> _deserializePoints(List<dynamic> points) {
    return points.map<LatLng>((dynamic list) {
      return LatLng(list[0], list[1]);
    }).toList();
  }

  List<List<LatLng>> _deserializeHoles(List<dynamic> holes) {
    return holes.map<List<LatLng>>((dynamic hole) {
      return hole.map<LatLng>((dynamic list) {
        return LatLng(list[0], list[1]);
      }).toList();
    }).toList();
  }

  void updatePolylines(Map<dynamic, dynamic>? polylineUpdates) {
    if (polylineUpdates == null) {
      return;
    }
    polylinesToAdd = _deserializePolylines(polylineUpdates['polylinesToAdd']);
    polylineIdsToRemove =
        _deserializePolylineIds(polylineUpdates['polylineIdsToRemove']);
    polylinesToChange =
        _deserializePolylines(polylineUpdates['polylinesToChange']);
  }

  Set<PolylineId> _deserializePolylineIds(List<dynamic>? polylineIds) {
    if (polylineIds == null) {
      return <PolylineId>{};
    }
    return polylineIds
        .map((dynamic polylineId) => PolylineId(polylineId))
        .toSet();
  }

  Set<Polyline> _deserializePolylines(dynamic polylines) {
    if (polylines == null) {
      return <Polyline>{};
    }
    final List<dynamic> polylinesData = polylines;
    final Set<Polyline> result = <Polyline>{};
    for (Map<dynamic, dynamic> polylineData
        in polylinesData.cast<Map<dynamic, dynamic>>()) {
      final String polylineId = polylineData['polylineId'];
      final bool visible = polylineData['visible'];
      final bool geodesic = polylineData['geodesic'];
      final List<LatLng> points = _deserializePoints(polylineData['points']);

      result.add(Polyline(
        polylineId: PolylineId(polylineId),
        visible: visible,
        geodesic: geodesic,
        points: points,
      ));
    }

    return result;
  }

  void updateCircles(Map<dynamic, dynamic>? circleUpdates) {
    if (circleUpdates == null) {
      return;
    }
    circlesToAdd = _deserializeCircles(circleUpdates['circlesToAdd']);
    circleIdsToRemove =
        _deserializeCircleIds(circleUpdates['circleIdsToRemove']);
    circlesToChange = _deserializeCircles(circleUpdates['circlesToChange']);
  }

  void updateTileOverlays(Map<String, dynamic> updateTileOverlayUpdates) {
    if (updateTileOverlayUpdates == null) {
      return;
    }
    final List<Map<dynamic, dynamic>>? tileOverlaysToAddList =
        updateTileOverlayUpdates['tileOverlaysToAdd'] != null
            ? List.castFrom<dynamic, Map<dynamic, dynamic>>(
                updateTileOverlayUpdates['tileOverlaysToAdd'])
            : null;
    final List<String>? tileOverlayIdsToRemoveList =
        updateTileOverlayUpdates['tileOverlayIdsToRemove'] != null
            ? List.castFrom<dynamic, String>(
                updateTileOverlayUpdates['tileOverlayIdsToRemove'])
            : null;
    final List<Map<dynamic, dynamic>>? tileOverlaysToChangeList =
        updateTileOverlayUpdates['tileOverlaysToChange'] != null
            ? List.castFrom<dynamic, Map<dynamic, dynamic>>(
                updateTileOverlayUpdates['tileOverlaysToChange'])
            : null;
    tileOverlaysToAdd = _deserializeTileOverlays(tileOverlaysToAddList);
    tileOverlayIdsToRemove =
        _deserializeTileOverlayIds(tileOverlayIdsToRemoveList);
    tileOverlaysToChange = _deserializeTileOverlays(tileOverlaysToChangeList);
  }

  Set<CircleId> _deserializeCircleIds(List<dynamic>? circleIds) {
    if (circleIds == null) {
      return <CircleId>{};
    }
    return circleIds.map((dynamic circleId) => CircleId(circleId)).toSet();
  }

  Set<Circle> _deserializeCircles(dynamic circles) {
    if (circles == null) {
      return <Circle>{};
    }
    final List<dynamic> circlesData = circles;
    final Set<Circle> result = <Circle>{};
    for (Map<dynamic, dynamic> circleData
        in circlesData.cast<Map<dynamic, dynamic>>()) {
      final String circleId = circleData['circleId'];
      final bool visible = circleData['visible'];
      final double radius = circleData['radius'];

      result.add(Circle(
        circleId: CircleId(circleId),
        visible: visible,
        radius: radius,
      ));
    }

    return result;
  }

  Set<TileOverlayId> _deserializeTileOverlayIds(List<String>? tileOverlayIds) {
    if (tileOverlayIds == null || tileOverlayIds.isEmpty) {
      return <TileOverlayId>{};
    }
    return tileOverlayIds
        .map((String tileOverlayId) => TileOverlayId(tileOverlayId))
        .toSet();
  }

  Set<TileOverlay> _deserializeTileOverlays(
      List<Map<dynamic, dynamic>>? tileOverlays) {
    if (tileOverlays == null || tileOverlays.isEmpty) {
      return <TileOverlay>{};
    }
    final Set<TileOverlay> result = <TileOverlay>{};
    for (Map<dynamic, dynamic> tileOverlayData in tileOverlays) {
      final String tileOverlayId = tileOverlayData['tileOverlayId'];
      final bool fadeIn = tileOverlayData['fadeIn'];
      final double transparency = tileOverlayData['transparency'];
      final int zIndex = tileOverlayData['zIndex'];
      final bool visible = tileOverlayData['visible'];

      result.add(TileOverlay(
        tileOverlayId: TileOverlayId(tileOverlayId),
        fadeIn: fadeIn,
        transparency: transparency,
        zIndex: zIndex,
        visible: visible,
      ));
    }

    return result;
  }

  void updateOptions(Map<dynamic, dynamic> options) {
    if (options.containsKey('compassEnabled')) {
      compassEnabled = options['compassEnabled'];
    }
    if (options.containsKey('mapToolbarEnabled')) {
      mapToolbarEnabled = options['mapToolbarEnabled'];
    }
    if (options.containsKey('cameraTargetBounds')) {
      final List<dynamic> boundsList = options['cameraTargetBounds'];
      cameraTargetBounds = boundsList[0] == null
          ? CameraTargetBounds.unbounded
          : CameraTargetBounds(LatLngBounds.fromList(boundsList[0]));
    }
    if (options.containsKey('mapType')) {
      mapType = MapType.values[options['mapType']];
    }
    if (options.containsKey('minMaxZoomPreference')) {
      final List<dynamic> minMaxZoomList = options['minMaxZoomPreference'];
      minMaxZoomPreference =
          MinMaxZoomPreference(minMaxZoomList[0], minMaxZoomList[1]);
    }
    if (options.containsKey('rotateGesturesEnabled')) {
      rotateGesturesEnabled = options['rotateGesturesEnabled'];
    }
    if (options.containsKey('scrollGesturesEnabled')) {
      scrollGesturesEnabled = options['scrollGesturesEnabled'];
    }
    if (options.containsKey('tiltGesturesEnabled')) {
      tiltGesturesEnabled = options['tiltGesturesEnabled'];
    }
    if (options.containsKey('trackCameraPosition')) {
      trackCameraPosition = options['trackCameraPosition'];
    }
    if (options.containsKey('zoomGesturesEnabled')) {
      zoomGesturesEnabled = options['zoomGesturesEnabled'];
    }
    if (options.containsKey('zoomControlsEnabled')) {
      zoomControlsEnabled = options['zoomControlsEnabled'];
    }
    if (options.containsKey('liteModeEnabled')) {
      liteModeEnabled = options['liteModeEnabled'];
    }
    if (options.containsKey('myLocationEnabled')) {
      myLocationEnabled = options['myLocationEnabled'];
    }
    if (options.containsKey('myLocationButtonEnabled')) {
      myLocationButtonEnabled = options['myLocationButtonEnabled'];
    }
    if (options.containsKey('trafficEnabled')) {
      trafficEnabled = options['trafficEnabled'];
    }
    if (options.containsKey('buildingsEnabled')) {
      buildingsEnabled = options['buildingsEnabled'];
    }
    if (options.containsKey('padding')) {
      padding = options['padding'];
    }
  }
}

class FakePlatformViewsController {
  FakePlatformGoogleMap? lastCreatedView;

  Future<dynamic> fakePlatformViewsMethodHandler(MethodCall call) {
    switch (call.method) {
      case 'create':
        final Map<dynamic, dynamic> args = call.arguments;
        final Map<dynamic, dynamic> params = _decodeParams(args['params'])!;
        lastCreatedView = FakePlatformGoogleMap(
          args['id'],
          params,
        );
        return Future<int>.sync(() => 1);
      default:
        return Future<void>.sync(() {});
    }
  }

  void reset() {
    lastCreatedView = null;
  }
}

Map<dynamic, dynamic>? _decodeParams(Uint8List paramsMessage) {
  final ByteBuffer buffer = paramsMessage.buffer;
  final ByteData messageBytes = buffer.asByteData(
    paramsMessage.offsetInBytes,
    paramsMessage.lengthInBytes,
  );
  return const StandardMessageCodec().decodeMessage(messageBytes);
}
