

import 'dart:convert';
import 'dart:typed_data';

import 'package:bus_infotainment/audio_cache.dart';
import 'package:bus_infotainment/backend/live_information.dart';
import 'package:bus_infotainment/backend/modules/info_module.dart';
import 'package:bus_infotainment/utils/NameBeautify.dart';
import 'package:bus_infotainment/utils/OrdinanceSurveyUtils.dart';
import 'package:fast_csv/csv_converter.dart';
import 'package:vector_math/vector_math.dart';

class BusSequences extends InfoModule {

  Map<String, BusRoute> routes = {};

  Map<String, BusDestination> destinations = {};

  BusSequences.fromCSV(String destinationsJson, String busSequencesCSV) {



    // Init the bus destinations
    Map<String, dynamic> destinationData = jsonDecode(destinationsJson);

    print("Destination rows: ${destinationData.length}");

    for (String destinationName in destinationData.keys) {
      try {
        Map<String, dynamic> destinationDetails = destinationData[destinationName];

        String blind = destinationName;

        List<String> location = destinationDetails['Location'].split(', ');
        double lat = double.parse(location[0]);
        double long = double.parse(location[1]);

        Vector2 grid = OSGrid.toNorthingEasting(lat, long);

        BusDestination destination = BusDestination();
        destination.destination = blind;
        destination.easting = grid.x;
        destination.northing = grid.y;

        destinations[blind] = destination;
      } catch (e) {
        print("Error parsing destination: $e");
      }
    }

    print("Loaded ${destinations.length} destinations");

    // Init the bus routes

    List<List<dynamic>> busSequenceRows = CsvConverter().convert(busSequencesCSV);
    busSequenceRows.removeAt(0);

    for (int i = 0; i < busSequenceRows.length; i++) {

      try
      {

        List<dynamic> entries = busSequenceRows[i];
        // print("Parsing bus sequence row $i: $entries");

        String routeNumber = entries[0].toString();

        BusRoute route = routes.containsKey(routeNumber) ? routes[routeNumber]! : BusRoute(routeNumber: routeNumber);

        int routeVariant = int.parse(entries[1]);

        BusRouteStop stop = BusRouteStop();

        stop.stopName = entries[6].toString();
        stop.stopCode = entries[4].toString();
        stop.easting = int.parse(entries[7]);
        stop.northing = int.parse(entries[8]);
        stop.heading = int.parse(entries[9] != "" ? entries[9] : "-1");

        BusRouteVariant variant = route.routeVariants.containsKey(routeVariant) ? route.routeVariants[routeVariant]! : BusRouteVariant(routeVariant: routeVariant, busRoute: route);

        variant.busStops.add(stop);


        route.routeVariants[routeVariant] = variant;
        routes[routeNumber] = route;

      }
      catch (e) {
        print("Error parsing bus sequence: $e");
      }
    }

    print("Loaded ${routes.length} routes");

    // Remove any empty routes
    List<String> emptyRoutes = [];
    for (String routeNumber in routes.keys) {
      if (routes[routeNumber]!.routeVariants.isEmpty) {
        emptyRoutes.add(routeNumber);
      }
    }

    for (String routeNumber in emptyRoutes) {
      routes.remove(routeNumber);
    }

  }

}

class BusRoute {

  String routeNumber = "";

  List<BusDestination> destinations = [];

  AnnouncementCache? announcementCache;

  Map<int, BusRouteVariant> routeVariants = {};

  BusRoute({
    this.routeNumber = "-1",
    this.announcementCache,
  });

}

class BusRouteVariant {

    int routeVariant = -1;

    List<BusRouteStop> busStops = [];

    late BusRoute busRoute;

    BusRouteVariant({
      this.routeVariant = -1,
      required this.busRoute,
    });

