import 'dart:async';
import 'dart:math';
import 'dart:ui';

import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_directions_api/google_directions_api.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:mohem_flutter_app/classes/app_permissions.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/theme/colors.dart';
// import 'package:geodesy/geodesy.dart' as geodesy;

//Created By Mr.Zohaib
class Location {
  static _Map map = _Map();

  static void havePermission(Function(bool) callback) {
    Geolocator.checkPermission().then((value) async {
      if (value == LocationPermission.denied) {
        value = await Geolocator.requestPermission();
        callback(![LocationPermission.denied, LocationPermission.deniedForever].contains(value));
      } else {
        callback(true);
      }
    });
  }

  static void isEnabled(Function(bool) callback) {
    Geolocator.isLocationServiceEnabled().then((value) => callback(value));
  }

  static bool _listeningSettingChange = true;

  static void listenGPS({bool change = true, Function(bool)? onChange}) async {
    _listeningSettingChange = change;
    if (change == false) return;

    Future.doWhile(() async {
      await Utils.delay(1000);
      var enable = await Geolocator.isLocationServiceEnabled();
      onChange!(enable);
      return _listeningSettingChange;
    });
  }

  static void getCurrentLocation(Function(LatLng?) callback, BuildContext context) {
    void done(Position position) {
      //AppStorage.sp.saveLocation(position);

      LatLng? myCurrentLocation = LatLng(position.latitude, position.longitude);
      callback(myCurrentLocation);
    }

    AppPermissions.location((granted) {
      if (granted) {
        Geolocator.getLastKnownPosition(forceAndroidLocationManager: true).then((value) {
          if (value == null) {
            Geolocator.getCurrentPosition().then((value) {
              done(value);
            });
          } else {
            done(value);
          }
        });
      } else {
        // AppPermissions
      }
    }, context);
  }

  // static LatLng locationAwayFrom(
  //     {required LatLng loc1, num distanceMeters = 200.0, num bearing = 270.0}) {
  //   geodesy.LatLng l1 = geodesy.LatLng(loc1.latitude, loc1.longitude);
  //   geodesy.LatLng destinationPoint = geodesy.Geodesy()
  //       .destinationPointByDistanceAndBearing(l1, distanceMeters, bearing);
  //   return LatLng(destinationPoint.latitude, destinationPoint.longitude);
  // }

  static Future<double> distanceTo(LatLng destination) async {
    var myLoc = await Geolocator.getLastKnownPosition();
    var distance = 0.0;
    if (myLoc != null) {
      distance = Geolocator.distanceBetween(destination.latitude, destination.longitude, myLoc.latitude, myLoc.longitude);
    }
    return distance;
  }
}

class _Map {
  Marker createMarker(
    String id, {
    required LatLng coordinates,
    BitmapDescriptor? icon,
    VoidCallback? onTap,
  }) {
    MarkerId markerId = MarkerId(id);
    return Marker(
      icon: icon ?? BitmapDescriptor.defaultMarker,
      markerId: markerId,
      position: coordinates,
      flat: false,
      // infoWindow: InfoWindow(title: id, snippet: '*'),
      onTap: onTap,
    );
  }

  CameraPosition initialCamera({required Completer<GoogleMapController> mapController, LatLng? position, double zoom = 12}) {
    position = position ?? LatLng(24.7249303, 46.5416656);
    CameraPosition riyadhEye = CameraPosition(
      target: position,
      zoom: zoom,
    );
    mapController.future.then((controller) {
      controller.animateCamera(CameraUpdate.newCameraPosition(riyadhEye));
    });
    return riyadhEye;
  }

  CameraPosition moveTo(LatLng location, {double zoom = 12, double direction = 0.0, required Completer<GoogleMapController> mapController, bool? animation}) {
    var camera = CameraPosition(target: location, zoom: zoom, bearing: direction);
    mapController.future.then((controller) {
      animation ?? false ? controller.animateCamera(CameraUpdate.newCameraPosition(camera)) : controller.moveCamera(CameraUpdate.newCameraPosition(camera));
    });
    return camera;
  }

  void moveCamera(CameraPosition camera, @required Completer<GoogleMapController> mapController, bool animation) {
    mapController.future.then((controller) {
      animation ? controller.animateCamera(CameraUpdate.newCameraPosition(camera)) : controller.moveCamera(CameraUpdate.newCameraPosition(camera));
    });
  }

