/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Management;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Security.Principal;

#pragma warning disable 618

namespace Apache.Geode.DUnitFramework
{
  using NUnit.Framework;
  
  public static class Util
  {
    /// <summary>
    /// Enumeration to specify logging level.
    /// For now this is same as the GEODE_NATIVE_HOME product LogLevel enumeration.
    /// </summary>
    public enum LogLevel
    {
      None,
      Error,
      Warning,
      Info,
      Default,
      Config,
      Fine,
      Finer,
      Finest,
      Debug,
      All
    }

    #region Public accessors and members

    /// <summary>
    /// Channel to communicate with the server -- this must be setup by
    /// each client process.
    /// </summary>
    public static IDriverComm DriverComm
    {
      get
      {
        return m_driverComm;
      }
      set
      {
        m_driverComm = value;
      }
    }

    /// <summary>
    /// The port to use for running the driver.
    /// </summary>
    public static int DriverPort
    {
      get
      {
        return m_driverPort;
      }
      set
      {
        m_driverPort = value;
      }
    }

    /// <summary>
    /// Channel to communicate with the BBserver -- this must be setup by
    /// each client process.
    /// </summary>
    public static IBBComm BBComm
    {
      get
      {
        return m_bbComm;
      }
      set
      {
        m_bbComm = value;
      }
    }

    /// <summary>
    /// The URL of the BlackBoard server when using an external one.
    /// </summary>
    public static string ExternalBBServer
    {
      get
      {
        return m_externalBBServer;
      }
      set
      {
        m_externalBBServer = value;
      }
    }

    /// <summary>
    /// Get or set the ID for a client -- this must be set by each client
    /// process for client and server side logging purpose.
    /// </summary>
    public static string ClientId
    {
      get
      {
        return m_clientId;
      }
      set
      {
        m_clientId = value;
      }
    }

    public static string AppDomainPrefix
    {
      get
      {
        return "APPDOMAIN:";
      }
    }

    public static string IPAddressString
    {
      get
      {
        if (m_ipAddress == null)
        {
          NetworkInterface[] adapters =
            NetworkInterface.GetAllNetworkInterfaces();
          if (adapters != null)
          {
            foreach (NetworkInterface adapter in adapters)
            {
              if (adapter.OperationalStatus == OperationalStatus.Up &&
                adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback)
              {
                UnicastIPAddressInformationCollection unicastAddresses =
                  adapter.GetIPProperties().UnicastAddresses;
                if (unicastAddresses != null)
                {
                  foreach (UnicastIPAddressInformation unicastAddress in
                    unicastAddresses)
                  {
                    IPHostEntry hostEntry;
                    try
                    {
                      hostEntry = Dns.GetHostByAddress(unicastAddress.Address);
                    }
                    catch
                    {
                      hostEntry = null;
                    }
                    if (hostEntry != null && hostEntry.HostName != null &&
                      hostEntry.HostName.Length > 0)
                    {
                      m_ipAddress = unicastAddress.Address;
                      break;
                    }
                  }
                  if (m_ipAddress != null) break;
                }
              }
            }
          }
        }
        return (m_ipAddress == null ? null : m_ipAddress.ToString());
      }
    }

    /// <summary>
    /// Get or set a number for the client.
    /// </summary>
    public static int ClientNum
    {
      get
      {
        return m_clientNum;
      }
      set
      {
        m_clientNum = value;
      }
    }

    public static bool LogAppend
    {
      get
      {
        return (m_log == null ? true : m_log.Append);
      }
      set
      {
        if (m_log == null)
        {
          throw new IllegalArgException("LogFile is not yet set.");
        }
        m_log.Append = value;
      }
    }

    public static string LogFile
    {
      get
      {
        return (m_log == null ? null : m_log.File);
      }
      set
      {
        if (value == null)
        {
          if (m_log != null)
          {
            m_log.Dispose();
          }
          m_log = null;
        }
        else if (m_log == null)
        {
          m_log = new UnitLog(value, true);
        }
        else
        {
          m_log.File = value;
          m_log.Append = true;
        }
      }
    }

    public static string LogPrefix
    {
      get
      {
        return m_logPrefix;
      }
      set
      {
        if (value == null)
        {
          m_logPrefix = DefaultLogPrefix;
        }
        else
        {
          m_logPrefix = value + ':';
        }
      }
    }

    public static string DUnitLogDir
    {
      get
      {
        if (m_logDir == null)
        {
          try
          {
            m_logDir = Util.GetEnvironmentVariable("DUNIT_LOGDIR");
            if (m_logDir.Length == 0)
            {
              m_logDir = null;
            }
          }
          catch (Exception)
          {
            m_logDir = null;
          }
        }
        return m_logDir;
      }
    }

    public static bool LogOnConsole
    {
      get
      {
        return m_logOnConsole;
      }
      set
      {
        m_logOnConsole = value;
      }
    }

    public static string AssemblyDir
    {
      get
      {
        if (m_assemblyDir == null)
        {
          m_assemblyDir = Util.NormalizePath(Path.GetDirectoryName(
            Assembly.GetExecutingAssembly().Location)).ToLower();
        }
        return m_assemblyDir;
      }
    }

    public static XmlNodeReaderWriter DefaultSettings
    {
      get
      {
        if (m_defaultSettings == null)
        {
          m_defaultSettings = XmlNodeReaderWriter.GetInstance("Settings.xml");
        }
        return m_defaultSettings;
      }
    }

    public static string HostName
    {
      get
      {
        if (m_hostName == null)
        {
          m_hostName = Dns.GetHostName();
        }
        return m_hostName;
      }
    }

    public static Process Process
    {
      get
      {
        if (m_proc == null)
        {
          m_proc = Process.GetCurrentProcess();
        }
        return m_proc;
      }
    }

    public static int PID
    {
      get
      {
        return Util.Process.Id;
      }
    }

    public static string ProcessName
    {
      get
      {
        return Util.Process.ProcessName;
      }
    }

