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

namespace EduNetworkBuilder
{
    [Serializable]
    public class NetworkLink : NetworkComponent
    {
        public HostNicID SrcNic;
        public HostNicID DstNic;
        public LinkType theLinkType = LinkType.normal;
        public bool isVisibleLink = true;  //False for wireless.  Skip drawing a line if it is there

        public NetworkLink() { }

        public NetworkLink(HostNicID source, HostNicID dest, LinkType type = LinkType.normal)
        {
            SrcNic = source;
            DstNic = dest;
            Network myNet = NB.GetNetwork();
            myNet.MarkAsLinked(source, GetUniqueIdentifier);
            myNet.MarkAsLinked(dest, GetUniqueIdentifier);
            theLinkType = type;
            NetworkDevice sDev = myNet.GetDeviceFromID(source);
            NetworkDevice dDev = myNet.GetDeviceFromID(dest);
            if (sDev == null || dDev == null) return;
            NetworkCard sNic = sDev.NicFromID(source);
            NetworkCard dNic = dDev.NicFromID(dest);
            NetworkCard AccessPoint = null;
            NetworkCard Client = null;
            bool IsWireless = false;
            if(sNic != null && dNic != null)
            { 
                if(sNic.GetNicType == NicType.wport)
                {
                    AccessPoint = sNic;
                    Client = dNic;
                    IsWireless = true;
                }
                if(dNic.GetNicType == NicType.wport)
                {
                    AccessPoint = dNic;
                    Client = sNic;
                    IsWireless = true;
                }
                if(IsWireless && AccessPoint != null && Client != null && AccessPoint.SSID != null && AccessPoint.SSID != "")
                {
                    bool donesomething=false;
                    if(AccessPoint.SSID != Client.SSID)
                    {
                        donesomething = true;
                        Client.SSID = AccessPoint.SSID;
                    }
                    if (AccessPoint.WirelessKey != Client.WirelessKey)
                    {
                        donesomething = true;
                        Client.WirelessKey = AccessPoint.WirelessKey;
                    }
                    if(donesomething)
                    {
                        MessageBox.Show(NB.Translate("NL_NetLinkSSID"));
                        NB.DoActionChangeComponent(myNet.GetDeviceFromID(Client.myID));
                    }
                }
            }
        }

        public NetworkLink(XmlNode theNode)
        {
            IsDirty = true;
            Load(theNode);
        }

        public HostNicID Src
        {
            get { return SrcNic; }
        }
        public HostNicID Dst
        {
            get { return DstNic; }
        }
        public void SetSourceDest(HostNicID src, HostNicID dst)
        {
            SrcNic = src;
            DstNic = dst;
        }

        public override bool Equals(NetworkComponent toCompare)
        {
            if (!(toCompare is NetworkLink)) return false; //comparing a link to a device
            NetworkLink CompareLink = (NetworkLink)toCompare;
            if (!SrcNic.Equals(CompareLink.SrcNic)) return false;
            if (!DstNic.Equals(CompareLink.DstNic)) return false;
            if (theLinkType != CompareLink.theLinkType) return false;
            if (isInvisible != CompareLink.isInvisible) return false;
            if (IsDirty != CompareLink.IsDirty) return false;
            if (UniqueIdentifier != CompareLink.UniqueIdentifier) return false;
            if (hostname != CompareLink.hostname) return false;
            if (isInvisible != CompareLink.isInvisible) return false;

            return true;
        }

        public override void Load(XmlNode theNode)
        {
            foreach (XmlNode Individual in theNode.ChildNodes)
            {
                XmlNodeType myNodetype = Individual.NodeType;
                if (myNodetype == XmlNodeType.Element)
                {
                    switch (Individual.Name.ToLower())
                    {
                        case "srcnic":
                            SrcNic = new HostNicID(Individual);
                            break;
                        case "dstnic":
                            DstNic = new HostNicID(Individual);
                            break;
                        case "hostname":
                            hostname = Individual.InnerText;
                            break;
                        case "linktype":
                            theLinkType = NB.ParseEnum<LinkType>(Individual.InnerText);
                            break;
                        case "uniqueidentifier":
                            int.TryParse(Individual.InnerText,out UniqueIdentifier);
                            break;
                        case "invisible":
                        case "isinvisible":
                            bool.TryParse(Individual.InnerText, out isInvisible);
                            break;
                    }
                }
            }
            Network myNet = NB.GetNetwork();
            NetworkDevice snd = myNet.GetDeviceFromID(SrcNic);
            NetworkDevice dnd = myNet.GetDeviceFromID(DstNic);
            if (snd != null && dnd != null && hostname == "")
                hostname = snd.hostname + "_link_" + dnd.hostname;
            myNet.MarkAsLinked(SrcNic, GetUniqueIdentifier);
            myNet.MarkAsLinked(DstNic, GetUniqueIdentifier);
        }

