import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:nmea/nmea.dart';
import 'package:terepi_seged/eov/convert_coordinate.dart';
import 'package:terepi_seged/eov/eov.dart';
import 'package:terepi_seged/gnss_sentences/gngga.dart';
import 'package:terepi_seged/gnss_sentences/gngst.dart';
import 'package:terepi_seged/gnss_sentences/gnrmc.dart';

class RtcmTestController extends GetxController {
  String gpsAddress = "E8:31:CD:14:8B:B2";
  String gpsName = "TiGNSS Rover-8BB2";
  Rx<bool> gpsIsConnected = false.obs;
  RxBool ntripIsConnected = false.obs;
  RxInt ntripDataPacketNumbers = 0.obs;
  String _messageBuffer = "";
  late BluetoothConnection connection;
  late Socket socket;
  late StreamSubscription socketStreamSubscription;
  RxInt gpsReceivedData = 0.obs;
  RxBool hasGpsValidData = false.obs;
  DateTime lastGpsRefreshTime = DateTime.now();
  String utcOfPositionFix = "";
  String utcDateOfPositionFix = "";
  RxInt ntripReceivedData = 0.obs;
  final NmeaDecoder nmeaDecoder = NmeaDecoder();

  NumberFormat formatEov = NumberFormat("##0,000.0", "hu-HU");
  NumberFormat formatWgs84Sec = NumberFormat('00.000', 'hu-HU');

  RxDouble gpsLatitude = 0.0.obs;
  RxString gpsLatitudeDirection = "".obs;
  RxDouble gpsLongitude = 0.0.obs;
  RxString gpsLongitudeDirection = "".obs;
  RxDouble gpsAltitude = 0.0.obs;
  RxInt gpsQuality = 0.obs;
  RxDouble gpsLatitudeError = 0.0.obs;
  RxDouble gpsLongitudeError = 0.0.obs;
  RxDouble gpsAltitudeError = 0.0.obs;
  Rx<DateTime> gpsDateTime = DateTime(2000).obs;
  Rx<Eov> eov = Eov(0, 0).obs;
  RxInt latDegree = 0.obs;
  RxInt latMin = 0.obs;
  RxDouble latSec = 0.0.obs;
  RxInt longDegree = 0.obs;
  RxInt longMin = 0.obs;
  RxDouble longSec = 0.0.obs;

  @override
  void onInit() {
    super.onInit();
    //Loading, Success, Error handle with 1 line of code
    nmeaDecoder
      ..registerTalkerSentence("GGA", (line) => Gngga(raw: line))
      ..registerTalkerSentence("RMC", (line) => Gnrmc(raw: line))
      ..registerTalkerSentence("GST", (line) => Gngst(raw: line));
  }

  @override
  void onClose() {
    super.onClose();
    FlutterBluetoothSerial.instance.setPairingRequestHandler(null);
    if (gpsIsConnected.value) {
      connection.close();
      // connection = null;
      print("BluetoothTestController dispose ....");
    }
  }

  void connectToGps() {
    if (gpsIsConnected == false) {
      BluetoothConnection.toAddress(gpsAddress).then((value) {
        connection = value;
        gpsIsConnected.value = true;
        print("GPS is connected ...");

        connection.input!.listen(_onDataReceived);
      });
    }
  }

  void disconnectFromGps() {
    if (gpsIsConnected.value) {
      connection.close();
      gpsIsConnected.value = false;
      print("GPS is disconnected ....");
    }
    if (ntripIsConnected.value) {
      disconnectFromNtripServer();
    }
  }

  void connectToNtripServer() async {
    socket = await Socket.connect(InternetAddress('3.23.52.207'), 2101,
        timeout: const Duration(seconds: 5));
    socket.asBroadcastStream;
    ntripIsConnected.value = true;

    socket.encoding = ascii;
    print("Connected to ntrip server ....");

    String header = "GET /KOVARIK HTTP/1.1\r\n";
    header += "User-Agent: SharpGps iter.dk\r\n";
    header += "Accept: */*\r\nConnection: close\r\n";
    header += "Authorization: Basic ${_toBase64("info@mail.app-dev.hu:")}\r\n";
    header += "Host:rtk2go.com:2101\r\n";
    header += "Ntrip-Vesrsion:Ntrip/2.0\r\n";
    header += "\r\n";

    // listen for responses from the server
    socketStreamSubscription = socket.listen(
      // handle data from the server
      (Uint8List data) {
        // "ICY 200 OK" - first response
        final serverResponse = String.fromCharCodes(data);
        ntripReceivedData.value = data.length;
        ntripDataPacketNumbers.value++;
        if (gpsIsConnected.value) {
          if (data.length > 12) {
            connection.output.add(data);
          }
        }
        print('Server: $ntripReceivedData');
      },

      // handle errors
      onError: (error) {
        print(error);
        socket.destroy();
      },

      // handle server ending connection
      onDone: () async {
        print('Server left.');
        socketStreamSubscription.cancel();
        await socket.flush();
        ntripIsConnected.value = false;
        ntripReceivedData.value = 0;
        socket.destroy();
      },
    );

    socket.add(_convertStringToUint8List(header));
  }