    public static int ThreadID
    {
      get
      {
        return Thread.CurrentThread.ManagedThreadId;
      }
    }

    public static string ThreadName
    {
      get
      {
        return Thread.CurrentThread.Name;
      }
    }

    public static LogLevel CurrentLogLevel
    {
      get
      {
        return m_logLevel;
      }
      set
      {
        m_logLevel = value;
      }
    }

    public static LogLevel DefaultLogLevel
    {
      get
      {
        return LogLevel.Info;
      }
    }

    public static MethodBase CurrentTest
    {
      get
      {
        StackFrame sf = new StackFrame(1, false);
        return sf.GetMethod();
      }
    }

    public static string CurrentTestName
    {
      get
      {
        StackFrame sf = new StackFrame(1, false);
        return sf.GetMethod().Name;
      }
    }

    public static ICollection<UnitFnMethod> RegisteredTestCompleteDelegates
    {
      get
      {
        return m_testCompleteMap.Keys;
      }
    }

    public static string SystemType
    {
      get
      {
        if (m_systemType == null)
        {
          m_systemType = Environment.OSVersion.Platform.ToString();
          m_systemType = m_systemType.Substring(0, 3).ToUpper();
        }
        return m_systemType;
      }
    }

    public static string MarkerString = Environment.NewLine + "------------" +
      "-------------------------------------------------------" +
      Environment.NewLine;

    #endregion

    #region Private members

    private static Random m_rnd = new Random((int)DateTime.Now.Ticks);
    private static IDriverComm m_driverComm = null;
    private static int m_driverPort = 0;
    private static IPAddress m_ipAddress = null;
    private static IBBComm m_bbComm = null;
    private static string m_externalBBServer = null;
    private static string m_clientId = "0";
    private static int m_clientNum = 0;
    private static string m_hostName = null;
    private static Process m_proc = null;
    private static volatile UnitLog m_log = null;
    private static LogLevel m_logLevel = DefaultLogLevel;
    private static string m_logDir = null;
    private static string m_logPrefix = DefaultLogPrefix;
    private static bool m_logOnConsole = false;
    private static string m_assemblyDir = null;
    private static XmlNodeReaderWriter m_defaultSettings = null;
    private static string m_systemType = null;
    private const string DefaultLogPrefix = "TEST:";
    private const string NoServerConnMsg = "Server connection not established.";
    private static Dictionary<UnitFnMethod, bool> m_testCompleteMap =
      new Dictionary<UnitFnMethod, bool>();
    

    static Util()
    {
      DriverPort = GetAvailablePort();
    }
    #endregion

    /// <summary>
    /// Convenience function to convert a string to a byte.
    /// </summary>
    /// <param name="str">The string to convert.</param>
    /// <returns>The converted byte.</returns>
    public static byte String2Byte(string str)
    {
      if (str.StartsWith("0x"))
      {
        return byte.Parse(str.Substring(2), System.Globalization.NumberStyles.HexNumber);
      }
      return byte.Parse(str);
    }

    /// <summary>
    /// Convenience function to convert a string to a 16-bit short integer.
    /// </summary>
    /// <param name="str">The string to convert.</param>
    /// <returns>The converted 16-bit integer.</returns>
    public static Int16 String2Int16(string str)
    {
      if (str.StartsWith("0x"))
      {
        return Int16.Parse(str.Substring(2), System.Globalization.NumberStyles.HexNumber);
      }
      return Int16.Parse(str);
    }

    /// <summary>
    /// Convenience function to convert a string to a 32-bit integer.
    /// </summary>
    /// <param name="str">The string to convert.</param>
    /// <returns>The converted 32-bit integer.</returns>
    public static Int32 String2Int32(string str)
    {
      if (str.StartsWith("0x"))
      {
        return Int32.Parse(str.Substring(2), System.Globalization.NumberStyles.HexNumber);
      }
      return Int32.Parse(str);
    }

    /// <summary>
    /// Convenience function to convert a string to a 64-bit integer.
    /// </summary>
    /// <param name="str">The string to convert.</param>
    /// <returns>The converted 64-bit integer.</returns>
    public static Int64 String2Int64(string str)
    {
      if (str.StartsWith("0x"))
      {
        return Int64.Parse(str.Substring(2), System.Globalization.NumberStyles.HexNumber);
      }
      return Int64.Parse(str);
    }

    /// <summary>
    /// Convenience function to convert a string with ':' separated bytes to bytes.
    /// </summary>
    /// <param name="str">The string with different bytes separated by ':'</param>
    /// <returns>Array of converted bytes.</returns>
    public static byte[] String2Bytes(string str)
    {
      string[] strSplit = str.Split(':');
      byte[] buffer = new byte[strSplit.Length];
      for (int i = 0; i < strSplit.Length; i++)
      {
        buffer[i] = String2Byte(strSplit[i]);
      }
      return buffer;
    }

    /// <summary>
    /// Convenience function to convert a string with ':' separated bytes to string.
    /// Normally only useful for UTF encoded strings.
    /// </summary>
    /// <param name="str">The string with different bytes separated by ':'</param>
    /// <returns>Array of converted string.</returns>
    public static string String2String(string str)
    {
      string[] strSplit = str.Split(':');
      char[] chars = new char[strSplit.Length];
      for (int i = 0; i < strSplit.Length; i++)
      {
        chars[i] = (char)String2Int16(strSplit[i]);
      }
      return new string(chars);
    }

    /// <summary>
    /// Convenience generic function to compare two arbitrary arrays.
    /// </summary>
    /// <typeparam name="T">The type of the arrays.</typeparam>
    /// <param name="testArray">
    /// The array of type '<typeparamref name="T"/>' to be tested.
    /// </param>
    /// <param name="expectedArray">
    /// The expected array of type '<typeparamref name="T"/>'.
    /// </param>
    public static bool CompareArrays<T>(T[] testArray, T[] expectedArray)
    {
      if (testArray == null)
      {
        return testArray == expectedArray;
      }
      if (expectedArray == null)
      {
        return false;
      }
      if (testArray.Length != expectedArray.Length)
      {
        return false;
      }
      for (int i = 0; i < expectedArray.Length; i++)
      {
        if (!testArray[i].Equals(expectedArray[i]))
        {
          return false;
        }
      }
      return true;
    }

