import 'package:protobuf/protobuf.dart';

import 'communicator.dart';
import 'packets/message_common.pb.dart';
import 'response_checker.dart';

mixin HeartbeatMixin on Communicator {
  late int _heartbeatIntervalTime;
  late int _heartbeatWaitTime;
  ResponseChecker<Communicator>? _heartbeatChecker;
  bool _waitingHeartbeatResponse = false;

  Future<void> close();

  void initHeartbeat(int intervalSec, int waitSec) {
    _heartbeatIntervalTime = intervalSec;
    _heartbeatWaitTime = waitSec;
  }

  /// interval 또는 wait가 0 이하이면 heartbeat를 비활성으로 본다. 이렇게 하면
  /// 다른 언어 구현과 동일하게 0으로 heartbeat를 끌 수 있고, 비활성일 때는
  /// 프레임 수신마다 timer를 churn하지 않는다.
  bool get _heartbeatEnabled =>
      _heartbeatIntervalTime > 0 && _heartbeatWaitTime > 0;

  void sendHeartBeat() {
    if (isAlive && _heartbeatEnabled) {
      _heartbeatChecker?.responded();
      _heartbeatChecker =
          ResponseChecker.second(this, _heartbeatIntervalTime, (client) {
        if (isAlive) {
          _waitingHeartbeatResponse = true;
          send(HeartBeat());
          _heartbeatChecker =
              ResponseChecker.second(this, _heartbeatWaitTime, (client) {
            if (isAlive) {
              close();
            }
          });
        }
      });
    }
  }

  void onHeartBeat(HeartBeat data) {
    if (_waitingHeartbeatResponse) {
      _waitingHeartbeatResponse = false;
    } else {
      send(HeartBeat());
    }
  }

  @override
  Future<void> send<T extends GeneratedMessage>(T data) async {
    if (isAlive) {
      try {
        await queuePacket(PacketBase()
          ..typeName = data.info_.qualifiedMessageName
          ..nonce = nextNonce()
          ..data = data.writeToBuffer());
      } catch (e) {
        close();
      }
    }
  }

  void stopHeartbeat() {
    _heartbeatChecker?.responded();
  }
}
