//------------------------------------------------------------------------------
// <copyright file="xlcmd.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      ExcelDriver command line interfacefor interacting with Excel driver in a
//      batch environment
// </summary>
//------------------------------------------------------------------------------

namespace Microsoft.Hpc.Excel
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Text;
    using Microsoft.Hpc.Excel;
    using Microsoft.Office.Interop.Excel;

    /// <summary>
    /// Command line interface for interacting with Excel driver in a batch environment
    /// </summary>
    public class XLCMD
    {
        /// <summary>
        /// Usage message to display when incorrect usage detected
        /// </summary>
        private const string Usage = @"Utility for driving Excel with a simple command-line interface.

XLCMD /input:[workbook path] /set:<range>=<value> /get:<range> /macro:<macroname> /output:[workbook save path]
  
  /input:[workbook path]        Specifies the .xlsx or .xlsm file to open.
  /set:<range>=<value>          Sets the specified <range> to <value>.
       <range> must be an A1-style reference to a single cell.
  /get:<range>                  Reads the specified <range> and writes the value 
                                to stdout.
        <range> must be an A1-style reference to a single cell.
  /macro:<macroname>            Runs the specified macro.
  /wait:<TRUE/FALSE>            Executes the specified commands and then waits
                                for Excel to exit if true. Defaults to false.
  /output:[workbook save path]  Specified the full file path at which to save the final workbook.
                                Please refer to release notes regarding the robustness of saving
                                multiple workbooks concurrently.
";

        /// <summary>
        /// The primary invocation point for XLCMD. Parses command line arguments and performs appropriate actions.
        /// </summary>
        /// <param name="args">command line arguments designating various actions to perform</param>
        /// <returns>success or failure return code</returns>
        public static int Main(string[] args)
        {
            // Flag to remember whether the user wants to wait and keep Excel open until they close it.
            bool waitForExit = false;

            // Maximum number of retry attempts
            const int MAXRETRYCOUNT = 5;

            // Excel Driver instance for interacting with Excel
            ExcelDriver xl = new ExcelDriver();

            try
            {
                // There should be exactly one /input parameter, which is the source workbook
                string[] sourceWorkbooks = Array.FindAll(args, delegate(string s) { return s.Substring(1, 5).ToLower().Equals("input"); });
                if (sourceWorkbooks.Length != 1)
                {
                    Console.Error.WriteLine(Usage);
                    return 1;
                }

                // There should be at most one /output paramater, which is the workbook to save the results to
                string[] outputWorkbooks = Array.FindAll(args, delegate(string s) { return s.Substring(1, 6).ToLower().Equals("output"); });
                if (outputWorkbooks.Length > 1)
                {
                    Console.Error.WriteLine(Usage);
                    return 1;
                }

                // Initialize an excel driver instance with the source workbook.
                string inputWorkbookPath = Path.GetFullPath(sourceWorkbooks[0].Substring("/input:".Length));
                xl.OpenWorkbook(inputWorkbookPath);

                // Now walk through all the arguments and execute each one.
                foreach (string s in args)
                {
                    // Arguments are in the form command:parameter
                    string[] arg = s.Split(new char[] { ':' }, 2);
                    if (arg.Length != 2)
                    {
                        Console.Error.WriteLine("ERROR: Unknown argument '{0}'\n", s);
                        Console.Error.WriteLine(Usage);
                        return 1;
                    }

                    // Select on the command portion of the command:parameter argument
                    switch (arg[0].ToLower())
                    {
                        // Set cell to value. Parameter specified in form cell=value
                        case "/set":
                        case "-set":
                            string[] cell = arg[1].Split(new char[] { '=' }, 2);
                            if (cell.Length != 2)
                            {
                                Console.Error.WriteLine("ERROR: Invalid /set argument '{0}'\n", arg[1]);
                                Console.Error.WriteLine(Usage);
                                return 1;
                            }

                            xl.SetCellValue(cell[0], cell[1]);
                            break;

                        // Get value from cell. Parameter specified in form of cell
                        case "/get":
                        case "-get":
                            Console.WriteLine(xl.GetCellValue(arg[1]));
                            break;

                        // Run macro specified on command line. No parameters are supported.
                        case "/macro":
                        case "-macro":
                            xl.RunMacro(arg[1]);
                            break;

                        // Set wait behavior. Either wait for user to close Excel before exiting or close Excel and exit.
                        case "/wait":
                        case "-wait":
                            bool parsedWait = false;
                            try
                            {
                                parsedWait = bool.Parse(arg[1]);
                            }
                            catch (FormatException)
                            {
                                Console.Error.WriteLine("Boolean value is invalid in parameter: " + s);
                                Console.Error.WriteLine("Wait defaulting to false.");
                            }
                            finally
                            {
                                waitForExit = parsedWait;
                            }

                            break;

                        case "/input":
                        case "-input":
                        case "/output":
                        case "-output":
                            // File paths handled elsewhere
                            break;

                        default:

                            Console.Error.WriteLine("ERROR: Unknown argument '{0}'\n", s);
                            Console.Error.WriteLine(Usage);
                            return 1;
                    }
                }

                // Save workbook to output iff output workbook supplied
                if (outputWorkbooks.Length == 1)
                {
                    string outputWorkbookPath = Path.GetFullPath(outputWorkbooks[0].Substring("/output:".Length));

                    int retryCount = 0;
                    Exception saveException = null;

                    // Attempt to save workbook to output path up to the maximum number of retries.
                    while (retryCount < MAXRETRYCOUNT)
                    {
                        saveException = null;
                        try
                        {
                            // Saving behavior is not consistently correct. Please consult release notes.
                            xl.Workbook.SaveAs(
                                                outputWorkbookPath,
                                                xl.Workbook.FileFormat,
                                                null,
                                                null,
                                                false,
                                                false,
                                                XlSaveAsAccessMode.xlNoChange,
                                                XlSaveConflictResolution.xlLocalSessionChanges,
                                                false,
                                                null,
                                                null,
                                                true);
                        }
                        catch (Exception ex)
                        {
                            saveException = ex;
                        }

                        // Check if exception occurred, and retry up to retry limit
                        if (saveException != null)
                        {
                            retryCount++;
                        }
                        else
                        {
                            break;
                        }
                    } // while retry

                    // If all retries failed, report error.
                    if (saveException == null)
                    {
                        Console.Error.WriteLine("Maximum retry count exceeded for saving output workbook.");
                        Console.Error.WriteLine(saveException);
                    }
                } // if output workbook

                if (waitForExit)
                {
                    // Wait for user to quit Excel before ending command line application
                    System.Diagnostics.Process excelProc = xl.ExcelProcess;

                    xl = null;

                    // Force garbage collection of COM objects. 
                    GC.Collect();
                    GC.WaitForPendingFinalizers();

                    // Force garbage collection of COM object members
                    GC.Collect();
                    GC.WaitForPendingFinalizers();

                    excelProc.WaitForExit();
                }
            }
            catch (Exception ex)
            {
                // Write out unhandled exceptions in execution.
                Console.Error.Write("\nUnhandled exception: \n{0}\n", ex);

                // Attempt to make application invisible before closing. This avoids a UI glitch where
                // Excel doesn't clean up it's window fully when it quits.
                if (xl.App != null)
                {
                    try
                    {
                        xl.App.Visible = false;
                    }
                    catch
                    {
                        // This can fail on remote nodes. If it does, silently fail.
                    }
                }
                
                return 1;
            }
            finally
            {
                if (xl != null)
                {
                    // Dispose exceldriver resources if allocated.
                    xl.Dispose();
                }

                // Force garbage collection of COM objects. 
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }

            // Complete successfully if all operations completed without error
            return 0;
        }
    }
}