    /// <summary>
    /// Convenience generic function to compare two arbitrary arrays.
    /// Checks if 'expectedArray' is a prefix of 'testArray'
    /// </summary>
    /// <typeparam name="T">The type of the arrays.</typeparam>
    /// <param name="testArray">
    /// The array of type '<typeparamref name="T"/>' to be tested.
    /// </param>
    /// <param name="expectedArray">
    /// The expected array of type '<typeparamref name="T"/>'.
    /// </param>
    public static bool CompareArraysPrefix<T>(T[] testArray, T[] expectedArray)
    {
      if (testArray == null)
      {
        return testArray == expectedArray;
      }
      if (expectedArray == null)
      {
        return false;
      }
      if (testArray.Length < expectedArray.Length)
      {
        return false;
      }
      for (int i = 0; i < expectedArray.Length; i++)
      {
        if (!testArray[i].Equals(expectedArray[i]))
        {
          return false;
        }
      }
      return true;
    }

    /// <summary>
    /// Convenience generic function to compare two arbitrary arrays
    /// using NUnit.
    /// </summary>
    /// <typeparam name="T">The type of the arrays.</typeparam>
    /// <param name="expectedArray">
    /// The expected array of type '<typeparamref name="T"/>'.
    /// </param>
    /// <param name="actualArray">
    /// The actual result array of type '<typeparamref name="T"/>'.
    /// </param>
    public static void CompareTestArrays<T>(T[] expectedArray, T[] actualArray)
    {
      Assert.IsNotNull(expectedArray, "The expectedBytes cannot be null");
      Assert.IsNotNull(actualArray, "The bytes read cannot be null");

      Assert.IsTrue(expectedArray.Length <= actualArray.Length,
        "The length of the resulting bytes is too small.{0}Expected: {1}{2}" +
        "Got: {3}", Environment.NewLine, expectedArray.Length,
        Environment.NewLine, actualArray.Length);
      for (int i = 0; i < expectedArray.Length; i++)
      {
        Assert.AreEqual(expectedArray[i], actualArray[i],
          "Incorrect result in array index {0}.", i);
      }
    }

    /// <summary>
    /// Convenience generic function to compare two arbitrary arrays with
    /// custom comparer using NUnit.
    /// </summary>
    /// <typeparam name="T">The type of the arrays.</typeparam>
    /// <param name="expectedArray">
    /// The expected array of type '<typeparamref name="T"/>'.
    /// </param>
    /// <param name="actualArray">
    /// The actual result array of type '<typeparamref name="T"/>'.
    /// </param>
    public static void CompareTestArrays<T>(T[] expectedArray, T[] actualArray,
      UnitFnMethodR<bool, T, T> compareDelegate)
    {
      Assert.IsNotNull(expectedArray, "The expectedBytes cannot be null");
      Assert.IsNotNull(actualArray, "The bytes read cannot be null");

      Assert.IsTrue(expectedArray.Length <= actualArray.Length,
        "The length of the resulting bytes is too small.{0}Expected: {1}{2}" +
        "Got: {3}", Environment.NewLine, expectedArray.Length,
        Environment.NewLine, actualArray.Length);
      for (int i = 0; i < expectedArray.Length; i++)
      {
        Assert.IsTrue(compareDelegate(expectedArray[i], actualArray[i]),
          "Incorrect result in array index {0}; Expected: {1}, Got: {2}.",
          i, expectedArray[i], actualArray[i]);
      }
    }

    /// <summary>
    /// Get a random double between 0.0 and 1.0.
    /// </summary>
    /// <returns>the random double value</returns>
    public static double Rand()
    {
      return m_rnd.NextDouble();
    }

    /// <summary>
    /// Get a random integer.
    /// </summary>
    /// <param name="max">
    /// The exclusive upper bound of the random integer.
    /// </param>
    /// <returns>the random integer</returns>
    public static int Rand(int max)
    {
      return m_rnd.Next(max);
    }

    /// <summary>
    /// Get a random integer.
    /// </summary>
    /// <param name="min">
    /// The inclusive lower bound of the random integer.
    /// </param>
    /// <param name="max">
    /// The exclusive upper bound of the random integer.
    /// </param>
    /// <returns>the random integer</returns>
    public static int Rand(int min, int max)
    {
      return m_rnd.Next(min, max);
    }

    /// <summary>
    /// Get random bytes in a buffer.
    /// </summary>
    /// <param name="buffer">The buffer to fill with the random bytes.</param>
    public static void RandBytes(byte[] buffer)
    {
      m_rnd.NextBytes(buffer);
    }

    /// <summary>
    /// Get an arrray of random bytes.
    /// </summary>
    /// <param name="size">The size of the random byte array.</param>
    /// <returns>An array containing random bytes.</returns>
    public static byte[] RandBytes(int size)
    {
      if (size > 0)
      {
        byte[] buffer = new byte[size];
        m_rnd.NextBytes(buffer);
        return buffer;
      }
      return null;
    }

    /// <summary>
    /// Get a free ephemeral port.
    /// </summary>
    /// <returns>the free port number</returns>
    public static int GetAvailablePort()
    {
      Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      try
      {
        s.Bind(new IPEndPoint(IPAddress.Any, 0));
        return ((IPEndPoint)s.LocalEndPoint).Port;
      }
      finally
      {
        s.Close();
      }
    }

    /// <summary>
    /// Swap two objects.
    /// </summary>
    /// <typeparam name="Tp">The type of the objects.</typeparam>
    /// <param name="first">The first object.</param>
    /// <param name="second">The second object.</param>
    public static void Swap<Tp>(ref Tp first, ref Tp second)
    {
      Tp tmp = first;
      first = second;
      second = tmp;
    }

