using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NeonTea.Quakeball.Networking.Packets;
using NeonTea.Quakeball.Players;
using NeonTea.Quakeball.Interface;
using NeonTea.Quakeball.TeaNet.Peers;
using NeonTea.Quakeball.TeaNet.Packets;

namespace NeonTea.Quakeball.Networking.Instances {
    public class Server : NetInstance {

        private NetChaperone Net;

        private Dictionary<ulong, NetPlayer> Players = new Dictionary<ulong, NetPlayer>();
        private ulong PlayerIdCounter;

        private NetPlayer LocalPlayer = new NetPlayer(ulong.MaxValue);

        private List<NetPlayer> PlayerList = new List<NetPlayer>();

        public override void Start(string address, int port, PeerMessageListener listener) {
            if (Peer != null) {
                return;
            }
            Peer = new Peer(Fingerprint);
            Peer.MessageListener = listener;
            Peer.Start(port);
            Peer.RegisterProtocol(new GameProtocol(this));
            Peer.StartListen(address, port);

            Net = GameObject.FindGameObjectWithTag("Net").GetComponent<NetChaperone>();

            Player obj = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();
            LocalPlayer.Controlled = obj;
            AddPlayer(LocalPlayer);
        }

        public override void Connected(Connection conn) {
            Terminal.Singleton.Println($"{conn.uid} Connected");
            foreach (NetPlayer p in Players.Values) {

                if (p.Controlled == null) { // Not yet initialized, sending later.
                    continue;
                }
                SpawnPckt spawn = new SpawnPckt();
                spawn.PlayerId = p.Id;
                spawn.Location = p.Controlled.transform.position;
                Peer.SendReliableLater(conn.uid, spawn);
            }
            NetPlayer RemotePlayer = new NetPlayer(PlayerIdCounter++);
            AddPlayer(RemotePlayer);
            SelfIdentPckt ident = new SelfIdentPckt();
            ident.PlayerId = RemotePlayer.Id;
            Peer.SendReliable(conn.uid, ident);
        }

        public override void Disconnected(Connection conn) {
            Terminal.Singleton.Println($"{conn.uid} Disconnected: {conn.ClosingReason}");
            if (Players.ContainsKey(conn.uid)) {
                RemovePlayer(Players[conn.uid]);
            }
        }

        public override void Handle(Connection conn, Packet packet) {
            if (packet is SpawnPckt) {
                SpawnPckt spawn = (SpawnPckt)packet;
                if (Players[conn.uid].Controlled == null) {
                    Player obj = Net.SpawnPlayer(spawn.Location).GetComponent<Player>();
                    Players[conn.uid].Controlled = obj;

                    spawn = new SpawnPckt();
                    spawn.PlayerId = conn.uid;
                    spawn.Location = obj.transform.position;
                    SendReliableToAll(spawn, except: spawn.PlayerId);
                }
            } else if (packet is PlayerUpdatePckt) {
                PlayerUpdatePckt updatePckt = (PlayerUpdatePckt)packet;
                if (Players[conn.uid].Controlled != null) {
                    updatePckt.PlayerId = conn.uid;
                    Players[conn.uid].Controlled.ProcessUpdatePacket(ref updatePckt);
                }
            } else if (packet is PlayerJumpPckt) {
                PlayerJumpPckt jump = (PlayerJumpPckt)packet;
                if (Players[conn.uid].Controlled != null) {
                    if (Players[conn.uid].Controlled.Jump()) {
                        jump.PlayerId = conn.uid;
                        SendReliableToAll(jump);
                    }
                }
            } else if (packet is PlayerSyncPacket) {
                PlayerSyncPacket sync = (PlayerSyncPacket)packet;
            }
        }

        private void AddPlayer(NetPlayer player) {
            Players.Add(player.Id, player);
            PlayerList.Add(player);
        }

        private void RemovePlayer(NetPlayer player) {
            Players.Remove(player.Id);
            PlayerList.Remove(player);
        }

        public void SendReliableToAll(Packet packet, ulong except = ulong.MaxValue) {
            foreach (NetPlayer p in Players.Values) {
                if (p.Id == ulong.MaxValue || p.Id == except) {
                    continue;
                }
                Peer.SendReliable(p.Id, packet);
            }
        }

        public void SendUnreliableToAll(Packet packet, ulong except = ulong.MaxValue) {
            foreach (NetPlayer p in Players.Values) {
                if (p.Id == ulong.MaxValue || p.Id == except) {
                    continue;
                }
                Peer.SendUnreliable(p.Id, packet);
            }
        }

        public override void UpdateLocalPlayer() {
            MultiplePlayerUpdatesPckt pckt = new MultiplePlayerUpdatesPckt(PlayerList);
            SendUnreliableToAll(pckt);
        }

        public override void LocalPlayerJump() {
            PlayerJumpPckt jump = new PlayerJumpPckt(LocalPlayer.Id);
            SendReliableToAll(jump);
        }

        public override void SendPlayerSync() {
            MultipleSyncsPckt pckt = new MultipleSyncsPckt(PlayerList);
            SendUnreliableToAll(pckt);
        }
    }
}