import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:grpc/grpc.dart';
import 'package:logger/logger.dart';
import 'package:ui/gen/signaler_service.pb.dart';
import 'package:ui/gen/signaler_service.pbgrpc.dart' as pb;
import 'package:fixnum/fixnum.dart';
import 'package:ui/session_service.dart';

class Call extends StatefulWidget {
  final pb.SignalerServiceClient client;
  final SessionService sessionService;
  final pb.Camera_Identifier cameraID;
  final String home;
  const Call(this.client, this.sessionService,
      {required this.cameraID, required this.home, super.key});

  @override
  CallState createState() => CallState();
}

class CallState extends State<Call> {
  Logger logger = Logger();
  final RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
  bool _ready = false;
  String statusLine = "Building...";

  @override
  initState() {
    super.initState();
    _connect();
  }

  _connect() async {
    _ready = false;
    logger.i("Init remote renderer");
    await _remoteRenderer.initialize();
    logger.i("Creating session");
    await _createSession();
  }

  _createSession() async {
    var callOptions = CallOptions(metadata: {
      'Authorization': await widget.sessionService.getAuthToken(widget.home)
    });

    var cancelCreate = Completer();
    var sendIceCandidates = Completer();

    var clientSession = await widget.client.createSession(
        pb.CreateSessionRequest(
          session: pb.Session(
            camera: widget.cameraID,
          ),
        ),
        options: callOptions);
    RTCPeerConnection peerConnection = await createPeerConnection({
      // Ice servers; just use the Google one for now
      'iceServers': [
        {'url': 'stun:stun.l.google.com:19302'}
      ],
    }, {
      /* Empty config */
    });

    peerConnection.onAddStream = (stream) {
      // Stream has been added; connect it to our renderer
      logger.i("Got stream from remote; connecting it");
      _remoteRenderer.srcObject = stream;
      _ready = true;
      setState(() {});
    };

    peerConnection.onIceCandidate = (candidate) async {
      await sendIceCandidates.future;
      logger.i("Sending ICE candidate");
      if (candidate.candidate == null) {
        await widget.client.createIceMessage(
            CreateIceMessageRequest(
                sessionIdentifier: clientSession.id,
                iceMessage: IceMessage(
                  noMoreCandidates: NoMoreCandidates(),
                )),
            options: callOptions);
        return;
      }

      await widget.client.createIceMessage(
          pb.CreateIceMessageRequest(
            sessionIdentifier: clientSession.id,
            iceMessage: pb.IceMessage(
              candidate: pb.IceCandidate(
                candidate: candidate.candidate,
                sdpMid: candidate.sdpMid,
                sdpLineIndex: candidate.sdpMLineIndex,
              ),
            ),
          ),
          options: callOptions);
    };

    peerConnection.onIceConnectionState = (state) {
      statusLine = "$state";
      setState(() {});

      switch (state) {
        //case RTCIceConnectionState.RTCIceConnectionStateClosed:
        //case RTCIceConnectionState.RTCIceConnectionStateDisconnected:
        case RTCIceConnectionState.RTCIceConnectionStateFailed:
          cancelCreate.complete(CallCancelled());
        //_connect();
        default:
        // do nothing
      }
    };

    peerConnection.onIceGatheringState = (state) async {
      logger.i("ICE gathering state $state");
      if (state == RTCIceGatheringState.RTCIceGatheringStateComplete) {
        await widget.client.createIceMessage(
            CreateIceMessageRequest(
                sessionIdentifier: clientSession.id,
                iceMessage: IceMessage(
                  noMoreCandidates: NoMoreCandidates(),
                )),
            options: callOptions);
      }
    };

    peerConnection.onRemoveStream = (stream) {};

    peerConnection.onDataChannel = (channel) {};

    // This will find the intersection of my candidates and the remote,
    // then propose one to use
    var offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer);
    // Send offer through signaling server
    await widget.client.createIceMessage(
        pb.CreateIceMessageRequest(
          sessionIdentifier: clientSession.id,
          iceMessage: pb.IceMessage(
            session: pb.IceSessionDescription(
              sdp: offer.sdp,
              sdpType: Int64(1), // offer
            ),
          ),
        ),
        options: callOptions);

    // Expect back a response
    var someResponse = await Future.any([
      widget.client.popIceMessage(
          pb.PopIceMessageRequest(sessionIdentifier: clientSession.id),
          options: callOptions),
      cancelCreate.future,
    ]);
    var resp = someResponse as pb.IceMessage;
    var session = resp.session;
    await peerConnection
        .setRemoteDescription(RTCSessionDescription(session.sdp, "answer"));

    sendIceCandidates.complete();

    // Get candidates from remote
    while (true) {
      var someResponse = await Future.any([
        widget.client.popIceMessage(
            pb.PopIceMessageRequest(sessionIdentifier: clientSession.id),
            options: callOptions),
        cancelCreate.future,
      ]);
      if (someResponse is CallCancelled) {
        break;
      }
      var resp = someResponse as pb.IceMessage;
      if (resp.hasCandidate()) {
        await peerConnection.addCandidate(RTCIceCandidate(
            resp.candidate.candidate,
            resp.candidate.sdpMid,
            resp.candidate.sdpLineIndex));
      } else if (resp.hasNoMoreCandidates()) {
        logger.i("No more candidates from remote");
        break;
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      Text(widget.cameraID.id),
      Text(statusLine),
      SizedBox(
          height: 320,
          child: _ready
              ? RTCVideoView(_remoteRenderer)
              : const Text("Loading...")),
    ]);
  }
}

class CallCancelled {}
