//------------------------------------------------------------------------------
// <copyright file="MonteCarloCLI.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Command line sample for use with ExcelRunner
// </summary>
//------------------------------------------------------------------------------

namespace MonteCarloCLI
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.ServiceModel;
    using System.Text;
    using System.Threading;
    using Microsoft.Hpc.Excel;
    using Microsoft.Hpc.Excel.ExcelService;
    using Microsoft.Hpc.Scheduler;
    using Microsoft.Hpc.Scheduler.Properties;
    using Microsoft.Hpc.Scheduler.Session;
    using Microsoft.Hpc.Scheduler.Session.Internal;

    /// <summary>
    /// Sample command line interface client application for use with ExcelRunner.
    /// </summary>
    public static class MonteCarlo
    {
        /// <summary>
        /// Usage string
        /// </summary>
        private static readonly string USAGE = "MonteCarloCLI <Local Workbook Path> <Shared Workbook Path> <Head Node> <Max Cores> [<Iterations = 10000> <Chunk = 500>]";

        /// <summary>
        /// Name of macro to call on remote workbook
        /// </summary>
        private static readonly string MACRONAME = "Worker";

        /// <summary>
        /// Job completion event 
        /// </summary>
        private static AutoResetEvent done;

        /// <summary>
        /// Failure condition for reporting
        /// </summary>
        private static bool exceptionCaught;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        /// <param name="args"> Command line arguments </param>
        public static void Main(string[] args)
        {
            // Syntax Check 
            if (args.Length != 4 && args.Length != 6)
            {
                Console.WriteLine("Invalid number of arguments.");
                Console.WriteLine(USAGE);
                return;
            }

            // Initialize variables
            exceptionCaught = false;
            done = new AutoResetEvent(false);

            // Parse maximum number of cores
            int maxCores;
            if (int.TryParse(args[3], out maxCores))
            {
                if (maxCores <= 0)
                {
                    Console.WriteLine("The number of cores requested must be greater than 0.");
                    Console.WriteLine(USAGE);
                    return;
                }
            }
            else
            {
                Console.WriteLine("Invalid value for <Max Cores>: " + args[2]);
                Console.WriteLine(USAGE);
                return;
            }

            // Get the head node name and workbook location
            string localWorkbookPath = args[0];
            string sharedWorkbookPath = args[1];
            string headNode = args[2];
            
            if (!File.Exists(localWorkbookPath)) 
            {
                Console.WriteLine("Please provide valid local workbook path: {0}", localWorkbookPath);
                Console.WriteLine(USAGE);
                return;
            }

            // Set default iterations and chunk size
            int maxIterations = 10000;
            int chunkSize = 500;

            // Allow user specification of iterations and chunk size
            if (args.Length > 4)
            {
                // Try to parse the number of iterations
                if (!int.TryParse(args[4], out maxIterations))
                {
                    Console.WriteLine("Invalid value for <Max Iterations>:" + args[3]);
                    Console.WriteLine(USAGE);
                    return;
                }

                // Try to parse the size of each work chunk
                if (!int.TryParse(args[5], out chunkSize))
                {
                    Console.WriteLine("Invalid value for <Chunk>:" + args[4]);
                    Console.WriteLine(USAGE);
                    return;
                }

                // Test values for validity
                if (chunkSize > maxIterations || chunkSize <= 0 || maxIterations <= 0)
                {
                    Console.WriteLine("<Chunk> cannot be greater than <Max Iterations> and both must be greater than 0");
                    Console.WriteLine(USAGE);
                    return;
                }
            } // if setting number of iterations and chunk size

            // Deploy workbook if remote path is different from local path
            if (!localWorkbookPath.Equals(sharedWorkbookPath))
            {
                System.IO.File.Copy(localWorkbookPath, sharedWorkbookPath, true);
            }

            // Initialize state variables
            double sumPayoffs = 0.0;
            double sumSquarePayoffs = 0.0;
            double avgPayoff = 0.0;
            double stdDev = 0.0;
            double stdErr = 0.0;
            int completed = 0;

            // Time calculations
            Stopwatch timer = Stopwatch.StartNew();

            Session session = null;
            BrokerClient<IExcelService> client = null;
            try
            {
                // Create new session
                SessionStartInfo info = InitSession(JobUnitType.Core, 1, maxCores, headNode, sharedWorkbookPath);
                Environment.SetEnvironmentVariable("DISABLE_V2SOA", "1");
                session = Session.CreateSession(info);
                
                // Create client using default binding information
                client = new BrokerClient<IExcelService>(session);

                // Initialize response variables
                int respCount = 0;
                object respLock = new object();
                AutoResetEvent allDone = new AutoResetEvent(false);

                // Set the response handler
                client.SetResponseHandler((BrokerResponseHandler<CalculateResponse>)delegate(BrokerResponse<CalculateResponse> response)
                {
                    lock(respLock)
                    {
                        // Extract hello message from response and print to command line
                        try
                        {
                            // Calculation result expected to be serialized double array holding 
                            // {partial sum, partial sum of squares}
                            byte[] calculationResult;
                            calculationResult = response.Result.CalculateResult;
                            object[] payoffs = WorkItem.Deserialize(calculationResult).Get<object[]>(0);

                            // Extract partial sum and add to running total
                            sumPayoffs += (double)payoffs[0];

                            // Extract partial sum of squares and add to running total
                            sumSquarePayoffs += (double)payoffs[1];
                            completed += chunkSize;

                            // Update global variables and report result
                            stdDev = Math.Sqrt(sumSquarePayoffs - ((sumPayoffs * sumPayoffs) / completed)) / (completed - 1);
                            stdErr = stdDev / Math.Sqrt(completed);
                            avgPayoff = sumPayoffs / completed;
                            Console.WriteLine("iteration {0} Avg payoff = {1}, standard Deviation = {2}, standard Error = {3}", completed, avgPayoff, stdDev, stdErr);
                    
                        }
                        catch (FaultException ex)
                        {
                            // Print any application errors in the service
                            Console.Error.WriteLine("Fault Exception: " + ex.Message);
                        }
                        catch (Exception ex)
                        {
                            // Non-application errors may be fatal, and the program should be allowed to finish
                            Console.Error.WriteLine("\nThere has been an error: {0}", ex.Message);
                            exceptionCaught = true;
                            allDone.Set();
                            return;
                        }

                        // Update the number of responses received 
                        respCount += chunkSize;
                        if (respCount == maxIterations)
                        {
                            // All responses have been received
                            allDone.Set();
                        }
                    }
                });
                
                // Create new work item to send the number of iterations per chunk
                WorkItem wi = new WorkItem();
                wi.Insert<int>(0, chunkSize);

                // Send macro name, serialized work item, and null to skip workbook version check.
                CalculateRequest myReq = new CalculateRequest(MACRONAME, WorkItem.Serialize(wi), null);

                // Generate calculation requests for each chunk of iterations
                for (int i = 0; i < maxIterations; i += chunkSize)
                {
                    client.SendRequest<CalculateRequest>(myReq);
                }

                client.EndRequests();
                allDone.WaitOne();
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine("\nThere has been an error: {0}", ex.Message);
                exceptionCaught = true;
            }
            finally
            {
                // Close the client and session if they were created
                if (client != null)
                {
                    client.Close(false);
                }
                if (session != null)
                {
                    session.Close();
                }
            }

            if (!exceptionCaught)
            {
                // Report performance data if successful
                Console.WriteLine("{0} iterations completed in {1} seconds.", maxIterations, timer.ElapsedTicks / Stopwatch.Frequency);
            }
        } // main

        /// <summary>
        /// Initialize the session start information with supplied values and defaults
        /// </summary>
        /// <param name="type"> granularity of resource requested </param>
        /// <param name="min"> minimum number of resource units </param>
        /// <param name="max"> maximum number of resource units </param>
        /// <param name="headNode"> name of the head node </param>
        /// <returns> start information for session creation </returns>
        private static SessionStartInfo InitSession(JobUnitType type, int min, int max, string headNode, string workbookPath)
        {
            // Use generic ExcelService
            SessionStartInfo info = new SessionStartInfo(headNode, "Microsoft.Hpc.Excel.ExcelService");
            info.ResourceUnitType = type;
            info.MinimumUnits = min;
            info.MaximumUnits = max;
            info.Environments.Add("Microsoft.Hpc.Excel.WorkbookPath", workbookPath);
            info.Secure = true;
            info.ShareSession = true;
            
            return info;
        }
    }
}