using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;

namespace EduNetworkBuilder
{
    public class ActionCollection
    {
        Network StartingState = null;
        public List<NetworkAction> NetActions = new List<NetworkAction>();
        NetworkAction CurrentNetAction = null;
        public bool HasUnsavedChanges = false;

        public Network GetNet { get { return StartingState; } }
        public int GetActionCount {  get { if (CurrentNetAction == null) return 0; return CurrentNetAction.Actions.Count; } }

        public void RegisterNet(Network starting)
        {
            StartingState = Network.DeepClone(starting);
            CurrentNetAction = FindAction(starting);
            if(CurrentNetAction == null)
                CurrentNetAction = new NetworkAction(starting);
        }

        public NetworkAction GetCurrentNetAction()
        {
            return CurrentNetAction;
        }

        public void Add(ActionClass What)
        {
            if(CurrentNetAction == null || CurrentNetAction.HasBeenStored == true)
            {
                Network start = NB.GetNetwork();
                if (start == null) return;//Do nothing
                CurrentNetAction = new NetworkAction(start);
            }
            CurrentNetAction.HasBeenStored = false;// We have a change.  That change has not been pushed
            CurrentNetAction.Add(What);
        }

        public NetworkAction FindAction(Network Fitting)
        {
            return FindAction(NameFromNet(Fitting));
        }
        public NetworkAction FindAction(string Fitting)
        {
            foreach(NetworkAction one in NetActions)
            {
                if (one.NetworkName == Fitting)
                    return one;
            }
            return null;
        }
        public bool CurrentNeedsStoring
        { get {
                if (CurrentNetAction == null) return false;
                return (CurrentNetAction.HasBeenStored == false);
            }
        }

        public ActionClass RunAction(int WhichIndex, bool NoteAllChanges = false)
        {
            if (CurrentNetAction == null) return null;
            if (WhichIndex < CurrentNetAction.Actions.Count)
            {
                CurrentNetAction.Actions[WhichIndex].DoAction(NoteAllChanges);
                return CurrentNetAction.Actions[WhichIndex];
            }
            return null;
        }

        /// <summary>
        /// Take the current actionlist and save it to the stored actionlists.
        /// We use this when we have successfully solved a network and want to store
        /// this solution process
        /// </summary>
        public void PushActionToList()
        {
            //remove an old one if it exists
            if (CurrentNetAction != null)
            {
                NetworkAction NA = GetActionlistFromName(CurrentNetAction.NetworkName);
                if (NA != null) NetActions.Remove(NA);
            }
            //Add this one
            CurrentNetAction.HasBeenStored = true; //Note that we have stored it
            NetActions.Add(CurrentNetAction);
            //Note that the long-term action list has some new actions which have not been saved
            HasUnsavedChanges = true;
        }

        /// <summary>
        /// Return true if we already have a solved state for this network
        /// </summary>
        /// <param name="starting"></param>
        /// <returns></returns>
        public bool AlreadyHaveNet(Network starting)
        {
            //Look up the network to see if we have it already
            return AlreadyHaveNet(NameFromNet(starting));
        }
        public bool AlreadyHaveNet(string starting)
        {
            NetworkAction NA = GetActionlistFromName(starting);
            if (NA != null) return true; //one exists
            return false;
        }
        public NetworkAction GetActionlistFromNet(Network starting)
        {
            return GetActionlistFromName(NameFromNet(starting));
        }
        public NetworkAction GetActionlistFromName(string starting)
        {
            foreach (NetworkAction NA in NetActions)
            {
                if (NA.NetworkName == starting) return NA;
            }
            return null;
        }
        private string NameFromNet(Network theNet)
        {
            return theNet.PuzzleName;
        }
    }

    public class NetworkAction
    {
        public string NetworkName = "";
        public List<ActionClass> Actions = new List<ActionClass>();
        /// <summary>
        /// This tells us if it has been stored to the long-term action list.
        /// </summary>
        public bool HasBeenStored = false;
        public bool HasPassed = false;
        public bool HasFailed = false;
        public NetworkAction() { }
        public NetworkAction(string Name)
        {
            NetworkName = Name;
            Actions.Clear();
        }
        public NetworkAction(Network Start)
        {
            if (Start == null)
                Start = NB.GetNetwork();
            if (Start != null)
                NetworkName = Start.PuzzleName;
            else
                NetworkName = "Empty";
            Actions.Clear();
        }
        public void Add(ActionClass What)
        {
            Actions.Add(What);
        }
        public void SetPassed()
        {
            HasPassed = true;
            HasFailed = false;
        }
        public void SetFailed()
        {
            HasPassed = false;
            HasFailed = true;
        }
    }

