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;
using System.Text.RegularExpressions;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.IO;

namespace EduNetworkBuilder
{
    [Serializable]
    public class NetTest
    {
        public string sHost = "";
        public string dHost = "";
        public Color WrongColor = Color.Red;
        public NetTestType TheTest = NetTestType.NeedsDefaultGW;
        public bool TaskWasDone = false;
        public int PacketNumber = -1;

        public NetTest() { }
        public NetTest(string srcHost, string dstHost, NetTestType tTest)
        {
            sHost = srcHost;
            dHost = dstHost;
            TheTest = tTest;
            SetInitialDoneState();
        }

        public NetTest(NetTest FromWhat)
        {
            sHost = FromWhat.sHost;
            dHost = FromWhat.dHost;
            TheTest = FromWhat.TheTest;
            SetInitialDoneState();
        }
        
        public NetTest(XmlNode theNode)
        {
            foreach (XmlNode Individual in theNode.ChildNodes)
            {
                XmlNodeType myNodetype = Individual.NodeType;
                if (myNodetype == XmlNodeType.Element)
                {
                    switch (Individual.Name.ToLower())
                    {
                        case "sourcehost":
                        case "shost":
                            sHost = Individual.InnerText;
                            break;
                        case "desthost":
                        case "dhost":
                            dHost = Individual.InnerText;
                            break;
                        case "thetest":
                            TheTest = NB.ParseEnum<NetTestType>(Individual.InnerText);
                            break;
                    }
                }
            }
            SetInitialDoneState();
        }

        public void SetInitialDoneState()
        {
            switch (TheTest)
            {
                case NetTestType.LockAll:
                case NetTestType.LockDHCP:
                case NetTestType.LockGateway:
                case NetTestType.LockIP:
                case NetTestType.LockNic:
                case NetTestType.LockRoute:
                case NetTestType.LockInterfaceVLAN:
                case NetTestType.LockNicVLAN:
                case NetTestType.LockVLANNames:
                case NetTestType.LockVLANsOnHost:
                case NetTestType.LockLocation:
                    TaskWasDone = true;
                    break;
            }
        }

        public void Save(XmlWriter writer)
        {
            writer.WriteStartElement("nettest");
            writer.WriteElementString("shost", sHost);
            writer.WriteElementString("dhost", dHost);
            writer.WriteElementString("thetest", TheTest.ToString());
            writer.WriteEndElement();
        }

        public void UpdateValuesFromAnother(NetTest WhatFrom)
        {
            dHost = WhatFrom.dHost;
            sHost = WhatFrom.sHost;
            TheTest = WhatFrom.TheTest;
            WrongColor = WhatFrom.WrongColor;
        }

        public bool Equals(NetTest CompareTo)
        {
            if (sHost != CompareTo.sHost) return false;
            if (dHost != CompareTo.dHost) return false;
            if (TheTest != CompareTo.TheTest) return false;
            if (WrongColor != CompareTo.WrongColor) return false;
            return true;
        }
        
        public bool Edit()
        {
            NetTestEditor nte = new NetTestEditor(this);
            NetTest copy = new NetTest(this);
            nte.ShowDialog();
            if (copy.Equals(this)) return false;
            return true;
        }

