using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using CommonLib.Misc;

namespace All_Purpose_Auto_Setup
{
   class WindowFeaturesManager
   {
      List<string> iisFeatures = new List<string>();

      // An event that can be raised, allowing other classes to
      // subscribe to it and do what they like with the message.
      public event Action<string, string, List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>> UpdateStatusDisplayAndLogEvent;

      public event Action<string, int, List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>> UpdateStatusDisplayAtLine;

      // event for thread to notify other threads that it has exited
      private AutoResetEvent _resetEvent = new AutoResetEvent(false);

      public ConfigFileManager.Ini_KeyValue<string> windowsFeatureListFile = new ConfigFileManager.Ini_KeyValue<string>("");

      Form m_parentForm;

      List<string> iisFeaturesToBeInstalled = new List<string>();
      private bool windowFeatureInstalledSuccessful = false;
      private bool needElevatedPermission = false;
      private bool otherErrorOccurred = false;
      private string dismErrorMsg = "";
      private int windowFeatureState = 1;
      private string currentWindowFeature;

      int m_progressLine = 0;

      public WindowFeaturesManager(Form parentForm)
      {
         m_parentForm = parentForm;
      }

      public bool enableWindowsOptionalFeatures(string sectionName)
      {
         bool setupSuccessful = true;

         string msg;
         string indentation = String.Empty;
         int featureInstalledCount = 0;

         currentWindowFeature = String.Empty;

         CommonLib.Windows.Forms.TextFormat.TextFontAndColor defaultTextProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         CommonLib.Windows.Forms.TextFormat.TextFontAndColor textProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         BackgroundWorker bgwInformUserToWait;

         bgwInformUserToWait = new BackgroundWorker();
         bgwInformUserToWait.WorkerSupportsCancellation = true;
         bgwInformUserToWait.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgwInformUserToWait_RunWorkerCompleted);
         bgwInformUserToWait.DoWork += new DoWorkEventHandler(bgwInformUserToWait_DoWork);

         ProcessStartInfo cmdStartInfo = new ProcessStartInfo();
         cmdStartInfo.FileName = @"C:\Windows\System32\cmd.exe";
         cmdStartInfo.RedirectStandardOutput = true;
         cmdStartInfo.RedirectStandardError = true;
         cmdStartInfo.RedirectStandardInput = true;
         cmdStartInfo.UseShellExecute = false;
         cmdStartInfo.CreateNoWindow = true;

         Process cmdProcess;
         //cmdProcess.StandardInput.WriteLine("Dism /online /disable-feature /featurename:IIS-FTPSvc /norestart");     //Execute command

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = Common.getSetupStepTitleColor();
         textProp.wordIndex = 0;
         textProp.wordCount = 100;
         textPropList.Add(textProp);

         DateTime dat1 = DateTime.Now;
         msg = "\n\n<-------------------Date and Time: " + dat1.ToString(@"MM/dd/yyyy     hh:mm:ss tt", System.Globalization.CultureInfo.InvariantCulture) + "------------------->";
         UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = Common.getSetupStepTitleColor();
         textProp.wordIndex = 0;
         textProp.wordCount = 100;
         // set font size
         textProp.textFont = new Font(textProp.textFont.Name, 12);
         // set font style
         textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
         textPropList.Add(textProp);

         msg = "\n-=Enabling Windows Features=-";
         UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

         parseConfigInfo(sectionName);

         setupSuccessful = verifyConfigInfo();

         frmSetupStatusDisplay displayFrm = (frmSetupStatusDisplay)m_parentForm;
         if (setupSuccessful)
         {
            getWindowsFeatures(iisFeatures);

            m_progressLine = displayFrm.updateStatusDisplayAndLog("\n  ", "", textPropList);
            if (!bgwInformUserToWait.IsBusy)
            {
               indentation = Common.getIndentation(1);
               bgwInformUserToWait.RunWorkerAsync(indentation + "Gathering information about windows features to be installed. Please wait");
            }

            windowFeatureState = 1;

            cmdProcess = new Process();
            cmdProcess.StartInfo = cmdStartInfo;
            cmdProcess.ErrorDataReceived += cmd_Error;
            cmdProcess.OutputDataReceived += cmd_WindowFeature_DataReceived;
            cmdProcess.EnableRaisingEvents = true;
            cmdProcess.Start();
            cmdProcess.BeginOutputReadLine();
            cmdProcess.BeginErrorReadLine();

            string system32Directory = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "sysnative", "dism.exe");
            cmdProcess.StandardInput.WriteLine(system32Directory + " /online /get-features");     //Execute command

