using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Globalization;
using System.Xml;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Net;

namespace EduNetworkBuilder
{
    [Serializable]
    public class NB_IPAddress
    {

        public UInt32 _ip;
        public UInt32 _mask;
        public UInt32 _gw;
        public IPAddressType myType;
        //Add in new values for the IP, Netmask, and gateway.  Will start migrating to use these.
        private IPAddress theIP;
        private IPAddress theNetmask;
        private IPAddress theGateway;

        private bool Unset = true;

        public NB_IPAddress() { } //used for reflection
        /// <summary>
        /// Duplicate an IP address structure
        /// </summary>
        /// <param name="Orig">The IP to clone from</param>
        public NB_IPAddress(NB_IPAddress Orig)
        {
            _ip = Orig._ip;
            _mask = Orig._mask;
            _gw = Orig._gw;
            myType = Orig.myType;
            theIP = Orig.theIP;
            theNetmask = Orig.theNetmask;
            theGateway = Orig.theGateway;
            Unset = false;
        } 

        /// <summary>
        /// Make a simple IP address with netmask
        /// </summary>
        /// <param name="ip">The new IP</param>
        /// <param name="mask">The new netmask</param>
        /// <param name="WhatType"></param>
        public NB_IPAddress(string ip, string mask, IPAddressType WhatType)
        {
            myType = WhatType;
            _ip = ip.ParseIp();
            _mask = mask.ParseIp();
            Unset = false;
        }

        /// <summary>
        /// Make a new IP address, netmask, with gateway.
        /// </summary>
        /// <param name="ip">The IP address</param>
        /// <param name="mask">The netmask</param>
        /// <param name="gw">The gateway to use</param>
        public NB_IPAddress(string ip, string mask, string gw)
        {
            myType = IPAddressType.route;
            _ip = ip.ParseIp();
            _mask = mask.ParseIp();
            _gw = gw.ParseIp();
            Unset = false;
        }

        /// <summary>
        /// Load an IP address from the XML file
        /// </summary>
        /// <param name="theNode">The XmlNode to load from</param>
        public NB_IPAddress(XmlNode theNode)
        {
            foreach (XmlNode Individual in theNode.ChildNodes)
            {
                XmlNodeType myNodetype = Individual.NodeType;
                if (myNodetype == XmlNodeType.Element)
                {
                    switch (Individual.Name.ToLower())
                    {
                        case "ip":
                            _ip = Individual.InnerText.ParseIp();
                            Unset = false;
                            break;
                        case "mask":
                            _mask = Individual.InnerText.ParseIp();
                            Unset = false;
                            break;
                        case "gateway":
                            _gw = Individual.InnerText.ParseIp();
                            Unset = false;
                            break;
                        case "type":
                            myType = NB.ParseEnum<IPAddressType>(Individual.InnerText);
                            break;
                    }
                }
            }
        }

        public void SetIP(UInt32 newIP)
        {
            _ip = newIP;
            Unset = false;
        }

        public bool Equals(NB_IPAddress CompareWith)
        {
            if (_ip != CompareWith._ip) return false;
            if (_mask != CompareWith._mask) return false;
            if (_gw != CompareWith._gw) return false;
            if (myType != CompareWith.myType) return false;
            return true;
        }

        public void Save(XmlWriter writer, string tag)
        {
            writer.WriteStartElement(tag);
            writer.WriteElementString("ip", _ip.ToIpString());
            writer.WriteElementString("mask", _mask.ToIpString());
            writer.WriteElementString("gateway", _gw.ToIpString());
            writer.WriteElementString("type", myType.ToString());
            writer.WriteEndElement();
        }

        public void Reparse(string ip, string mask, string gw)
        {
            _ip = ip.ParseIp();
            _mask = mask.ParseIp();
            _gw = gw.ParseIp();
            Unset = false;
        }
        public void Reparse(string ip, string mask)
        {
            _ip = ip.ParseIp();
            _mask = mask.ParseIp();
            Unset = false;
        }

        public IPAddressType GetAddressType
        {
            get { return myType; }
        }

        public bool IsLocal(NB_IPAddress dest)
        {
            if (dest == null) return false;
            UInt32 answer = dest.GetIP & _mask;
            if ((dest.GetIP & _mask) == NetworkAddress)
                return true;
            return false;

        }

        public bool Edit(NetworkDevice FromWhat, Form ParentForm, string message="", bool JustPinging = false)
        {
            IPAddressEntry IPe = new IPAddressEntry(this, FromWhat, ParentForm, JustPinging);
            if (message != "")
                IPe.Text = message;
            return IPe.Edit();
        }

