﻿using System;
using BeardedManStudios;
using BeardedManStudios.Forge.Networking;
using BeardedManStudios.Forge.Networking.Frame;
using BeardedManStudios.Forge.Networking.Unity;
using Dissonance.Networking;

namespace Dissonance.Integrations.ForgeNetworkingRemastered
{
    public class ForgeRemasteredClient
        : BaseClient<ForgeRemasteredServer, ForgeRemasteredClient, ForgeRemasteredPeer>
    {
        private readonly ForgeRemasteredCommsNetwork _net;
        private readonly BMSByte _sendBuffer = new BMSByte();

        private NetWorker _networker;

        public ForgeRemasteredClient(ForgeRemasteredCommsNetwork net)
            : base(net)
        {
            _net = net;
        }

        public override void Connect()
        {
            _networker = NetworkManager.Instance.Networker;
            _networker.binaryMessageReceived += ForgeNetworkMessageReceived;

            Connected();
        }

        public override void Disconnect()
        {
            if (_networker != null)
                _networker.binaryMessageReceived -= ForgeNetworkMessageReceived;
            _networker = null;

            base.Disconnect();
        }

        #region receive
        private void ForgeNetworkMessageReceived(NetworkingPlayer player, [NotNull] FrameStream frame, NetWorker networker)
        {
            if (frame.GroupId != _net.VoiceDataChannelToClient && frame.GroupId != _net.SystemMessagesChannelToClient)
                return;

            NetworkReceivedPacket(new ArraySegment<byte>(frame.StreamData.byteArr, frame.StreamData.StartPointer, frame.StreamData.Size));
        }

        protected override void ReadMessages()
        {
            //messages are received in event handler, no work is needed here
        }
        #endregion

        #region send
        private void Send(ArraySegment<byte> packet, int channel, bool reliable)
        {
            if (_net.PreprocessPacketToServer(packet))
                return;

            _sendBuffer.Clear();
            _sendBuffer.BlockCopy(packet.Array, packet.Offset, packet.Count);

            var message = new Binary(
                _networker.Time.Timestep,
                _networker is BaseTCP,
                _sendBuffer,
                Receivers.Server,
                channel,
                _networker is BaseTCP
            );

            var udp = _networker as BaseUDP;
            if (udp != null)
            {
                udp.Send(message, reliable);
            }
            else
            {
                var host = FindHost();

                ((TCPServer)_networker).Send(host.TcpClientHandle, message);
            }
        }

        [NotNull] private static NetworkingPlayer FindHost()
        {
            for (int i = 0; i < NetworkManager.Instance.Networker.Players.Count; i++)
            {
                var p = NetworkManager.Instance.Networker.Players[i];
                if (p.IsHost)
                    return p;
            }

            throw new DissonanceException("Cannot find a host in the Forge networking session");
        }

        protected override void SendUnreliable(ArraySegment<byte> packet)
        {
            Send(packet, _net.SystemMessagesChannelToServer, false);
        }

        protected override void SendReliable(ArraySegment<byte> packet)
        {
            Send(packet, _net.SystemMessagesChannelToServer, true);
        }
        #endregion
    }
}