    /// <summary>
    /// Start a process with the given path and arguments.
    /// </summary>
    /// <remarks>
    /// This function starts the process using shell (cmd.exe on windows)
    /// when the <see cref="Util.LogDir"/> is not set.
    /// </remarks>
    /// <param name="procPath">The path of the process to start.</param>
    /// <param name="procArgs">The arguments to be passed.</param>
    /// <param name="useShell">Whether to start in the DOS cmd shell.</param>
    /// <param name="startDir">
    /// The starting directory for the process;
    /// pass null to use the current working directory.
    /// </param>
    /// <param name="proc">The created Process as an out parameter.</param>
    /// <returns>Whether the process sucessfully started.</returns>
    public static bool StartProcess(string procPath, string procArgs,
      bool useShell, string startDir, bool redirectStdOut,
      bool redirectStdIn, bool redirectStdErr, out Process proc)
    {
      return StartProcess(procPath, procArgs, useShell, startDir, redirectStdOut,
      redirectStdIn, redirectStdErr, false, out proc);
    }

    public static bool StartProcess(string procPath, string procArgs,
      bool useShell, string startDir, bool redirectStdOut,
      bool redirectStdIn, bool redirectStdErr, bool createNoWindow, out Process proc)
    {
      return StartProcess(procPath, procArgs, useShell, startDir, redirectStdOut,
        redirectStdIn, redirectStdErr, createNoWindow, new Dictionary<string, string>(), out proc);
    }

    public static bool StartProcess(string procPath, string procArgs,
      bool useShell, string startDir, bool redirectStdOut,
      bool redirectStdIn, bool redirectStdErr, bool createNoWindow, IDictionary<string, string> environment, out Process proc)
    {
      ProcessStartInfo pInfo = new ProcessStartInfo(procPath, procArgs);

      // Force launch without a shell. This allows launching FwkClient.exe without a window so tests can run in CI.
      useShell = false;

      foreach(var e in environment)
      {
        pInfo.EnvironmentVariables[e.Key] = e.Value;
      }

      if (!useShell)
      {
        if (m_externalBBServer != null || createNoWindow)
        {
          pInfo.CreateNoWindow = true;
        }
        pInfo.UseShellExecute = false;
      }
      if (startDir != null && startDir.Length > 0)
      {
        pInfo.WorkingDirectory = startDir;
      }
      pInfo.RedirectStandardOutput = redirectStdOut;
      pInfo.RedirectStandardInput = redirectStdIn;
      pInfo.RedirectStandardError = redirectStdErr;
      proc = new Process();
      proc.StartInfo = pInfo;
      return proc.Start();
    }
    #region Methods for client side logging

    /// <summary>
    /// Format the message with time and other stuff appropriate to be
    /// written into the log.
    /// </summary>
    /// <param name="prefix">A string to be prefixed in the log line.</param>
    /// <param name="message">The message to be logged.</param>
    /// <returns></returns>
    public static string GetLogLine(string prefix, string message)
    {
      return GetLogLine(prefix, message, HostName + ':' + PID + ' ' + ThreadID);
    }

    /// <summary>
    /// Format the message with time and other stuff appropriate to be
    /// written into the log.
    /// </summary>
    /// <param name="logLevel">A string representing the logging level.</param>
    /// <param name="message">The message to be logged.</param>
    /// <param name="idString">The ID of process,thread to be logged.</param>
    /// <returns></returns>
    public static string GetLogLine(string prefix, string message, string idString)
    {
      DateTime now = DateTime.Now;

      return '[' + prefix + ' ' + now.ToShortDateString() + ' ' +
        now.Hour.ToString("D02") + ':' + now.Minute.ToString("D02") + ':' +
        now.Second.ToString("D02") + '.' + now.Millisecond.ToString("D03") +
        ' ' + idString + "] " + message + Environment.NewLine;
    }

    /// <summary>
    /// Log a message with the <see cref="CurrentLogLevel" />
    /// logging level on the client side.
    /// </summary>
    /// <param name="message">The message to log.</param>
    public static void Log(string message)
    {
      Log(m_logLevel, message);
    }

    /// <summary>
    /// Log a formatted message with the <see cref="CurrentLogLevel" />
    /// logging level on the client side.
    /// </summary>
    /// <param name="format">The formatted message to log.</param>
    /// <param name="args">
    /// The objects arguments to write into the formatted string.
    /// </param>
    public static void Log(string format, params object[] args)
    {
      Log(m_logLevel, string.Format(format, args));
    }

    /// <summary>
    /// Log a formatted message with the given
    /// logging level on the client side.
    /// </summary>
    /// <param name="logLevel">The required logging level.</param>
    /// <param name="format">The formatted message to log.</param>
    /// <param name="args">
    /// The objects arguments to write into the formatted string.
    /// </param>
    public static void Log(LogLevel logLevel, string format,
      params object[] args)
    {
      Log(logLevel, string.Format(format, args));
    }

    /// <summary>
    /// Log a message with the given
    /// logging level on the client side.
    /// </summary>
    /// <param name="logLevel">The required logging level.</param>
    /// <param name="message">The message to log.</param>
    public static void Log(LogLevel logLevel, string message)
    {
      string logLine = GetLogLine(m_logPrefix + logLevel.ToString(), message);
      RawLog(logLine);
      if (LogOnConsole && LogFile != null)
      {
        Console.WriteLine(logLine);
      }
    }

    /// <summary>
    /// Write the given message to the log file.
    /// </summary>
    /// <param name="message">The message to log.</param>
    public static void RawLog(string message)
    {
      if (m_log == null)
      {
        Console.WriteLine(message);
      }
      else
      {
        lock (m_log)
        {
          m_log.Write(message);
        }
      }
    }

    #endregion

    #region Convenience methods to perform server operations

    /// <summary>
    /// Log a message with the <see cref="CurrentLogLevel"/>
    /// logging level on the main server thread.
    /// </summary>
    /// <param name="message">The message to log.</param>
    public static void ServerLog(string message)
    {
      ServerLog(m_logLevel, message);
    }