  void scrollBy({double x = 0, double y = 0, required Completer<GoogleMapController> mapController, bool animation = true}) {
    var camera = CameraUpdate.scrollBy(x, y);
    mapController.future.then((controller) {
      animation ? controller.animateCamera(camera) : controller.moveCamera(camera);
    });
  }

  // void goToCurrentLocation({Completer<GoogleMapController>? mapController, double? direction = 0.0, bool? animation}) {
  //   Location.getCurrentLocation((location) {
  //     moveTo(location!, zoom: 17, mapController: mapController!, animation: animation, direction: direction!);
  //   });
  // }

  var routes = Map<String, DirectionsRoute>();

  void setRoutePolylines(LatLng? source, LatLng? destination, Set<Polyline> polylines, Completer<GoogleMapController> mapController, Function(DirectionsRoute?) completion) {
    if (source == null || destination == null) {
      completion(null);
      return;
    }

    var origin = '${source.latitude},${source.longitude}';
    var destin = '${destination.latitude},${destination.longitude}';
    var routeId = '$origin->$destination';

    void createPolyline(DirectionsRoute results) {
      List<LatLng> polylineCoordinates = results.overviewPath!.map((e) => LatLng(e.latitude, e.longitude)).toList();
      PolylineId id = PolylineId("route");
      Polyline polyline = Polyline(
        polylineId: id,
        color: accentColor,
        width: 5,
        jointType: JointType.round,
        startCap: Cap.roundCap,
        endCap: Cap.roundCap,
        points: polylineCoordinates,
      );

      polylines.removeWhere((element) => true);
      polylines.add(polyline);

      LatLngBounds bound = getBounds(coordinates: polylineCoordinates);
      focusCameraToLatLngBounds(bound: bound, mapController: mapController, padding: 100);
      completion(routes[routeId]);
    }

    var availableRoute = routes[routeId];
    if (availableRoute == null) {
      var request = DirectionsRequest(origin: origin, destination: destin);
      DirectionsService().route(request, (response, status) {
        if (status == DirectionsStatus.ok && response.routes!.isNotEmpty) {
          routes[routeId] = response.routes!.first;
          createPolyline(response.routes!.first);
        }
      });
    } else {
      createPolyline(availableRoute);
    }
  }

  LatLngBounds getBounds({required List<LatLng> coordinates}) {
    var lngs = coordinates.map<double>((c) => c.longitude).toList();
    var lats = coordinates.map<double>((c) => c.latitude).toList();

    double bottomMost = lngs.reduce(min);
    double topMost = lngs.reduce(max);
    double leftMost = lats.reduce(min);
    double rightMost = lats.reduce(max);

    LatLngBounds bounds = LatLngBounds(
      northeast: LatLng(rightMost, topMost),
      southwest: LatLng(leftMost, bottomMost),
    );
    return bounds;

    double? x0, x1, y0, y1;
    for (LatLng latLng in coordinates) {
      if (x0 == null) {
        x0 = x1 = latLng.latitude;
        y0 = y1 = latLng.longitude;
      } else {
        if (latLng.latitude > x1!) x1 = latLng.latitude;
        if (latLng.latitude < x0) x0 = latLng.latitude;
        if (latLng.longitude > y1!) y1 = latLng.longitude;
        if (latLng.longitude < y0!) y0 = latLng.longitude;
      }
    }
    return LatLngBounds(northeast: LatLng(x1!, y1!), southwest: LatLng(x0!, y0!));
  }

  void focusCameraToLatLngBounds({LatLngBounds? bound, Completer<GoogleMapController>? mapController, double? padding}) async {
    if (bound == null) return;

    CameraUpdate camera = CameraUpdate.newLatLngBounds(bound, padding!);
    GoogleMapController controller = await mapController!.future;
    controller.animateCamera(camera);
  }

  void focusCameraTo2Points({LatLng? point1, LatLng? point2, Completer<GoogleMapController>? mapController, double? padding}) async {
    var source = point1;
    var destination = point2;
    if (source != null && destination != null) {
      // 'package:google_maps_flutter_platform_interface/src/types/location.dart': Failed assertion: line 72 pos 16: 'southwest.latitude <= northeast.latitude': is not true.
      LatLngBounds bound;
      if (source.latitude <= destination.latitude) {
        bound = LatLngBounds(southwest: source, northeast: destination);
      } else {
        bound = LatLngBounds(southwest: destination, northeast: source);
      }

      if (bound == null) return;

      focusCameraToLatLngBounds(bound: bound, mapController: mapController, padding: padding);
    }
  }
}
