using System;
using System.Collections.Generic;
using DeStream.Bitcoin.P2P.Peer;
using DeStream.Bitcoin.Utilities;

namespace DeStream.Bitcoin.P2P.Protocol.Behaviors
{
    public interface INetworkPeerBehavior
    {
        INetworkPeer AttachedPeer { get; }
        void Attach(INetworkPeer peer);
        void Detach();
        INetworkPeerBehavior Clone();
    }

    public abstract class NetworkPeerBehavior : INetworkPeerBehavior
    {
        private object cs = new object();
        private List<IDisposable> disposables = new List<IDisposable>();
        public INetworkPeer AttachedPeer { get; private set; }

        protected abstract void AttachCore();

        protected abstract void DetachCore();

        public abstract object Clone();

        protected void RegisterDisposable(IDisposable disposable)
        {
            this.disposables.Add(disposable);
        }

        public void Attach(INetworkPeer peer)
        {
            Guard.NotNull(peer, nameof(peer));

            if (this.AttachedPeer != null)
                throw new InvalidOperationException("Behavior already attached to a peer");

            lock (this.cs)
            {
                if (Disconnected(peer))
                    return;

                this.AttachedPeer = peer;
                 
                this.AttachCore();
            }
        }

        protected void AssertNotAttached()
        {
            if (this.AttachedPeer != null)
                throw new InvalidOperationException("Can't modify the behavior while it is attached");
        }

        private static bool Disconnected(INetworkPeer peer)
        {
            return (peer.State == NetworkPeerState.Created) || (peer.State == NetworkPeerState.Disconnecting) ||
                   (peer.State == NetworkPeerState.Failed) || (peer.State == NetworkPeerState.Offline);
        }

        public void Detach()
        {
            lock (this.cs)
            {
                if (this.AttachedPeer == null)
                    return;

                this.DetachCore();
                foreach (IDisposable dispo in this.disposables)
                    dispo.Dispose();

                this.disposables.Clear();
                this.AttachedPeer = null;
            }
        }

        INetworkPeerBehavior INetworkPeerBehavior.Clone()
        {
            return (INetworkPeerBehavior)this.Clone();
        }
    }
}