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

namespace EduNetworkBuilder
{
    [Serializable]
    public class NetworkInterface
    {
        public NB_IPAddress myIP;
        public string nic_name;  //eth0, eth0:0, etc
        public HostNicID AttachedToHostNic;
        public List<VLANInfo> VLANs = new List<VLANInfo>() { new VLANInfo(1, VLANTagType.Untagged ) };
        //We should have a feature: none, IP_in_IP_Tunnel, encrypted_vpn, vlan, etc

        public NetworkInterface() { }

        public NetworkInterface(string name, string IP, string Mask, HostNicID AttachedTo)
        {
            nic_name = name;
            myIP = new NB_IPAddress(IP, Mask,IPAddressType.ip);
            AttachedToHostNic = AttachedTo;
        }

        public NetworkInterface(XmlNode theNode, HostNicID AttachedTo)
        {
            AttachedToHostNic = AttachedTo;
            foreach (XmlNode Individual in theNode.ChildNodes)
            {
                XmlNodeType myNodetype = Individual.NodeType;
                if (myNodetype == XmlNodeType.Element)
                {
                    switch (Individual.Name.ToLower())
                    {
                        case "myip":
                            myIP = new NB_IPAddress(Individual);
                            break;
                        case "nicname":
                            nic_name = Individual.InnerText;
                            break;
                        case "vlan":
                            if (Individual.Attributes != null && Individual.Attributes["ID"] != null)
                            {
                                int ID;
                                VLANTagType Tag = NB.TryParseEnum<VLANTagType>(Individual.InnerText, VLANTagType.Untagged);
                                int.TryParse(Individual.Attributes["ID"].Value, out ID);
                                VLANInfo current = GetVLANInfo(ID);
                                if (current != null) VLANs.Remove(current); //we are replacing it
                                VLANs.Add(new VLANInfo(ID, Tag));
                            }
                            break;
                    }
                }
            }
        }

        public NetworkInterface(NetworkInterface CopyFrom)
        {
            myIP = new NB_IPAddress(CopyFrom.myIP.GetIPString, CopyFrom.myIP.GetMaskString, CopyFrom.myIP.GetAddressType);
            nic_name = CopyFrom.nic_name;
            foreach(VLANInfo VLI in CopyFrom.VLANs)
            {
                VLANs.Add(new VLANInfo(VLI.ID, VLI.Tag));
            }
        }

        /// <summary>
        /// When we are doing replays and we load in a network interface using reflection,
        /// it can create a duplicate VLAN tag (1,untagged).  Rebuilding these fixes that.
        /// </summary>
        public void UpdateVLANsAfterClone()
        {
            List<VLANInfo> tmpVLANs = new List<VLANInfo>();
            tmpVLANs.AddRange(VLANs);
            VLANs.Clear();
            foreach(VLANInfo VLI in tmpVLANs)
            {
                bool found = false;
                foreach(VLANInfo VLI2 in VLANs)
                {
                    if(VLI2.ID == VLI.ID)
                    {
                        VLI2.Tag = VLI.Tag;
                        found = true;
                    }
                }
                if (!found)
                    VLANs.Add(VLI);
            }
        }

        public VLANInfo GetVLANInfo(int id)
        {
            VLANInfo status = null;
            foreach(VLANInfo one in VLANs)
            {
                if (one.ID == id)
                    status = one;
            }
            return status;
        }

        public bool Equals(NetworkInterface CompareWith)
        {
            if (!myIP.Equals(CompareWith.myIP)) return false;
            if (nic_name != CompareWith.nic_name) return false;
            if (!AttachedToHostNic.Equals(CompareWith.AttachedToHostNic)) return false;
            if (VLANs.Count != CompareWith.VLANs.Count) return false;
            for (int i = 0; i < VLANs.Count; i++)
                if (!VLANs[i].Equals(CompareWith.VLANs[i])) return false;
            return true;
        }

        public VLANTagType GetVLANTag(int id)
        {
            VLANInfo vli = GetVLANInfo(id);
            if (vli != null) return vli.Tag;
            return VLANTagType.Forbidden; //if not defined, it is forbidden
        }

