// 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.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osuTK;

namespace osu.Game.Rulesets.Catch.Tests
{
    [TestFixture]
    public partial class TestSceneHyperDash : TestSceneCatchPlayer
    {
        protected override bool Autoplay => true;

        private int hyperDashCount;
        private bool inHyperDash;

        [Test]
        public void TestHyperDash()
        {
            AddStep("reset count", () =>
            {
                inHyperDash = false;
                hyperDashCount = 0;

                // this needs to be done within the frame stable context due to how quickly hyperdash state changes occur.
                Player.DrawableRuleset.FrameStableComponents.OnUpdate += _ =>
                {
                    var catcher = Player.ChildrenOfType<Catcher>().FirstOrDefault();

                    if (catcher == null)
                        return;

                    if (catcher.HyperDashing != inHyperDash)
                    {
                        inHyperDash = catcher.HyperDashing;
                        if (catcher.HyperDashing)
                            hyperDashCount++;
                    }
                };
            });

            AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);

            for (int i = 0; i < 11; i++)
            {
                int count = i + 1;
                AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count);
            }
        }

        protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
        {
            var beatmap = new Beatmap
            {
                BeatmapInfo =
                {
                    Ruleset = ruleset,
                    Difficulty = new BeatmapDifficulty
                    {
                        CircleSize = 3.6f,
                        SliderMultiplier = 1,
                    },
                }
            };

            beatmap.ControlPointInfo.Add(0, new TimingControlPoint());

            // Should produce a hyper-dash (edge case test)
            beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true });
            beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true });

            double startTime = 3000;

            const float left_x = 0.02f * CatchPlayfield.WIDTH;
            const float right_x = 0.98f * CatchPlayfield.WIDTH;

            createObjects(() => new Fruit { X = left_x });
            createObjects(() => new TestJuiceStream(right_x), 1);
            createObjects(() => new TestJuiceStream(left_x), 1);
            createObjects(() => new Fruit { X = right_x });
            createObjects(() => new Fruit { X = left_x });
            createObjects(() => new Fruit { X = right_x });
            createObjects(() => new TestJuiceStream(left_x), 1);

            beatmap.ControlPointInfo.Add(startTime, new TimingControlPoint
            {
                BeatLength = 50
            });

            createObjects(() => new TestJuiceStream(left_x)
            {
                Path = new SliderPath(new[]
                {
                    new PathControlPoint(Vector2.Zero),
                    new PathControlPoint(new Vector2(512, 0))
                })
            }, 1);

            createObjects(() => new Fruit { X = right_x }, count: 2, spacing: 0, spacingAfterGroup: 400);
            createObjects(() => new TestJuiceStream(left_x)
            {
                Path = new SliderPath(new[]
                {
                    new PathControlPoint(Vector2.Zero),
                    new PathControlPoint(new Vector2(0, 300))
                })
            }, count: 1, spacingAfterGroup: 150);
            createObjects(() => new Fruit { X = left_x }, count: 1, spacing: 0, spacingAfterGroup: 400);
            createObjects(() => new Fruit { X = right_x }, count: 2, spacing: 0);

            return beatmap;

            void createObjects(Func<CatchHitObject> createObject, int count = 3, float spacing = 140, float spacingAfterGroup = 700)
            {
                for (int i = 0; i < count; i++)
                {
                    var hitObject = createObject();
                    hitObject.StartTime = startTime + i * spacing;
                    beatmap.HitObjects.Add(hitObject);
                }

                startTime += spacingAfterGroup;
            }
        }

        private class TestJuiceStream : JuiceStream
        {
            public TestJuiceStream(float x)
            {
                X = x;

                Path = new SliderPath(new[]
                {
                    new PathControlPoint(Vector2.Zero),
                    new PathControlPoint(new Vector2(30, 0)),
                });
            }
        }
    }
}