//#define Debug

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.IO;
using System.Diagnostics;

namespace Disco.ClientBootstrapper
{
    class BootstrapperLoop
    {

        public Thread LoopThread;
        public delegate void LoopCompleteCallback();
        private LoopCompleteCallback mLoopCompleteCallback;
        private IStatus statusUI;
        private string tempWorkingDirectory;
        private StringBuilder errorMessage;
        private Process clientProcess;

        public BootstrapperLoop(IStatus StatusUI, LoopCompleteCallback Callback)
        {
            this.statusUI = StatusUI;
            this.mLoopCompleteCallback = Callback;
            this.errorMessage = new StringBuilder();
        }

        public void Start()
        {
            this.LoopThread = new Thread(new ThreadStart(loopHost));
            this.LoopThread.Start();
        }

        private void loopHost()
        {
            try
            {
                loop();
            }
            catch (Exception ex)
            {
                if (ex.GetType() == typeof(ThreadAbortException))
                    return;
                if (ex.GetType() == typeof(ThreadInterruptedException))
                    return;
                Program.WriteAppError(ex);
                throw;
            }
        }

        private void loop()
        {

#if Debug
            statusUI.UpdateStatus("Waiting for Debugger", "Please wait...", true, -1);
            try
            {
                do
                {
                    System.Threading.Thread.Sleep(10);
                } while (!System.Diagnostics.Debugger.IsAttached);
            }
            catch (Exception ex)
            {
                statusUI.UpdateStatus("Error", ex.Message, true, -1);
                return;
            }
#else
            statusUI.UpdateStatus("System Preparation (Bootstrapper)", "Starting", "Please wait...", true, -1);
#endif

            tempWorkingDirectory = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco\\Temp");
            if (!Directory.Exists(tempWorkingDirectory))
                Directory.CreateDirectory(tempWorkingDirectory);

            // Check for Network Connectivity
            statusUI.UpdateStatus(null, "Detecting Network", "Checking network connectivity, Please wait...", true, -1);
            if (!Interop.NetworkInterop.PingDisco())
            {
                statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1);
                statusUI_WriteAdapterInfo();

                if (!Interop.NetworkInterop.PingDisco())
                {
                    // Check for Wireless
                    var hasWireless = (Interop.NetworkInterop.NetworkAdapters.Count(na => na.IsWireless) > 0);
                    if (hasWireless)
                    {
                        // True: Do wireless loop
                        statusUI.UpdateStatus(null, "Configuring Wireless Network", "Wireless adapter detected, Configuring...", true, -1);
                        Interop.NetworkInterop.ConfigureWireless();
                        statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0);
                        for (int i = 0; i < 100; i++)
                        {
                            statusUI_WriteAdapterInfo();
                            statusUI.UpdateStatus(null, null, null, true, i);
                            Program.SleepThread(500, false);
                            if (Interop.NetworkInterop.PingDisco())
                                break;
                        }
                        if (!Interop.NetworkInterop.PingDisco())
                        {
                            statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false);
                            Program.SleepThread(3000, false);
                        }
                    }

                    if (!Interop.NetworkInterop.PingDisco())
                    {
                        // Instruct user to connect network cable
                        statusUI.UpdateStatus(null, "Please connect the network cable", null);
                        for (int i = 0; i < 100; i++)
                        {
                            statusUI_WriteAdapterInfo();
                            statusUI.UpdateStatus(null, null, null, true, i);
                            Program.SleepThread(500, false);
                            if (Interop.NetworkInterop.PingDisco())
                                break;
                        }
                    }
                }    
                
                if (!Interop.NetworkInterop.PingDisco())
                {
                    // Client Failed
                    if (this.mLoopCompleteCallback != null)
                    {
                        this.mLoopCompleteCallback.BeginInvoke(null, null);
                    }
                    return;
                }
            }

            // Download Client
            statusUI.UpdateStatus(null, "Downloading", "Retrieving Preparation Client, Please wait...", true, -1);
            string clientSourceLocation = Path.Combine(tempWorkingDirectory, "PreparationClient.zip");
            using (var webClient = new WebClient())
            {
                // Don't use a proxy when downloading the Client
                webClient.Proxy = new WebProxy();

                webClient.DownloadFile("http://disco:9292/Services/Client/PreparationClient", clientSourceLocation);
            }

            // Unzip Client
            statusUI.UpdateStatus(null, "Extracting", "Retrieving Preparation Client, Please wait...", true, -1);
            string clientLocation = Path.Combine(tempWorkingDirectory, "PreparationClient");
            if (!Directory.Exists(clientLocation))
                Directory.CreateDirectory(clientLocation);
            using (var clientSource = Ionic.Zip.ZipFile.Read(clientSourceLocation))
            {
                clientSource.ExtractAll(clientLocation, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently);
            }

            // Launch Client
            statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1);
            ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"))
            {
                WorkingDirectory = clientLocation,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                UseShellExecute = false,
            };
            using (clientProcess = Process.Start(clientProcessStart))
            {
                // Read StdOutput until End
                try
                {
                    clientProcess.OutputDataReceived += new DataReceivedEventHandler(clientProcess_OutputDataReceived);
                    clientProcess.BeginOutputReadLine();
                    clientProcess.WaitForExit();
                }
                catch (Exception) { throw; }
                finally
                {
                    try { clientProcess.CloseMainWindow(); }
                    catch (Exception) { }
                }
            }
            clientProcess = null;

            // Cleanup
            if (Directory.Exists(tempWorkingDirectory))
                Directory.Delete(tempWorkingDirectory, true);
            Interop.CertificateInterop.RemoveTempCerts();

            // Pause if Error
            if (this.errorMessage.Length > 0)
            {
                Program.SleepThread(10000, true);
            }

            // End Of Loop
            if (this.mLoopCompleteCallback != null)
            {
                this.mLoopCompleteCallback.BeginInvoke(null, null);
            }
        }

        void statusUI_WriteAdapterInfo()
        {

            var info = new StringBuilder();
            foreach (var na in Interop.NetworkInterop.NetworkAdapters)
            {
                if (na.IsWireless)
                {
                    info.AppendLine(string.Format("{0}: {1}", na.NetConnectionID, na.WirelessConnectionStatusMeaning(na.WirelessConnectionStatus)));
                }
                else
                {
                    info.AppendLine(string.Format("{0}: {1}", na.NetConnectionID, na.ConnectionStatusMeaning(na.ConnectionStatus)));
                }
            }
            statusUI.UpdateStatus(null, null, info.ToString());

        }

        void clientProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (!string.IsNullOrWhiteSpace(e.Data))
            {
                System.Diagnostics.Debug.WriteLine(string.Format("OUTPUT: {0}", e.Data));
                var data = e.Data.Substring(1).Split(new char[] { ',' });
                switch (e.Data[0])
                {
                    case '#':
                        if (data.Length == 4)
                        {
                            statusUI.UpdateStatus(null, data[0].Replace("{comma}", ","), data[1].Replace("{comma}", ",").Replace("{newline}", Environment.NewLine), bool.Parse(data[2]), int.Parse(data[3]));
                        }
                        break;
                    case '!':
                        Program.PostBootstrapperActions = new List<string>(data);
                        break;
                }
            }
        }

        //void clientProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        //{
        //    if (!string.IsNullOrEmpty(e.Data))
        //    {
        //        System.Diagnostics.Debug.WriteLine(string.Format("ERROR: {0}", e.Data));
        //        this.errorMessage.AppendLine(e.Data);
        //        statusUI.UpdateStatus(null, "An Error Occurred", this.errorMessage.ToString(), false);
        //    }
        //}

    }
}