        public int GetNonForbiddenVLANID()
        {
            foreach(VLANInfo vin in VLANs)
            {
                if (vin.Tag != VLANTagType.Forbidden)
                    return vin.ID;
            }
            return -999;
        }

        public void SetVLANTag(int id, VLANTagType Tag)
        {
            bool foundit = false;
            Network theNet = NB.GetNetwork();
            if (theNet == null)
                return;
            NetworkDevice ND = theNet.GetDeviceFromID(AttachedToHostNic);
            if (ND == null)
                return;
            NetworkCard NC = ND.NicFromID(AttachedToHostNic);
            if (NC == null)
                return;
            NicType NT = NC.GetNicType;
            bool isPort = NT == NicType.port;

            //Ports (switches) can have multiple tagged ports.
            //WAN, ETH0, etc, can only have one tagged or untagged per interface
            //nothing can have multiple untagged ports
            //    If something is untagged, everything else is forbidden

            foreach (VLANInfo one in VLANs)
            {
                if (one.ID == id)
                {
                    one.Tag = Tag;
                    foundit = true;
                }
                else
                {
                    if (Tag == VLANTagType.Untagged)
                        one.Tag = VLANTagType.Forbidden;  //Only one untagged port allowed at a time
                    if (Tag == VLANTagType.Tagged && !isPort) //Ports can have multiple tags on one interface
                        one.Tag = VLANTagType.Forbidden;
                    if (Tag == VLANTagType.Tagged && isPort)
                    {
                        if(one.Tag != VLANTagType.Tagged)
                            one.Tag = VLANTagType.Forbidden; //We cannot have an untagged port and tagged ports at the same time
                    }
                }
            }
            if (!foundit)
                VLANs.Add(new VLANInfo(id, Tag));
        }

        public void LockOutVLANInterface()
        {
            Network theNet = NB.GetNetwork();
            if (theNet == null)
                return;
            foreach(VLANName one in theNet.VlanNames)
            {
                SetVLANTag(one.ID, VLANTagType.Forbidden);
            }
        }

