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

import 'package:event/event.dart';
import 'package:flutter/material.dart';
import 'package:flutter_nsd/flutter_nsd.dart';
import 'package:get_it/get_it.dart';
import 'package:skillcourt/pad.dart';
import 'package:skillcourt/pair.dart';

final getIt = GetIt.I;
Map<NsdServiceInfo, Pad> socketsMap = <NsdServiceInfo, Pad>{};
List<NsdServiceInfo> services = <NsdServiceInfo>[];
int countHit = 0;

class PadManagerPage extends StatefulWidget {
  static const routeName = 'pad-manager';

  const PadManagerPage({Key? key}) : super(key: key);

  Map<NsdServiceInfo, Pad> getMap() {
    return socketsMap;
  }

  List<NsdServiceInfo> getServices() {
    return services;
  }

  void setHits() {
    countHit = 0;
  }

  int getHits() {
    return countHit;
  }

  @override
  State createState() => _PadManagerPageState();
}

class _PadManagerPageState extends State<PadManagerPage> {
  final flutterNsd = FlutterNsd();
  late String padResult;
  late Event hasResult;

  bool initialStart = true;
  bool _scanning = false;

  _PadManagerPageState();

  @override
  void initState() {
    super.initState();

    // Try one restart if initial start fails, which happens on hot-restart of
    // the flutter app.
    flutterNsd.stream.listen(
          (NsdServiceInfo service) {
        final RegExp exp = RegExp('Agipad-*');
        setState(() {
          if (exp.hasMatch(service.name!) && !services.contains(service)) {
            services.add(service);
          }
        });
      },
      onError: (e) async {
        if (e is NsdError) {
          if (e.errorCode == NsdErrorCode.startDiscoveryFailed &&
              initialStart) {
            await stopDiscovery();
          } else if (e.errorCode == NsdErrorCode.discoveryStopped &&
              initialStart) {
            initialStart = false;
            await startDiscovery();
          }
        }
      },
    );
  }

  ///Starts network service discovery on the local network looking for services with the
  ///Service Type '_http._tcp.' You can run the command '`dns-sd -B _http._tcp.`' on your terminal to
  ///check those services yourself.
  ///
  Future<void> startDiscovery() async {
    if (_scanning) return;

    setState(() {
      _scanning = true;
    });
    await flutterNsd.discoverServices('_http._tcp.');
  }

  Future<void> stopDiscovery() async {
    final servicesIter = services.iterator;
    final servicesCopy = List<NsdServiceInfo>.from(services);
    while (servicesIter.moveNext()) {
      if (socketsMap[servicesIter.current] == null) {
        servicesCopy.remove(servicesIter.current);
      }
    }

    setState(() {
      services = servicesCopy;
      if (_scanning) flutterNsd.stopDiscovery();

      _scanning = false;
    });
  }

  ///Attempts to connect to socket using the NSD service info hostname and port 30000.
  Future<void> connectSocket(int index) async {
    final Pad pad = await Pad.init(services[index]);

    pad.registerListener(receiveMessage);
    padResult = '';
    hasResult = Event();
    hasResult.subscribe((args) {
      //If we recieve a message from the pad that says '1', we add the Pad to the sockets Map.
      if (padResult == '1') {
        pad.alive = true;

        setState(() {
          socketsMap[services[index]] = pad;
        });
      }
      if (padResult == '0') {
        print('unsubscribing');
        hasResult.unsubscribeAll();
      }
      if (padResult == '3') {
        countHit += 1;
      }

      //hasResult.unsubscribeAll();
    });
    pad.send('1');
  }

  void receiveMessage(dynamic msg) {
    padResult = utf8.decode(msg as Uint8List);
    hasResult.broadcast();
  }

  Future<void> disconnectSocket(int index) async {
    final rp = ReceivePort();
    socketsMap[services[index]]!.send(null);
    final msg = await rp.first;

    setState(() {
      if (msg == 'done') {
        socketsMap.remove(services[index]);
        services.removeAt(index);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    if (initialStart) {
      socketsMap = (ModalRoute.of(context)?.settings.arguments
      as Pair<List<NsdServiceInfo>, Map<NsdServiceInfo, Pad>>)
          .right;
      services = (ModalRoute.of(context)?.settings.arguments
      as Pair<List<NsdServiceInfo>, Map<NsdServiceInfo, Pad>>)
          .left;
    }
    return Scaffold(
      appBar: AppBar(
          title:
          const Text('Pad Manager', style: TextStyle(color: Colors.white))),
      body: WillPopScope(
          onWillPop: _pop,
          child: Column(
            children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  ElevatedButton(
                    child: const Text('Start'),
                    onPressed: () async => startDiscovery(),
                  ),
                  ElevatedButton(
                    child: const Text('Stop'),
                    onPressed: () async => stopDiscovery(),
                  ),
                ],
              ),
              Expanded(
                child: _buildMainWidget(context),
              ),
            ],
          )),
    );
  }

  Widget _buildMainWidget(BuildContext context) {
    if (services.isEmpty && _scanning) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    } else if (services.isEmpty && !_scanning) {
      return const SizedBox.shrink();
    } else {
      return ListView.builder(
        itemBuilder: (context, index) => ListTile(
          title: Text(services[index].name ?? 'Invalid service name'),
          onTap: () async => !socketsMap.containsKey(services[index])
              ? connectSocket(index)
              : disconnectSocket(index),
          trailing: socketsMap.containsKey(services[index])
              ? const Text(
            'Connected',
            style: TextStyle(color: Colors.green),
          )
              : const Text(
            'Disconnected',
            style: TextStyle(color: Colors.red),
          ),
        ),
        itemCount: services.length,
      );
    }
  }

  Future<bool> _pop() {
    Navigator.pop(context, Pair(services, socketsMap));
    return Future.value(true);
  }
}
