import 'dart:collection';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_nsd/flutter_nsd.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:get_it/get_it.dart';
import 'package:skillcourt/pad.dart';
import 'package:skillcourt/pages/pad_manager.dart';
import 'package:skillcourt/pages/pad_placement_form.dart';
import 'package:skillcourt/pair.dart';
import 'package:skillcourt/station.dart';

final getIt = GetIt.instance;
Map<int, Offset> padLocations = <int, Offset>{};
Map<int, int> padRotations = <int, int>{};
Offset? playerLocation;
final _key = GlobalKey();

class StationCreationForm extends StatefulWidget {
  static const routeName = 'station-creation-form';

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

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

class _StationCreationFormState extends State<StationCreationForm> {
  String dropdownValue = 'Select Object';
  final Station _station = getIt.get<Station>();
  Map<NsdServiceInfo, Pad> socketsMap = <NsdServiceInfo, Pad>{};
  List<NsdServiceInfo> services = <NsdServiceInfo>[];
  Queue<Map<int, Offset>> prevPadLocations = Queue<Map<int, Offset>>();
  Queue<Map<int, int>> prevPadRotations = Queue<Map<int, int>>();
  Map<int, Offset> savedPadLocations = <int, Offset>{};
  Map<int, int> savedPadRotations = <int, int>{};