        private string TestDescription(NetTestVerbosity amount)
        {
            string toreturn = "";
            switch(amount)
            {
                case NetTestVerbosity.basic:
                    if(TheTest != NetTestType.ReadContextHelp)
                        toreturn = NB.Translate("NT_TstDscriptProblem");
                    else
                        toreturn = NB.Translate("_ReadContext");
                    break;
                case NetTestVerbosity.hints:
                    switch (TheTest)
                    {
                        case NetTestType.DeviceIsFrozen:
                            toreturn = NB.Translate("NT_TstDescriptFrozenDevice");
                            break;
                        case NetTestType.DeviceNeedsUPS:
                            toreturn = NB.Translate("NT_TstDescriptNeedsUPS");
                            break;
                        case NetTestType.DeviceBlowsUpWithPower:
                            toreturn = NB.Translate("NT_TstDescriptBadPowersupply");
                            break;
                        case NetTestType.NeedsDefaultGW:
                            toreturn = NB.Translate("NT_TstDscriptGteway");
                            break;
//                        case NetTestType.NeedsPingToHost:
//                            toreturn = "Cannot ping";
//                            break;
                        case NetTestType.NeedsRouteToNet:
                            toreturn = NB.Translate("NT_TstDscriptRout");
                            break;
                        case NetTestType.NeedsLocalIPTo:
                            toreturn = NB.Translate("NT_TstDscriptIP");
                            break;
                        case NetTestType.NeedsLinkToDevice:
                            toreturn = NB.Translate("NT_TstDiscriptConnect");
                            break;
                        case NetTestType.SuccessfullyArps:
                            toreturn = NB.Translate("NT_TstDiscriptARP");
                            break;
                        case NetTestType.SuccessfullyDHCPs:
                            toreturn = NB.Translate("NT_TstDiscriptDHCPIP");
                            break;
                        case NetTestType.SuccessfullyTraceroutes:
                            toreturn = NB.Translate("NT_TstDiscriptTraceroute");
                            break;
                        case NetTestType.SuccessfullyPings:
                        case NetTestType.SuccessfullyPingsAgain:
                        case NetTestType.SuccessfullyPingsWithoutLoop:
                            toreturn = NB.Translate("NT_TstDiscriptPing");
                            break;
                        case NetTestType.HelpRequest:
                            toreturn = NB.Translate("NT_TstDiscriptHelp");
                            break;
                        case NetTestType.FailedPing:
                            toreturn = NB.Translate("NT_TstDiscriptPingHost");
                            break;
                        case NetTestType.DHCPServerEnabled:
                            toreturn = NB.Translate("NT_TstDiscriptDHCP");
                            break;
                        case NetTestType.LockAll:
                            toreturn = NB.Translate("NT_TstDiscriptLock");
                            break;
                        case NetTestType.LockDHCP:
                            toreturn = NB.Translate("NT_TstDiscriptLockDHCP");
                            break;
                        case NetTestType.LockIP:
                            toreturn = NB.Translate("NT_TstDiscriptLockIP");
                            break;
                        case NetTestType.LockNic:
                            toreturn = NB.Translate("NT_TstDiscriptLockNIC");
                            break;
                        case NetTestType.LockRoute:
                            toreturn = NB.Translate("NT_TstDiscriptLockRout");
                            break;
                        case NetTestType.LockGateway:
                            toreturn = NB.Translate("NT_TstDiscriptGteway");
                            break;
                        case NetTestType.LockVLANNames:
                            toreturn = NB.Translate("NT_TstDiscriptLockVLANNames");
                            break;
                        case NetTestType.LockVLANsOnHost:
                            toreturn = NB.Translate("NT_TstDiscriptLockVLAN");
                            break;
                        case NetTestType.LockNicVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptLockNicVLAN");
                            break;
                        case NetTestType.LockInterfaceVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptLockInterfaceVLAN");
                            break;
                        case NetTestType.LockLocation:
                            toreturn = NB.Translate("NT_TstDiscriptLockLocation");
                            break;
                        case NetTestType.NeedsTaggedVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptNeedsTaggedVLAN");
                            break;
                        case NetTestType.NeedsUntaggedVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptNeedsUntaggedVLAN");
                            break;
                        case NetTestType.NeedsForbiddenVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptNeedsForbiddenVLAN");
                            break;
                        case NetTestType.DeviceNICSprays:
                            toreturn = NB.Translate("NT_TstDeviceNICSprays") + ":";
                            break;
                        case NetTestType.ReadContextHelp:
                            toreturn = NB.Translate("_ReadContext");
                            break;
                    }
                    break;
                case NetTestVerbosity.full:
                    switch (TheTest)
                    {
                        case NetTestType.DeviceIsFrozen:
                            toreturn = NB.Translate("NT_TstDescriptFrozenDevice2");
                            break;
                        case NetTestType.DeviceNeedsUPS:
                            toreturn = NB.Translate("NT_TstDescriptNeedsUPS2");
                            break;
                        case NetTestType.DeviceBlowsUpWithPower:
                            toreturn = NB.Translate("NT_TstDescriptBadPowersupply2");
                            break;
                        case NetTestType.NeedsDefaultGW:
                            toreturn = NB.Translate("NT_TstDiscriptGteway2");
                            break;
//                        case NetTestType.NeedsPingToHost:
//                            toreturn = "Cannot ping host:";
//                            break;
                        case NetTestType.NeedsRouteToNet:
                            toreturn = NB.Translate("NT_TstDiscriptRout2");
                            break;
                        case NetTestType.NeedsLocalIPTo:
                            toreturn = NB.Translate("NT_TstDiscriptIP2");
                            break;
                        case NetTestType.NeedsLinkToDevice:
                            toreturn = NB.Translate("NT_TstDiscriptLink");
                            break;
                        case NetTestType.SuccessfullyArps:
                            toreturn = NB.Translate("NT_TstDiscriptARP2");
                            break;
                        case NetTestType.SuccessfullyDHCPs:
                            toreturn = NB.Translate("NT_TstDiscriptDHCPIP2");
                            break;
                        case NetTestType.SuccessfullyTraceroutes:
                            toreturn = NB.Translate("NT_TstDiscriptTraceroute2");
                            break;
                        case NetTestType.SuccessfullyPings:
                        case NetTestType.SuccessfullyPingsWithoutLoop:
                        case NetTestType.SuccessfullyPingsAgain:
                            toreturn = NB.Translate("NT_TstDiscriptPing2");
                            break;
                        case NetTestType.HelpRequest:
                            toreturn = NB.Translate("NT_TstDiscriptHelp2");
                            break;
                        case NetTestType.FailedPing:
                            toreturn = NB.Translate("NT_TstDiscriptPingFail");
                            break;
                        case NetTestType.DHCPServerEnabled:
                            toreturn = NB.Translate("NT_TstDiscriptDHCP2");
                            break;
                        case NetTestType.LockAll:
                            toreturn = NB.Translate("NT_TstDiscriptLock") + ":";
                            break;
                        case NetTestType.LockDHCP:
                            toreturn = NB.Translate("NT_TstDiscriptLockDHCP") + ":";
                            break;
                        case NetTestType.LockIP:
                            toreturn = NB.Translate("NT_TstDiscriptLockIP") + ":";
                            break;
                        case NetTestType.LockNic:
                            toreturn = NB.Translate("NT_TstDiscriptLockNIC") + ":";
                            break;
                        case NetTestType.LockRoute:
                            toreturn = NB.Translate("NT_TstDiscriptLockRout") + ":";
                            break;
                        case NetTestType.LockGateway:
                            toreturn = NB.Translate("NT_TstDiscriptGteway") + ":";
                            break;
                        case NetTestType.LockVLANNames:
                            toreturn = NB.Translate("NT_TstDiscriptLockVLANNames") + ":";
                            break;
                        case NetTestType.LockVLANsOnHost:
                            toreturn = NB.Translate("NT_TstDiscriptLockVLAN") + ":";
                            break;
                        case NetTestType.LockNicVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptLockNicVLAN") + ":";
                            break;
                        case NetTestType.LockInterfaceVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptLockInterfaceVLAN") + ":";
                            break;
                        case NetTestType.LockLocation:
                            toreturn = NB.Translate("NT_TstDiscriptLockLocation") + ":";
                            break;
                        case NetTestType.NeedsTaggedVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptNeedsTaggedVLAN") + ":";
                            break;
                        case NetTestType.NeedsUntaggedVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptNeedsUntaggedVLAN") + ":";
                            break;
                        case NetTestType.NeedsForbiddenVLAN:
                            toreturn = NB.Translate("NT_TstDiscriptNeedsForbiddenVLAN") + ":";
                            break;
                        case NetTestType.ReadContextHelp:
                            toreturn = NB.Translate("_ReadContext");
                            break;
                        case NetTestType.DeviceNICSprays:
                            toreturn = NB.Translate("NT_TstDeviceNICSprays") + ":";
                            break;
                    }
                    break;
                case NetTestVerbosity.none:
                    toreturn = "";
                    break;
            }
            return toreturn;
        }