        public void Save(XmlWriter writer)
        {
            writer.WriteStartElement("interface");
            writer.WriteElementString("nicname", nic_name);
            myIP.Save(writer, "myip");
            foreach (VLANInfo one in VLANs)
            {
                writer.WriteStartElement("VLAN");
                writer.WriteAttributeString("ID", one.ID.ToString());
                writer.WriteString(one.Tag.ToString());
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }

        public string InterfaceString(bool UseCidr = false)
        {
            string tstring = myIP.GetIP.ToIpString();
            if (!UseCidr)
            {
                tstring += " - " + myIP.GetMask.ToIpString();
            }
            else
            {
                int cidr = myIP.CIDRNumber();
                string answer = cidr.ToString();
                if (!myIP.ValidCIDR()) answer = "?";
                tstring += "/" + answer;
            }
            return tstring;
        }

        public string RouteString(string nic_name, string GW)
        {
            string tstring = myIP.IPFormat(GW); ;
            return tstring;
        }

        /// <summary>
        /// Pop up a window to edit this address.
        /// </summary>
        public void EditAddress(Form ParentForm)
        {
            Network myNet = NB.GetNetwork();
            NetworkDevice ND = myNet.GetDeviceFromID(AttachedToHostNic);
            IPAddressEntry ipe = new IPAddressEntry(myIP,ND, ParentForm);
            ipe.ShowDialog();
        }
        public bool isLocal(NB_IPAddress tIp, bool AllowZeroMatch = true)
        {
            if (tIp == null) return false;
            if (!AllowZeroMatch && (myIP == null || myIP.GetIP == 0))
                return false;
            if (myIP.IsLocal(tIp))
                return true;
            return false;
        }

        /// <summary>
        /// We are sending the packet out this interface.
        /// </summary>
        /// <param name="tPacket"></param>
        public void ProcessOutboundPacket(Packet tPacket)
        {
            //tPacket.InboundInterface = null; //forget the interface we had come in on now that we are leaving.
            //if (tPacket.sourceIP == null || tPacket.sourceIP.GetIP.ToIpString() == NB.ZeroIPString)
            //{
            //    //This happens if we are starting a new packet.  We should also do this if we are masquerading.
            //    tPacket.sourceIP = new IPAddress(myIP.GetIP.ToIpString(), "", IPAddressType.ip_only); //We only want the IP address 
            //}
            //VLAN stuff
            VLANInfo VI = OutgoingVLAN(tPacket.VLANID);
            VLANTagType What = VI.Tag;
            Network theNet = NB.GetNetwork();
            NetworkDevice HD = theNet.GetDeviceFromID(AttachedToHostNic);
            if (HD == null) return; //Oops!  Something went wrong.
            string hostname = HD.hostname;
            //added to fix the issue with vpns and pings - 11-09-2016
            //Only do this on non-ports? 
            tPacket.TraversalInformation.AddPath(hostname, TraversalTechnology.network_interface, nic_name);
            NetworkCard nic = HD.NicFromID(AttachedToHostNic);
            if(nic != null && nic.GetNicType != NicType.port && nic.GetNicType != NicType.wport)
                tPacket.TsourceIP = myIP;
            if(tPacket.TsourceIP == null || tPacket.TsourceIP.GetIPString == NB.ZeroIPString)
                tPacket.TsourceIP = myIP;

            if (What == VLANTagType.Forbidden)
            {
                //we drop it silently
                //Console.WriteLine("  -- Forbidding outbound packet. " + tPacket.VLANID + "  " + HD.hostname + " " + VI.ID);
                string errString = string.Format(NB.Translate("NI_VLANOut"), hostname, tPacket.destIP.GetIPString);
                tPacket.AddMessage(DebugLevel.switching, errString);
                tPacket.Tracking.Status = errString;
                tPacket.MyStatus = PacketStatus.finished_ok;
                return;
            }
            if(What == VLANTagType.Untagged)
            {
                //We strip off the tagging
                tPacket.VLANID = NB.UntaggedVLAN; //set to the default vlan
            }
            if(What == VLANTagType.Tagged)
            {
                //We actually do not do anything.  The tag remains intact.
                tPacket.VLANID = VI.ID;
            }
        }

        private VLANInfo IncomingVLAN(int ID)
        {
            //Search through incoming vlan stuff to find the right one
            //If the packet is tagged with the ID, and the port is tagged, return that
            //If the packet is untagged, return the one that is untagged
            VLANInfo newVLANinfo = null;
            if (ID != NB.UntaggedVLAN) //It is tagged
            {
                foreach(VLANInfo vi in VLANs)
                {
                    if (vi.ID == ID)
                    {
                        //if (vi.Tag == VLANTagType.Forbidden)
                        //    Console.WriteLine("  About to be forbidden.");
                        return vi;
                    }
                }
                //We do not have one set yet.  Add a new one
                newVLANinfo = new VLANInfo(ID, VLANTagType.Forbidden);
                //Console.WriteLine("  Creating forbidden vlan - " + ID);
                VLANs.Add(newVLANinfo);
                return newVLANinfo;
            }
            else //the packet is untagged.
            {
                foreach (VLANInfo vi in VLANs)
                {
                    if (vi.Tag == VLANTagType.Untagged) return vi;
                }
                //We do not have an "untagged" vlan.  Return the settings for vlan1
                foreach (VLANInfo vi in VLANs)
                {
                    if (vi.ID == 1) return vi;
                }
            }
            //We should never get here.  This is just a fall-through
            newVLANinfo = new VLANInfo(ID, VLANTagType.Forbidden);
            VLANs.Add(newVLANinfo);
            return newVLANinfo;
        }

        private VLANInfo OutgoingVLAN(int ID)
        {
            //Search through outgoing vlan stuff to find the right one
            //If the packet is tagged with the ID, and the port is tagged, return that
            //If the packet is untagged, return the one that is untagged
            VLANInfo newVLANinfo = null;
            if (ID != NB.UntaggedVLAN) //the packet is tagged
            {
                foreach (VLANInfo vi in VLANs)
                {
                    if (vi.ID == ID)
                    {
                        //if (vi.Tag == VLANTagType.Forbidden)
                        //    Console.WriteLine("  About to be outbound forbidden.");
                        return vi;
                    }
                }
                //We do not have one set yet.  Add a new one
                newVLANinfo = new VLANInfo(ID, VLANTagType.Forbidden);
                //Console.WriteLine("  Creating outbound forbidden vlan - " + ID);
                VLANs.Add(newVLANinfo);
                return newVLANinfo;
            }
            else //the packet is untagged.
            {
                //we should find the untagged vlan
                //if no untagged vlan, find the tagged vlan

                foreach (VLANInfo vi in VLANs)
                {
                    if (vi.Tag == VLANTagType.Untagged) return vi;
                }
                foreach (VLANInfo vi in VLANs)
                {
                    if (vi.Tag == VLANTagType.Tagged) return vi;
                }
                //We do not have an "untagged" vlan.  Return the settings for vlan1
                foreach (VLANInfo vi in VLANs)
                {
                    if (vi.ID == 1) return vi;
                }
            }
            //We should never get here.  This is just a fall-through
            newVLANinfo = new VLANInfo(ID, VLANTagType.Forbidden);
            VLANs.Add(newVLANinfo);
            return newVLANinfo;
        }

        public void ProcessInboundPacket(Packet tPacket)
        {
            bool isgood = false;
            if (tPacket.destIP != null && tPacket.destIP.GetIP == myIP.GetIP) isgood = true;
            if (tPacket.destIP != null && myIP.IsLocal(tPacket.destIP)) isgood = true; 
            if (myIP.NetworkAddress == myIP.GetIP) isgood = true;

            Network MyNet = NB.GetNetwork();
            NetworkDevice ND = MyNet.GetDeviceFromID(AttachedToHostNic);

            tPacket.InboundInterface = this;
            tPacket.TraversalInformation.AddPath(ND.hostname, TraversalTechnology.network_interface, nic_name);
            if (isgood)
            {
                //anything we should do here?
                //Mainly vlan if we are a vlan.
                VLANInfo VI = IncomingVLAN(tPacket.VLANID);
                VLANTagType What = VI.Tag;
                Network theNet = NB.GetNetwork();
                NetworkDevice HD = theNet.GetDeviceFromID(AttachedToHostNic);
                if (HD == null) return;
                string hostname = HD.hostname;

                if (What == VLANTagType.Forbidden)
                {
                    //This vlan packet is deliberately forbidden.  Reject it (fail)
                    string errString = string.Format(NB.Translate("NI_VLANInForbidden"), hostname, tPacket.destIP.GetIPString);
                    tPacket.AddMessage(DebugLevel.switching, errString);
                    tPacket.Tracking.Status = errString;
                    tPacket.MyStatus = PacketStatus.finished_ok;
                    return;
                }
                if (What == VLANTagType.Untagged)
                {
                    //If it is (default vlan), this is OK.  Otherwise drop. 
                    //Untagged means we expect it to be vlan of (untagged) on the cable side
                    if(tPacket.VLANID != NB.UntaggedVLAN)
                    {
                        //Oops.  We need to reject the packet
                        string errString = string.Format(NB.Translate("NI_VLANInUntagged"), hostname, tPacket.destIP.GetIPString);
                        tPacket.AddMessage(DebugLevel.switching, errString);
                        tPacket.Tracking.Status = errString;
                        tPacket.MyStatus = PacketStatus.finished_ok;
                        return;
                    }
                    else //We need to tag the packet with the new VLAN-ID
                    {
                        tPacket.VLANID = VI.ID; //The packet is now tagged
                    }
                }
                if (What == VLANTagType.Tagged)
                {
                    //If the packet is tagged, and the vlan expected tagged, all is good.
                    if (tPacket.VLANID != NB.UntaggedVLAN && tPacket.VLANID != VI.ID)
                    {
                        //Oops.  We need to reject the packet
                        string errString = string.Format(NB.Translate("NI_VLANInMisMatch"), hostname, tPacket.destIP.GetIPString);
                        tPacket.AddMessage(DebugLevel.switching, errString);
                        tPacket.Tracking.Status = errString;
                        tPacket.MyStatus = PacketStatus.finished_ok;
                        return;
                    }
                    else
                        tPacket.VLANID = VI.ID; //Make sure it is properly tagged.
                }
            }

        }
    }
}
