import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_sound/public/flutter_sound_player.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/models/action_model.dart';

class ActionTimer with ChangeNotifier {
  ActionModel? actionModel;
  double _progress = 0;
  int _currentTime = 0;
  final List<ItemScrollController> _scrollControllers = [];
  final FlutterSoundPlayer _mPlayer = FlutterSoundPlayer();

  ActionTimer();

  Map get state => actionModel?.state ?? _stateConstructor();
  ActionStatus get status => actionModel?.status ?? ActionStatus.pending;
  bool get started => status == ActionStatus.started;
  bool get paused => status == ActionStatus.paused;
  bool get pending => status == ActionStatus.pending;
  bool get complete => status == ActionStatus.complete;
  bool get available => paused | pending;
  List<Set> get sets => actionModel!.sets;
  List<Item> get items => actionModel!.items;
  Set get currentSet => sets[state['currentSet']];
  Reps get currentRep => currentSet.reps[state['currentRep']];
  Item get currentAction => allActions[state['currentAction']];
  int get currentTime => _currentTime;
  dynamic get currentValue => currentAction.valueType == RepType.time
      ? currentTime
      : currentAction.value;
  List<Item> get allActions => actionModel?.allItems ?? [];
  String get repType =>
      actionModel!.action.repType == RepType.time ? 'Seconds' : 'Reps';
  int? get repLength => currentRep.value;
  int? get repCount => currentRep.count;
  dynamic get repValue =>
      actionModel!.action.repType == RepType.time ? repLength : repCount;
  double get progress => _progress;
  int get totalTime => actionModel!.totalTime;
  Timer? _periodicTimer;

  Map _stateConstructor() {
    return {
      'currentSet': 0,
      'currentRep': 0,
      'currentTime': 0,
      'currentAction': 0
    };
  }

  void setup(ActionModel actionModel, ItemScrollController scrollController,
      [bool resetOnLoad = true]) async {
    _scrollControllers.add(scrollController);

    if (resetOnLoad) {
      if (this.actionModel == actionModel) {
        reset();
        _scrollControllers.add(scrollController);
      }

      this.actionModel = actionModel;
      setAction(currentAction.id);
    }
  }

  Future pause() async =>
      await actionModel?.updateStatus(ActionStatus.paused).whenComplete(() {
        _periodicTimer?.cancel();
        notifyListeners();

        // _mPlayer.stopPlayer();
        // Be careful : you must `close` the audio session when you have finished with it.
      });

  Future start() async {
    await actionModel!.updateStatus(ActionStatus.started);
    await _mPlayer.openPlayer();

    Uint8List? countTone;
    Uint8List? finishTone;
    await rootBundle
        .load('assets/audio/count_tone.wav')
        .then((data) => countTone = data.buffer.asUint8List());
    await rootBundle
        .load('assets/audio/count_finish.mp3')
        .then((data) => finishTone = data.buffer.asUint8List());

    // start timer
    if (_periodicTimer == null || _periodicTimer!.isActive == false) {
      _periodicTimer =
          Timer.periodic(const Duration(seconds: 1), (Timer timer) async {
        switch (currentAction.valueType) {
          case RepType.count:
            break;
          case RepType.time:
            _currentTime--;

            if (_currentTime <= 3 && _currentTime != 0) {
              await _mPlayer.startPlayer(
                  fromDataBuffer: countTone, codec: Codec.pcm16WAV);
            }

            if (_currentTime == 0) {
              // move to next action
              await _mPlayer.startPlayer(
                  fromDataBuffer: finishTone, codec: Codec.mp3);
              await setAction(state['currentAction'] + 1);
            }

            await updateProgress();
            notifyListeners();
        }
      });
    }

    notifyListeners();
  }

  Future close() async => await actionModel!
          .updateStatus(ActionStatus.complete)
          .whenComplete(() async {
        _periodicTimer!.cancel();
        _mPlayer.closePlayer();
        notifyListeners();
      });

  Future reset() async {
    await actionModel?.updateStatus(ActionStatus.pending);
    await actionModel?.updateState(json.encode(_stateConstructor()));
    _periodicTimer?.cancel();
    _progress = 0;
    _scrollControllers.clear();
    _mPlayer.closePlayer();
    notifyListeners();
  }

  Future clear() async {
    await reset();
  }

  double timeUsed() {
    Iterable<Item> usedItems = allActions.getRange(0, state['currentAction']);
    return usedItems.fold(0.0, (p, c) => p + c.value!);
  }

  double totalComplete() {
    Iterable<Item> usedItems = allActions.getRange(0, state['currentAction']);
    return usedItems.length / allActions.length;
  }

  updateProgress() {
    double repUsed = (currentAction.value - currentTime) / currentAction.value;
    _progress =
        totalComplete() + ((repUsed < 0 ? 0 : repUsed) / allActions.length);
    notifyListeners();
  }

  setAction(int actionNum, [bool isManual = false]) async {
    if (actionNum < allActions.length) {
      Item item = allActions[actionNum];
      Map newState = state;

      newState['currentAction'] = actionNum;
      newState['currentSet'] = item.parentId;
      newState['currentRep'] = item.id;
      newState['currentTime'] = _currentTime = item.value!;

      await actionModel!
          .updateState(json.encode(newState))
          .whenComplete(() async {
        // if manual select, pause next action
        if (isManual) {
          await pause();
          await updateProgress();
        }

        int index = currentAction.parentId != null
            ? currentAction.parentId!
            : currentAction.id;

        if (_scrollControllers.isNotEmpty) {
          for (int i = 0; i < _scrollControllers.length; i++) {
            ItemScrollController sc = _scrollControllers[i];

            sc.scrollTo(
                index: index,
                duration: Duration(milliseconds: 500),
                curve: Curves.easeInOutCubic);
          }
        }
        // _scrollController?.scrollTo(
        //     index: index,
        //     duration: Duration(milliseconds: 500),
        //     curve: Curves.easeInOutCubic);
      });
    } else {
      await actionModel?.updateStatus(ActionStatus.complete).whenComplete(() {
        _periodicTimer?.cancel();
        notifyListeners();
      });
    }

    notifyListeners();
  }
}