        public bool Edit(NetworkDevice FromWhat, NB_IPAddress DHCPif, Form ParentForm, bool JustPinging = false)
        {
            IPAddressEntry IPe = new IPAddressEntry(this, FromWhat, ParentForm, JustPinging);
            return IPe.Edit(FromWhat, DHCPif);
        }

        public NB_IPAddress(string ip)
        {
            if (ip == null) ip = NB.ZeroIPString;
            myType = IPAddressType.ip;
            _ip = ip.ParseIp();
            var mySplitVal = ip.Split('/');
            if (mySplitVal.Count() < 2)
            {
                //We do not have a cidr.  We need to guess
                //For now, use 255.255.255.0
                _mask = "255.255.255.0".ParseIp();
                mySplitVal = ip.Split('.');
                if(mySplitVal.Count() > 0)
                {
                    //If it is not one of these three, we already use 255.255.255.0
                    if(mySplitVal[0] == "10")
                    {
                        _mask = "255.0.0.0".ParseIp();
                    }
                    if (mySplitVal[0] == "172")
                    {
                        _mask = "255.255.0.0".ParseIp();
                    }
                    if (mySplitVal[0] == "192")
                    {
                        _mask = "255.255.255.0".ParseIp();
                    }
                }
            }
            else
            {                
                _mask = MaskNumFromCIDRString(mySplitVal[1]);
            }
        }

        static UInt32 MaskNumFromCIDRString(string cidr)
        {
            UInt32 tInt = 0;
            int cdr;
            int.TryParse(cidr, out cdr);
            for (int loop = 0; loop < 32; loop++)
            {
                tInt = (tInt << 1);
                if (loop < cdr) tInt++;
            }
            
            return tInt;
        }

        public bool Equals(UInt32 IP)
        {
            return IP == _ip;
        }

        public bool Equals(UInt32 IP, UInt32 mask)
        {
            return (IP == _ip && mask == _mask);
        }


        public UInt32 NumberOfHosts
        {
            get { return ~_mask + 1; }
        }

        public UInt32 GetIP
        {
            get { return _ip; }
        }
        public string GetIPString
        {
            get { return _ip.ToIpString(); }
        }
        public string GetBroadcastString
        {
            get { return BroadcastAddress.ToIpString(); }
        }
        public string GetMaskString
        {
            get { return _mask.ToIpString(); }
        }

        public UInt32 GetMask
        {
            get { return _mask; }
        }
        public UInt32 GetGateway
        {
            get { return _gw; }
        }

        public UInt32 NetworkAddress
        {
            get { return _ip & _mask; }
        }

        public UInt32 BroadcastAddress
        {
            get { return NetworkAddress + ~_mask; }
        }

        /// <summary>
        /// Return the list of hosts in the subnet.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<UInt32> Hosts()
        {
            for (var host = NetworkAddress + 1; host < BroadcastAddress; host++)
            {
                yield return host;
            }
        }
        public string IPFormat()
        {
            return IPFormat(_gw.ToIpString());
        }
        public string PadIt(string Addr)
        {
            string myaddr = Addr.PadRight(12);
            return myaddr;
        }
        public string IPFormat(string gw)
        {
            string tstring = string.Format(NB.Translate("IPA_IPFormatStr"), 
                PadIt(_ip.ToIpString()), PadIt(_mask.ToIpString()), PadIt(gw));
            return tstring;
        }

        /// <summary>
        /// Return the CIDR number for this address.  This is the number of 1s before the first 0
        /// </summary>
        /// <returns></returns>
        public int CIDRNumber()
        {
            string mask = GetMask.ToBitString();
            mask = Regex.Replace(mask, "0", "");
            return mask.Length;
        }

        /// <summary>
        /// Return true if the subnet mask is really a true CIDR string /8, /16, etc.
        /// It is false if the subnet mask creates a mask that does not map to CIDR.  For
        /// example, 255.255.255.250 is not a real subnet mask.  252 is.
        /// </summary>
        /// <returns>True if there are no 1s after the first 0</returns>
        public bool ValidCIDR()
        {
            int cidr = CIDRNumber();
            UInt32 tMask = MaskNumFromCIDRString(cidr.ToString());
            if (tMask == _mask) return true;
            return false;
        }

        #region IP Status Funcs
        /// <summary>
        /// If the IP address has not yet been assigned
        /// </summary>
        public bool IsUnassigned() {
            if (GetIPString == NB.ZeroIPString) return true; //Sometimes we use 0.0.0.0 as unset.
            return Unset; 
        }

        /// <summary>
        /// If the IP address is the same as the broadcast address
        /// </summary>
        public bool IsBroadcast() 
        { 
            uint BA = BroadcastAddress;
            if (_ip == BA) return true;
            return false;
        }


