using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;

namespace Emby.Server.Implementations.Networking.IPNetwork
{
    public class IPNetworkCollection : IEnumerable<IPNetwork>, IEnumerator<IPNetwork>
    {

        private BigInteger _enumerator;
        private byte _cidrSubnet;
        private IPNetwork _ipnetwork;

        private byte _cidr => this._ipnetwork.Cidr;

        private BigInteger _broadcast => IPNetwork.ToBigInteger(this._ipnetwork.Broadcast);

        private BigInteger _lastUsable => IPNetwork.ToBigInteger(this._ipnetwork.LastUsable);
        private BigInteger _network => IPNetwork.ToBigInteger(this._ipnetwork.Network);
#if TRAVISCI
        public
#else
        internal
#endif
        IPNetworkCollection(IPNetwork ipnetwork, byte cidrSubnet)
        {

            int maxCidr = ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128;
            if (cidrSubnet > maxCidr)
            {
                throw new ArgumentOutOfRangeException(nameof(cidrSubnet));
            }

            if (cidrSubnet < ipnetwork.Cidr)
            {
                throw new ArgumentException("cidr");
            }

            this._cidrSubnet = cidrSubnet;
            this._ipnetwork = ipnetwork;
            this._enumerator = -1;
        }

        #region Count, Array, Enumerator

        public BigInteger Count
        {
            get
            {
                var count = BigInteger.Pow(2, this._cidrSubnet - this._cidr);
                return count;
            }
        }

        public IPNetwork this[BigInteger i]
        {
            get
            {
                if (i >= this.Count)
                {
                    throw new ArgumentOutOfRangeException(nameof(i));
                }

                var last = this._ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6
                    ? this._lastUsable : this._broadcast;
                var increment = (last - this._network) / this.Count;
                var uintNetwork = this._network + ((increment + 1) * i);
                var ipn = new IPNetwork(uintNetwork, this._ipnetwork.AddressFamily, this._cidrSubnet);
                return ipn;
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator<IPNetwork> IEnumerable<IPNetwork>.GetEnumerator()
        {
            return this;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this;
        }

        #region IEnumerator<IPNetwork> Members

        public IPNetwork Current => this[this._enumerator];

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            // nothing to dispose
            return;
        }

        #endregion

        #region IEnumerator Members

        object IEnumerator.Current => this.Current;

        public bool MoveNext()
        {
            this._enumerator++;
            if (this._enumerator >= this.Count)
            {
                return false;
            }
            return true;

        }

        public void Reset()
        {
            this._enumerator = -1;
        }

        #endregion

        #endregion

    }
}