        public string GetDescription(NetTestVerbosity amount)
        {
            string toreturn = "";
            if(TheTest == NetTestType.ReadContextHelp)
            {
                return TestDescription(amount) + " " + sHost;
            }
            switch(amount)
            {
                case NetTestVerbosity.basic:
                    toreturn = sHost + " " + TestDescription(amount);
                    break;
                case NetTestVerbosity.hints:
                    toreturn = sHost + " " + TestDescription(amount);
                    break;
                case NetTestVerbosity.full:
                    toreturn = sHost + " " + TestDescription(amount) + " " + dHost;
                    break;
                case NetTestVerbosity.none:
                    toreturn = "";
                    break;
            }
            return toreturn;
        }


        public bool TestPiecesExist()
        {
            Network theNet = NB.GetNetwork();
            NetworkDevice Source = theNet.GetDeviceFromName(sHost);
            NetworkDevice Dest = theNet.GetDeviceFromName(dHost);
            if (Source == null) return false;
            if (TheTest == NetTestType.DHCPServerEnabled) return true; //dest does not matter
            if (Dest == null) return false;
            return true;
        }

        public bool ColorItemsIfNeeded(bool ChangeColor)
        {
            bool WasDone = TaskWasDone;
            if (TheTest == NetTestType.DeviceNICSprays)
                ChangeColor = false; //we do not show this
            if(!TestComplete())
            {
                if(TheTest == NetTestType.ReadContextHelp && ChangeColor)
                {
                    BuilderWindow myWin = NB.GetBuilderWin();
                    if(myWin != null)
                    {
                        Control ctl = myWin.GetControlNamed(sHost);
                        if (ctl == null) return false;
                        ctl.BackColor = WrongColor;
                    } 
                    return true;
                }
                if (TheTest == NetTestType.HelpRequest && dHost == "?Button" && ChangeColor)
                {
                    BuilderWindow myWin = NB.GetBuilderWin();
                    if (myWin != null)
                    {
                        Control ctl = myWin.GetControlNamed("btnHelp");
                        if (ctl == null) return false;
                        if(ctl.BackColor != WrongColor)
                            ctl.BackColor = WrongColor;
                    }
                    return true;
                }
                if (TheTest == NetTestType.HelpRequest && dHost == "ViewButton" && ChangeColor)
                {
                    BuilderWindow myWin = NB.GetBuilderWin();
                    if (myWin != null)
                    {
                        Control ctl = myWin.GetControlNamed("btnCaptions");
                        if (ctl == null) return false;
                        if (ctl.BackColor != WrongColor)
                            ctl.BackColor = WrongColor;
                    }
                    return true;
                }
                Network theNet = NB.GetNetwork();
                NetworkDevice Source = theNet.GetDeviceFromName(sHost);
                if(Source!= null)
                {
                    if (ChangeColor)
                    {
                        Source.BackgroundColor = WrongColor;
                        Source.IsDirty = true; //Make sure we re-draw it
                    }
                    return true;  //We have a test that is not completed
                }
                return true;
            }
            if(WasDone == false)
            {
                //We just solved it for the first time
                TaskWasDone = true;
                NB.PlaySound(NBSoundType.success);
                NB.MarkToUpdate();
            }
            return false; //No need to color anything
        }