  void disconnectFromNtripServer() async {
    socketStreamSubscription.cancel();
    await socket.flush();
    socket.close();
    socket.destroy();
    ntripReceivedData.value = 0;
    ntripIsConnected.value = false;
    print("Disconnect from ntrip server....");
  }

  void _onDataReceived(Uint8List data) {
    String sentence = "";

    print("Bluetooth received -> ${data.length} byte(s)");
    String dataString = String.fromCharCodes(data);
    int index = data.indexOf(13);

    if (~index != 0) {
      sentence = _messageBuffer + dataString.substring(0, index);
      _messageBuffer = dataString.substring(index);
    } else {
      _messageBuffer = _messageBuffer + dataString;
    }

    // print("Message ($count): $sentence");
    _processGnssMessage(sentence);
  }

  String _toBase64(String str) {
    final bytes = ascii.encode(str);
    final base64Str = base64.encode(bytes);

    return base64Str;
  }

  Uint8List _convertStringToUint8List(String str) {
    final List<int> codeUnits = str.codeUnits;
    final Uint8List unit8List = Uint8List.fromList(codeUnits);
    return unit8List;
  }

  void _processGnssMessage(String message) {
    LineSplitter lineSplitter = const LineSplitter();
    List<String> lines = lineSplitter.convert(message);

    if (lines.isEmpty) {
      return;
    }

    for (String line in lines) {
      if (line.trim().isEmpty) {
        continue;
      }
      if (line.startsWith("\$GNGGA")) {
        final sentence = nmeaDecoder.decode(line);
        if (sentence!.valid && sentence is Gngga) {
          hasGpsValidData.value = sentence.gpsQualityIndicator > 0;
          if (hasGpsValidData.value) {
            if (DateTime.now().difference(lastGpsRefreshTime).inSeconds >= 2) {
              lastGpsRefreshTime = DateTime.now();
              utcOfPositionFix = sentence.utcOfPositionFix;
              gpsLatitude.value = sentence.latitude;
              gpsLatitudeDirection.value = sentence.latitudeDirection;
              gpsLongitude.value = sentence.longitude;
              gpsLongitudeDirection.value = sentence.longitudeDirection;
              gpsAltitude.value = sentence.altitudeAboveMeanSeaLevel;
              gpsQuality.value = sentence.gpsQualityIndicator;
              eov.value = ConvertCoordinate.ConvertWgsToEov(
                  gpsLatitude.value, gpsLongitude.value);
              latDegree.value = ConvertCoordinate.toDegree(gpsLatitude.value);
              latMin.value = ConvertCoordinate.toMinute(gpsLatitude.value);
              latSec.value = ConvertCoordinate.toSecond(gpsLatitude.value);
              longDegree.value = ConvertCoordinate.toDegree(gpsLongitude.value);
              longMin.value = ConvertCoordinate.toMinute(gpsLongitude.value);
              longSec.value = ConvertCoordinate.toSecond(gpsLongitude.value);
            }
          }
        }
      }
      if (line.startsWith("\$GNGST") && hasGpsValidData.value) {
        final sentence = nmeaDecoder.decode(line);
        if (sentence!.valid && sentence is Gngst) {
          gpsLatitudeError.value = sentence.latitudeError;
          gpsLongitudeError.value = sentence.longitudeError;
          gpsAltitudeError.value = sentence.heightError;
        }
      }
      if (line.startsWith("\$GNRMC") && hasGpsValidData.value) {
        final sentence = nmeaDecoder.decode(line);
        if (sentence!.valid && sentence is Gnrmc) {
          utcDateOfPositionFix = sentence.date;
          if (utcDateOfPositionFix.isNotEmpty && utcOfPositionFix.isNotEmpty) {
            gpsDateTime.value = DateTime(
                2000 +
                    int.parse(
                        "${utcDateOfPositionFix[4]}${utcDateOfPositionFix[5]}"),
                int.parse(
                    "${utcDateOfPositionFix[2]}${utcDateOfPositionFix[3]}"),
                int.parse(
                    "${utcDateOfPositionFix[0]}${utcDateOfPositionFix[1]}"),
                int.parse("${utcOfPositionFix[0]}${utcOfPositionFix[1]}"),
                int.parse("${utcOfPositionFix[2]}${utcOfPositionFix[3]}"),
                int.parse("${utcOfPositionFix[4]}${utcOfPositionFix[5]}"));
          }
        }
      }
    }
  }

  String getGpsQualityIndicator({required int quality}) {
    String qualityStr = "Invalid";
    switch (quality) {
      case 0:
        {
          qualityStr = "Invalid";
        }
        break;
      case 1:
        {
          qualityStr = "Standard GPS (2D/3D)";
        }
        break;
      case 2:
        {
          qualityStr = "Differential GPS";
        }
        break;
      case 4:
        {
          qualityStr = "RTK Fix";
        }
        break;
      case 5:
        {
          qualityStr = "RTK Float";
        }
        break;
      case 6:
        {
          qualityStr = "Estimated (DR) Fix";
        }
        break;
      default:
        {
          qualityStr = "Invalid (-1)";
        }
    }
    return qualityStr;
  }
}