  @override
  void initState() {
    if (_station.hasPads) {
      for (var i = 0; i < _station.padsCount; i++) {
        services.add(_station.getPadWithIndex(i)!.nsdServiceInfo);
        socketsMap[_station.getPadWithIndex(i)!.nsdServiceInfo] =
            _station.getPadWithIndex(i)!;
        if (_station.getPadWithIndex(i)!.location != null) {
          padLocations[i] = _station.getPadWithIndex(i)!.location!;
          padRotations[i] = _station.getPadWithIndex(i)!.angle!;
        }
      }
      savedPadLocations = Map.from(padLocations);
      savedPadRotations = Map.from(padRotations);
    }
    if (_station.playerLocation != null) {
      padLocations[-1] = _station.playerLocation!;
    }
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: const Text('Station Creation Form',
                style: TextStyle(color: Colors.black))),
        body: WillPopScope(
            onWillPop: _pop,
            child: Stack(children: <Widget>[
              SingleChildScrollView(
                physics: const AlwaysScrollableScrollPhysics(),
                padding: EdgeInsets.zero,
                child: Column(children: <Widget>[
                  Column(
                    mainAxisAlignment: MainAxisAlignment.start,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Padding(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 10.0, vertical: 15.0),
                          child: PlatformTextFormField(
                              onChanged: _station.setName,
                              initialValue: _station.name,
                              material: (_, __) => MaterialTextFormFieldData(
                                  controller: null,
                                  maxLines: 1,
                                  decoration: const InputDecoration(
                                      labelText: 'Station Name',
                                      labelStyle:
                                          TextStyle(color: Colors.black))),
                              cupertino: (_, __) => CupertinoTextFormFieldData(
                                  controller: null,
                                  maxLines: 1,
                                  placeholder: 'Station Name'))),
                      Padding(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 10.0, vertical: 15.0),
                          child: Row(children: <Widget>[
                            Padding(
                                padding: const EdgeInsets.symmetric(
                                    horizontal: 0, vertical: 10),
                                child: DropdownButton<String>(
                                  value: dropdownValue,
                                  icon: const Icon(Icons.arrow_downward),
                                  iconSize: 24,
                                  elevation: 16,
                                  style: const TextStyle(color: Colors.green),
                                  underline: Container(
                                    height: 2,
                                    color: Colors.green,
                                  ),
                                  onChanged: (String? newValue) {
                                    setState(() {
                                      dropdownValue = newValue!;
                                    });
                                  },
                                  items: _buildPadWidget()
                                      .map<DropdownMenuItem<String>>(
                                          (String value) {
                                    return DropdownMenuItem<String>(
                                      value: value,
                                      child: Text(value),
                                    );
                                  }).toList(),
                                )),
                            const Spacer(),
                            Padding(
                                padding: const EdgeInsets.symmetric(
                                    horizontal: 0, vertical: 10),
                                child: PlatformTextButton(
                                    onPressed: () => _getSockets(),
                                    child: const Text('Pads'))),
                            const Spacer(),
                          ])),
                      Padding(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 10.0, vertical: 15.0),
                          child: PlatformTextFormField(
                              onChanged: _station.setDescription,
                              initialValue: _station.description,
                              material: (_, __) => MaterialTextFormFieldData(
                                  controller: null,
                                  maxLines: 1,
                                  decoration: const InputDecoration(
                                      labelText: 'Description of Station',
                                      labelStyle:
                                          TextStyle(color: Colors.black))),
                              cupertino: (_, __) => CupertinoTextFormFieldData(
                                  controller: null,
                                  maxLines: 1,
                                  placeholder: 'Station Name')))
                    ],
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      const Spacer(),
                      Padding(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 0, vertical: 10),
                          child: Draggable(
                            feedback: _genPadIcon(),
                            data: _getPadPayload(),
                            child: Text(getIndexOfPad(
                                        socketsMap, dropdownValue) ==
                                    -1
                                ? dropdownValue == 'Player'
                                    ? 'Place Player'
                                    : ''
                                : 'Place pad ${getIndexOfPad(socketsMap, dropdownValue) + 1}'),
                          )),
                      const Spacer(),
                      Padding(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 0, vertical: 10),
                          child: PlatformTextButton(
                              onPressed: () => _undoButton(),
                              child: const Text('Undo'))),
                      const Spacer(),
                      Padding(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 0, vertical: 5),
                          child: PlatformTextButton(
                              onPressed: () => _saveLocations(),
                              child: const Text('Save'))),
                      const Spacer(),
                      Padding(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 0, vertical: 5),
                          child: PlatformTextButton(
                              onPressed: () => _resetForm(),
                              child: const Text('Reset'))),
                      const Spacer(),
                    ],
                  ),
                  DragTarget<Pad>(
                      onAcceptWithDetails: _dragTargetAccept,
                      builder:
                          (context, candidateContainers, rejectedContainers) {
                        return CustomPaint(
                          foregroundPainter: PadPainter(),
                          child: Container(
                            key: _key,
                            color: Colors.blueGrey,
                            alignment: Alignment.bottomCenter,
                            width: 600,
                            height: 400,
                            margin: const EdgeInsets.all(15),
                          ),
                        );
                      })
                ]),
              ),
            ])));
  }

  Future<bool> _pop() {
    for (final pad in socketsMap.entries) {
      if (_station.getPadWithNSI(pad.key) == null) {
        // pad.value.send(null);
        socketsMap.remove(pad.key);
      }
    }
    return Future.value(true);
  }

  void _saveLocations() {
    setState(() {
      savedPadLocations = Map.from(padLocations);
      savedPadRotations = Map.from(padRotations);
      for (final key in savedPadLocations.keys) {
        _station.getPadWithIndex(key)?.location = savedPadLocations[key];
        _station.getPadWithIndex(key)?.angle = savedPadRotations[key];
      }
    });
  }

  void _resetForm() {
    setState(() {
      for (final key in savedPadLocations.keys) {
        _station.getPadWithIndex(key)?.location = null;
        _station.getPadWithIndex(key)?.angle = null;
      }
      prevPadLocations = Queue<Map<int, Offset>>();
      prevPadRotations = Queue<Map<int, int>>();
      padRotations = <int, int>{};
      padLocations = <int, Offset>{};
    });
  }

  void _undoButton() {
    setState(() {
      if (prevPadLocations.length >= 2) {
        prevPadLocations.removeLast();
        prevPadRotations.removeLast();
      } else {
        prevPadLocations = Queue<Map<int, Offset>>();
        prevPadLocations.add(<int, Offset>{});
        prevPadRotations = Queue<Map<int, int>>();
        prevPadRotations.add(<int, int>{});
      }
      padLocations = prevPadLocations.last;
      padRotations = prevPadRotations.last;
    });
  }

  Pad? _getPadPayload() {
    final index = getIndexOfPad(socketsMap, dropdownValue);
    if (index >= 0) {
      return socketsMap.entries.elementAt(index).value;
    }
  }

  List<String> _buildPadWidget() {
    if (socketsMap.isEmpty) {
      return List.generate(
          2, (index) => index == 0 ? 'Select Object' : 'Player');
    }
    final socketsIterable = socketsMap.entries;
    return List.generate(socketsMap.length + 2, (index) {
      if (index == 0) {
        return 'Select Object';
      } else if (index == 1) {
        return 'Player';
      } else {
        return socketsIterable.elementAt(index - 2).key.name!;
      }
    });
  }

  int getIndexOfPad(Map<NsdServiceInfo, Pad> socketsMap, String dropdown) {
    if (socketsMap.isEmpty) {
      return -1;
    }
    final socketsIterable = socketsMap.entries;
    for (var i = 0; i < socketsIterable.length; i++) {
      if (socketsIterable.elementAt(i).key.name == dropdown) {
        return i;
      }
    }
    return -1;
  }

  Future<void> _dragTargetAccept(DragTargetDetails<Pad> details) async {
    if (dropdownValue == 'Player') {
      padLocations[-1] = details.offset;
    } else if (padLocations[-1] != null) {
      final formResult = PadPlacementForm(details.offset.dx.round() + 30,
          details.offset.dy.round() + 15, 0, padLocations[-1]!);
      await formResult.displayTextInputDialog(context);
      setState(() {
        padLocations[getIndexOfPad(socketsMap, dropdownValue)] =
            Offset(formResult.xPos - 30, formResult.yPos - 15);
        padRotations[getIndexOfPad(socketsMap, dropdownValue)] = formResult.rot;
        prevPadLocations.add(Map.from(padLocations));
        prevPadRotations.add(Map.from(padRotations));
      });
    } else {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
        content: Text('Please place the player first before placing pads.'),
      ));
    }
  }

  Widget _genPadIcon() {
    final index = getIndexOfPad(socketsMap, dropdownValue);
    if (dropdownValue == 'Select Object') {
      return const Text('No object selected');
    } else if (dropdownValue == 'Player') {
      return Card(
          shape:
              RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
          child: const Text('Player'));
    }
    return Card(
        shape:
            RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
        child: Text('Pad ${index + 1}'));
  }

  Future<void> _getSockets() async {
    final pair = await Navigator.pushNamed(context, PadManagerPage.routeName,
            arguments: Pair(services, socketsMap))
        as Pair<List<NsdServiceInfo>, Map<NsdServiceInfo, Pad>>;
    setState(() {
      services = pair.left;
      socketsMap = pair.right;
      for (final entry in socketsMap.entries) {
        if (_station.getPadIndexWithNSI(entry.key) == null) {
          _station.addPad(entry.value);
          // entry.value.padNum = _station.getPadIndexWithNSI(entry.key)!;
        }
      }
    });
  }
}

class PadPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final RenderBox? box =
        _key.currentContext!.findRenderObject() as RenderBox?;
    final Offset position = box!.localToGlobal(Offset.zero);
    final playerPaint = Paint()
      ..color = Colors.black
      ..strokeWidth = 7;
    if (padLocations.containsKey(-1)) {
      canvas.drawCircle(
          Offset(padLocations[-1]!.dx - position.dx,
              padLocations[-1]!.dy - position.dy),
          7,
          playerPaint);
    }
    final squarePaint = Paint()
      ..color = Colors.white
      ..strokeWidth = 4;
    for (final i in padRotations.keys) {
      late final Color c;
      switch (i) {
        case 0:
          c = Colors.black;
          break;
        case 1:
          c = Colors.blue;
          break;
        case 2:
          c = Colors.green;
          break;
        case 3:
          c = Colors.red;
          break;
      }
      final paint = Paint()
        ..color = c
        ..strokeWidth = 30
        ..style = PaintingStyle.fill;
      final Offset topLeft = Offset(padLocations[i]!.dx - position.dx + 30,
          padLocations[i]!.dy - position.dy + 15);
      final Path quadPath = Path()
        ..moveTo(topLeft.dx, topLeft.dy)
        ..relativeLineTo(-30, -15)
        ..relativeLineTo(60, 0)
        ..relativeLineTo(0, 30)
        ..relativeLineTo(-60, 0)
        ..relativeLineTo(0, -30)
        ..relativeLineTo(30, 15)
        ..close();
      rotate(canvas, topLeft.dx + 30, topLeft.dy + 15,
          padRotations[i]! * pi / 180);
      canvas.drawPath(quadPath, paint);
      canvas.drawLine(Offset(topLeft.dx, topLeft.dy - 15),
          Offset(topLeft.dx, topLeft.dy + 15), squarePaint);
      canvas.drawLine(Offset(topLeft.dx - 16, topLeft.dy + 3),
          Offset(topLeft.dx, topLeft.dy + 15), squarePaint);
      canvas.drawLine(Offset(topLeft.dx + 16, topLeft.dy + 3),
          Offset(topLeft.dx, topLeft.dy + 15), squarePaint);
      for (var j = 0; j <= i; j++) {
        canvas.drawRect(
            Rect.fromLTRB(
                topLeft.dx + (j.isEven ? -25 : 21),
                topLeft.dy + (j <= 2 ? -10 : 6),
                topLeft.dx + (j.isEven ? -21 : 25),
                topLeft.dy + (j <= 2 ? -6 : 10)),
            squarePaint);
      }
      rotate(canvas, topLeft.dx + 30, topLeft.dy + 15,
          -padRotations[i]! * pi / 180);
    }
  }

  void rotate(Canvas canvas, double cx, double cy, double angle) {
    canvas.translate(cx, cy);
    canvas.rotate(angle);
    canvas.translate(-cx, -cy);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}
