// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;

namespace osu.Game.Rulesets.Osu.Tests.Editor
{
    [TestFixture]
    public partial class TestSceneOsuComposerSelection : TestSceneOsuEditor
    {
        protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);

        [Test]
        public void TestSelectAfterFadedOut()
        {
            var slider = new Slider
            {
                StartTime = 0,
                Position = new Vector2(100, 100),
                Path = new SliderPath
                {
                    ControlPoints =
                    {
                        new PathControlPoint(),
                        new PathControlPoint(new Vector2(100))
                    }
                }
            };
            AddStep("add slider", () => EditorBeatmap.Add(slider));

            moveMouseToObject(() => slider);

            AddStep("seek after end", () => EditorClock.Seek(750));
            AddStep("left click", () => InputManager.Click(MouseButton.Left));
            AddAssert("slider not selected", () => EditorBeatmap.SelectedHitObjects.Count == 0);

            AddStep("seek to visible", () => EditorClock.Seek(650));
            AddStep("left click", () => InputManager.Click(MouseButton.Left));
            AddUntilStep("slider selected", () => EditorBeatmap.SelectedHitObjects.Single() == slider);
        }

        [Test]
        public void TestContextMenuShownCorrectlyForSelectedSlider()
        {
            var slider = new Slider
            {
                StartTime = 0,
                Position = new Vector2(100, 100),
                Path = new SliderPath
                {
                    ControlPoints =
                    {
                        new PathControlPoint(),
                        new PathControlPoint(new Vector2(100))
                    }
                }
            };
            AddStep("add slider", () => EditorBeatmap.Add(slider));

            moveMouseToObject(() => slider);
            AddStep("left click", () => InputManager.Click(MouseButton.Left));
            AddUntilStep("slider selected", () => EditorBeatmap.SelectedHitObjects.Single() == slider);

            AddStep("move mouse to centre", () => InputManager.MoveMouseTo(blueprintContainer.ChildrenOfType<SliderBodyPiece>().Single().ScreenSpaceDrawQuad.Centre));
            AddStep("right click", () => InputManager.Click(MouseButton.Right));
            AddUntilStep("context menu is visible", () => contextMenuContainer.ChildrenOfType<OsuContextMenu>().Single().State == MenuState.Open);
        }

        [Test]
        public void TestSelectionIncludingSliderPreservedOnClick()
        {
            var firstSlider = new Slider
            {
                StartTime = 0,
                Position = new Vector2(0, 0),
                Path = new SliderPath
                {
                    ControlPoints =
                    {
                        new PathControlPoint(),
                        new PathControlPoint(new Vector2(100))
                    }
                }
            };
            var secondSlider = new Slider
            {
                StartTime = 1000,
                Position = new Vector2(100, 100),
                Path = new SliderPath
                {
                    ControlPoints =
                    {
                        new PathControlPoint(),
                        new PathControlPoint(new Vector2(100, -100))
                    }
                }
            };
            var hitCircle = new HitCircle
            {
                StartTime = 200,
                Position = new Vector2(300, 0)
            };

            AddStep("add objects", () => EditorBeatmap.AddRange(new HitObject[] { firstSlider, secondSlider, hitCircle }));
            AddStep("select last 2 objects", () => EditorBeatmap.SelectedHitObjects.AddRange(new HitObject[] { secondSlider, hitCircle }));

            moveMouseToObject(() => secondSlider);
            AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
            AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count == 2);
        }

        [Test]
        public void TestControlClickAddsControlPointsIfSingleSliderSelected()
        {
            var firstSlider = new Slider
            {
                StartTime = 0,
                Position = new Vector2(0, 0),
                Path = new SliderPath
                {
                    ControlPoints =
                    {
                        new PathControlPoint(),
                        new PathControlPoint(new Vector2(100))
                    }
                }
            };
            var secondSlider = new Slider
            {
                StartTime = 1000,
                Position = new Vector2(200, 200),
                Path = new SliderPath
                {
                    ControlPoints =
                    {
                        new PathControlPoint(),
                        new PathControlPoint(new Vector2(100, -100))
                    }
                }
            };

            AddStep("add objects", () => EditorBeatmap.AddRange(new HitObject[] { firstSlider, secondSlider }));
            AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange(new HitObject[] { secondSlider }));

            AddStep("move mouse to middle of slider", () =>
            {
                var pos = blueprintContainer.SelectionBlueprints
                                            .First(s => s.Item == secondSlider)
                                            .ChildrenOfType<SliderBodyPiece>().First()
                                            .ScreenSpaceDrawQuad.Centre;

                InputManager.MoveMouseTo(pos);
            });
            AddStep("control-click left mouse", () =>
            {
                InputManager.PressKey(Key.ControlLeft);
                InputManager.Click(MouseButton.Left);
                InputManager.ReleaseKey(Key.ControlLeft);
            });
            AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1));
            AddAssert("slider has 3 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(3));
        }

        [Test]
        public void TestControlClickDoesNotAddSliderControlPointsIfMultipleObjectsSelected()
        {
            var firstSlider = new Slider
            {
                StartTime = 0,
                Position = new Vector2(0, 0),
                Path = new SliderPath
                {
                    ControlPoints =
                    {
                        new PathControlPoint(),
                        new PathControlPoint(new Vector2(100))
                    }
                }
            };
            var secondSlider = new Slider
            {
                StartTime = 1000,
                Position = new Vector2(200, 200),
                Path = new SliderPath
                {
                    ControlPoints =
                    {
                        new PathControlPoint(),
                        new PathControlPoint(new Vector2(100, -100))
                    }
                }
            };

            AddStep("add objects", () => EditorBeatmap.AddRange(new HitObject[] { firstSlider, secondSlider }));
            AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange(new HitObject[] { firstSlider, secondSlider }));

            AddStep("move mouse to middle of slider", () =>
            {
                var pos = blueprintContainer.SelectionBlueprints
                                            .First(s => s.Item == secondSlider)
                                            .ChildrenOfType<SliderBodyPiece>().First()
                                            .ScreenSpaceDrawQuad.Centre;

                InputManager.MoveMouseTo(pos);
            });
            AddStep("control-click left mouse", () =>
            {
                InputManager.PressKey(Key.ControlLeft);
                InputManager.Click(MouseButton.Left);
                InputManager.ReleaseKey(Key.ControlLeft);
            });
            AddAssert("selection not preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1));
            AddAssert("second slider not selected",
                () => blueprintContainer.SelectionBlueprints.First(s => s.Item == secondSlider).IsSelected,
                () => Is.False);
            AddAssert("slider still has 2 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(2));
        }

        private ComposeBlueprintContainer blueprintContainer
            => Editor.ChildrenOfType<ComposeBlueprintContainer>().First();

        private ContextMenuContainer contextMenuContainer
            => Editor.ChildrenOfType<ContextMenuContainer>().First();

        private void moveMouseToObject(Func<HitObject> targetFunc)
        {
            AddStep("move mouse to object", () =>
            {
                var pos = blueprintContainer.SelectionBlueprints
                                            .First(s => s.Item == targetFunc())
                                            .ChildrenOfType<HitCirclePiece>()
                                            .First().ScreenSpaceDrawQuad.Centre;

                InputManager.MoveMouseTo(pos);
            });
        }
    }
}