// <copyright file="ElementSelectingTest.cs" company="Selenium Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC 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.
// </copyright>

using NUnit.Framework;

namespace OpenQA.Selenium;

[TestFixture]
public class ElementSelectingTest : DriverTestFixture
{
    private const string assertCannotPerformActionFormat_action_element = "Expected exception - should not be able to {0} element {1}";

    [Test]
    public void ShouldBeAbleToSelectAnEnabledUnselectedCheckbox()
    {
        driver.Url = formsPage;
        AssertCanSelect(this.EnabledUnselectedCheckbox);
    }

    [Test]
    public void ShouldBeAbleToSelectAnEnabledUnselectedRadioButton()
    {
        driver.Url = formsPage;
        AssertCanSelect(this.EnabledUnselectedRadioButton);
    }

    [Test]
    public void ShouldNotBeAbleToSelectADisabledCheckbox()
    {
        driver.Url = formsPage;
        AssertCannotSelect(this.DisabledUnselectedCheckbox);
    }

    [Test]
    public void ShouldNotBeAbleToSelectADisabledCheckboxDisabledWithRandomString()
    {
        driver.Url = formsPage;
        AssertCannotSelect(this.RandomlyDisabledSelectedCheckbox);
    }

    [Test]
    public void ShouldNotBeAbleToSelectADisabledRadioButton()
    {
        driver.Url = formsPage;
        AssertCannotSelect(this.DisabledUnselectedRadioButton);
    }

    [Test]
    public void ShouldNotBeAbleToSelectADisabledRadioButtonDisabledWithRandomString()
    {
        driver.Url = formsPage;
        AssertCannotSelect(this.RandomlyDisabledUnselectedRadioButton);
    }

    [Test]
    public void SelectingRadioButtonShouldUnselectItsSibling()
    {
        driver.Url = formsPage;

        IWebElement originallySelected = this.EnabledSelectedRadioButton;
        AssertSelected(originallySelected);

        IWebElement toSelect = this.EnabledUnselectedRadioButton;
        AssertNotSelected(toSelect);

        toSelect.Click();
        AssertNotSelected(originallySelected);
        AssertSelected(toSelect);
    }

    [Test]
    public void ShouldBeAbleToToggleAnEnabledUnselectedCheckbox()
    {
        driver.Url = formsPage;

        IWebElement checkbox = this.EnabledUnselectedCheckbox;
        AssertNotSelected(checkbox);

        checkbox.Click();
        AssertSelected(checkbox);

        checkbox.Click();
        AssertNotSelected(checkbox);
    }

    [Test]
    public void ShouldBeAbleToToggleAnEnabledSelectedCheckbox()
    {
        driver.Url = formsPage;

        IWebElement checkbox = this.EnabledSelectedCheckbox;
        AssertSelected(checkbox);

        checkbox.Click();
        AssertNotSelected(checkbox);

        checkbox.Click();
        AssertSelected(checkbox);
    }

    [Test]
    public void ClickingOnASelectedRadioButtonShouldLeaveItSelected()
    {
        driver.Url = formsPage;

        IWebElement button = this.EnabledSelectedRadioButton;
        Assert.That(button.Selected, "Radio button should be selected");

        button.Click();

        Assert.That(button.Selected, "Radio button should be selected");
    }

    [Test]
    public void ShouldBeAbleToToggleEnabledMultiSelectOption()
    {
        driver.Url = formsPage;
        AssertCanToggle(this.SelectedMultipleSelectOption);
    }

    [Test]
    public void ShouldBeAbleToToggleSelectableCheckboxByClickingOnIt()
    {
        driver.Url = formsPage;

        IWebElement checkbox = this.EnabledUnselectedCheckbox;
        AssertNotSelected(checkbox);

        checkbox.Click();
        AssertSelected(checkbox);

        checkbox.Click();
        AssertNotSelected(checkbox);
    }

    [Test]
    public void ShouldBeAbleToSelectSelectableRadioButtonByClickingOnIt()
    {
        driver.Url = formsPage;

        IWebElement radioButton = this.EnabledUnselectedRadioButton;
        AssertNotSelected(radioButton);

        radioButton.Click();
        AssertSelected(radioButton);

        radioButton.Click();
        AssertSelected(radioButton);
    }

    [Test]
    public void ClickingDisabledSelectedCheckboxShouldBeNoop()
    {
        driver.Url = formsPage;
        AssertClickingPreservesCurrentlySelectedStatus(this.RandomlyDisabledSelectedCheckbox);
    }

