
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';

import 'package:bus_infotainment/backend/live_information.dart';
import 'package:bus_infotainment/backend/modules/info_module.dart';
import 'package:bus_infotainment/tfl_datasets.dart';
import 'package:bus_infotainment/utils/OrdinanceSurveyUtils.dart';
import 'package:bus_infotainment/utils/audio%20wrapper.dart';
import 'package:flutter/foundation.dart';
import 'package:geolocator/geolocator.dart';
import 'package:vector_math/vector_math.dart';

class TrackerModule extends InfoModule {

  // Constructor
  TrackerModule() {
    locationStream();
    if (!kIsWeb)
    {
      Geolocator.getLastKnownPosition().then((Position? position) {
        this._position = position;
        updateNearestStop();
      });
    }
    liveInformation.routeVariantDelegate.addListener((routeVariant) {
      print("Route variant changed");
      updateNearestStop();
    });
  }

  // Location Tracker - will update the recorded location when the user moves.
  Position? _position;
  Position? get position => _position;

  StreamSubscription<Position> locationStream() => Geolocator.getPositionStream(
    locationSettings: const LocationSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 1,
    )
  ).listen((Position position) {

    if (position == null) {
      return;
    }

    this._position = position;
    updateNearestStop();
  });

  // Location Refresher - will update the recorded location periodically.
  Timer refreshTimer() => Timer.periodic(Duration(seconds: 1), (timer) async {
    _position = await Geolocator.getCurrentPosition();
  });

  BusRouteStop? nearestStop;
  bool hasArrived = false;


  Future<void> updateNearestStop() async {
    if (liveInformation.getRouteVariant() == null) {
      return;
    }

    // Get the closest stop
    BusRouteStop closestStop = liveInformation.getRouteVariant()!.busStops.first;
    double closestDistance = OSGrid
        .toNorthingEasting(_position!.latitude, _position!.longitude)
        .distanceTo(Vector2(closestStop.easting.toDouble(), closestStop.northing.toDouble()));

    for (BusRouteStop stop in liveInformation.getRouteVariant()!.busStops) {
      double distance = OSGrid
          .toNorthingEasting(_position!.latitude, _position!.longitude)
          .distanceTo(Vector2(stop.easting.toDouble(), stop.northing.toDouble()));

      if (distance < closestDistance) {
        closestStop = stop;
        closestDistance = distance;
      }
    }

    double relativeDistance = _calculateRelativeDistance(closestStop, _position!.latitude, _position!.longitude);



    if (relativeDistance < -10) {
      print("Closest stop is behind us: ${closestStop.formattedStopName}");
      print("Relative distance: $relativeDistance");

      int stopIndex = liveInformation.getRouteVariant()!.busStops.indexOf(closestStop);

      int maxStops = liveInformation.getRouteVariant()!.busStops.length;

      closestStop = liveInformation.getRouteVariant()!.busStops[min(stopIndex + 1, maxStops)];

      print("Closest stop is now: ${closestStop.formattedStopName}");
    } else {
      print("Closest stop is in front of us: ${closestStop.formattedStopName}");
    }

    bool preExisting = true;

    if (nearestStop != closestStop) {
      nearestStop = closestStop;
      hasArrived = false;
      preExisting = false;
    }

    print("Closest stop is the same as before");

    double distance = OSGrid
        .toNorthingEasting(_position!.latitude, _position!.longitude)
        .distanceTo(Vector2(nearestStop!.easting.toDouble(), nearestStop!.northing.toDouble()));

    // get the speed in mph
    double speed = _position!.speed * 2.23694;
    print("Speed: $speed");

    Duration? duration;
    {
      // Testing some audio stuff
      Uint8List? audioBytes = liveInformation.announcementModule.announcementCache[nearestStop!.getAudioFileName()];

      if (audioBytes != null) {
        AudioWrapperByteSource audioSource = AudioWrapperByteSource(audioBytes!);

        AudioWrapper audio = AudioWrapper();

        await audio.loadSource(audioSource);

        duration = await audio.getDuration();

        print("Duration of audio: $duration");
      } else {
        print("Audio bytes are null");
        duration = Duration(seconds: 0);
      }


    }

    // get the estimated distance travelled in 5 seconds, in meters
    double distanceTravelled = speed * (3 + duration!.inSeconds);

    // adjust for the it takes to send the announcement to other devices
    distance -= distanceTravelled;

    // get the time to the stop in seconds
    double timeToStop = distance / speed;

    print("Distance to stop: $distance");
    print("Time to stop: $timeToStop");

    int secondsBefore = 7;

    print("Seconds before: $secondsBefore");

    if ((timeToStop < secondsBefore ) && !hasArrived && relativeDistance > 0) {
      print("We are at the stop");
      hasArrived = true;
      liveInformation.announcementModule.queueAnnounceByAudioName(
        displayText: "${nearestStop!.formattedStopName}",
        audioNames: [
          // "A_NEXT_STOP_001.mp3",
          nearestStop!.getAudioFileName()
        ],
        sendToServer: true
      );
    }


    if (!hasArrived && !preExisting) {
      liveInformation.announcementModule.queueAnnounceByAudioName(
        displayText: "${closestStop.formattedStopName}",
        audioNames: [],
        sendToServer: true
      );
    }



    print("Closest stop: ${closestStop.formattedStopName} in ${closestDistance.round()} meters");
  }

}

double _calculateRelativeDistance(BusRouteStop stop, double latitude, double longitude) {

  List<double> toLatLong = OSGrid.toLatLong(stop.northing.toDouble(), stop.easting.toDouble());

  Vector2 stopPoint = OSGrid.toNorthingEasting(toLatLong[0], toLatLong[1]);
  Vector2 currentPoint = OSGrid.toNorthingEasting(latitude, longitude);

  // calculate the heading from the current point to the stop point
  // 0 degrees is north, 90 degrees is east, 180 degrees is south, 270 degrees is west
  double toHeading = degrees(atan2(stopPoint.x - currentPoint.x, stopPoint.y - currentPoint.y));
  toHeading = (toHeading + 360) % 360;

  // get the dot product of the heading and the stop heading
  double dotProduct = cos(radians(toHeading)) * cos(radians(stop.heading.toDouble())) + sin(radians(toHeading)) * sin(radians(stop.heading.toDouble()));

  return (dotProduct.sign) * _calculateDistance(latitude, longitude, toLatLong[0], toLatLong[1]);
}

double _calculateDistance(double lat1, double lon1, double lat2, double lon2) {

  // Convert to eastings and northings
  Vector2 point1 = OSGrid.toNorthingEasting(lat1, lon1);
  Vector2 point2 = OSGrid.toNorthingEasting(lat2, lon2);

  return point1.distanceTo(point2);

}