﻿using System;
using System.Collections.Generic;
using Dissonance.Datastructures;
using Dissonance.Extensions;
using Dissonance.Networking;
using UnityEngine;
using BNetworking = BeardedManStudios.Network.Networking;

namespace Dissonance.Integrations.ForgeNetworking
{
    [HelpURL("https://placeholder-software.co.uk/dissonance/docs/Basics/Quick-Start-Forge/")]
    public class ForgeCommsNetwork
        : BaseCommsNetwork<ForgeServer, ForgeClient, ForgePeer, Unit, Unit>
    {
        public int VoiceDataChannelToServer = 57729872;
        public int SystemMessagesChannelToServer = 57729873;
        public int VoiceDataChannelToClient = 57729874;
        public int SystemMessagesChannelToClient = 57729875;
        
        private readonly ConcurrentPool<byte[]> _loopbackBuffers = new ConcurrentPool<byte[]>(8, () => new byte[1024]);
        private readonly List<ArraySegment<byte>> _loopbackQueue = new List<ArraySegment<byte>>();

        protected override ForgeServer CreateServer(Unit details)
        {
            return new ForgeServer(this);
        }

        protected override ForgeClient CreateClient(Unit details)
        {
            return new ForgeClient(this);
        }

        protected override void Update()
        {
            if (IsInitialized)
            {
                if (BNetworking.PrimarySocket != null)
                {
                    if (BNetworking.PrimarySocket.IsServer)
                    {
                        if (Mode != NetworkMode.Host)
                            RunAsHost(Unit.None, Unit.None);
                    }
                    else
                    {
                        if (Mode != NetworkMode.Client)
                            RunAsClient(Unit.None);
                    }
                }
                else
                {
                    if (Mode != NetworkMode.None)
                        Stop();
                }

                //Send looped back packets
                for (var i = 0; i < _loopbackQueue.Count; i++)
                {
                    if (Client != null)
                        Client.NetworkReceivedPacket(_loopbackQueue[i]);

                    // Recycle the packet into the pool of byte buffers
                    // ReSharper disable once AssignNullToNotNullAttribute (Justification: ArraySegment array is not null)
                    _loopbackBuffers.Put(_loopbackQueue[i].Array);
                }
                _loopbackQueue.Clear();
            }

            base.Update();
        }
        
        public bool PreprocessPacketToClient(ArraySegment<byte> packet, ForgePeer destination, uint channel, bool reliable)
        {
            //Forge does not seem to handle loopback in all situations (when sending to a specific player)
            //This runs before the server sends any packets and if it returns true the packet is not sent.
            //We'll use this to intercept loopback packets and deliver them ourselves.

            //This should never even be called if this peer is not the host!
            if (!Mode.IsServerEnabled())
            {
                Log.Error("Dissonance Forge server packet preprocessing running, but this peer is not a server");
                return false;
            }

            //Is this loopback?
            if (destination.Peer.NetworkId != BNetworking.PrimarySocket.Me.NetworkId)
                return false;

            //This is loopback!

            // Don't immediately deliver the packet, add it to a queue and deliver it next frame. This prevents the local client from executing "within" ...
            // ...the local server which can cause confusing stack traces.
            _loopbackQueue.Add(packet.CopyTo(_loopbackBuffers.Get()));

            return true;
        }

        public bool PreprocessPacketToServer(ArraySegment<byte> packet, uint channel, bool reliable)
        {
            //Technically this isn't needed, forge handles loopback just fine sending from client -> server
            //However this is slightly more efficient (no point running through all that forge logic when
            //we could just pass a pointer across directly)
            
            //This should never even be called if this peer is not a client!
            if (!Mode.IsClientEnabled())
            {
                Log.Error("Forge client packet preprocessing running, but this peer is not a client");
                return false;
            }

            //Is this loopback?
            if (!Mode.IsServerEnabled())
                return false;

            //This is loopback!

            //Since this is loopback destination == source (by definition)
            Server.NetworkReceivedPacket(source: new ForgePeer(BNetworking.PrimarySocket.Me), data: packet);

            return true;
        }
    }
}
