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

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

        private NetChaperone Net;

        private Connection Server;

        private bool SelfIdentified = false;

        public float Ping { get; private set; }
        private float LastPingReceived;
        private byte LastPingIdent;

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

            LocalPlayer = new NetPlayer(ulong.MaxValue - 1);
            LocalPlayer.Controlled = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();
        }

        public override void Start(string address, int port, string nick, PeerMessageListener listener) {
            if (Peer.Running) {
                return;
            }
            Players.Clear();
            Peer = new Peer(Fingerprint);
            Peer.MessageListener = listener;
            Peer.Start(0);
            byte ident = Peer.RegisterProtocol(new GameProtocol(this));
            Peer.Connect(address, port, ident);

            LocalPlayer.Nick = nick;
        }

        public override void OnStop() {
            Peer.Disconnect(Server.uid);
            Server = null;
            foreach (NetPlayer p in Players.Values) {
                if (p.Id != LocalPlayer.Id) {
                    GameObject.Destroy(p.Controlled.gameObject);
                }
            }
            Players.Clear();
            Ping = 0;
        }


        public override void Connected(Connection conn) {
            Terminal.Singleton.Println($"{conn.uid} Connected");
            if (Server == null) {
                Server = conn;
            } else {
                Peer.Disconnect(conn.uid);
            }
        }

        public override void Disconnected(Connection conn) {
            Terminal.Singleton.Println($"{conn.uid} Disconnected: {conn.ClosingReason}");
            if (Server == conn) {
                Server = null;
                foreach (NetPlayer p in Players.Values) {
                    if (p.Id != LocalPlayer.Id) {
                        GameObject.Destroy(p.Controlled.gameObject);
                    }
                }
                Players.Clear();
                PlayerList.Clear();
                Peer.Stop();
                Ping = 0;
            }
        }

        public override void Handle(Connection conn, Packet packet) {
            if (packet is SelfIdentPckt) {
                SelfIdentPckt ident = (SelfIdentPckt)packet;
                LocalPlayer.Id = ident.PlayerId;
                LocalPlayer.Team = ident.Team;
                LocalPlayer.Controlled.NetId = LocalPlayer.Id;
                AddPlayer(LocalPlayer);
                SelfIdentified = true;

                NicknamePckt nickpckt = new NicknamePckt(LocalPlayer.Nick);
                Peer.SendReliableLater(Server.uid, nickpckt);

                ReadyPckt ready = new ReadyPckt();
                Peer.SendReliable(Server.uid, ready);
            } else if (packet is NicknamePckt) {
                NicknamePckt nick = (NicknamePckt)packet;
                if (nick.PlayerId != LocalPlayer.Id) {
                    Players[nick.PlayerId].Nick = nick.Nick;
                }
            } else if (packet is SpawnPckt) {
                SpawnPckt spawn = (SpawnPckt)packet;
                if (spawn.IsInitial) {
                    if (spawn.PlayerId == LocalPlayer.Id) {
                        HandlePlayerRespawn(spawn.PlayerId, spawn.Location);
                    } else {
                        Player obj = Net.SpawnPlayer(spawn.Location).GetComponent<Player>();
                        Players[spawn.PlayerId].Controlled = obj;
                    }
                } else if (Players[spawn.PlayerId].Controlled.IsDead) {
                    if (Players.ContainsKey(spawn.PlayerId)) {
                        HandlePlayerRespawn(spawn.PlayerId, spawn.Location);
                    }
                }
            } else if (packet is PlayerInitPckt) {
                PlayerInitPckt init = (PlayerInitPckt)packet;
                if (Players.ContainsKey(conn.uid)) {
                    return; // Got the same init twice
                }
                NetPlayer player = new NetPlayer(init.PlayerId);
                player.Nick = init.NicknamePckt.Nick;
                player.Team = init.Team;
                Player obj = Net.SpawnPlayer(init.SpawnPckt.Location).GetComponent<Player>();
                player.Controlled = obj;
                AddPlayer(player);
            } else if (packet is DeadPckt) {
                DeadPckt dead = (DeadPckt)packet;
                if (Players.ContainsKey(dead.DeadPlayerId)) {
                    HandlePlayerDeath(dead.DeadPlayerId, dead.KillerPlayerId);
                }
            } else if (packet is MultiplePlayerUpdatesPckt) {
                MultiplePlayerUpdatesPckt multiple = (MultiplePlayerUpdatesPckt)packet;
                foreach (PlayerUpdatePckt pckt in multiple.Updates) {
                    HandleUpdatePlayer(pckt);
                }
            } else if (packet is MultipleSyncsPckt) {
                MultipleSyncsPckt multiple = (MultipleSyncsPckt)packet;
                foreach (PlayerSyncPacket pckt in multiple.Syncs) {
                    HandleSyncPckt(pckt);
                }
            } else if (packet is PlayerActionPckt) {
                PlayerActionPckt action = (PlayerActionPckt)packet;
                HandleAction(action);
            } else if (packet is PingPckt) {
                PingPckt ping = (PingPckt)packet;
                ping.ClientReceived = true;
                if (!ping.ServerReceived) {
                    Peer.SendReliable(Server.uid, ping);
                    LastPingIdent = ping.Identifier;
                    LastPingReceived = Time.time;
                } else {
                    if (LastPingIdent == ping.Identifier) {
                        Ping = Time.time - LastPingReceived;
                    } else {
                        Ping = Instances.Server.PingInterval + 0.001f;
                    }
                    LocalPlayer.Ping = Ping;
                    UpdatePingBias();
                }
            } else if (packet is PingListPckt) {
                PingListPckt pinglist = (PingListPckt)packet;
                foreach (PingInfo info in pinglist.Pings) {
                    if (info.PlayerId != LocalPlayer.Id) {
                        Players[info.PlayerId].Ping = info.Ping;
                    }
                }
            } else if (packet is HitPckt) {
                HitPckt hit = (HitPckt)packet;
                if (Players[hit.Target].Controlled != null) {
                    Players[hit.Target].Controlled.Hit(hit.Source);
                }
            }
        }

        private void HandleUpdatePlayer(PlayerUpdatePckt pckt) {
            if (pckt.PlayerId == LocalPlayer.Id && Players[pckt.PlayerId] != null) {
                return; // Ignore, again.
            }
            Players[pckt.PlayerId].Controlled.ProcessUpdatePacket(pckt);
        }

        private void HandleSyncPckt(PlayerSyncPacket syncPckt) {
            if (Players[syncPckt.PlayerId] != null && (syncPckt.Unsynced || syncPckt.PlayerId != LocalPlayer.Id)) {
                Players[syncPckt.PlayerId].Controlled.ProcessSyncPacket(syncPckt);
            }
        }

        private void HandleAction(PlayerActionPckt action) {
            switch (action.Action) {
                case PlayerAction.Jump:
                    Players[action.PlayerId].Controlled.Jump();
                    break;
                case PlayerAction.Shoot:
                    ShootData shootData = (ShootData)action.Serializable;
                    HandleSyncPckt(shootData.SyncPckt);
                    Players[action.PlayerId].Controlled.Shoot();
                    break;
            }
        }

        private void UpdatePingBias() {
            foreach (NetPlayer p in Players.Values) {
                if (p.Id != LocalPlayer.Id) {
                    p.Controlled.PingBias = Ping;
                }
            }

        }

        public override void UpdateLocalPlayer() {
            if (SelfIdentified && Server != null) {
                PlayerUpdatePckt pckt = LocalPlayer.Controlled.CreateUpdatePacket();
                Peer.SendUnreliable(Server.uid, pckt);
            }
        }

        public override void LocalPlayerAction(PlayerAction action, Serializable serializable = null) {
            if (SelfIdentified && Server != null) {
                PlayerActionPckt actionPckt = new PlayerActionPckt(action, serializable);
                Peer.SendReliable(Server.uid, actionPckt);
            }
        }

        public override void SendPlayerSync() {
            if (SelfIdentified && Server != null) {
                PlayerSyncPacket packet = LocalPlayer.Controlled.CreateSyncPacket();
                Peer.SendUnreliable(Server.uid, packet);
            }
        }

        public void HandlePlayerDeath(ulong dead, ulong killer) {
            if (Players[dead].Controlled != null) {
                Players[dead].Controlled.Dead(killer);
            }
        }

        public void HandlePlayerRespawn(ulong uid, Vector3 respawn) {
            if (Players[uid].Controlled != null) {
                Players[uid].Controlled.Respawn(respawn);
            }
        }
    }
}