    /// <summary>
    /// Log a message with the given
    /// logging level on the main server thread.
    /// </summary>
    /// <param name="logLevel">The required logging level.</param>
    /// <param name="message">The message to log.</param>
    public static void ServerLog(LogLevel logLevel, string message)
    {
      if (DriverComm != null)
      {
        DriverComm.Log(m_clientId, logLevel.ToString(), message);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    /// <summary>
    /// Log a formatted message with the <see cref="CurrentLogLevel"/>
    /// logging level on the main server thread.
    /// </summary>
    /// <param name="format">The formatted message to log.</param>
    /// <param name="args">
    /// The objects arguments to write into the formatted string.
    /// </param>
    public static void ServerLog(string format, params object[] args)
    {
      ServerLog(m_logLevel, format, args);
    }

    /// <summary>
    /// Log a formatted message with the given
    /// logging level on the main server thread.
    /// </summary>
    /// <param name="logLevel">The required logging level.</param>
    /// <param name="format">The formatted message to log.</param>
    /// <param name="args">
    /// The objects arguments to write into the formatted string.
    /// </param>
    public static void ServerLog(LogLevel logLevel, string format,
      params object[] args)
    {
      if (DriverComm != null)
      {
        string message = string.Format(format, args);
        DriverComm.Log(m_clientId, logLevel.ToString(), message);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    /// <summary>
    /// Write an object with a given key and black-board on the server.
    /// Useful for communication between different client processes. There is
    /// intentionally no data protection i.e. any client can (over-)write any
    /// object. The value should be Serializable i.e. the class (and all the
    /// types that it contains) should be marked with [Serializable] attribute.
    /// </summary>
    /// <param name="bbName">The name of the black-board to use.</param>
    /// <param name="key">The key of the object to write.</param>
    /// <param name="value">The value of the object.</param>
    public static void BBSet(string bbName, string key, object value)
    {
      if (BBComm != null)
      {
        BBComm.WriteObject(bbName + '.' + key, value);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    /// <summary>
    /// Read the value of an object for the given key and black-board from the
    /// server. Useful for communication between different client processes.
    /// </summary>
    /// <param name="bbName">The name of the black-board to use.</param>
    /// <param name="key">The key of the object to read.</param>
    /// <returns>The value of the object with the given key.</returns>
    public static object BBGet(string bbName, string key)
    {
      if (BBComm != null)
      {
        return BBComm.ReadObject(bbName + '.' + key);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    /// <summary>
    /// Remove the value of an object for the given key and black-board from the
    /// server. Useful for communication between different client processes.
    /// </summary>
    /// <param name="bbName">The name of the black-board to use.</param>
    /// <param name="key">The key of the object to remove.</param>
    public static void BBRemove(string bbName, string key)
    {
      if (BBComm != null)
      {
        BBComm.RemoveObject(bbName + '.' + key);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    public static int BBAdd(string bbName, string key, int incValue)
    {
      if (BBComm != null)
      {
        return BBComm.AddInt(bbName + '.' + key, incValue);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    public static int BBIncrement(string bbName, string key)
    {
      if (BBComm != null)
      {
        return BBComm.AddOrSetInt(bbName + '.' + key, 1);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    public static int BBDecrement(string bbName, string key)
    {
      if (BBComm != null)
      {
        return BBComm.AddInt(bbName + '.' + key, -1);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    /// <summary>
    /// Signal that the client is up and running, so that the server
    /// can initiate connection to the client.
    /// </summary>
    public static void ClientListening()
    {
      if (DriverComm != null)
      {
        DriverComm.ClientListening(m_clientId);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    /// <summary>
    /// Client callback to request server to start a task on another Windows machine.
    /// </summary>
    /// <param name="clientId">The ID of the client.</param>
    /// <param name="hostName">
    /// The host name where the task is to be started.
    /// </param>
    /// <param name="taskSpec">
    /// A specification of the task to be executed on the new client.
    /// </param>
    /// <returns>Whether the task successfully executed.</returns>
    public static bool RunClientWinTask(string clientId, string hostName, string taskSpec)
    {
      if (DriverComm != null)
      {
        return DriverComm.RunWinTask(clientId, hostName, taskSpec);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    /// <summary>
    /// Client callback to request server to run a shell command on another machine.
    /// </summary>
    /// <param name="clientId">The ID of the client.</param>
    /// <param name="hostName">
    /// The host name where the task is to be started.
    /// </param>
    /// <param name="shellCmd">
    /// Command to be executed using the shell.
    /// </param>
    /// <param name="envVars">
    /// An optional list of environment variables to be set in the shell.
    /// </param>
    /// <returns>The standard output of the task.</returns>
    public static string RunClientShellTask(string clientId, string hostName,
      string shellCmd, Dictionary<string, string> envVars)
    {
      if (DriverComm != null)
      {
        return DriverComm.RunShellTask(clientId, hostName, shellCmd, envVars);
      }
      else
      {
        throw new NoServerConnectionException(NoServerConnMsg);
      }
    }

    #endregion

    /// <summary>
    /// Normalize a given path by finding its full path and
    /// converting to POSIX convention.
    /// </summary>
    /// <param name="path">The path to normalize.</param>
    /// <returns>The normalized path.</returns>
    public static string NormalizePath(string path)
    {
      return Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar).Replace('\\', '/');
    }

    /// <summary>
    /// Delete a share (if any) of the given directory.
    /// </summary>
    /// <param name="directoryPath">
    /// The path of the directory that is shared.
    /// </param>
    public static void DeleteSharePath(string directoryPath)
    {
      directoryPath = directoryPath.Replace(@"\", @"\\");
      using (ManagementObjectSearcher searcher =
        new ManagementObjectSearcher("SELECT Name FROM Win32_Share WHERE Path='" +
        directoryPath + "'"))
      {
        ManagementObjectCollection objColl = searcher.Get();
        foreach (ManagementObject obj in objColl)
        {
          obj.Delete();
          obj.Dispose();
        }
      }
    }

    /// <summary>
    /// Delete a share (if any) of the given name.
    /// </summary>
    /// <param name="shareName">The name of the share.</param>
    public static void DeleteShareName(string shareName)
    {
      using (ManagementObjectSearcher searcher =
        new ManagementObjectSearcher("SELECT Name FROM Win32_Share WHERE Name='" +
        shareName + "'"))
      {
        ManagementObjectCollection objColl = searcher.Get();
        foreach (ManagementObject obj in objColl)
        {
          obj.Delete();
          obj.Dispose();
        }
      }
    }

    /// <summary>
    /// Check whether the given share name already exists.
    /// </summary>
    /// <param name="shareName">The name of the share to check.</param>
    /// <returns>
    /// True if the share name already exists; false otherwise.
    /// </returns>
    public static bool ShareExists(string shareName)
    {
      using (ManagementObjectSearcher searcher =
        new ManagementObjectSearcher("SELECT Name FROM Win32_Share WHERE Name='" +
        shareName + "'"))
      {
        ManagementObjectCollection objColl = searcher.Get();
        return (objColl != null && objColl.Count > 0);
      }
    }

    public static ManagementObject permissionForShareFolder()
    {
      // Create a WMI instance of the principal (Win32_Trustee).
      //Getting the Sid value is not required for Vista.
      NTAccount account = new NTAccount(System.Environment.UserDomainName, System.Environment.UserName);
      SecurityIdentifier sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
      byte[] sidArray = new byte[sid.BinaryLength];
      sid.GetBinaryForm(sidArray, 0);

      ManagementObject Trustee = new ManagementClass(new ManagementPath("Win32_Trustee"), null);
      Trustee["Domain"] = System.Environment.UserDomainName;
      Trustee["Name"] = System.Environment.UserName;
      Trustee["SID"] = sidArray;
      //Create a WMI instance of Win32_Ace, assign the Trustee to this Win32_Ace instance.
      ManagementObject AdminACE = new ManagementClass(new ManagementPath("Win32_Ace"), null);
      AdminACE["AccessMask"] = 2032127;
      AdminACE["AceFlags"] = 3;
      AdminACE["AceType"] = 0;
      AdminACE["Trustee"] = Trustee;

      //To know what values you need to put there, check msdn (link). I actually encourage you to write an enum flag to encapsulate those values.
      //In nut shell, 2032127 is for full access, Access Flags 3 is for non-container and container child objects to inherit the ACE, and Ace Type 0 is to allow the trustee to access it.
      //Create a WMI instance of the security descriptor (Win32_SecurityDescriptor)

      ManagementObject secDescriptor = new ManagementClass(new ManagementPath("Win32_SecurityDescriptor"), null);
      secDescriptor["ControlFlags"] = 4; //SE_DACL_PRESENT
      secDescriptor["DACL"] = new object[] { AdminACE };

      return secDescriptor;
      //Now, create a WMI instance of Win32_Share, and setup the security.

      //        ManagementObject share = new ManagementObject(@"\\ContosoServer\root\cimv2:Win32_Share.Name='JohnShare'");
      //share.InvokeMethod("SetShareInfo", new object[] {Int32.MaxValue, "This is John's share", secDescriptor});
      //Check the return value of the Invoke, the method returns an Object, convert it to Int32. 


    }

    /// <summary>
    /// Make a given directory shared, overwriting previous share (if any).
    /// </summary>
    /// <param name="directoryPath">The directory to be made shared.</param>
    /// <param name="sharePrefix">
    /// The prefix for the share name.
    /// The actual name will be of the form '[prefix][num]' for the first free
    /// share name available for all integer [num] starting from 1.
    /// </param>
    /// <param name="shareDesc">A description for the share.</param>
    /// <returns>The share name of the directory.</returns>
    public static string ShareDirectory(string directoryPath,
      string sharePrefix, string shareDesc)
    {
      string resShareName = null;
      DeleteSharePath(directoryPath);
      using (ManagementClass manage = new ManagementClass("Win32_Share"))
      {
        int num = 1;
        string shareName;
        while (ShareExists((shareName = sharePrefix + num)))
        {
          num++;
        }

        ManagementBaseObject inputArgs = manage.GetMethodParameters("Create");
        inputArgs["Name"] = shareName;
        inputArgs["Path"] = directoryPath;
        inputArgs["Description"] = shareDesc;
        inputArgs["Type"] = 0; // Disk share type
        inputArgs["Access"] = permissionForShareFolder();
        ManagementBaseObject outParams = manage.InvokeMethod("Create", inputArgs, null);
        uint ret = (uint)outParams.Properties["ReturnValue"].Value;
        if (ret == 0)
        {
          resShareName = "//" + IPAddressString + '/' + shareName;
        }
        // Set the correct permissions on the share:
        // full control for the current user while readable for "Everyone"
        //ManagementObject v_theTrusteeObject =
        //  new ManagementClass("Win32_Trustee").CreateInstance();
        //Object[] v_theSID = { 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 };
        //v_theTrusteeObject["Domain"] = null;
        //v_theTrusteeObject["Name"] = "Everyone";
        //v_theTrusteeObject["SID"] = v_theSID;

        //ManagementObject v_theAceObject =
        //  new ManagementClass("Win32_ACE").CreateInstance();
        //v_theAceObject["AccessMask"] = (uint)0xffffffff;
        //v_theAceObject["AceFlags"] = 0x3;
        //v_theAceObject["AceType"] = 0;
        //v_theAceObject["Trustee"] = v_theTrusteeObject;

        //ManagementObject v_theSecDescObject =
        //  new ManagementClass("Win32_SecurityDescriptor").CreateInstance();
        //object[] v_theAceArray = { v_theAceObject };
        //v_theSecDescObject["DACL"] = v_theAceArray;

        //ManagementBaseObject secArgs = manage.GetMethodParameters("SetShareInfo");
        //secArgs["Access"] = v_theSecDescObject;
        //ManagementBaseObject secOut = manage.InvokeMethod("SetShareInfo", secArgs, null);
      }
      return resShareName;
    }

    /// <summary>
    /// Check whether the given hostname belongs to this host
    /// </summary>
    /// <param name="hostName">The hostname to check</param>
    /// <returns>True if the given hostname belongs to this host</returns>
    public static bool IsHostMyself(string hostName)
    {
      if (hostName.ToLower() == "localhost")
      {
        return true;
      }
      // Get the IP addresses associated with the hostName
      IPAddress[] ipAddresses = Dns.GetHostAddresses(hostName);
      if (ipAddresses != null)
      {
        // Check if any of those IP address is of a network interface
        foreach (IPAddress ipAddress in ipAddresses)
        {
          NetworkInterface[] adapters =
            NetworkInterface.GetAllNetworkInterfaces();
          if (adapters != null)
          {
            foreach (NetworkInterface adapter in adapters)
            {
              foreach (UnicastIPAddressInformation ipInfo in
                adapter.GetIPProperties().UnicastAddresses)
              {
                if (ipInfo.Address.Equals(ipAddress))
                {
                  return true;
                }
              }
            }
          }
        }
      }
      return false;
    }

    /// <summary>
    /// Return a string for host type converting to standard string
    /// representation of 3 characters.
    /// </summary>
    /// <param name="hostTypeStr">
    /// A host type string typically obtained by invoking uname
    /// </param>
    public static string GetHostType(string hostTypeStr)
    {
      if (!string.IsNullOrEmpty(hostTypeStr))
      {
        try
        {
          hostTypeStr = hostTypeStr.Trim().Substring(0, 3).ToUpper();
          if (hostTypeStr == "CYG")
          {
            hostTypeStr = "WIN";
          }
        }
        catch (ArgumentOutOfRangeException)
        {
        }
      }
      else 
        hostTypeStr = "WIN";
      return hostTypeStr;
    }

    /// <summary>
    /// Get value of given environment variable accomodating for the
    /// convention of variables like [varname].[system type]
    /// </summary>
    /// <param name="envVar">
    /// The name of the environment variable. If [envVar].[system type]
    /// is set then value of that variable is returned as result else
    /// value of [envVar] is returned.
    /// </param>
    /// <param name="systemType">
    /// A string representing the type of the system.
    /// This is typically obtained using uname command.
    /// </param>
    public static string GetEnvironmentVariable(string envVar,
      string systemType)
    {
      string envVal;
      if (systemType == null)
      {
        systemType = SystemType;
      }
      if (envVar.EndsWith('.' + systemType))
      {
        envVal = Environment.GetEnvironmentVariable(envVar);
        if (envVal == null || envVal.Length == 0)
        {
          envVal = Environment.GetEnvironmentVariable(envVar.Substring(0,
            envVar.Length - systemType.Length - 1));
        }
      }
      else
      {
        envVal = Environment.GetEnvironmentVariable(envVar + '.' + systemType);
        if (envVal == null || envVal.Length == 0)
        {
          envVal = Environment.GetEnvironmentVariable(envVar);
        }
      }
      return envVal;
    }

    /// <summary>
    /// Get value of given environment variable accomodating for the
    /// convention of variables like [varname].[system type]
    /// </summary>
    /// <param name="envVar">
    /// The name of the environment variable. If [envVar].[system type]
    /// is set then value of that variable is returned as result else
    /// value of [envVar] is returned.
    /// </param>
    public static string GetEnvironmentVariable(string envVar)
    {
      return GetEnvironmentVariable(envVar, SystemType);
    }

    /// <summary>
    /// Set given environment variable to given value accomodating for the
    /// convention of variables like [varname].[system type]
    /// </summary>
    /// <param name="envVar">
    /// The name of the environment variable. This can be of the form
    /// [varname].[system type] and if the [system type] matches the type of
    /// this system then just [varname] is also set to the given value else
    /// value of [varname].[my system type] is also set to the given value.
    /// </param>
    /// <param name="envVal">The value of the environment variable</param>
    public static void SetEnvironmentVariable(string envVar, string envVal)
    {
      Environment.SetEnvironmentVariable(envVar, envVal);
      if (envVar.EndsWith('.' + SystemType))
      {
        Environment.SetEnvironmentVariable(envVar.Substring(0,
          envVar.Length - SystemType.Length - 1), envVal);
      }
      else
      {
        string currentVal = Environment.GetEnvironmentVariable(
          envVar + '.' + SystemType);
        if (currentVal == null || currentVal.Length == 0)
        {
          Environment.SetEnvironmentVariable(envVar + '.' + SystemType,
            envVal);
        }
      }
    }

    /// <summary>
    /// Hide the Plink password arg (if any) from the given args.
    /// </summary>
    /// <remarks>
    /// Will not work properly if the password contains space/tab characters.
    /// </remarks>
    /// <param name="sshArgs">The arguments to the plink command.</param>
    /// <returns>The arguments with the password hidden.</returns>
    public static string HideSshPassword(string sshArgs)
    {
      return Regex.Replace(sshArgs, @"-pw[ \t]*[^ \t]*", "-pw [hidden]");
    }


    /// <summary>
    /// Hide the Psexec password arg (if any) from the given args.
    /// </summary>
    /// <remarks>
    /// Will not work properly if the password contains space/tab characters.
    /// </remarks>
    /// <param name="psexecArgs">The arguments to the psexec command.</param>
    /// <returns>The arguments with the password hidden.</returns>
    public static string HidePsexecPassword(string psexecArgs)
    {
      return Regex.Replace(psexecArgs, @" -p [ \t]*[^ \t]*", "-p [hidden]");
    }
    
    /// <summary>
    /// Register a delegate to be invoked when the test on client completes.
    /// </summary>
    /// <param name="deleg">The delegate to be registered.</param>
    public static void RegisterTestCompleteDelegate(UnitFnMethod deleg)
    {
      if (deleg != null)
      {
        m_testCompleteMap[deleg] = true;
      }
    }

    /// <summary>
    /// Get the base log directory for all tests for the given host type.
    /// </summary>
    /// <param name="hostType">The host type of the node.</param>
    /// <returns>
    /// Path of the log directory for the host or "." if environment is not
    /// properly set up.
    /// </returns>
    public static string GetFwkLogDir(string hostType)
    {
      string baseDir = Util.GetEnvironmentVariable("FWK_LOGDIR", hostType);
      if (baseDir != null && baseDir.Length > 0)
      {
        string logDir = Util.NormalizePath(Util.LogFile);
        int logSuffixPos = logDir.IndexOf("/FwkCS_");
        string logSuffix = logDir.Substring(logSuffixPos + 1);
        int logSuffixEndPos = logSuffix.IndexOf('/');
        baseDir = baseDir + '/' + logSuffix.Substring(0, logSuffixEndPos);
      }
      return baseDir;
    }

    /// <summary>
    /// Get the base directory where logs go for the current test for
    /// the given host type.
    /// </summary>
    /// <param name="hostType">The host type of the node.</param>
    /// <returns>
    /// Path of the log directory for the host or "." if environment is not
    /// properly set up.
    /// </returns>
    public static string GetLogBaseDir(string hostType)
    {
      string baseDir = null;
      if (Util.LogFile != null && Util.LogFile.Length > 0)
      {
        string logDir = Util.GetEnvironmentVariable("FWK_LOGDIR", hostType);
        baseDir = Util.NormalizePath(Path.GetDirectoryName(Util.LogFile));
        if (logDir != null && logDir.Length > 0)
        {
          int baseSuffixPos = baseDir.IndexOf("/FwkCS_");
          baseDir = logDir + baseDir.Substring(baseSuffixPos);
        }
      }
      if (baseDir == null || baseDir.Length == 0)
      {
        baseDir = ".";
      }
      return baseDir;
    }

    /// <summary>
    /// Try to copy the given set of files (matching the given pattern) for
    /// source directory to destination directory.
    /// </summary>
    /// <param name="sourceDir">source directory containing the files</param>
    /// <param name="pattern">pattern to match the files</param>
    /// <param name="destDir">destination directory for the files</param>
    /// <param name="recursive">true to do recursive copy</param>
    public static void CopyFiles(string sourceDir, string pattern,
      string destDir, bool recursive)
    {
      try
      {
        SearchOption opt = (recursive ? SearchOption.AllDirectories :
          SearchOption.TopDirectoryOnly);
        sourceDir = NormalizePath(sourceDir);
        string[] files = Directory.GetFiles(sourceDir, pattern, opt);
        for (int index = 0; index < files.Length; ++index)
        {
          try
          {
            string file = NormalizePath(files[index]);
            string fileName = Path.GetFileName(file);
            string dirPath = Util.NormalizePath(destDir + '/' +
              Path.GetDirectoryName(file.Replace(sourceDir, "")));
            if (!Directory.Exists(dirPath))
            {
              Directory.CreateDirectory(dirPath);
            }
            File.Copy(file, dirPath + '/' + fileName);
          }
          catch (Exception)
          {
          }
        }
      }
      catch (Exception)
      {
      }
    }
  }

  /// <summary>
  /// A simple generic reference class that wraps a value type.
  /// </summary>
  /// <typeparam name="T">The type to be wrapped.</typeparam>
  /// <remarks>
  /// This class is useful to make a value type act as a reference type
  /// so that different parts of code can modify same value.
  /// </remarks>
  public class RefValue<T>
    where T : struct
  {
    public T Value
    {
      get
      {
        return m_value;
      }
      set
      {
        m_value = value;
      }
    }

    public RefValue(T value)
    {
      m_value = value;
    }

    public override bool Equals(object obj)
    {
      return m_value.Equals(obj);
    }

    public override int GetHashCode()
    {
      return m_value.GetHashCode();
    }

    public override string ToString()
    {
      return m_value.ToString();
    }

    public static implicit operator T(RefValue<T> value)
    {
      return value.m_value;
    }

    public static explicit operator RefValue<T>(T value)
    {
      return new RefValue<T>(value);
    }

    private T m_value;
  }

  public class PaceMeter
  {
    private DateTime m_endTime;
    private int m_current;
    private TimeSpan m_waitTime;
    private int m_opsLimit;

    public PaceMeter(int ops)
      : this(ops, 1)
    {
    }

    public PaceMeter(int ops, int seconds)
    {
      m_current = 0;
      m_opsLimit = 0;
      if (ops > 0 && seconds > 0)
      {
        if (ops < seconds) // This is the "seconds per op" case
        {
          m_opsLimit = ops;
          m_waitTime = TimeSpan.FromSeconds(seconds);
        }
        else
        {
          int opsSec = ops / seconds;
          if (opsSec > 4) // Use sample interval of 0.25 secs
          {
            m_opsLimit = opsSec / 4;
            m_waitTime = TimeSpan.FromSeconds(0.25);
          }
          else // Use sample interval of 1.0 secs
          {
            m_opsLimit = opsSec;
            m_waitTime = TimeSpan.FromSeconds(1.0);
          }
        }
        ResetCurrent(DateTime.Now);
      }
    }

    public void CheckPace()
    {
      if (m_opsLimit > 0)
      {
        if (++m_current >= m_opsLimit)
        {
          DateTime now = DateTime.Now;
          if (m_endTime > now)
          {
            TimeSpan sleepSpan = m_endTime - now;
            //Util.Log("PaceMeter:CheckPace:: Expected to end at: {0}:{1}:{2}.{3}, sleep span: {4}",
            //  m_endTime.Hour.ToString("D02"), m_endTime.Minute.ToString("D02"),
            //  m_endTime.Second.ToString("D02"), m_endTime.Millisecond.ToString("D03"), sleepSpan);
            Thread.Sleep(sleepSpan);
          }
          ResetCurrent(now);
        }
      }
    }

    private void ResetCurrent(DateTime now)
    {
      m_current = 0;
      m_endTime = now + m_waitTime;
    }
  }
}