        public void SetDone(int PacketID = -1)
        {
            if(TaskWasDone == false)
            {
                if (TheTest == NetTestType.FailedPing || TheTest == NetTestType.SuccessfullyArps
                    || TheTest == NetTestType.SuccessfullyDHCPs || TheTest == NetTestType.SuccessfullyPings
                    || TheTest == NetTestType.SuccessfullyPingsAgain || TheTest == NetTestType.SuccessfullyTraceroutes 
                    || TheTest == NetTestType.SuccessfullyPingsWithoutLoop)
                    PacketNumber = PacketID; //Track the packetID of the first packet to complete the task
                NB.PlaySound(NBSoundType.success);
                NB.MarkToUpdate();
                if (NB.DebugTests) { Console.WriteLine("  Marking Test Complete: " + TestDumpString()); }

            }
            TaskWasDone = true;
        }

        public string TestDumpString()
        {
            string OurStatus = sHost + " -> " + dHost + "  " + TheTest.ToString();
            return OurStatus;
        }
        public string GetInterfaceFromVLANInterfaceRequirement()
        {
            Match result = Regex.Match(dHost, @"(?<interface>[A-z:0-9]+) - (?<id>\d+)");
            if (result.Groups["interface"] != null) return result.Groups["interface"].Value;
            return "";
        }