        public void MarkAsLinked()
        {
            Network myNet = NB.GetNetwork();
            if (myNet != null)
            {
                myNet.MarkAsLinked(SrcNic, GetUniqueIdentifier);
                myNet.MarkAsLinked(DstNic, GetUniqueIdentifier);
            }
        }

        public override void Save(XmlWriter writer)
        {
            writer.WriteStartElement("link");
            SrcNic.Save(writer, "SrcNic");
            DstNic.Save(writer, "DstNic");
            writer.WriteElementString("hostname", hostname);
            writer.WriteElementString("linktype", theLinkType.ToString());
            writer.WriteElementString("uniqueidentifier", UniqueIdentifier.ToString());
            if(isInvisible)
                writer.WriteElementString("isinvisible", isInvisible.ToString());
            writer.WriteEndElement();
        }


        public override void Destroy()
        {
            Network myNet = NB.GetNetwork();
            if (myNet != null)
            {
                myNet.MarkAsUnlinked(SrcNic, GetUniqueIdentifier);
                myNet.MarkAsUnlinked(DstNic, GetUniqueIdentifier);
            }
        }

        public bool IsSource(int ID)
        {
            if (SrcNic.HostID == ID)
                return true;
            else return false;
        }
        public bool IsDest(int ID)
        {
            if (DstNic.HostID == ID)
                return true;
            else return false;
        }
        //Have func to verify both ends are powered on
        public bool isLive()
        { 
            return false;
        }

        /// <summary>
        /// Check to see if the link is connected to the specified nic at either end
        /// </summary>
        /// <param name="toFind">The unique identifier of the link</param>
        /// <returns>True if it has it</returns>
        public bool HasLink(HostNicID toFind)
        {
            if (SrcNic.Equals(toFind) || DstNic.Equals(toFind))
                return true;
            return false;
        }

        public double LinkDistance()
        {
            Network myNet = NB.GetNetwork();
            if (myNet == null) 
                return -1;
            NetworkDevice sDev = myNet.GetDeviceFromID(SrcNic);
            NetworkDevice dDev = myNet.GetDeviceFromID(DstNic);
            if (sDev == null || dDev == null)
                return -1;
            double distance = myNet.distance(sDev.myLocation(), dDev.myLocation());
            return distance;
        }

        /// <summary>
        /// Check that the link works.  If not, drop the link.  It usually only 
        /// fails in wireless if the ssid and key do not match
        /// </summary>
        public bool VerifyLinkIntegrity()
        {
            Network myNet = NB.GetNetwork();
            if (myNet == null) return false;
            NetworkDevice sDev = myNet.GetDeviceFromID(SrcNic);
            NetworkDevice dDev = myNet.GetDeviceFromID(DstNic);
            if (sDev == null || dDev == null) return false;
            NetworkCard sNic = sDev.NicFromID(SrcNic);
            NetworkCard dNic = dDev.NicFromID(DstNic);
            bool deleteme=false;
            if (sNic != null && dNic != null)
            {
                if (sNic.GetNicType == NicType.wport || dNic.GetNicType == NicType.wport)
                {
                    if (sNic.WirelessKey != dNic.WirelessKey)
                        deleteme = true;
                    if (sNic.SSID == "" || sNic.SSID == null) //if no SSID set
                        deleteme = true;
                    if (dNic.SSID == "" || dNic.SSID == null) //if no SSID set
                        deleteme = true;
                    if (sNic.SSID != dNic.SSID)
                        deleteme = true;
                    if (LinkDistance() > NB.WirelessMaxUnsuccessfulLink)
                        deleteme = true;
                    if (sDev.PowerOff)
                        deleteme = true;
                    if (dDev.PowerOff)
                        deleteme = true;
                }
                if (sNic.GetNicType == NicType.wlan)
                {
                    if (sDev.PowerOff || dDev.PowerOff)
                        deleteme = true;
                }
                if (dNic.GetNicType == NicType.wlan)
                {
                    if (sDev.PowerOff || dDev.PowerOff)
                        deleteme = true;
                }
            }
            if (sNic != null && dNic != null)
                if (sNic.isWireless() != dNic.isWireless())
                    deleteme = true;
            if(deleteme)
            {
                sDev.RemoveLinkTo(dDev.hostname); //this removes this link
                return true;
            }
            return false;
        }

