//------------------------------------------------------------------------------
// <copyright file="HPCBPClient.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Wrapper for the HPCBP client web service
// </summary>
//------------------------------------------------------------------------------

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

    /// <summary>
    /// Implements main module for the console application
    /// </summary>
    public class HPCBPClient
    {

        static int main(string[] args)
        {
            if (args.Length != 4)
            {
                Console.WriteLine("HPCBPClient [JSDL job file] [service URL] [username] [password]");
                return 1;
            }

            string jsdlFileName = args[0];
            string serviceUrl = args[1];
            string userName = args[2];
            string password = args[3];

            HPCBPClient hpcbp = new HPCBPClient(serviceUrl, userName, password);

            EndpointReferenceType[] eprs = new EndpointReferenceType[1];
            eprs[0] = hpcbp.CreateActivity(jsdlFileName);

            GetActivityStatusResponseType[] status = null;
            do
            {
                Thread.Sleep(5000);
                status = hpcbp.GetActivityStatuses(eprs);
            } while (status[0].ActivityStatus.state == ActivityStateEnumeration.Pending || status[0].ActivityStatus.state == ActivityStateEnumeration.Running);

            return 0;
        }

        private BESFactoryPortTypeClient factoryProxy = null;
        private BESManagementPortTypeClient managementProxy = null;

        // Configuration from a file
        public HPCBPClient(string userName, string password)
        {
            factoryProxy = new BESFactoryPortTypeClient("RemoteScheduler");
            managementProxy = new BESManagementPortTypeClient("RemoteSchedulerMgmt");
            doUsernamePasswordConfiguration(userName, password);
            doTimeStampConfiguration();
        }

        // Configuration from a file, prompt for username and password
        public HPCBPClient()
        {
            string userName = null;
            SecureString password = null;
            bool fSave;

            factoryProxy = new BESFactoryPortTypeClient("RemoteScheduler");
            managementProxy = new BESManagementPortTypeClient("RemoteSchedulerMgmt");
            Utility.PromptForCredentials(ServiceUri(), ref userName, ref password, out fSave, true, IntPtr.Zero);
            doUsernamePasswordConfiguration(userName, Utility.UnsecureString(password));
            doTimeStampConfiguration();
        }

        // Explicit configuration 
        public HPCBPClient(string serviceUrl, string userName, string password)
        {
            BasicHttpBinding binding = new BasicHttpBinding(new BasicHttpSecurityMode());
            binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCredential;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
            binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
            binding.SendTimeout = TimeSpan.FromMinutes(2.0);
            Console.WriteLine("Send Timeout:" + binding.SendTimeout);
            EndpointAddress endpoint = new EndpointAddress(serviceUrl);
            factoryProxy = new BESFactoryPortTypeClient(binding, endpoint);
            managementProxy = new BESManagementPortTypeClient(binding, endpoint);
            doUsernamePasswordConfiguration(userName, password);
            doTimeStampConfiguration();
        }

        public string ServiceUri()
        {
            return factoryProxy.Endpoint.Address.Uri.AbsoluteUri;
        }

        private void doUsernamePasswordConfiguration(string userName, string password)
        {
            BasicHttpBinding factoryBinding = factoryProxy.Endpoint.Binding as BasicHttpBinding;
            BasicHttpBinding managementBinding = factoryProxy.Endpoint.Binding as BasicHttpBinding;

            if (factoryBinding == null || managementBinding == null)
            {
                Console.WriteLine("Binding to remote schedulers must be basicHttpBinding");
                return;
            }

            // Secure communication channel with idenitifying username & password in WS-Security
            if (factoryBinding.Security.Mode == BasicHttpSecurityMode.TransportWithMessageCredential &&
                managementBinding.Security.Mode == BasicHttpSecurityMode.TransportWithMessageCredential)
            {
                if (factoryBinding.Security.Message.ClientCredentialType == BasicHttpMessageCredentialType.UserName &&
                    managementBinding.Security.Message.ClientCredentialType == BasicHttpMessageCredentialType.UserName)
                {
                    factoryProxy.ClientCredentials.UserName.UserName = userName;
                    factoryProxy.ClientCredentials.UserName.Password = password;
                    managementProxy.ClientCredentials.UserName.UserName = userName;
                    managementProxy.ClientCredentials.UserName.Password = password;
                }
            }
        }

        private void doTimeStampConfiguration()
        {
            BasicHttpBinding factoryBinding = factoryProxy.Endpoint.Binding as BasicHttpBinding;
            BasicHttpBinding managementBinding = factoryProxy.Endpoint.Binding as BasicHttpBinding;

            if (factoryBinding == null || managementBinding == null)
            {
                Console.WriteLine("Binding to remote schedulers must be basicHttpBinding");
                return;
            }

            // To ensure interoperability with gSOAP deployment from LSF - no timestamp
            BindingElementCollection fbec = factoryBinding.CreateBindingElements();
            SecurityBindingElement fsbe = fbec.Find<SecurityBindingElement>();
            if (fsbe != null)
            {
                fsbe.IncludeTimestamp = false;
                factoryProxy.Endpoint.Binding = new CustomBinding(fbec);
            }
            BindingElementCollection mbec = managementBinding.CreateBindingElements();
            SecurityBindingElement msbe = mbec.Find<SecurityBindingElement>();
            if (msbe != null)
            {
                msbe.IncludeTimestamp = false;
                managementProxy.Endpoint.Binding = new CustomBinding(mbec);
            }
        }

        /// <summary>
        /// Creates a BES activity
        /// </summary>
        /// <param name="proxy">Proxy to HPC Profile Service</param>
        /// <param name="fileName">JSDL file name</param>
        public EndpointReferenceType CreateActivity(string fileName)
        {
            JobDefinition_Type jobDefinition = null;

            // Load JSDL document from file
            using (XmlTextReader reader = new XmlTextReader(new FileStream(fileName, FileMode.Open)))
            {
                jobDefinition = (JobDefinition_Type)Utility.Deserialize(reader);
            }
            return SendCreateActivityMessage(jobDefinition);
        }

        private EndpointReferenceType SendCreateActivityMessage(JobDefinition_Type jobDefinition)
        {
            ActivityDocumentType activityDocument = new ActivityDocumentType();
            CreateActivity createActivity = new CreateActivity(activityDocument, null, null);
            CreateActivityResponse response = null;

            activityDocument.JobDefinition = jobDefinition;

            // Create BES activity
            try
            {
                response = factoryProxy.CreateActivity(createActivity);
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.NotAuthorizedFaultType>)
            {
                Console.WriteLine(HPCBPClient.NotAuthorizedFaultMessage);
                return null;
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.NotAcceptingNewActivitiesFaultType>)
            {
                Console.WriteLine(HPCBPClient.NotAcceptingNewActivitiesFaultMessage);
                return null;
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.UnsupportedFeatureFaultType> f)
            {
                Console.WriteLine(HPCBPClient.UnsupportedFeatureFaultMessage);
                if (f.Detail.Nodes != null)
                {
                    foreach (XmlNode node in f.Detail.Nodes)
                    {
                        Console.WriteLine(node.InnerText);
                    }
                }
                return null;
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.InvalidRequestMessageFaultType> f)
            {
                Console.WriteLine(HPCBPClient.InvalidRequestMesssageFaultMessage);
                if (f.Detail.Nodes != null)
                {
                    foreach (XmlNode node in f.Detail.Nodes)
                    {
                        Console.WriteLine(node.InnerText);
                    }
                }
                return null;
            }

            return response.ActivityIdentifier;
        }

        /// <summary>
        /// Displays one or more activities' documents based on the array of activity EPRs 
        /// </summary>
        /// <param name="proxy">Array of activity EPRs that documents are required for</param>
        public GetActivityDocumentResponseType[] GetActivityDocuments(EndpointReferenceType[] activityEPR)
        {
            GetActivityDocumentsResponse response = null;

            // Get activity document(s)
            try
            {
                response = factoryProxy.GetActivityDocuments(new GetActivityDocuments(activityEPR, null, null));
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.UnknownActivityIdentifierFaultType> f)
            {
                Console.WriteLine(HPCBPClient.UnknownActivityIdentifierFaultMessage);
                DisplayAnyDetail(f);
                return null;
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.NotAuthorizedFaultType> f)
            {
                Console.WriteLine(HPCBPClient.NotAuthorizedFaultMessage + f.Detail);
                return null;
            }

            if (response.Response.Length == 0)
            {
                return null;
            }

            return response.Response;
        }

        private void DisplayAnyDetail(FaultException<schemas.ggf.org.bes._2006._08.besfactory.UnknownActivityIdentifierFaultType> f)
        {
            if (f.Detail != null)
            {
                foreach (XmlNode node in f.Detail.Nodes)
                {
                    Console.WriteLine(node.InnerXml);
                }
            }
        }

        /// <summary>
        /// Display the status of one or more activity
        /// </summary>
        /// <param name="proxy">Array of Activity EPRs to find the status of</param>
        /// /// UnkownActivityIdentifier
        public GetActivityStatusResponseType[] GetActivityStatuses(EndpointReferenceType[] actiivtyEPRs)
        {
            GetActivityStatusesResponse response = null;

            // Get the status of one or more activity
            try
            {
                response = factoryProxy.GetActivityStatuses(new GetActivityStatuses(actiivtyEPRs, null, null));
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.UnknownActivityIdentifierFaultType> f)
            {
                Console.WriteLine(HPCBPClient.UnknownActivityIdentifierFaultMessage);
                DisplayAnyDetail(f);
                return null;
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.NotAuthorizedFaultType> f)
            {
                Console.WriteLine(HPCBPClient.NotAuthorizedFaultMessage + f.Detail);
                return null;
            }
            return response.Response;

        }

        /// <summary>
        /// Terminates the specified activity or activities
        /// </summary>
        /// <param name="proxy">Array of Actiivty EPRs that are to be terminated</param>
        public TerminateActivityResponseType[] TerminateActivity(EndpointReferenceType[] activityEPRs)
        {
            TerminateActivitiesResponse response = null;

            try
            {
                response = factoryProxy.TerminateActivities(new TerminateActivities(activityEPRs, null, null));
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.UnknownActivityIdentifierFaultType> f)
            {
                Console.WriteLine(HPCBPClient.UnknownActivityIdentifierFaultMessage);
                DisplayAnyDetail(f);
                return null;
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.NotAuthorizedFaultType> f)
            {
                Console.WriteLine(HPCBPClient.NotAuthorizedFaultMessage + f.Detail);
                return null;
            }
            return response.Response;
        }

        /// <summary>
        /// Retrieves the factory attribtues document which is an XML document that describes
        /// the cluster the HPC profile service fronts
        /// </summary>
        /// <param name="proxy"></param>
        public FactoryResourceAttributesDocumentType GetFactoryAttributesDocument(BasicFilterType basicFilter, AdvancedFilterType advancedFilter)
        {
            GetFactoryAttributesDocument request = new GetFactoryAttributesDocument();
            GetFactoryAttributesDocumentResponse response = null;

            request.Any = new XmlElement[2];
            request.Any[0] = Utility.ConvertToXmlElement(basicFilter, HPCBPClient.BasicFilterElementName, HPCBPClient.BasicFilterNamespaceURI);
            if (advancedFilter != null)
            {
                request.Any[1] = Utility.ConvertToXmlElement(advancedFilter, HPCBPClient.AdvancedFilterElementName, HPCBPClient.AdvancedFilterNamespaceURI);
            }

            // Retrieve document
            response = factoryProxy.GetFactoryAttributesDocument(request);

            return response.FactoryResourceAttributesDocument;
        }

        /// <summary>
        /// Determine the status of the BES Container in relation to creating new activities
        /// </summary>
        public bool isAcceptingNewActivites()
        {
            BasicFilterType basicFilter = new BasicFilterType();
            FactoryResourceAttributesDocumentType response = null;

            // Just return the status of the container - not the activities running in it or the resources it represents
            basicFilter.ActivityReferences = false;
            basicFilter.ActivityReferencesSpecified = true;
            basicFilter.ContainedResources = false;
            basicFilter.ContainedResourcesSpecified = true;

            // Retrieve document
            response = this.GetFactoryAttributesDocument(basicFilter, null);

            // Report on aceepting new activities status
            return response.IsAcceptingNewActivities;
        }


        /// <summary>
        /// Requests the BES Factory to start accepting new activities
        /// </summary>
        public bool StartAcceptingNewActivities()
        {
            StartAcceptingNewActivities request = new StartAcceptingNewActivities();
            StartAcceptingNewActivitiesResponse response = null;

            // Retrieve document
            try
            {
                response = managementProxy.StartAcceptingNewActivities(request);
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.NotAuthorizedFaultType>)
            {
                Console.WriteLine(HPCBPClient.NotAuthorizedFaultMessage);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Requests the BES Factory to stop accepting new activities
        /// </summary>
        public bool StopAcceptingNewActivities()
        {
            StopAcceptingNewActivities request = new StopAcceptingNewActivities();
            StopAcceptingNewActivitiesResponse response = null;

            // Retrieve document
            try
            {
                response = managementProxy.StopAcceptingNewActivities(request);
            }
            catch (FaultException<schemas.ggf.org.bes._2006._08.besfactory.NotAuthorizedFaultType>)
            {
                Console.WriteLine(HPCBPClient.NotAuthorizedFaultMessage);
                return false;
            }
            return true;
        }

        public static string NotAuthorizedFaultMessage = "NotAuthorizedFault: The user credentials were not accepted by the underlying cluster resource for this operation. Please contact the administrator of the HPCBP web service.";
        public static string NotAcceptingNewActivitiesFaultMessage = "NotAcceptingNewActivitiesFault: The BES Container is currently not accepting new activities. Please contact the administrator of the HPCBP web service or try again later.";
        public static string UnsupportedFeatureFaultMessage = "UnsupportedFeatureFault: A well formed JSDL document had an element that was not recognised or is not supported by the underlying cluster resource.";
        public static string InvalidRequestMesssageFaultMessage = "InvalidRequestMessageFault: The incoming message to the HPCBP web service was found to be missing an element:";
        public static string UnknownActivityIdentifierFaultMessage = "UnknownActivityIdenitiferFault: The BES Container did not recognize one of the activities:";

        public static string BasicFilterElementName = "BasicFilter";
        public static string BasicFilterNamespaceURI = "http://schemas.ogf.org/hpcp/2007/01/bp/BasicFilter";
        public static string AdvancedFilterElementName = "AdvancedFilter";
        public static string AdvancedFilterNamespaceURI = "http://schemas.ogf.org/hpcp/2007/11/bp/AdvancedFilter";
        public static string JSDLNamespaceURI = @"http://schemas.ggf.org/jsdl/2005/11/jsdl";
        public static string FileStagingNamespaceURI = @"http://schemas.ggf.org/hpcp/2007/10/hpc-profile-filestaging";
        public static string HPCS2008SOAJobNamespaceURI = "http://schemas.microsoft.com/HPCS2008/hpcbp/soa";
        public static string SOAP11NamespaceURI = @"http://schemas.xmlsoap.org/soap/envelope/";
        public static string HPCS2008HPCBPNamespaceURI = "http://schemas.microsoft.com/HPCS2008/hpcbp";
    }
}