            cmdProcess.StandardInput.WriteLine("exit");                  //Execute exit.

            cmdProcess.WaitForExit();
            cmdProcess.Close();

            if (!bgwInformUserToWait.CancellationPending && bgwInformUserToWait.IsBusy)
            {
               bgwInformUserToWait.CancelAsync();
               _resetEvent.WaitOne();
               _resetEvent.Reset();
            }

            windowFeatureState++;

            displayFrm.removeStatusDisplayLine(m_progressLine, true);
            if (needElevatedPermission)
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
               textProp.wordIndex = 0;
               textProp.wordCount = 2;
               // set font style
               textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
               textPropList.Add(textProp);

               indentation = Common.getIndentation(1);
               msg = "\n" + indentation + "- FAILED. Need administrator privilege to access Windows Features.";
               UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
               setupSuccessful = false;
            }
            else if (otherErrorOccurred)
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
               textProp.wordIndex = 0;
               textProp.wordCount = 2;
               // set font style
               textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
               textPropList.Add(textProp);

               indentation = Common.getIndentation(1);
               msg = "\n" + indentation + "- FAILED. " + dismErrorMsg;
               UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
               setupSuccessful = false;
            }
            else if (iisFeaturesToBeInstalled.Count == 0)
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.SYSTEM_SKIP);
               textProp.wordIndex = 0;
               textProp.wordCount = 2;
               // set font style
               textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
               textPropList.Add(textProp);

               indentation = Common.getIndentation(1);
               msg = "\n" + indentation + "- SKIPPED. All necessary windows features are already enabled.";
               UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
            }
            else
            {
               m_progressLine = displayFrm.updateStatusDisplayAndLog("\n  ", "", textPropList);
               int featureCountLine = m_progressLine;
               foreach (string feature in iisFeaturesToBeInstalled)
               {
                  indentation = Common.getIndentation(1);
                  msg = indentation + "Features enabled: " + featureInstalledCount.ToString() + " / " + iisFeaturesToBeInstalled.Count.ToString();

                  // set font to default text and color
                  textProp.textFont = defaultTextProp.textFont;
                  textProp.textColor = Color.FromArgb(5, 112, 1);
                  textProp.wordIndex = 0;
                  textProp.wordCount = 2;
                  // set font style
                  textPropList.Add(textProp);

                  // set font to default text and color
                  textProp.textFont = defaultTextProp.textFont;
                  textProp.textColor = Color.FromArgb(5, 112, 1);
                  textProp.wordIndex = 2;
                  textProp.wordCount = 10;
                  // set font style
                  textProp.textFont = new Font(textProp.textFont.FontFamily, 10, FontStyle.Bold);
                  textPropList.Add(textProp);

                  UpdateStatusDisplayAtLine(msg, featureCountLine, textPropList);

                  m_progressLine = displayFrm.updateStatusDisplayAndLog("\n  ", "", textPropList);
                  if (!bgwInformUserToWait.IsBusy)
                  {
                     indentation = Common.getIndentation(1);
                     bgwInformUserToWait.RunWorkerAsync(indentation + "Enabling " + feature + ". Please wait");
                  }

                  windowFeatureInstalledSuccessful = false;

                  dismErrorMsg = "";
                  cmdProcess = new Process();
                  cmdProcess.StartInfo = cmdStartInfo;
                  cmdProcess.ErrorDataReceived += cmd_Error;
                  cmdProcess.OutputDataReceived += cmd_WindowFeature_DataReceived;
                  cmdProcess.EnableRaisingEvents = true;
                  cmdProcess.Start();
                  cmdProcess.BeginOutputReadLine();
                  cmdProcess.BeginErrorReadLine();

                  cmdProcess.StandardInput.WriteLine(system32Directory + " /online /enable-feature /featurename:" + feature + " /all /norestart");     //Execute command
                  cmdProcess.StandardInput.WriteLine("exit");                  //Execute exit.

                  cmdProcess.WaitForExit();
                  cmdProcess.Close();

                  if (!bgwInformUserToWait.CancellationPending && bgwInformUserToWait.IsBusy)
                  {
                     bgwInformUserToWait.CancelAsync();
                     _resetEvent.WaitOne();
                     _resetEvent.Reset();
                  }

                  displayFrm.removeStatusDisplayLine(m_progressLine, true);

                  if (!windowFeatureInstalledSuccessful)
                  {
                     // set font to default text and color
                     textProp.textFont = defaultTextProp.textFont;
                     textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
                     textProp.wordIndex = 0;
                     textProp.wordCount = 2;
                     // set font style
                     textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
                     textPropList.Add(textProp);

                     msg = "\n" + Common.getIndentation(1) + "- FAILED. Failed enabling " + feature + "." + dismErrorMsg;
                     setupSuccessful = false;
                     UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
                     break;
                  }
                  else
                  {
                     featureInstalledCount++;
                     msg = indentation + "Features enabled: " + featureInstalledCount.ToString() + " / " + iisFeaturesToBeInstalled.Count.ToString();

                     // set font to default text and color
                     textProp.textFont = defaultTextProp.textFont;
                     textProp.textColor = Color.FromArgb(5, 112, 1);
                     textProp.wordIndex = 0;
                     textProp.wordCount = 2;
                     // set font style
                     textPropList.Add(textProp);

                     // set font to default text and color
                     textProp.textFont = defaultTextProp.textFont;
                     textProp.textColor = Color.FromArgb(5, 112, 1);
                     textProp.wordIndex = 2;
                     textProp.wordCount = 10;
                     // set font style
                     textProp.textFont = new Font(textProp.textFont.FontFamily, 10, FontStyle.Bold);
                     textPropList.Add(textProp);

                     UpdateStatusDisplayAtLine(msg, featureCountLine, textPropList);
                     msg = "\n" + Common.getIndentation(1) + "Enabling " + feature + " - SUCCESS";
                     UpdateStatusDisplayAndLogEvent("", msg, textPropList);
                  }
               }

               if (setupSuccessful)
               {
                  // set font to default text and color
                  textProp.textFont = defaultTextProp.textFont;
                  textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.SUCCESS);
                  textProp.wordIndex = 0;
                  textProp.wordCount = 2;
                  // set font style
                  textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
                  textPropList.Add(textProp);

                  msg = "\n" + Common.getIndentation(1) + "- SUCCESS";
                  UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
               }
            }
         }

         return setupSuccessful;
      }

      void getWindowsFeatures(List<string> featureList)
      {
         string line;
         StreamReader file = new StreamReader(windowsFeatureListFile.iniValue);

         while ((line = file.ReadLine()) != null)
         {
            line = line.Trim();
            if (line.Length > 0)
               iisFeatures.Add(line);
         }

         file.Close();
      }

      private void bgwInformUserToWait_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
      {
         // First, handle the case where an exception was thrown. 
         if (e.Error != null)
         {

         }
         else if (e.Cancelled)  // user cancelled
         {

         }
         else // thread completed on its own
         {

         }
         _resetEvent.Set();
      }

      private void bgwInformUserToWait_DoWork(object sender, DoWorkEventArgs e)
      {
         BackgroundWorker bgw = sender as BackgroundWorker;

         CommonLib.Windows.Forms.TextFormat.TextFontAndColor defaultTextProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         CommonLib.Windows.Forms.TextFormat.TextFontAndColor textProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         string text = (string)e.Argument;
         string dots = "";

         while (true)
         {
            if (bgw != null && bgw.CancellationPending)
            {
               if (e != null)
                  e.Cancel = true;

               break;
            }

            if (dots.Length < 3)
               dots += ".";
            else
               dots = ".";

            if (StringManip.GetPhraseWordIndexInText(text, "enabling") > -1)
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Color.FromArgb(157, 150, 19);
               textProp.wordIndex = StringManip.GetPhraseWordIndexInText(text, "enabling");
               textProp.wordCount = 1;
               // set font style
               textProp.textFont = new Font(textProp.textFont.FontFamily, 11, FontStyle.Bold);
               textPropList.Add(textProp);

               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Color.FromArgb(42, 176, 173);
               textProp.wordIndex = StringManip.GetPhraseWordIndexInText(text, "enabling") + 1;
               textProp.wordCount = StringManip.GetPhraseWordIndexInText(text, "please wait") - textProp.wordIndex;
               // set font style
               textProp.textFont = new Font(textProp.textFont.FontFamily, 11, FontStyle.Bold);
               textPropList.Add(textProp);

               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Color.FromArgb(157, 150, 19);
               textProp.wordIndex = StringManip.GetPhraseWordIndexInText(text, "please wait");
               textProp.wordCount = 10;
               // set font style
               textProp.textFont = new Font(textProp.textFont.FontFamily, 11, FontStyle.Bold);
               textPropList.Add(textProp);
            }
            else
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Color.FromArgb(17, 75, 237);
               textProp.wordIndex = 0;
               textProp.wordCount = 100;
               // set font style
               textProp.textFont = new Font(textProp.textFont.FontFamily, 13, FontStyle.Bold);
               textPropList.Add(textProp);
            }

            UpdateStatusDisplayAtLine(text + dots, m_progressLine, textPropList);

            Thread.Sleep(500);
         }
      }

      void cmd_WindowFeature_DataReceived(object sender, DataReceivedEventArgs e)
      {
         Match regExMatch;
         string result;

         if (e.Data == null)
         {
            return;
         }

         if (windowFeatureState == 1)
         {
            regExMatch = Regex.Match(e.Data, @"error: 740", RegexOptions.IgnoreCase);
            if (regExMatch.Success)
            {
               needElevatedPermission = true;
            }

            if (otherErrorOccurred && e.Data.Length > 0)
            {
               dismErrorMsg += ". " + e.Data;
            }

            regExMatch = Regex.Match(e.Data, @"error: \d+", RegexOptions.IgnoreCase);
            if (regExMatch.Success)
            {
               otherErrorOccurred = true;
               dismErrorMsg += e.Data;
            }

            regExMatch = Regex.Match(e.Data, @"feature name\s+:\s+(.+)$", RegexOptions.IgnoreCase);
            if (regExMatch.Success)
            {
               currentWindowFeature = regExMatch.Groups[1].Value;
            }

            regExMatch = Regex.Match(e.Data, @"state\s+:\s+disabled", RegexOptions.IgnoreCase);
            if (regExMatch.Success)
            {
               result = iisFeatures.Find(item => item == currentWindowFeature);

               if (result != null && result.Length > 0)
               {
                  iisFeaturesToBeInstalled.Add(result);
               }
            }
         }
         else
         {
            if (otherErrorOccurred && e.Data.Length > 0)
            {
               dismErrorMsg += ". " + e.Data;
            }

            regExMatch = Regex.Match(e.Data, @"error: \d+", RegexOptions.IgnoreCase);
            if (regExMatch.Success)
            {
               otherErrorOccurred = true;
               dismErrorMsg += e.Data;
            }

            regExMatch = Regex.Match(e.Data, @".*successfully.*", RegexOptions.IgnoreCase);
            if (regExMatch.Success)
            {
               windowFeatureInstalledSuccessful = true;
            }
         }
      }

      void cmd_Error(object sender, DataReceivedEventArgs e)
      {
         //Console.WriteLine("Error from other process");
         //Console.WriteLine(e.Data);
      }

      private void parseConfigInfo(string sectionName)
      {
         windowsFeatureListFile.iniSectionName = sectionName;
         windowsFeatureListFile.iniKeyName = "windows_feature_list_file";
         windowsFeatureListFile.iniValue = ConfigFileManager.ms_configGeneralInfo.iniFile.ReadValue(sectionName, windowsFeatureListFile.iniKeyName);
      }

      bool verifyConfigInfo()
      {
         bool setupSuccessful = true;
         ConfigFileManager.Ini_KeyValue<string> iniVal = new ConfigFileManager.Ini_KeyValue<string>("");
         string indentation = String.Empty;
         string msg, tempPath;

         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         if (windowsFeatureListFile.iniValue.Length > 0)
         {
            tempPath = Path.GetFullPath(windowsFeatureListFile.iniValue);

            if (!File.Exists(tempPath))
            {
               indentation = Common.getIndentation(1);
               msg = "\n" + indentation + "- FAILED. "
                     + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                     + "\n" + indentation + "Section: " + windowsFeatureListFile.iniSectionName
                     + "\n" + indentation + "Key: " + windowsFeatureListFile.iniKeyName
                     + "\n" + indentation + "Value: " + windowsFeatureListFile.iniValue
                     + "\n" + indentation + "Error Description: The path defined in the value above is invalid. Translated path " + tempPath;

               Common.formatConfigIniFailureMessage(msg, textPropList);

               UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
               setupSuccessful = false;
            }
            else
               windowsFeatureListFile.iniValue = tempPath;
         }

         return setupSuccessful;
      }
   }
}