    [Test]
    public void ClickingDisabledUnselectedCheckboxShouldBeNoop()
    {
        driver.Url = formsPage;
        AssertClickingPreservesCurrentlySelectedStatus(this.DisabledUnselectedCheckbox);
    }

    [Test]
    public void ClickingDisabledSelectedRadioButtonShouldBeNoop()
    {
        driver.Url = formsPage;
        AssertClickingPreservesCurrentlySelectedStatus(this.DisabledSelectedRadioButton);
    }

    [Test]
    public void ClickingDisabledUnselectedRadioButtonShouldBeNoop()
    {
        driver.Url = formsPage;
        AssertClickingPreservesCurrentlySelectedStatus(this.DisabledUnselectedRadioButton);
    }

    private static void AssertNotSelected(IWebElement element)
    {
        AssertSelected(element, false);
    }

    private static void AssertSelected(IWebElement element)
    {
        AssertSelected(element, true);
    }

    private static void AssertSelected(IWebElement element, bool isSelected)
    {
        Assert.That(element.Selected, Is.EqualTo(isSelected), string.Format("Expected element {0} to be {1} but was {2}", Describe(element), SelectedToString(isSelected), SelectedToString(!isSelected)));
    }

    private static void AssertCannotSelect(IWebElement element)
    {
        bool previous = element.Selected;
        element.Click();
        Assert.That(element.Selected, Is.EqualTo(previous));
    }

    private static void AssertCanSelect(IWebElement element)
    {
        AssertNotSelected(element);

        element.Click();
        AssertSelected(element);
    }

    private static void AssertClickingPreservesCurrentlySelectedStatus(IWebElement element)
    {
        bool currentSelectedStatus = element.Selected;
        try
        {
            element.Click();
        }
        catch (InvalidElementStateException)
        {
            // This is expected, as we are clicking disabled elements.
        }

        AssertSelected(element, currentSelectedStatus);
    }

    private static string SelectedToString(bool isSelected)
    {
        return isSelected ? "[selected]" : "[not selected]";
    }

    private static string Describe(IWebElement element)
    {
        return element.GetAttribute("id");
    }

    private static void AssertCanToggle(IWebElement element)
    {
        bool originalState = element.Selected;

        AssertSelected(element, originalState);

        AssertTogglingSwapsSelectedStateFrom(element, originalState);
        AssertTogglingSwapsSelectedStateFrom(element, !originalState);
    }

    private static void AssertTogglingSwapsSelectedStateFrom(IWebElement element, bool originalState)
    {
        element.Click();
        bool isNowSelected = element.Selected;
        Assert.That(originalState, Is.Not.EqualTo(isNowSelected), string.Format("Expected element {0} to have been toggled to {1} but was {2}", Describe(element), SelectedToString(!originalState), SelectedToString(originalState)));
        AssertSelected(element, !originalState);
    }

    //TODO: Test disabled multi-selects
    //TODO: Test selecting options


    private IWebElement EnabledUnselectedCheckbox
    {
        get
        {
            return driver.FindElement(By.Id("checky"));
        }
    }

    private IWebElement EnabledSelectedCheckbox
    {
        get
        {
            return driver.FindElement(By.Id("checkedchecky"));
        }
    }

    private IWebElement DisabledUnselectedCheckbox
    {
        get
        {
            return driver.FindElement(By.Id("disabledchecky"));
        }
    }

    private IWebElement RandomlyDisabledSelectedCheckbox
    {
        get
        {
            return driver.FindElement(By.Id("randomly_disabled_checky"));
        }
    }

    private IWebElement EnabledUnselectedRadioButton
    {
        get
        {
            return driver.FindElement(By.Id("peas"));
        }
    }

    private IWebElement EnabledSelectedRadioButton
    {
        get
        {
            return driver.FindElement(By.Id("cheese_and_peas"));
        }
    }

    private IWebElement DisabledSelectedRadioButton
    {
        get
        {
            return driver.FindElement(By.Id("lone_disabled_selected_radio"));
        }
    }

    private IWebElement DisabledUnselectedRadioButton
    {
        get
        {
            return driver.FindElement(By.Id("nothing"));
        }
    }

    private IWebElement RandomlyDisabledUnselectedRadioButton
    {
        get
        {
            return driver.FindElement(By.Id("randomly_disabled_nothing"));
        }
    }

    private IWebElement SelectedMultipleSelectOption
    {
        get
        {
            IWebElement select = driver.FindElement(By.Name("multi"));
            return select.FindElements(By.TagName("option"))[0];
        }
    }

    private IWebElement NonSelectableElement
    {
        get
        {
            return driver.FindElement(By.TagName("div"));
        }
    }
}
