using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using OtherAssembly;

using White.Core;
using White.Core.Configuration;
using White.Core.UIItems;
using White.Core.UIItems.WindowItems;

using Debug = System.Diagnostics.Debug;

namespace UnitTests
{
    [TestClass]
    public class UnitTest1 : IWaitHook
    {
        private Application application;

        private Window mainWindow;

        [TestInitialize]
        public void TestInitialize()
        {
            application = Launch();
            mainWindow = GetTestHarnessWindowViaWhiteApi(application);
        }

        [TestCleanup]
        public void TestCleanup()
        {
            CloseAndAssertTestHarnessIsNotRunning();
            application.Dispose();
        }

        [TestMethod]
        public void AdderUXRunner()
        {
            EventSynch.WaitOne(2000);

            var one = mainWindow.Get<TextBox>("One");
            var two = mainWindow.Get<TextBox>("Two");
            var total = mainWindow.Get<TextBox>("Total");

            var add = mainWindow.Get<Button>("Add");

            one.Text = "1";
            two.Text = "2";

            add.Click();
            EventSynch.WaitOne(1000);

            Assert.AreEqual("3", total.Text);
        }

        protected Application Launch()
        {
            var processStartInfo = GetProcessStartInfo();
            Application result = null;
            try
            {
                result = Application.Launch(processStartInfo);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }

            return result;
        }

        protected Window GetTestHarnessWindowViaWhiteApi(Application manager)
        {
            // basic get window via White API logic
            Assert.IsNotNull(manager, "Failed to launch TestHarness.exe");

            Window mainWindow = null;
            int retryCounter = 0;
            while (true)
            {
                try
                {
                    mainWindow = manager.GetWindow("MainWindow");
                    break;
                }
                catch
                {
                    if (retryCounter == 5)
                    {
                        break;
                    }
                    else
                    {
                        retryCounter++;
                        Thread.Sleep(1000);
                    }
                }
            }

            Assert.IsNotNull(mainWindow, "Failed to find 'MainWindow'");
            CoreAppXmlConfiguration.Instance.AdditionalWaitHook = this;
            return mainWindow;
        }

        private ProcessStartInfo GetProcessStartInfo()
        {
            var processStartInfo = new ProcessStartInfo
            {
                WorkingDirectory = GetWorkingDirectory(),
                FileName = Executable,
                UseShellExecute = true,
                CreateNoWindow = false
            };
            return processStartInfo;
        }

        private string GetWorkingDirectory()
        {
            string codeBase = Assembly.GetExecutingAssembly().CodeBase;
            UriBuilder uri = new UriBuilder(codeBase);
            string path = Uri.UnescapeDataString(uri.Path);
            var directoryName = Path.GetDirectoryName(path);
            return directoryName;
        }

        public void WaitFor(UIItemContainer itemContainer)
        {
            var maxWait = TimeSpan.FromSeconds(5);
            var start = DateTime.Now;
            Console.WriteLine("Checking condition {0} for {1} state {2}", this.waitCondition.Method, itemContainer, this.waitCondition());
            if (!this.waitCondition())
            {
                Console.WriteLine("Condition false.");
                while (!this.waitCondition() && DateTime.Now - start < maxWait)
                {
                    Thread.Sleep(100);
                }

                Console.WriteLine("After Wait {0}.", WaitCondition);
                this.waitCondition = () => true;
                this.EventSynch.Set();
            }
        }

        protected readonly AutoResetEvent EventSynch = new AutoResetEvent(false);

        private Func<bool> waitCondition = () => true;

        protected Func<bool> WaitCondition
        {
            get
            {
                return this.waitCondition;
            }

            set
            {
                if (!value())
                {
                    Console.WriteLine("Wait condition false, storing delegate");
                    this.waitCondition = value;
                }
                else
                {
                    this.waitCondition = () => true;
                    Console.WriteLine("Wait condition already true, setting eventsynch");
                    this.EventSynch.Set();
                }
            }
        }


        protected void CloseAndAssertTestHarnessIsNotRunning()
        {
            if (CheckIfTestHarnessProcessIsRunning())
            {
                KillTestHarnessProcessAndLogResults();
            }

            var stillRunning = CheckIfTestHarnessProcessIsRunning();
            Assert.IsFalse(stillRunning, "Process {0} still found to be running even after attempts to close it", Executable);
        }

        private static bool CheckIfTestHarnessProcessIsRunning()
        {
            if (Process.GetProcessesByName(ProcessName).Length > 0)
            {
                // give the harness a chance to close
                Thread.Sleep(1000);
            }

            return Process.GetProcessesByName(ProcessName).Length > 0;
        }

        private static void KillTestHarnessProcessAndLogResults()
        {
            var sb = new StringBuilder();
            try
            {
                Process[] processes = Process.GetProcessesByName(ProcessName);
                if (processes.Length > 0)
                {
                    sb.AppendLine(string.Format("Process {0} is running {1} instances", ProcessName, processes.Length));
                    foreach (var process in processes)
                    {
                        sb.AppendLine("Attempting to kill a process instance now");
                        process.Kill();
                    }
                }
            }
            catch (Exception e)
            {
                sb.AppendLine();
                sb.AppendLine("Exception in CloseAndAssertTestHarnessIsNotRunning");
                sb.AppendLine(e.ToString());
            }
            finally
            {
                Console.WriteLine(sb.ToString());
            }
        }

        private static string ProcessName
        {
            get
            {
                return "TestCodeCoverage";
            }
        }

        protected virtual string Executable
        {
            get
            {
                return "TestCodeCoverage.exe";
            }
        }
    }
}