    public class ActionClass
    {
        public NBAction Action = NBAction.none;
        public int SourceID = -1; //used for basically everything
        public NB_IPAddress Destination; //used for pinging, arp, traceroute
        public HostNicID SourceNIC; //used for links
        public HostNicID DestNic; //Used for links
        public Point Location; //Used when making a new device or moving an old device
        public NetworkComponentType newItemType = NetworkComponentType.none; //Making new device
        public NetworkComponent ChangedComponent = null;

        public void DoAction(bool NoteAllChanges=false)
        {
            Network myNet = NB.GetNetwork();
            NetworkDevice source = myNet.GetDeviceFromID(SourceID);
            NetworkComponent sourceC = myNet.GetComponentFromID(SourceID);
            NetworkLink sourceNL;
            string indent = "   ";
            switch (Action)
            {
                case NBAction.changecomponent:
                    //This could be a link or a device we are changing.
                    if(ChangedComponent is NetworkDevice)
                    {
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_ChangeDevice") + " " + source.hostname); }
                        source.UpdateFromComponent(ChangedComponent, NoteAllChanges); //Copy any changes across
                        source.SetImageFromType(); //The image was not saved.  Re-make it
                    }
                    else
                    { //It is a link.  Delete the old, make the new.  Mark as linked
                        if(source != null)
                            myNet.RemoveComponent(source);
                        sourceNL = (NetworkLink)ChangedComponent;
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_Replace_Link") + " " + sourceNL.Src.HostName + " - " + sourceNL.Dst.HostName); }
                        myNet.MarkAsLinked(sourceNL.Src, sourceNL.GetUniqueIdentifier);
                        myNet.MarkAsLinked(sourceNL.Dst, sourceNL.GetUniqueIdentifier);
                        myNet.AddItem(ChangedComponent);
                    }
                    if(NB.DebugActions) { Console.WriteLine("Changing a component: " + source.hostname); }
                    break;
                case NBAction.changelocation:
                    if (source != null)
                    {
                        source.ChangeLocation(Location);
                        source.UnHide(); //In case it was hidden
                        if (NB.DebugActions) { Console.WriteLine("Changing location for: " + source + " " + Location.X + ":" +Location.Y); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_MoveDevice") + " " + source.hostname + " - " +Location.X + ":" + Location.Y); }
                    }
                    break;
                case NBAction.deletecomponent:
                    //Deleting the item is easy, but we also need to delete any links to that item
                    List<HostNicID> NicIDs = new List<HostNicID>();
                    if (source != null)
                    {
                        if (myNet.ItemIsCritical(source.hostname))
                            return; //we cannot delete this
                        NicIDs = source.GetHostNicIDs();
                        foreach (HostNicID nicID in NicIDs)
                        {
                            myNet.RemoveLinksToNic(nicID);
                        }
                        myNet.RemoveComponent(source);
                        if (NB.DebugActions) { Console.WriteLine("Deleting a device: " + source.hostname); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_DeleteDevice") + " " + source.hostname + " - " + source.myType.ToString()); }
                    } else if(sourceC != null && sourceC is NetworkLink)
                    {
                        if (myNet.ItemIsCritical(sourceC.hostname))
                            return; //We cannot remove this link
                        NetworkLink SourceL = (NetworkLink)sourceC;
                        SourceL.Destroy(); //Mark both ends as being deleted
                        myNet.RemoveComponent(SourceL); //Get rid of this link
                        if (NB.DebugActions) { Console.WriteLine("Deleting a link: " + source.hostname); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_DeleteLink") + " " + SourceL.Src.HostName + " - " + SourceL.Dst.HostName); }
                    }
                    break;
                case NBAction.newdevice:
                    bool CanDo = true;
                    Point tLoc = myNet.clickedPosCentered(Location);
                    Point CenteredLocation = new Point(Location.X - (Location.X - tLoc.X), Location.Y - ((Location.Y - tLoc.Y)));
                    Point TopLeft = Location;
                    Point TopRight = new Point(TopLeft.X + myNet.itemsize, TopLeft.Y);
                    Point BottomRight = new Point(TopLeft.X + myNet.itemsize, TopLeft.Y + myNet.itemsize);
                    Point BottomLeft = new Point(TopLeft.X, TopLeft.Y + myNet.itemsize);
                    ChangedComponent = null; //if we cannot place it, this = null for reference
                    if (myNet.BlockedByTree(Location)) CanDo = false;
                    if (myNet.BlockedByTree(TopLeft)) CanDo = false;
                    if (myNet.BlockedByTree(TopRight)) CanDo = false;
                    if (myNet.BlockedByTree(BottomRight)) CanDo = false;
                    if (myNet.BlockedByTree(BottomLeft)) CanDo = false;
                    if (CanDo)
                    {
                        NetworkDevice newdevice = (NetworkDevice)myNet.AddItem(newItemType, Location);

                        if (NB.DebugActions && source != null) { Console.WriteLine("Adding a Device: " + newdevice.hostname); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_AddDevice") + " " + newdevice.hostname + " - " + newdevice.myType.ToString()); }
                    }
                    else
                    {
                        NB.SetBuilderWindowStatis(NB.Translate("NB_TreePlacementError"));
                        if (NB.DebugActions) { Console.WriteLine("Unable to add device: " + ChangedComponent.hostname); }
                    }
                    break;
                case NBAction.dhcp:
                    if (source != null)
                    {
                        source.DHCPRequestFromHere();
                        if (NB.DebugActions) { Console.WriteLine("Requesting DHCP: " + source.hostname); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_RequestDHCP") + " " + source.hostname); }
                    }
                    break;
                case NBAction.arp:
                    if (source != null)
                    {
                        //We have not implimented this well.  But, here it is anyway.  We would do this if showing ethernet stuff
                        source.AskArpFromHere(Destination);
                        if (NB.DebugActions) { Console.WriteLine("Requesting ARP: " + source.hostname); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_RequestARP") + " " + source.hostname + " - " + Destination.GetIPString); }
                    }
                    break;
                case NBAction.cleararp:
                    if (source != null)
                    {
                        //We have not implimented this well.  But, here it is anyway.  We would do this if showing ethernet stuff
                        source.ClearArps();
                        if (NB.DebugActions) { Console.WriteLine("Clearing ARP: " + source.hostname); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_ClearArp") + " " + source.hostname + " - " + Destination.GetIPString); }
                    }
                    break;
                case NBAction.ping:
                    if (source != null)
                    {
                        source.PingFromHere(Destination);
                        if (NB.DebugActions) { Console.WriteLine("Pinging " + Destination.GetIPString + " from " + source.hostname); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_DoPing") + " " + source.hostname + " - " + Destination.GetIPString); }
                    }
                    break;
                case NBAction.traceroute:
                    if (source != null)
                    {
                        source.TracerouteFromHere(Destination);
                        if (NB.DebugActions) { Console.WriteLine("Traceroute: " + Destination.GetIPString + " from " + source.hostname); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_DoTraceroute") + " " + source.hostname + " - " + Destination.GetIPString ); }
                    }
                    break;
                case NBAction.replace:
                    if(sourceC is NetworkDevice)
                    {
                        source.ClearIPs(); //reset the device
                        source.IsBurned = false; //If it had been burned before, it is no longer burned
                        source.PowerOff = true;
                        source.BadSprayCount = 0;
                        //Mark the replace test as "done"
                        myNet.RegisterDeviceReset(source.hostname); //replacing something powers it off

                        myNet.RegisterDeviceReplaced(source.hostname); //replace it.
                        if (NB.DebugActions) { Console.WriteLine("Replacing device: " + source.hostname); }
                        if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_Replace_Device") + " " + source.hostname); }
                    }
                    else if (sourceC is NetworkLink)
                    {
                        sourceNL = (NetworkLink)sourceC;

                        if (sourceNL.theLinkType != LinkType.wireless)
                        {
                            HostNicID sourceID = sourceNL.Src;
                            HostNicID destID = sourceNL.Dst;
                            myNet.RemoveComponent(sourceNL);
                            NetworkLink nNL = new NetworkLink(sourceID, destID, LinkType.normal);
                            myNet.AddItem(nNL);
                            if (NB.DebugActions) { Console.WriteLine("Replacing link: " + sourceID.HostName + " - " + destID.HostName); }
                            if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_Replace_Link") + " " + sourceID.HostName + " - " + destID.HostName); }
                        }
                    }
                    break;
                case NBAction.replaceUPS:
                    myNet.RegisterUPSAdded(source.hostname);
                    if (NB.DebugActions) { Console.WriteLine("Replacing UPS on: " + source.hostname); }
                    if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_Replace_UPS") + " " + source.hostname); }
                    break;
                case NBAction.poweroff:
                    source.PowerOff = true;
                    myNet.RegisterDeviceReset(source.hostname);
                    if (NB.DebugActions) { Console.WriteLine("Powering off: " + source.hostname); }
                    if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_Powering_OFF") + " " + source.hostname); }
                    break;
                case NBAction.poweron:
                    source.PowerOff = false;
                    if (NB.DebugActions) { Console.WriteLine("Powering on: " + source.hostname); }
                    if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_Powering_ON") + " " + source.hostname); }
                    //We might see about exploding the device here.
                    break;
                case NBAction.reset:
                    source.ClearIPs(); //reset the device - IPs and VLANs
                    if (NB.DebugActions) { Console.WriteLine("Resetting: " + source.hostname); }
                    if (NoteAllChanges) { NB.AddNetMessage("", indent + NB.Translate("AC_Resetting") + " " + source.hostname); }
                    //We might see about exploding the device here.
                    break;

            }
        }
    }
}