    BusDestination? _destination;
    BusDestination? get destination {
      if (_destination == null) {

        // Get the nearest destination
        BusDestination? nearestDestinationA;
        double nearestDistanceA = double.infinity;

        Vector2 stopLocation = Vector2(busStops.last.northing.toDouble(), busStops.last.easting.toDouble());

        for (BusDestination destination in busRoute.destinations) {

          Vector2 destinationLocation = Vector2(destination.northing.toDouble(), destination.easting.toDouble());

          double distance = stopLocation.distanceTo(destinationLocation);

          if (distance < nearestDistanceA) {
            nearestDestinationA = destination;
            nearestDistanceA = distance;
          }

        }

        // get the nearest destination from global pool of destinations
        BusDestination? nearestDestinationB;
        double nearestDistanceB = double.infinity;

        for (BusDestination destination in LiveInformation().busSequences.destinations.values) {

          Vector2 destinationLocation = Vector2(destination.northing.toDouble(), destination.easting.toDouble());

          double distance = stopLocation.distanceTo(destinationLocation);

          if (distance < nearestDistanceB) {
            nearestDestinationB = destination;
            nearestDistanceB = distance;
          }

        }

        print("Nearest destination A: $nearestDestinationA, distance: $nearestDistanceA");
        print("Nearest destination B: $nearestDestinationB, distance: $nearestDistanceB");

        // Choose the nearest destination
        if (nearestDistanceA < nearestDistanceB) {
          _destination = nearestDestinationA;
        } else {
          _destination = nearestDestinationB;
        }

      }

      return _destination;
    }

    @override
    String toString() {
      return 'BusRouteVariant{routeVariant: $routeVariant, busRoute: ${busRoute.routeNumber}, busStops: ${busStops.length}, destinations: ${busRoute.destinations.length}}';
    }
}

class BusRouteStop {

  String stopName = "";

  int easting = -1;
  int northing = -1;
  int heading = -1;

  String stopCode = "";

  String get formattedStopName {
    return NameBeautify.beautifyStopName(stopName);
  }

  String getAudioFileName() {

    // Convert the stop name to all caps
    String stopName = this.stopName.toUpperCase();

    stopName = NameBeautify.beautifyStopName(stopName);

    // replace & with N
    stopName = stopName.replaceAll('&', 'N');

    stopName = stopName.replaceAll('/', '');

    stopName = stopName.replaceAll('\'', '');

    stopName = stopName.replaceAll('  ', ' ');

    // Replace space with underscore
    stopName = stopName.replaceAll(' ', '_');
    stopName = stopName.replaceAll('-', '_');

    // convert to all caps
    stopName = stopName.toUpperCase();

    stopName = "S_${stopName}_001.mp3";

    return stopName;


  }

}

class BusDestination {

  String destination = "";

  double easting = -1;
  double northing = -1;

  Future<Uint8List> getAudioBytes() async {

    // Convert the stop name to all caps
    String name = destination.toUpperCase();

    name = NameBeautify.beautifyStopName(name);

    // replace & with N
    name = name.replaceAll('&', 'N');

    name = name.replaceAll('/', '');

    name = name.replaceAll('\'', '');

    name = name.replaceAll('  ', ' ');

    // Replace space with underscore
    name = name.replaceAll(' ', '_');
    name = name.replaceAll('-', '_');

    // convert to all caps
    name = name.toUpperCase();

    String audioNameA = "D_${name}_001.mp3";
    String audioNameB = "S_${name}_001.mp3";
    String audioNameC = "A_${name}_001.mp3";

    print("Audio name A: $audioNameA");
    print("Audio name B: $audioNameB");
    print("Audio name C: $audioNameC");

    await LiveInformation().announcementModule.announcementCache.loadAnnouncementsFromBytes(await LiveInformation().announcementModule.getBundleBytes(), [audioNameA, audioNameB, audioNameC]);

    Uint8List? audioBytesA = LiveInformation().announcementModule.announcementCache[audioNameA];
    Uint8List? audioBytesB = LiveInformation().announcementModule.announcementCache[audioNameB];
    Uint8List? audioBytesC = LiveInformation().announcementModule.announcementCache[audioNameC];

    if (audioBytesA != null) return audioBytesA;
    if (audioBytesB != null) return audioBytesB;
    if (audioBytesC != null) return audioBytesC;

    print("No audio bytes found for $name");

    return Uint8List(0);

  }


}