        public override void Print(Image BaseImage, CaptionType DrawTitle)
        {
            if (isInvisible) return; //We do not print the line if it is invisible

            //Find the XY of the connected items
            Network myNet = NB.GetNetwork();
            NetworkDevice Src = myNet.HostMatchingHostNicID(SrcNic);
            NetworkDevice Dst = myNet.HostMatchingHostNicID(DstNic);
            //Draw a line between them
            if (Src == null || Dst == null) return;

            Point sPoint = Src.GetCenter();
            Point dPoint = Dst.GetCenter();
            Pen tPen = new Pen(Color.Black, 4);
            if(theLinkType == LinkType.wireless)
            {
                //tPen = new Pen(Color.DarkSlateGray);
                tPen.DashCap = System.Drawing.Drawing2D.DashCap.Round;
                tPen.DashPattern = new float[] { 8.0F, 2.0F};
            }
            Graphics.FromImage(BaseImage).DrawLine(tPen,sPoint,dPoint);
            IsDirty = false;  //we have printed, we are no longer dirty.
        }

        public Point PositionOnLine(nb_direction direction, int Percentage)
        {
            Network myNet = NB.GetNetwork();
            if(Percentage < 0) Percentage =0;
            if(Percentage > 100) Percentage = 100;
            NetworkDevice Src = myNet.HostMatchingHostNicID(SrcNic);
            NetworkDevice Dst = myNet.HostMatchingHostNicID(DstNic);
            //Draw a line between them
            if (Src == null || Dst == null) 
                return new Point(-1,-1);

            Point sPoint = Src.GetCenter();
            Point dPoint = Dst.GetCenter();

            double deltax = (sPoint.X - dPoint.X) / 100.0;
            double deltay = (sPoint.Y - dPoint.Y) / 100.0;
            Point answer;
            if(direction == nb_direction.to_dst)
                answer = new Point(sPoint.X-(int)(deltax * Percentage), sPoint.Y-(int)(deltay * Percentage));
            else
                answer = new Point((int)(deltax * Percentage) + dPoint.X, (int)(deltay * Percentage) + dPoint.Y);
            return answer;
        }

        public HostNicID RandomEndpoint()
        {
            Random rnd = NB.GetRandom();
            HostNicID theID;
            if (rnd.Next(2) == 0) theID = SrcNic;
            else theID = DstNic;

            return theID;
        }

        public List<string> UsedNicIDStrings()
        {
            List<string> usedLinks = new List<string>();
            //If the nic is a wireless end point, we should not count it.  We can have multiple Nics linked to one AP
            usedLinks.Add(SrcNic.HostNicIDString);
            usedLinks.Add(DstNic.HostNicIDString);
            return usedLinks;
        }

        public bool AtLocation(Point NetworkPoint)
        {
            Network myNet = NB.GetNetwork();
            NetworkDevice Src = myNet.HostMatchingHostNicID(SrcNic);
            NetworkDevice Dst = myNet.HostMatchingHostNicID(DstNic);
            int Slop = 10;
            int AutoWin = Slop * 2;
            //Draw a line between them
            if (Src == null || Dst == null)
                return false;

            Point sPoint = Src.GetCenter();
            Point dPoint = Dst.GetCenter();
            Rectangle SlopRec = new Rectangle(
                Math.Min(sPoint.X, dPoint.X) - Slop,
                Math.Min(sPoint.Y, dPoint.Y) - Slop,
                Math.Abs(sPoint.X - dPoint.X) + (Slop * 2),
                Math.Abs(sPoint.Y - dPoint.Y) + (Slop * 2));
            Rectangle ActualRec = new Rectangle(
                Math.Min(sPoint.X, dPoint.X),
                Math.Min(sPoint.Y, dPoint.Y),
                Math.Abs(sPoint.X - dPoint.X),
                Math.Abs(sPoint.Y - dPoint.Y));
            if (SlopRec.Contains(NetworkPoint))
            {
                if (ActualRec.Width < AutoWin || ActualRec.Height < AutoWin) return true; //we are close enough
                //We are inside it.  Now to figure out if we are somewhere along the line
                Rectangle smallrec = new Rectangle(Math.Min(sPoint.X, dPoint.X),
                               Math.Min(sPoint.Y, NetworkPoint.Y),
                               Math.Abs(sPoint.X - NetworkPoint.X),
                               Math.Abs(sPoint.Y - NetworkPoint.Y));
                double deltax = smallrec.Width / (double)ActualRec.Width;
                double deltay    = smallrec.Height / (double)ActualRec.Height;
                double TrueDelta = Math.Abs(Math.Abs(deltax) - Math.Abs(deltay));
                if (TrueDelta > .15) return false;
                return true;
            }
            return false;
        }


