import 'dart:async';
import 'dart:io';

import 'package:esense_flutter/esense.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:sense_the_rhythm/models/arrow_direction.dart';
import 'package:sense_the_rhythm/models/input_direction.dart';

class ESenseInput {
  static final instance = ESenseInput._();

  ESenseManager eSenseManager = ESenseManager('unknown');
  ValueNotifier<String> deviceStatus = ValueNotifier('Disconnected');
  StreamSubscription? subscription;

  String eSenseDeviceName = '';
  bool connected = false;
  bool sampling = false;

  int sampleRate = 20;

  InputDirection inputDirection = InputDirection();
  int x = 0;
  int y = 0;
  int z = 0;

  ESenseInput._() {
    _listenToESense();
  }

  Future<bool> _askForPermissions() async {
    if (!Platform.isAndroid && !Platform.isIOS) return false;
    if (!await Permission.bluetooth.serviceStatus.isEnabled) {
      deviceStatus.value = "Bluetooth is disabled!";
      return false;
    }
    if (!(await Permission.bluetoothScan.request().isGranted &&
        await Permission.bluetoothConnect.request().isGranted &&
        await Permission.bluetooth.request().isGranted)) {
      print(
          'WARNING - no permission to use Bluetooth granted. Cannot access eSense device.');
      deviceStatus.value = "Insufficiant Permissions";
      return false;
    }
    // for some strange reason, Android requires permission to location for Bluetooth to work.....?
    if (Platform.isAndroid) {
      if (!(await Permission.locationWhenInUse.request().isGranted)) {
        print(
            'WARNING - no permission to access location granted. Cannot access eSense device.');
        deviceStatus.value = "Insufficiant Permissions";
        return false;
      }
    }
    return true;
  }

  void _listenToESense() {
    // if you want to get the connection events when connecting,
    // set up the listener BEFORE connecting...
    eSenseManager.connectionEvents.listen((event) {
      print('CONNECTION event: $event');

      // when we're connected to the eSense device, we can start listening to events from it
      // if (event.type == ConnectionType.connected) _listenToESenseEvents();

      connected = false;
      switch (event.type) {
        case ConnectionType.connected:
          deviceStatus.value = 'Connected';
          connected = true;
          _startListenToSensorEvents();
          break;
        case ConnectionType.unknown:
          deviceStatus.value = 'Unknown';
          break;
        case ConnectionType.disconnected:
          deviceStatus.value = 'Disconnected';
          sampling = false;
          _pauseListenToSensorEvents();
          break;
        case ConnectionType.device_found:
          deviceStatus.value = 'Device_found';
          break;
        case ConnectionType.device_not_found:
          deviceStatus.value = 'Device_not_found';
          break;
      }
    });
  }

  Stream<ButtonEventChanged> buttonEvents() {
    return eSenseManager.eSenseEvents
        .where((event) => event.runtimeType == ButtonEventChanged)
        .cast();
  }

  void _startListenToSensorEvents() async {
    // // any changes to the sampling frequency must be done BEFORE listening to sensor events
    print('setting sampling frequency...');
    bool successs = await eSenseManager.setSamplingRate(sampleRate);
    if (successs) {
      print('setSamplingRate success');
    } else {
      print('setSamplingRate fail');
    }

    // subscribe to sensor event from the eSense device
    subscription = eSenseManager.sensorEvents.listen((event) {
      // print('SENSOR event: $event');
      if (event.gyro != null) {
        _parseGyroData(event.gyro!);
      }
    });
    sampling = true;
  }

  void _pauseListenToSensorEvents() async {
    subscription?.cancel();
    sampling = false;
  }

  void _parseGyroData(List<int> data) {
    // Float value in deg/s = Gyro value / Gyro scale factor
    // The default configuration is +- 500deg/s for the gyroscope.
    x = (x + (15 * data[0] ~/ (500 * sampleRate))) % 360;
    y = (y + (15 * data[1] ~/ (500 * sampleRate))) % 360;
    z = (z + (15 * data[2] ~/ (500 * sampleRate))) % 360;
    print('$x, $y, $z');
    // print('${(z.toDouble() / 500.0 * (1.0 / sampleRate.toDouble())) * 7.5}');
    // print('${z.toDouble() / 500.0 * (1.0 / 10.0)}');
  }

  void resetAngles() {
    inputDirection.reset();
    x = 0;
    y = 0;
    z = 0;
  }

  InputDirection getInputDirection(ArrowDirection expect) {
    inputDirection.up = z > 180 && z < 340;
    inputDirection.down = z > 20 && z < 180;
    inputDirection.left = y > 0 && y < 180;
    inputDirection.right = y > 180 && y < 360;

    if (expect == ArrowDirection.up && inputDirection.up ||
        expect == ArrowDirection.down && inputDirection.down) {
      y = 0;
      print("ehit");
    }
    if (expect == ArrowDirection.left && inputDirection.left ||
        expect == ArrowDirection.right && inputDirection.right) {
      z = 0;
      print("ehit");
    }

    return inputDirection;
  }

  Future<void> connectToESense(String deviceName) async {
    if (!connected) {
      bool permissionSuccessfull = await _askForPermissions();
      if (!permissionSuccessfull) return;
      print('Trying to connect to eSense device namend \'$deviceName\'');
      eSenseDeviceName = deviceName;
      eSenseManager.deviceName = deviceName;
      bool connecting = await eSenseManager.connect();
      print(
          'Trying to connect to eSense device namend \'${eSenseManager.deviceName}\'');

      deviceStatus.value = connecting ? 'connecting...' : 'connection failed';
      print(deviceStatus.value);
    }
  }
}
