//------------------------------------------------------------------------------
// <copyright file="MainModule.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Client console application's main module
// </summary>
//------------------------------------------------------------------------------

namespace Microsoft.Hpc.HpcBasicProfile
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
    using System.Xml;
    using System.Xml.Serialization;
    using System.Security;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Threading;
    using System.Security.Principal;
    using System.Globalization;

    using Microsoft.Hpc.Scheduler.Session;

    /// <summary>
    /// Implements main module for the console application
    /// </summary>
    internal class MainModule
    {
        /// <summary>
        /// Caches activityIDs of newly created activities
        /// </summary>
        private static List<EndpointReferenceType> activityIDCache = new List<EndpointReferenceType>();

        /// <summary>
        /// Console application's main entry point
        /// </summary>
        /// <param name="args">Commandline arguments</param>
        private static void Main(string[] args)
        {
            string userName = null;
            SecureString password = null;
            bool fSave;
            string defaultUserName = WindowsIdentity.GetCurrent().Name;
            HPCBPClient hpcbp = null;

            // Ensure JSDL filepath was specified
            if (args.Length < 1)
            {
                Console.WriteLine("WSSchedulerClient filename [service url]");
                return;
            }

            // Process command line arguments
            string fileName = args[0];

            for (int arg = 1; arg < args.Length; arg++)
            {
                if (args[arg].StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
                {
                    if (hpcbp == null)
                    {
                        Utility.PromptForCredentials(args[arg], ref userName, ref password, out fSave, true, IntPtr.Zero);
                        hpcbp = new HPCBPClient(args[arg], userName, Utility.UnsecureString(password));
                    }
                    else
                    {
                        Console.WriteLine("Only one service url allowed");
                        return;
                    }
                }
                else
                {
                    Console.WriteLine("Invalid parameter:" + args[arg]);
                    return;
                }
            }

            if (hpcbp == null)
            {
                // Configure from config file
                hpcbp = new HPCBPClient();
            }

            Console.WriteLine();
            Console.WriteLine("Scheduler = {0}", hpcbp.ServiceUri());
            Console.WriteLine("JSDL FileName = {0}", fileName);

#if DEBUG
            //  Pause to give services a chance to start on F5
            Thread.Sleep(2000);
#endif

            while (true)
            {
                try
                {
                    Console.WriteLine();
                    Console.WriteLine("Commands");
                    Console.WriteLine("----------------");
                    Console.WriteLine("[1]\tCreate Activity {0}", fileName);
                    Console.WriteLine("[2]\tGet Activity Document");
                    Console.WriteLine("[3]\tGet Activity Status");
                    Console.WriteLine("[4]\tTerminate Activity");
                    Console.WriteLine("[5]\tGet Factory Attributes Document");
                    Console.WriteLine("[6]\tBES Container Status");
                    Console.WriteLine("[7]\tStart Accepting New Activities");
                    Console.WriteLine("[8]\tStop Accepting New Activities");
                    Console.WriteLine("[9]\tStart a SOA job from an activity");
                    Console.WriteLine("[0]\tRun a SOA job");
                    Console.WriteLine();
                    Console.WriteLine("[a]\tList my Activities");
                    Console.WriteLine("[b]\tList my Running Activities");
                    Console.WriteLine("[c]\tList my Activities submitted today");
                    Console.WriteLine("[d]\tList compacted contained resources");
                    Console.WriteLine("[e]\tList a node's dynamic resources");
                    Console.WriteLine("[z]\tCreate a 'fake' EPR");
                    Console.WriteLine("----------------");
                    ConsoleKeyInfo keyInfo = Console.ReadKey(true);

                    // Process the user specified command
                    switch (keyInfo.KeyChar)
                    {
                        case '1':
                            EndpointReferenceType epr = null;
                            epr = hpcbp.CreateActivity(fileName);
                            StoreActivity(epr);
                            break;

                        case '2':
                            GetActivityDocuments(hpcbp);
                            break;

                        case '3':
                            GetActivityStatuses(hpcbp);
                            break;

                        case '4':
                            TerminateActivity(hpcbp);
                            break;

                        case '5':
                            GetFactoryAttributesWithBasicFilter(hpcbp);
                            break;

                        case '6':
                            // Report on aceepting new activities status
                            Console.WriteLine("BES Container Status - Accepting New Activities:" + hpcbp.isAcceptingNewActivites());
                            break;

                        case '7':
                            if (hpcbp.StartAcceptingNewActivities())
                            {
                                Console.WriteLine("Setting the container to start accept new activities");
                            }
                            break;

                        case '8':
                            if (hpcbp.StopAcceptingNewActivities())
                            {
                                Console.WriteLine("Setting the container to stop accept new activities");
                            }
                            break;

                        case '9':
                            // Get an activity
                            ConnectToSOA(userName, Utility.UnsecureString(password));
                            break;

                        case '0':
                            // Create a SOA job
                            RunSOAJob("stevenn-test1", "CcpEchoSvc");
                            break;

                        case 'a':
                            ListUserActivities(hpcbp,userName);
                            break;

                        case 'b':
                            ListUserStateActivities(hpcbp,userName,ActivityStateEnumeration.Running);
                            break;

                        case 'c':
                            ListUserTodaysActivities(hpcbp,userName);
                            break;

                        case 'd':
                            CompactedContainedResources(hpcbp);
                            break;

                        case 'e':
                            DynamicNodeResources(hpcbp);
                            break;

                        case 'z':
                            Console.WriteLine("Enter the job ID...");
                            String jobID = Console.ReadLine();
                            Console.WriteLine("Enter the task ID...");
                            String taskID = Console.ReadLine();

                            int job;
                            int task;

                            if (!Int32.TryParse(jobID, out job) || !Int32.TryParse(taskID, out task))
                            {
                                Console.WriteLine("Unparseable job or task IDs");
                                return;
                            }

                            epr = Utility.CreateActivityID(hpcbp.ServiceUri(), job, task, HPCBPClient.HPCS2008HPCBPNamespaceURI);
                            StoreActivity(epr);
                            break;

                        default:
                            Console.WriteLine("Invalid command");
                            break;
                    }
                }

                // Catch all exceptions and dump to stdout
                catch (Exception e)
                {
                    Console.WriteLine("Exception = {0}", e.ToString());
                }
            }
        }

        static int asyncResultCount = 0;
        static AutoResetEvent AsyncResultsDone = new AutoResetEvent(false);
        class RequestState
        {
            int input;
            EchoSvcClient client;
            public RequestState(EchoSvcClient client, int input)
            {
                this.client = client;
                this.input = input;
            }
            public int Input
            {
                get { return input; }
            }
            public string GetResult(IAsyncResult result)
            {
                return client.EndEcho(result);
            }
        }

        static void EchoCallback(IAsyncResult result)
        {
            RequestState state = result.AsyncState as RequestState;
            Console.WriteLine("Resonse ({0}) = {1}", state.Input, state.GetResult(result));
            if (Interlocked.Decrement(ref asyncResultCount) <= 0)
            {
                AsyncResultsDone.Set();
            }
        }

        static private void RunSOAJob(string cluster,string serviceName)
        {
            SessionStartInfo info = new SessionStartInfo(cluster,serviceName);

            Console.WriteLine("Starting session");
            using (Session session = Session.CreateSession(info))
            {
                EchoSvcClient client = new EchoSvcClient(new NetTcpBinding(SecurityMode.Transport, false), session.EndpointReference);
                asyncResultCount = 10;
                for (int i = 0; i < 10; i++)
                {
                    client.BeginEcho("Hello World", EchoCallback, new RequestState(client, i));
                }
                AsyncResultsDone.WaitOne();
                client.Close();
            }
            Console.WriteLine("Finished session");
        }

        static private void ConnectToSOA(string userName, string password)
        {
            int activityIndex = 0;
            string activityIndexString = null;
            EndpointAddress address = null;

            // If no address specified - can hardwire an established SOA session into the client for testing
            if (address == null)
            {
                // Get activity whose document is to be retrieved
                Console.WriteLine("Enter activity index to find a SOA job endpoint...");
                activityIndexString = Console.ReadLine();

                // If the user wants to get a specific activity document
                // Parse activity index that was entered
                if (!Int32.TryParse(activityIndexString, out activityIndex))
                {
                    Console.WriteLine("Invalid activity index");
                    return;
                }

                // Check if the index was in range
                if ((activityIndex > activityIDCache.Count) || (activityIndex <= 0))
                {
                    Console.WriteLine("Activity index out of range");
                    return;
                }

                EndpointReferenceType epr = activityIDCache[activityIndex - 1];
                ReferenceParametersType referenceParameters = epr.ReferenceParameters;

                if (referenceParameters != null)
                {
                    foreach (XmlElement element in referenceParameters.Any)
                    {
                        if (element.Name.Equals("SessionEPR") && element.NamespaceURI.Equals(HPCBPClient.HPCS2008SOAJobNamespaceURI))
                        {
                            address = new EndpointAddress(new Uri(element.InnerText));
                        }
                    }
                }
            }

            if (address == null)
            {
                Console.WriteLine("Could not find a SOA endpoint in this EPR");
                return;
            }

            Console.WriteLine("Connecting to SOA job:"+address);

            // Set up a secure HTTPS binding
            BasicHttpBinding binding = new BasicHttpBinding(new BasicHttpSecurityMode());
            binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
            binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCredential;

            EchoSvcClient client = new EchoSvcClient(binding, address);
            client.ClientCredentials.UserName.UserName = userName;
            client.ClientCredentials.UserName.Password = password;

            asyncResultCount = 10;
            for (int i = 0; i < 10; i++)
            {
                client.BeginEcho("Hello World", EchoCallback, new RequestState(client, i));
            }
            AsyncResultsDone.WaitOne();
            client.Close();
            
            Console.WriteLine("Finished session");
        }

        /// <summary>
        /// Creates a BES activity
        /// </summary>
        /// <param name="proxy">Proxy to HPC Profile Service</param>
        /// <param name="fileName">JSDL file name</param>
        private static void StoreActivity(EndpointReferenceType epr)
        {
            if (epr == null)
            {
                return;
            }

            // Save activity's ID in cache so user can reference them by index instead of XML
            activityIDCache.Add(epr);

            // Dump activity information to stdout
            Console.WriteLine("Activity created.\r\n\tActivity Index = {0}", activityIDCache.Count);

            ReferenceParametersType referenceParameters = epr.ReferenceParameters;

            if (referenceParameters != null)
            {
                foreach (XmlElement element in referenceParameters.Any)
                {
                    Console.WriteLine("\t\t{0}", element.OuterXml);
                }
            }
        }

        /// <summary>
        /// Displays one or more activities' documents
        /// </summary>
        /// <param name="proxy">Proxy to HPC Profile Service</param>
        private static void GetActivityDocuments(HPCBPClient hpcbp)
        {
            int activityIndex = 0;
            string activityIndexString = null;
            GetActivityDocumentResponseType[] responses = null;
            XmlSerializer serializer = new XmlSerializer(typeof(JobDefinition_Type),
                                                                        new XmlAttributeOverrides(),
                                                                        new Type[] { typeof(HPCProfileApplication_Type) },
                                                                        new XmlRootAttribute("JobDefinition"),
                                                                        HPCBPClient.JSDLNamespaceURI);
            StringBuilder sb = new StringBuilder();

            // Get activity whose document is to be retrieved
            Console.WriteLine("Enter activity index or 'A' to retrieve all activity documents...");
            activityIndexString = Console.ReadLine();

            // If the user wants to get a specific activity document
            if ((activityIndexString != "A") && (activityIndexString != "a"))
            {
                // Parse activity index that was entered
                if (!Int32.TryParse(activityIndexString, out activityIndex))
                {
                    Console.WriteLine("Invalid activity index");
                    return;
                }

                // Check if the index was in range
                if ((activityIndex > activityIDCache.Count) || (activityIndex <= 0))
                {
                    Console.WriteLine("Activity index out of range");
                    return;
                }

                Console.WriteLine("Getting activity document...");

                responses = hpcbp.GetActivityDocuments(new EndpointReferenceType[] { activityIDCache[activityIndex - 1] });
            }
            
            // Otherwise get all the activity documents
            else
            {
                responses = hpcbp.GetActivityDocuments(activityIDCache.ToArray());
            }

            if (responses != null)
            {
                Console.WriteLine("Got activity documents.");

                // Serialize the documents to XML and dump to stdout
                foreach (GetActivityDocumentResponseType activityDocument in responses)
                {
                    using (StringWriter writer = new StringWriter(sb, NumberFormatInfo.InvariantInfo))
                    {
                        serializer.Serialize(writer, activityDocument.JobDefinition);
                    }

                    Console.WriteLine("----------------------------------------");
                    Console.WriteLine(sb.ToString());
                    Console.WriteLine("----------------------------------------");
                }
            }
            else
            {
                Console.WriteLine("No activity documents returned.");
            }
        }

        /// <summary>
        /// Display the status of one or more activity
        /// </summary>
        /// <param name="proxy">Proxy to HPC Profile Service</param>
        /// /// UnkownActivityIdentifier
        private static void GetActivityStatuses(HPCBPClient hpcbp)
        {
            int activityIndex = 0;
            EndpointReferenceType[] eprs = null;
            GetActivityStatusResponseType[] activityStatuses = null;

            // Get activity whose status is to be retrieved
            Console.WriteLine("Enter activity index...");
            string activityIndexString = Console.ReadLine();

            // If user want the status of a specific activity
            if ((activityIndexString == "A") || (activityIndexString == "a"))
            { 
                // Get the status of all the activities
                eprs = activityIDCache.ToArray();
            }
            // If user wants to inject XML that will generate a fault
            else if ((activityIndexString == "T") || (activityIndexString == "t"))
            {
                eprs = new EndpointReferenceType[] { new EndpointReferenceType() };
                eprs[0].Address = new AttributedURIType();
                eprs[0].Address.Value = @"http://notavalidurl.com";
            } 
            else
            {
                // Parse job index that was entered
                if (!Int32.TryParse(activityIndexString, out activityIndex))
                {
                    Console.WriteLine("Invalid activity index");
                    return;
                }

                // Check if the index was in range
                if ((activityIndex > activityIDCache.Count) || (activityIndex <= 0))
                {
                    Console.WriteLine("Activity index out of range");
                    return;
                }

                eprs = new EndpointReferenceType[] { activityIDCache[activityIndex - 1] };
            }
            
            Console.WriteLine("Getting activity EPRs..."); 
             
            // Get the status of one or more activity
            try
            {
                activityStatuses = hpcbp.GetActivityStatuses(eprs);             
            }
            catch (FaultException<UnknownActivityIdentifierFaultType> f)
            {
                Console.WriteLine(HPCBPClient.UnknownActivityIdentifierFaultMessage + f.Detail.Message);
                return;
            }

            if (activityStatuses != null && activityStatuses[0] != null)
            {
                // Dump returned activity status to stdout
                foreach (GetActivityStatusResponseType jobStatus in activityStatuses)
                {
                    if (jobStatus.Fault == null)
                    {
                        Console.WriteLine("Activity status {0} is {1}.", activityIndex, jobStatus.ActivityStatus.state);
                        if (jobStatus.ActivityStatus.Any != null)
                        {
                            foreach (XmlElement element in jobStatus.ActivityStatus.Any)
                            {
                                if (element.NamespaceURI.CompareTo(HPCBPClient.FileStagingNamespaceURI)==0)
                                {
                                    Console.WriteLine("Extended job state:" + element.Name);
                                }
                                if (element.NamespaceURI.CompareTo(HPCBPClient.SOAP11NamespaceURI)==0)
                                {
                                    Console.WriteLine("File Transfer Fault:" + element.InnerText);
                                }
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine("Activity status {0} generated a fault:{1}", activityIndex, jobStatus.Fault.faultstring );
                    }
                }
            }
            else
            {
                Console.WriteLine("No activity returned.");
            }
        }

        /// <summary>
        /// Terminates the specified activity or activities
        /// </summary>
        /// <param name="proxy">Proxy to HPC Profile Service</param>
        private static void TerminateActivity(HPCBPClient hpcbp)
        {
            EndpointReferenceType[] eprs = null;
            TerminateActivityResponseType[] activityStatuses = null;
            int activityIndex = 0;

            // Get activity to be terminated
            Console.WriteLine("Enter activity index...");
            string activityIndexString = Console.ReadLine();

            // If the user wants to terminate a specific activity
            if ((activityIndexString != "A") && (activityIndexString != "a"))
            {
                // Parse job index that was entered
                if (!Int32.TryParse(activityIndexString, out activityIndex))
                {
                    Console.WriteLine("Invalid activity index");
                    return;
                }

                // Check if the index was in range
                if ((activityIndex > activityIDCache.Count) || (activityIndex <= 0))
                {
                    Console.WriteLine("Activity index out of range");
                    return;
                }

                eprs = new EndpointReferenceType[] { activityIDCache[activityIndex - 1] };
            }
            
            // Otherwise terminate all the activities
            else
            {
                eprs = activityIDCache.ToArray();
            }

            Console.WriteLine("Getting job...");

            activityStatuses = hpcbp.TerminateActivity(eprs);

            if (activityStatuses != null && activityStatuses[0] != null)
            {
                // Dump results to stdout
                foreach (TerminateActivityResponseType activityStatus in activityStatuses)
                {
                    if (activityStatus.Terminated)
                    {
                        Console.WriteLine("Activity Successfully Terminated");
                    }
                    else
                    {
                        // Do we have a reason as to why it was not terminated?
                        if (activityStatus.Fault != null)
                        {
                            Console.WriteLine("Fault:" + activityStatus.Fault.faultstring);
                        }
                    }
                }
            }
            else
            {
                Console.WriteLine("No activities were terminated.");
            }
        }

        private static void ListUserActivities(HPCBPClient hpcbp,string userName)
        {
            BasicFilterType basicFilter = new BasicFilterType();
            basicFilter.ActivityReferences = true;
            basicFilter.ActivityReferencesSpecified = true;
            basicFilter.ContainedResources = false;
            basicFilter.ContainedResourcesSpecified = true;

            AdvancedFilterType advancedFilter = new AdvancedFilterType();
            advancedFilter.UserName = new string[1];
            advancedFilter.UserName[0] = userName;

            DisplayFactoryAttributesDocument(hpcbp.GetFactoryAttributesDocument(basicFilter,advancedFilter));
        }

        private static void ListUserStateActivities(HPCBPClient hpcbp,string userName,ActivityStateEnumeration state)
        {
            BasicFilterType basicFilter = new BasicFilterType();
            basicFilter.ActivityReferences = true;
            basicFilter.ActivityReferencesSpecified = true;
            basicFilter.ContainedResources = false;
            basicFilter.ContainedResourcesSpecified = true;

            AdvancedFilterType advancedFilter = new AdvancedFilterType();
            advancedFilter.UserName = new string[1];
            advancedFilter.UserName[0] = userName;
            advancedFilter.State = new ActivityStateEnumeration[1];
            advancedFilter.State[0] = state;

            DisplayFactoryAttributesDocument(hpcbp.GetFactoryAttributesDocument(basicFilter,advancedFilter));
        }

        private static void ListUserTodaysActivities(HPCBPClient hpcbp,string userName)
        {
            BasicFilterType basicFilter = new BasicFilterType();
            basicFilter.ActivityReferences = true;
            basicFilter.ActivityReferencesSpecified = true;
            basicFilter.ContainedResources = false;
            basicFilter.ContainedResourcesSpecified = true;

            AdvancedFilterType advancedFilter = new AdvancedFilterType();
            advancedFilter.UserName = new string[1];
            advancedFilter.UserName[0] = userName;
            advancedFilter.DateTimeRange = new DateTimeRangeType[1];
            advancedFilter.DateTimeRange[0] = new DateTimeRangeType();
            TimeSpan day = new TimeSpan(24,0,0);
            advancedFilter.DateTimeRange[0].from = DateTime.UtcNow.Subtract(day);
            advancedFilter.DateTimeRange[0].until = DateTime.UtcNow;

            DisplayFactoryAttributesDocument(hpcbp.GetFactoryAttributesDocument(basicFilter,advancedFilter));
        }

        private static void CompactedContainedResources(HPCBPClient hpcbp)
        {
            BasicFilterType basicFilter = new BasicFilterType();
            basicFilter.ActivityReferences = false;
            basicFilter.ActivityReferencesSpecified = true;
            basicFilter.ContainedResources = true;
            basicFilter.ContainedResourcesSpecified = true;

            AdvancedFilterType advancedFilter = new AdvancedFilterType();
            advancedFilter.CompactResources = "";
            //advancedFilter.CompactResourcesSpecified = true;
            DisplayFactoryAttributesDocument(hpcbp.GetFactoryAttributesDocument(basicFilter, advancedFilter));
        }


        /// <summary>
        /// Displays all or a selected set of a node's dynamic resources
        /// </summary>
        /// <param name="hpcbp">Proxy to HPC Basic Profile Service</param>
        private static void DynamicNodeResources(HPCBPClient hpcbp)
        {
            BasicFilterType basicFilter = new BasicFilterType();
            basicFilter.ActivityReferences = false;
            basicFilter.ActivityReferencesSpecified = true;
            basicFilter.ContainedResources = false;
            basicFilter.ContainedResourcesSpecified = true;

            // Specift the nodes for the dynamic resources
            Console.WriteLine("Enter a comma seperated list of node names or leave blank to return details for all nodes ...");
            string specifedNodes = Console.ReadLine();

            AdvancedFilterType advancedFilter = new AdvancedFilterType();
            advancedFilter.NodeStatus = specifedNodes;
            DisplayFactoryAttributesDocument(hpcbp.GetFactoryAttributesDocument(basicFilter, advancedFilter));
        }

        /// <summary>
        /// Retrieves the factory attribtues document which is an XML document that describes
        /// the cluster the HPC profile service fronts
        /// </summary>
        /// <param name="proxy">Proxy to HPC Profile Service</param>
        private static void GetFactoryAttributesWithBasicFilter(HPCBPClient hpcbp)
        {
            BasicFilterType basicFilter = new BasicFilterType();
 
            // Prompt as to whether the cluster's activities should be returned
            Console.WriteLine("Include Activities (Y/N)...");
            string key = Console.ReadLine();
            basicFilter.ActivityReferences = (key.ToLower() == "y") ? true : false;
            basicFilter.ActivityReferencesSpecified = true;

            // Prompt as to whether the cluster's contained resources should be returned
            Console.WriteLine("Include Contained Resources (Y/N)...");
            key = Console.ReadLine();
            basicFilter.ContainedResources = (key.ToLower() == "y") ? true : false;
            basicFilter.ContainedResourcesSpecified = true;

            DisplayFactoryAttributesDocument(hpcbp.GetFactoryAttributesDocument(basicFilter,null));
        }

        /// <summary>
        /// Retrieves the factory attribtues document which is an XML document that describes
        /// the cluster the HPC profile service fronts
        /// </summary>
        /// <param name="proxy">Proxy to HPC Profile Service</param>
        private static void DisplayFactoryAttributesDocument(FactoryResourceAttributesDocumentType factoryDetails)
        {
            StringBuilder sb = new StringBuilder();
            XmlSerializer serializer = new XmlSerializer(typeof(FactoryResourceAttributesDocumentType),
                                                                     new XmlAttributeOverrides(),
                                                                     new Type[] { },
                                                                     new XmlRootAttribute("FactoryResourceAttributesDocument"),
                                                                     HPCBPClient.JSDLNamespaceURI);

            // Dump the document to stdout
            using (StringWriter writer = new StringWriter(sb, NumberFormatInfo.InvariantInfo))
            {
                serializer.Serialize(writer, factoryDetails );
            }

            Console.WriteLine("----------------------------------------");
            Console.WriteLine("Attributes Document");
            Console.WriteLine("----------------------------------------");
            Console.WriteLine(sb.ToString());
            Console.WriteLine("----------------------------------------");
        }


        
    }
}