using NUnit.Framework;
using OpenQA.Selenium.Environment;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace OpenQA.Selenium
{
    [TestFixture]
    public class WindowSwitchingTest : DriverTestFixture
    {
        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        public void ShouldSwitchFocusToANewWindowWhenItIsOpenedAndNotStopFutureOperations()
        {
            driver.Url = xhtmlTestPage;
            String current = driver.CurrentWindowHandle;

            driver.FindElement(By.LinkText("Open new window")).Click();
            Assert.AreEqual("XHTML Test Page", driver.Title);

            WaitFor(WindowCountToBe(2), "Window count was not 2");
            WaitFor(WindowWithName("result"), "Could not find window with name 'result'");
            WaitFor(() => { return driver.Title == "We Arrive Here"; }, "Browser title was not 'We Arrive Here'");
            Assert.AreEqual("We Arrive Here", driver.Title);

            driver.Url = iframesPage;
            string handle = driver.CurrentWindowHandle;
            driver.FindElement(By.Id("iframe_page_heading"));
            driver.SwitchTo().Frame("iframe1");
            Assert.AreEqual(driver.CurrentWindowHandle, handle);
            driver.SwitchTo().DefaultContent();
            driver.Close();

            driver.SwitchTo().Window(current);
            //Assert.AreEqual("XHTML Test Page", driver.Title);
        }

        [Test]
        public void ShouldThrowNoSuchWindowException()
        {
            driver.Url = xhtmlTestPage;
            String current = driver.CurrentWindowHandle;
            try
            {
                driver.SwitchTo().Window("invalid name");
            }
            catch (NoSuchWindowException)
            {
                // This is expected.
            }

            driver.SwitchTo().Window(current);
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        public void ShouldThrowNoSuchWindowExceptionOnAnAttemptToGetItsHandle()
        {
            driver.Url = (xhtmlTestPage);
            String current = driver.CurrentWindowHandle;
            int currentWindowHandles = driver.WindowHandles.Count;

            driver.FindElement(By.LinkText("Open new window")).Click();

            WaitFor(WindowCountToBe(2), "Window count was not 2");
            Assert.AreEqual(2, driver.WindowHandles.Count);

            WaitFor(WindowWithName("result"), "Could not find window with name 'result'");
            driver.SwitchTo().Window("result");
            driver.Close();

            try
            {
                string currentHandle = driver.CurrentWindowHandle;
                Assert.Fail("NoSuchWindowException expected");
            }
            catch (NoSuchWindowException)
            {
                // Expected.
            }
            finally
            {
                driver.SwitchTo().Window(current);
            }
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        public void ShouldThrowNoSuchWindowExceptionOnAnyOperationIfAWindowIsClosed()
        {
            driver.Url = (xhtmlTestPage);
            String current = driver.CurrentWindowHandle;
            int currentWindowHandles = driver.WindowHandles.Count;

            driver.FindElement(By.LinkText("Open new window")).Click();

            WaitFor(WindowCountToBe(2), "Window count was not 2");
            Assert.AreEqual(2, driver.WindowHandles.Count);

            WaitFor(WindowWithName("result"), "Could not find window with name 'result'");
            driver.SwitchTo().Window("result");
            driver.Close();

            try
            {
                try
                {
                    string title = driver.Title;
                    Assert.Fail("NoSuchWindowException expected");
                }
                catch (NoSuchWindowException)
                {
                    // Expected.
                }

                try
                {
                    driver.FindElement(By.TagName("body"));
                    Assert.Fail("NoSuchWindowException expected");
                }
                catch (NoSuchWindowException)
                {
                    // Expected.
                }
            }
            finally
            {
                driver.SwitchTo().Window(current);
            }
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        public void ShouldThrowNoSuchWindowExceptionOnAnyElementOperationIfAWindowIsClosed()
        {
            driver.Url = (xhtmlTestPage);
            String current = driver.CurrentWindowHandle;
            int currentWindowHandles = driver.WindowHandles.Count;

            driver.FindElement(By.LinkText("Open new window")).Click();

            WaitFor(WindowCountToBe(2), "Window count was not 2");
            Assert.AreEqual(2, driver.WindowHandles.Count);

            WaitFor(WindowWithName("result"), "Could not find window with name 'result'");
            driver.SwitchTo().Window("result");
            IWebElement body = driver.FindElement(By.TagName("body"));
            driver.Close();

            try
            {
                string bodyText = body.Text;
                Assert.Fail("NoSuchWindowException expected");
            }
            catch (NoSuchWindowException)
            {
                // Expected.
            }
            finally
            {
                driver.SwitchTo().Window(current);
            }
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        [NeedsFreshDriver(IsCreatedBeforeTest = true, IsCreatedAfterTest = true)]
        public void ShouldBeAbleToIterateOverAllOpenWindows()
        {
            driver.Url = xhtmlTestPage;
            driver.FindElement(By.Name("windowOne")).Click();
            WaitFor(WindowCountToBe(2), "Window count was not 2");
            driver.FindElement(By.Name("windowTwo")).Click();
            WaitFor(WindowCountToBe(3), "Window count was not 3");

            ReadOnlyCollection<string> allWindowHandles = driver.WindowHandles;

            // There should be three windows. We should also see each of the window titles at least once.
            List<string> seenHandles = new List<string>();
            foreach (string handle in allWindowHandles)
            {
                Assert.That(seenHandles, Has.No.Member(handle));
                driver.SwitchTo().Window(handle);
                seenHandles.Add(handle);
            }

            Assert.AreEqual(3, allWindowHandles.Count);
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        public void ClickingOnAButtonThatClosesAnOpenWindowDoesNotCauseTheBrowserToHang()
        {
            driver.Url = xhtmlTestPage;

            String currentHandle = driver.CurrentWindowHandle;

            driver.FindElement(By.Name("windowThree")).Click();

            driver.SwitchTo().Window("result");

            try
            {
                IWebElement closeElement = WaitFor(() => { return driver.FindElement(By.Id("close")); }, "Could not find element with id 'close'");
                closeElement.Click();
            }
            finally
            {
                driver.SwitchTo().Window(currentHandle);
                driver.FindElement(By.Id("linkId"));
            }
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        public void CanCallGetWindowHandlesAfterClosingAWindow()
        {
            bool isIEDriver = TestUtilities.IsInternetExplorer(driver);
            bool isIE6 = TestUtilities.IsIE6(driver);

            driver.Url = xhtmlTestPage;

            String currentHandle = driver.CurrentWindowHandle;

            driver.FindElement(By.Name("windowThree")).Click();

            driver.SwitchTo().Window("result");

            try
            {
                IWebElement closeElement = WaitFor(() => { return driver.FindElement(By.Id("close")); }, "Could not find element with id 'close'");
                closeElement.Click();
                if (isIEDriver && !isIE6)
                {
                    IAlert alert = WaitFor<IAlert>(AlertToBePresent(), "No alert found");
                    alert.Accept();
                }
                ReadOnlyCollection<string> handles = driver.WindowHandles;
                // If we make it this far, we're all good.
            }
            finally
            {
                driver.SwitchTo().Window(currentHandle);
            }
        }

        [Test]
        public void CanObtainAWindowHandle()
        {
            driver.Url = xhtmlTestPage;

            String currentHandle = driver.CurrentWindowHandle;

            Assert.That(currentHandle, Is.Not.Null);
        }

        [Test]
        public void FailingToSwitchToAWindowLeavesTheCurrentWindowAsIs()
        {
            driver.Url = xhtmlTestPage;
            String current = driver.CurrentWindowHandle;

            try
            {
                driver.SwitchTo().Window("i will never exist");
                Assert.Fail("Should not be ablt to change to a non-existant window");
            }
            catch (NoSuchWindowException)
            {
                // expected
            }

            String newHandle = driver.CurrentWindowHandle;

            Assert.AreEqual(current, newHandle);
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        [NeedsFreshDriver(IsCreatedBeforeTest = true, IsCreatedAfterTest = true)]
        public void CanCloseWindowWhenMultipleWindowsAreOpen()
        {
            driver.Url = xhtmlTestPage;
            driver.FindElement(By.Name("windowOne")).Click();

            WaitFor(WindowCountToBe(2), "Window count was not 2");

            ReadOnlyCollection<string> allWindowHandles = driver.WindowHandles;

            // There should be two windows. We should also see each of the window titles at least once.
            Assert.AreEqual(2, allWindowHandles.Count);
            string handle1 = allWindowHandles[1];
            driver.SwitchTo().Window(handle1);
            driver.Close();

            WaitFor(WindowCountToBe(1), "Window count was not 1");

            allWindowHandles = driver.WindowHandles;
            Assert.AreEqual(1, allWindowHandles.Count);
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        [NeedsFreshDriver(IsCreatedBeforeTest = true, IsCreatedAfterTest = true)]
        public void CanCloseWindowAndSwitchBackToMainWindow()
        {
            driver.Url = xhtmlTestPage;

            ReadOnlyCollection<string> currentWindowHandles = driver.WindowHandles;
            string mainHandle = driver.CurrentWindowHandle;

            driver.FindElement(By.Name("windowOne")).Click();

            WaitFor(WindowCountToBe(2), "Window count was not 2");

            ReadOnlyCollection<string> allWindowHandles = driver.WindowHandles;

            // There should be two windows. We should also see each of the window titles at least once.
            Assert.AreEqual(2, allWindowHandles.Count);

            foreach (string handle in allWindowHandles)
            {
                if (handle != mainHandle)
                {
                    driver.SwitchTo().Window(handle);
                    driver.Close();
                }
            }

            driver.SwitchTo().Window(mainHandle);

            string newHandle = driver.CurrentWindowHandle;
            Assert.AreEqual(mainHandle, newHandle);

            Assert.AreEqual(1, driver.WindowHandles.Count);
        }

        [Test]
        [NeedsFreshDriver(IsCreatedBeforeTest = true, IsCreatedAfterTest = true)]
        public void ClosingOnlyWindowShouldNotCauseTheBrowserToHang()
        {
            driver.Url = xhtmlTestPage;
            driver.Close();
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        [NeedsFreshDriver(IsCreatedBeforeTest = true, IsCreatedAfterTest = true)]
        [IgnoreBrowser(Browser.Firefox, "https://github.com/mozilla/geckodriver/issues/610")]
        public void ShouldFocusOnTheTopMostFrameAfterSwitchingToAWindow()
        {
            driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("window_switching_tests/page_with_frame.html");

            ReadOnlyCollection<string> currentWindowHandles = driver.WindowHandles;
            string mainWindow = driver.CurrentWindowHandle;

            driver.FindElement(By.Id("a-link-that-opens-a-new-window")).Click();
            WaitFor(WindowCountToBe(2), "Window count was not 2");

            driver.SwitchTo().Frame("myframe");

            driver.SwitchTo().Window("newWindow");
            driver.Close();
            driver.SwitchTo().Window(mainWindow);

            driver.FindElement(By.Name("myframe"));
        }

        //------------------------------------------------------------------
        // Tests below here are not included in the Java test suite
        //------------------------------------------------------------------
        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        public void ShouldGetBrowserHandles()
        {
            driver.Url = xhtmlTestPage;
            driver.FindElement(By.LinkText("Open new window")).Click();

            WaitFor(WindowCountToBe(2), "Window count was not 2");

            string handle1, handle2;
            handle1 = driver.CurrentWindowHandle;

            System.Threading.Thread.Sleep(1000);
            driver.SwitchTo().Window("result");
            handle2 = driver.CurrentWindowHandle;

            ReadOnlyCollection<string> handles = driver.WindowHandles;

            // At least the two handles we want should be there.
            Assert.Contains(handle1, handles, "Should have contained current handle");
            Assert.Contains(handle2, handles, "Should have contained result handle");

            // Some (semi-)clean up..
            driver.SwitchTo().Window(handle2);
            driver.Close();
            driver.SwitchTo().Window(handle1);
            driver.Url = macbethPage;
        }

        [Test]
        [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
        [NeedsFreshDriver(IsCreatedAfterTest = true)]
        public void CloseShouldCloseCurrentHandleOnly()
        {
            driver.Url = xhtmlTestPage;
            driver.FindElement(By.LinkText("Open new window")).Click();

            WaitFor(WindowCountToBe(2), "Window count was not 2");

            string handle1, handle2;
            handle1 = driver.CurrentWindowHandle;

            driver.SwitchTo().Window("result");
            handle2 = driver.CurrentWindowHandle;

            driver.Close();

            SleepBecauseWindowsTakeTimeToOpen();

            ReadOnlyCollection<string> handles = driver.WindowHandles;

            Assert.That(handles, Has.No.Member(handle2), "Invalid handle still in handle list");
            Assert.That(handles, Contains.Item(handle1), "Valid handle not in handle list");
        }

        [Test]
        public void ShouldBeAbleToCreateANewWindow()
        {
            driver.Url = xhtmlTestPage;
            string originalHandle = driver.CurrentWindowHandle;
            driver.SwitchTo().NewWindow(WindowType.Tab);
            WaitFor(WindowCountToBe(2), "Window count was not 2");
            string newWindowHandle = driver.CurrentWindowHandle;
            driver.Close();
            driver.SwitchTo().Window(originalHandle);
            Assert.That(newWindowHandle, Is.Not.EqualTo(originalHandle));
        }

        private void SleepBecauseWindowsTakeTimeToOpen()
        {
            try
            {
                System.Threading.Thread.Sleep(1000);
            }
            catch (Exception)
            {
                Assert.Fail("Interrupted");
            }
        }

        private Func<bool> WindowCountToBe(int desiredCount)
        {
            return () =>
            {
                return driver.WindowHandles.Count == desiredCount;
            };
        }

        private Func<bool> WindowWithName(string name)
        {
            return () =>
            {
                try
                {
                    driver.SwitchTo().Window(name);
                    return true;
                }
                catch (NoSuchWindowException)
                {
                }

                return false;
            };
        }

        private Func<IAlert> AlertToBePresent()
        {
            return () =>
            {
                IAlert alert = null;
                try
                {
                    alert = driver.SwitchTo().Alert();
                }
                catch (NoAlertPresentException)
                {
                }

                return alert;
            };
        }
    }
}