        /// <summary>
        /// If the IP address is the same as the network address
        /// </summary>
        public bool IsNetwork() 
        {
            uint NA = NetworkAddress;
            if (_ip == NA) return true;
            return false; 
        }

        /// <summary>
        /// If the IP address is the loopback address.  Different for IPv4 than IPv6
        /// </summary>
        public bool IsLoopback() 
        {
            if (GetIPString == NB.LoopbackIPString) return true;
            return false;
        }


        /// <summary>
        /// If the IP address is 0.0.0.0 or the equavelent
        /// </summary>
        /// <returns>true if it is zeroes could be unset or anywhere</returns>
        public bool IsZeroString() {
            if (_ip == 0) return true;
            return false; 
        }

        #endregion

        #region Break an IP
        public string GenRandomIPOrMask()
        {
            Random rnd = NB.GetRandom();
            int mask1 = rnd.Next(256);
            int mask2 = rnd.Next(256);
            int mask3 = rnd.Next(256);
            int mask4 = rnd.Next(256);
            string answer = mask1 + "." + mask2 + "." + mask3 + "." + mask4;
            return answer;
        }

        public string randomizeOctects(string original)
        {
            string[] parts = original.Split('.');
            if (original == NB.ZeroIPString) return original;
            if (parts.Count() == 4 && parts[0] == parts[1] && parts[1] == parts[2] && parts[2] == parts[3]) return original;
            bool done = false;
            List<string> toRandomize = new List<string>(parts);
            List<string> randomized = new List<string>();
            string neworder = "";
            while (!done)
            {
                randomized = NB.Randomize<string>(toRandomize);
                neworder = string.Join(".", randomized.ToArray());
                if(neworder != original)
                    done = true;
            }
            return neworder;
        }

        public NB_IPAddress BreakIPNetmaskZero()
        {
            NB_IPAddress one = new NB_IPAddress(GetIPString, NB.ZeroIPString, myType); //0.0.0.0
            return one;
        }
        public NB_IPAddress BreakIPNetmask32()
        {
            NB_IPAddress one = new NB_IPAddress(GetIPString, NB.BroadcastIPString, myType);  //255.255.255.0
            return one;
        }
        public NB_IPAddress BreakIPNetmaskRandom()
        {
            string randomMask = "";
            do
            {
                randomMask = GenRandomIPOrMask();
            } while (randomMask == GetMaskString);
            NB_IPAddress one = new NB_IPAddress(GetIPString, randomMask, myType);
            return one;
        }
        public NB_IPAddress BreakIPNetmaskMangle()
        {
            string newmask = randomizeOctects(GetMaskString);
            NB_IPAddress one = new NB_IPAddress(GetIPString, newmask, myType);
            return one;
        }

        public NB_IPAddress BreakIPAddressZero()
        {
            NB_IPAddress one = new NB_IPAddress(NB.ZeroIPString, GetIPString, myType);
            return one;
        }
        public NB_IPAddress BreakIPAddress32()
        {
            NB_IPAddress one = new NB_IPAddress(NB.BroadcastIPString, GetIPString, myType);
            return one;
        }
        public NB_IPAddress BreakIPAddressRandom()
        {
            string randomIP = "";
            do
            {
                randomIP = GenRandomIPOrMask();
            } while (randomIP == GetMaskString);
            NB_IPAddress one = new NB_IPAddress(randomIP, GetMaskString, myType);
            return one;
        }
        public NB_IPAddress BreakIPAddressMangle()
        {
            string newip = randomizeOctects(GetMaskString);
            NB_IPAddress one = new NB_IPAddress(newip, GetMaskString, myType);
            return one;
        }
        #endregion
    }

    public static class IpHelpers
    {
        public static string ToIpString(this UInt32 value)
        {
            var bitmask = 0xff000000;
            var parts = new string[4];
            for (var i = 0; i < 4; i++)
            {
                var masked = (value & bitmask) >> ((3 - i) * 8);
                bitmask >>= 8;
                parts[i] = masked.ToString(CultureInfo.InvariantCulture);
            }
            return String.Join(".", parts);
        }

        public static string ToBitString(this UInt32 value)
        {
            var item = System.Convert.ToString(value, 2);
            return (string)item;
        }

        public static UInt32 ParseIp(this string ipAddress)
        {
            if (ipAddress == null) ipAddress = "";
            var gw = ipAddress.Split('/'); //Pull off any cdr
            var mySplitVal = gw[0].Split('.');
            if (mySplitVal.Count() != 4) 
                return 0;
            UInt32 ip = 0;
            uint part;
            if (mySplitVal.Count() == 4)
            {
                for (var i = 0; i < 4; i++)
                {
                    part = 0;
                    UInt32.TryParse(mySplitVal[i], out part);
                    ip = (ip << 8) + part;
                }
            }
            return ip;
        }
    }
}