        public int GetIDFromVLANInterfaceRequirement()
        {
            int answer = -1;
            Match result = Regex.Match(dHost, @"(?<interface>[A-z:0-9]+) - (?<id>\d+)");
            if (result.Groups["id"] != null) int.TryParse( result.Groups["id"].Value, out answer);
            return answer;
        }


        /// <summary>
        /// See if the test has been solved
        /// </summary>
        /// <returns></returns>
        public bool TestComplete(Network UseThisNet = null)
        {
            Network theNet = UseThisNet;
            if(theNet == null) theNet= NB.GetNetwork();
            NetworkDevice Source = theNet.GetDeviceFromName(sHost);
            NetworkDevice Dest = theNet.GetDeviceFromName(dHost);
            NB_IPAddress gw;
            NB_IPAddress tAddr;

            switch(TheTest)
            {
                case NetTestType.NeedsDefaultGW:
                    if (Source == null) return false; //Unable to do it.  Do not count it against them.
                    if (Dest == null) return false; //Unable to do it.  Do not count it against them.

                    gw = Source.GetGateway();
                    if(Dest.HasIPAddress(gw))
                    {
                        //It has the IP.  Is it local to the source?
                        if(Source.LocalNic(gw) != null)
                        {
                            //The default gw is set to the IP of the dest
                            //The IP address chosen is "local" to the source host.  So it should ping between them
                            return true;
                        }
                    }
                    return false;  //Something is not set right.
                case NetTestType.NeedsRouteToNet:
                    if (Source == null) return false; //Unable to do it.  Do not count it against them.
                    tAddr = theNet.DNSLookup(Source,dHost);
                    if (tAddr == null || tAddr.GetIPString == NB.ZeroIPString)
                        tAddr = new NB_IPAddress(dHost);
                    if (Source.HasRouteMatching(tAddr))
                    {
                        NB_IPAddress route = Source.RouteMatching(tAddr);
                        if (Source.LocalNic(new NB_IPAddress(route.GetGateway.ToIpString())) == null)
                            return false; //The gateway specified is not local to the device.  We cannot get to it
                        return true;
                    }
                    return false; //if we get here, it has failed somehow
                case NetTestType.NeedsLocalIPTo:
                    if (Source == null) return false; //Unable to do it.  Do not count it against them.
                    if (Dest == null) return false; //Unable to do it.  Do not count it against them.
                    tAddr = Source.LocalDeviceIP(Dest);
                    NB_IPAddress dAddress = Dest.LocalDeviceIP(Source);
                    if (Dest.HasIPAddress(tAddr)) return false; //They gave the same address to the source that the dest has.
                    if (!theNet.HasUniqueIP(tAddr, Source)) return false; //Verify we have not given the IP to someone else
                    if (tAddr != null &&
                        tAddr.GetIPString != NB.ZeroIPString)
                    {
                        if(dAddress != null & dAddress.GetMask == tAddr.GetMask)
                            return true;
                    }
                    return false;  //Something is not set right.
                case NetTestType.NeedsLinkToDevice:
                    if (Source == null) return false; //Unable to do it.  Do not count it against them.
                    if (Dest == null) return false; //Unable to do it.  Do not count it against them.
                    if (Source.HasLinkTo(dHost)) return true;
                    return false;  //Something is not set right.
                case NetTestType.SuccessfullyArps:
                case NetTestType.SuccessfullyDHCPs:
                case NetTestType.SuccessfullyPings:
                case NetTestType.SuccessfullyPingsWithoutLoop:
                case NetTestType.SuccessfullyPingsAgain:
                case NetTestType.SuccessfullyTraceroutes:
                case NetTestType.HelpRequest:
                case NetTestType.ReadContextHelp:
                case NetTestType.FailedPing:
                case NetTestType.DeviceIsFrozen:
                case NetTestType.DeviceBlowsUpWithPower:
                case NetTestType.DeviceNeedsUPS:
                case NetTestType.DeviceNICSprays:
                    return TaskWasDone; //This variable will tell us if these tests have been done.
                case NetTestType.DHCPServerEnabled:
                    if (Source == null) return false; //Unable to do it.  Do not count it against them.
                    //dHost is either "true" or "false"
                    string srvr = Source.GetIsDHCPServer().ToString().ToLower();
                    string shouldbe = dHost.ToLower();
                    if (srvr == shouldbe) 
                        return true;
                    break;
                case NetTestType.NeedsForbiddenVLAN:
                case NetTestType.NeedsTaggedVLAN:
                case NetTestType.NeedsUntaggedVLAN:
                    if (Source == null) return false; //Unable to do it.  Do not count it against them.
                    string ifname = GetInterfaceFromVLANInterfaceRequirement();
                    if (ifname == "") return false; //unable to do it.
                    string nicname = Regex.Replace(ifname, @":\w*","");
                    int id = GetIDFromVLANInterfaceRequirement();
                    //Find the nic
                    NetworkCard nic = Source.NicFromName(nicname);
                    if (nic == null) return false; //no matching nic
                    //find the interface
                    NetworkInterface nif = nic.InterfaceFromName(ifname);
                    if (nif == null) return false; //no matchint interface

                    //find the entry for the vlan
                    VLANInfo vi = nif.GetVLANInfo(id);
                    if (vi == null) return false; //No such vlan or something.
                    if (TheTest == NetTestType.NeedsForbiddenVLAN && vi.Tag == VLANTagType.Forbidden) return true;
                    if (TheTest == NetTestType.NeedsUntaggedVLAN && vi.Tag == VLANTagType.Untagged) return true;
                    if (TheTest == NetTestType.NeedsTaggedVLAN && vi.Tag == VLANTagType.Tagged) return true;
                    break;
                case NetTestType.LockAll:
                case NetTestType.LockDHCP:
                case NetTestType.LockIP:
                case NetTestType.LockNic:
                case NetTestType.LockRoute:
                case NetTestType.LockGateway:
                case NetTestType.LockInterfaceVLAN:
                case NetTestType.LockNicVLAN:
                case NetTestType.LockVLANsOnHost:
                case NetTestType.LockVLANNames:
                case NetTestType.LockLocation:
                    return true; //Nothing to solve.  We just lock it so it cannot be changed.
            }
            return false;
        }


        public static T Clone<T>(T source)
        {
            if (!typeof(T).IsSerializable)
            {
                throw new ArgumentException(NB.Translate("NC_CloneSerialzable"), NB.Translate("_source"));
            }

            // Don't serialize a null object, simply return the default for that object
            if (Object.ReferenceEquals(source, null))
            {
                return default(T);
            }

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            using (stream)
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)formatter.Deserialize(stream);
            }
        }
    }
}