        public void TakeDamageIfNeeded(Point location, Packet tPacket)
        {
            Network myNet = NB.GetNetwork();

            if (myNet.DeviceIsOverDamaging(theLinkType, location))
            {
                tPacket.health -= 10;
                //Console.WriteLine("  health=" + tPacket.health.ToString());
            }
            if (theLinkType == LinkType.wireless)
            {
                if (myNet.DeviceInTree(theLinkType, location))
                {
                    tPacket.health -= 40;
                }
            }
        }

        /// <summary>
        /// Have the packet traverse the network
        /// </summary>
        /// <param name="tPacket"></param>
        public override void DoMoving(Packet tPacket)
        {
            NetworkDevice movingTo = null;
            Network myNet = NB.GetNetwork();
            HostNicID target;
            int oldpercent = tPacket.myLinkPercent;
            Point oldLocation = PositionOnLine(tPacket.myDirection, tPacket.myLinkPercent);
            tPacket.IncrementDistance();
            Point newLocation = PositionOnLine(tPacket.myDirection, tPacket.myLinkPercent);
            List<Point> PointsToCheck = new List<Point>();
            PointsToCheck.Add(newLocation);

            double distance = myNet.pixeldistance(oldLocation, newLocation);
            if(distance > (double)myNet.itemsize / 2)
            {
                //We are traveling too fast to accurately take damage.  take some snapshot point along the way
                int count = (int)(distance / ((double)myNet.itemsize / 2));
                int newpercent = tPacket.myLinkPercent;
                int delta = Math.Abs(newpercent - oldpercent) / (count +1);
                if (delta < 1) count = 1;
                if (delta > 5) count = 5;
                int start = newpercent + delta;
                int end = oldpercent;

                if(oldpercent - newpercent < 0)
                {
                    start = oldpercent + delta;
                    end = newpercent;
                }
                for(int percent = start; percent < end; percent+= delta )
                {
                    Point next = PositionOnLine(tPacket.myDirection, percent);
                    PointsToCheck.Add(next);
                }
            }

            foreach(Point one in PointsToCheck)
                TakeDamageIfNeeded(one, tPacket);

            if(theLinkType == LinkType.wireless)
            {
                NetworkDevice sDev = myNet.GetDeviceFromID(SrcNic);
                NetworkDevice dDev = myNet.GetDeviceFromID(DstNic);
                distance = myNet.distance(sDev, dDev) * (tPacket.myLinkPercent * 0.01);
                if(distance > NB.WirelessMaxSuccessfulLink)
                {
                    //The wireless link is too far.  Drop the packet
                    tPacket.Tracking.AddMessage(DebugLevel.info, this, NB.Translate("NL_WirelessDropped"));
                    tPacket.Tracking.Status = NB.Translate("NL_WirelessDropped");
                    tPacket.Tracking.Finished = true;
                    tPacket.MyStatus = PacketStatus.finished_failed;
                }
            }
            if(tPacket.health <= 0)
            {
                //The link is broken.  Drop the packet
                tPacket.Tracking.AddMessage(DebugLevel.info, this, NB.Translate("NL_DoMoveCorruptLight"));
                tPacket.Tracking.Status = NB.Translate("NL_DoMoveDrop");
                tPacket.Tracking.Finished = true;
                tPacket.MyStatus = PacketStatus.finished_failed;
            }
            if(theLinkType == LinkType.broken && tPacket.myLinkPercent > 50)
            {
                //The link is broken.  Drop the packet
                tPacket.Tracking.AddMessage(DebugLevel.info, this, NB.Translate("NL_DoMoveCorruptWire"));
                tPacket.Tracking.Status = NB.Translate("NL_DoMoveDrop");
                tPacket.MyStatus = PacketStatus.finished_failed;
            }
            if(tPacket.Arrived())
            {
                //We need to pass it to the recieving device
                if (tPacket.myDirection == nb_direction.to_dst)
                {
                    movingTo= myNet.GetDeviceFromID(DstNic.HostID);
                    target = DstNic;
                }
                else
                {
                    movingTo = myNet.GetDeviceFromID(SrcNic.HostID);
                    target = SrcNic;
                }
                if(movingTo == null)
                {
                    tPacket.AddMessage(DebugLevel.info, NB.Translate("NL_DoMoveNoEnd"));
                    tPacket.Tracking.Status = NB.LeftPad(hostname) + string.Format(NB.Translate("NL_DoMoveNoEnd2"));
                    tPacket.MyStatus = PacketStatus.finished_failed;
                }
                else
                {
                    movingTo.DoInputFromLink(tPacket, target);
                }
            }
        }

    }
}
