// 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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Scoring;

namespace osu.Game.Rulesets.Mania
{
    public class ManiaRuleset : Ruleset
    {
        public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods) => new DrawableManiaRuleset(this, beatmap, mods);
        public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
        public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);

        public const string SHORT_NAME = "mania";

        public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);

        public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
        {
            if (mods.HasFlag(LegacyMods.Nightcore))
                yield return new ManiaModNightcore();
            else if (mods.HasFlag(LegacyMods.DoubleTime))
                yield return new ManiaModDoubleTime();

            if (mods.HasFlag(LegacyMods.Perfect))
                yield return new ManiaModPerfect();
            else if (mods.HasFlag(LegacyMods.SuddenDeath))
                yield return new ManiaModSuddenDeath();

            if (mods.HasFlag(LegacyMods.Autoplay))
                yield return new ManiaModAutoplay();

            if (mods.HasFlag(LegacyMods.Easy))
                yield return new ManiaModEasy();

            if (mods.HasFlag(LegacyMods.FadeIn))
                yield return new ManiaModFadeIn();

            if (mods.HasFlag(LegacyMods.Flashlight))
                yield return new ManiaModFlashlight();

            if (mods.HasFlag(LegacyMods.HalfTime))
                yield return new ManiaModHalfTime();

            if (mods.HasFlag(LegacyMods.HardRock))
                yield return new ManiaModHardRock();

            if (mods.HasFlag(LegacyMods.Hidden))
                yield return new ManiaModHidden();

            if (mods.HasFlag(LegacyMods.Key1))
                yield return new ManiaModKey1();

            if (mods.HasFlag(LegacyMods.Key2))
                yield return new ManiaModKey2();

            if (mods.HasFlag(LegacyMods.Key3))
                yield return new ManiaModKey3();

            if (mods.HasFlag(LegacyMods.Key4))
                yield return new ManiaModKey4();

            if (mods.HasFlag(LegacyMods.Key5))
                yield return new ManiaModKey5();

            if (mods.HasFlag(LegacyMods.Key6))
                yield return new ManiaModKey6();

            if (mods.HasFlag(LegacyMods.Key7))
                yield return new ManiaModKey7();

            if (mods.HasFlag(LegacyMods.Key8))
                yield return new ManiaModKey8();

            if (mods.HasFlag(LegacyMods.Key9))
                yield return new ManiaModKey9();

            if (mods.HasFlag(LegacyMods.NoFail))
                yield return new ManiaModNoFail();

            if (mods.HasFlag(LegacyMods.Random))
                yield return new ManiaModRandom();
        }

        public override IEnumerable<Mod> GetModsFor(ModType type)
        {
            switch (type)
            {
                case ModType.DifficultyReduction:
                    return new Mod[]
                    {
                        new ManiaModEasy(),
                        new ManiaModNoFail(),
                        new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()),
                    };

                case ModType.DifficultyIncrease:
                    return new Mod[]
                    {
                        new ManiaModHardRock(),
                        new MultiMod(new ManiaModSuddenDeath(), new ManiaModPerfect()),
                        new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
                        new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()),
                        new ManiaModFlashlight(),
                    };

                case ModType.Conversion:
                    return new Mod[]
                    {
                        new MultiMod(new ManiaModKey4(),
                            new ManiaModKey5(),
                            new ManiaModKey6(),
                            new ManiaModKey7(),
                            new ManiaModKey8(),
                            new ManiaModKey9(),
                            new ManiaModKey1(),
                            new ManiaModKey2(),
                            new ManiaModKey3()),
                        new ManiaModRandom(),
                        new ManiaModDualStages(),
                        new ManiaModMirror(),
                    };

                case ModType.Automation:
                    return new Mod[]
                    {
                        new MultiMod(new ManiaModAutoplay(), new ModCinema()),
                    };

                case ModType.Fun:
                    return new Mod[]
                    {
                        new MultiMod(new ModWindUp(), new ModWindDown())
                    };

                default:
                    return new Mod[] { };
            }
        }

        public override string Description => "osu!mania";

        public override string ShortName => SHORT_NAME;

        public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };

        public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);

        public override int? LegacyID => 3;

        public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();

        public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo);

        public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);

        public ManiaRuleset(RulesetInfo rulesetInfo = null)
            : base(rulesetInfo)
        {
        }

        public override IEnumerable<int> AvailableVariants
        {
            get
            {
                for (int i = 1; i <= 9; i++)
                    yield return (int)PlayfieldType.Single + i;
                for (int i = 2; i <= 18; i += 2)
                    yield return (int)PlayfieldType.Dual + i;
            }
        }

        public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0)
        {
            switch (getPlayfieldType(variant))
            {
                case PlayfieldType.Single:
                    return new VariantMappingGenerator
                    {
                        LeftKeys = new[]
                        {
                            InputKey.A,
                            InputKey.S,
                            InputKey.D,
                            InputKey.F
                        },
                        RightKeys = new[]
                        {
                            InputKey.J,
                            InputKey.K,
                            InputKey.L,
                            InputKey.Semicolon
                        },
                        SpecialKey = InputKey.Space,
                        SpecialAction = ManiaAction.Special1,
                        NormalActionStart = ManiaAction.Key1,
                    }.GenerateKeyBindingsFor(variant, out _);

                case PlayfieldType.Dual:
                    int keys = getDualStageKeyCount(variant);

                    var stage1Bindings = new VariantMappingGenerator
                    {
                        LeftKeys = new[]
                        {
                            InputKey.Number1,
                            InputKey.Number2,
                            InputKey.Number3,
                            InputKey.Number4,
                        },
                        RightKeys = new[]
                        {
                            InputKey.Z,
                            InputKey.X,
                            InputKey.C,
                            InputKey.V
                        },
                        SpecialKey = InputKey.Tilde,
                        SpecialAction = ManiaAction.Special1,
                        NormalActionStart = ManiaAction.Key1
                    }.GenerateKeyBindingsFor(keys, out var nextNormal);

                    var stage2Bindings = new VariantMappingGenerator
                    {
                        LeftKeys = new[]
                        {
                            InputKey.Number7,
                            InputKey.Number8,
                            InputKey.Number9,
                            InputKey.Number0
                        },
                        RightKeys = new[]
                        {
                            InputKey.O,
                            InputKey.P,
                            InputKey.BracketLeft,
                            InputKey.BracketRight
                        },
                        SpecialKey = InputKey.BackSlash,
                        SpecialAction = ManiaAction.Special2,
                        NormalActionStart = nextNormal
                    }.GenerateKeyBindingsFor(keys, out _);

                    return stage1Bindings.Concat(stage2Bindings);
            }

            return new KeyBinding[0];
        }

        public override string GetVariantName(int variant)
        {
            switch (getPlayfieldType(variant))
            {
                default:
                    return $"{variant}K";

                case PlayfieldType.Dual:
                {
                    var keys = getDualStageKeyCount(variant);
                    return $"{keys}K + {keys}K";
                }
            }
        }

        /// <summary>
        /// Finds the number of keys for each stage in a <see cref="PlayfieldType.Dual"/> variant.
        /// </summary>
        /// <param name="variant">The variant.</param>
        private int getDualStageKeyCount(int variant) => (variant - (int)PlayfieldType.Dual) / 2;

        /// <summary>
        /// Finds the <see cref="PlayfieldType"/> that corresponds to a variant value.
        /// </summary>
        /// <param name="variant">The variant value.</param>
        /// <returns>The <see cref="PlayfieldType"/> that corresponds to <paramref name="variant"/>.</returns>
        private PlayfieldType getPlayfieldType(int variant)
        {
            return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v);
        }

        private class VariantMappingGenerator
        {
            /// <summary>
            /// All the <see cref="InputKey"/>s available to the left hand.
            /// </summary>
            public InputKey[] LeftKeys;

            /// <summary>
            /// All the <see cref="InputKey"/>s available to the right hand.
            /// </summary>
            public InputKey[] RightKeys;

            /// <summary>
            /// The <see cref="InputKey"/> for the special key.
            /// </summary>
            public InputKey SpecialKey;

            /// <summary>
            /// The <see cref="ManiaAction"/> at which the normal columns should begin.
            /// </summary>
            public ManiaAction NormalActionStart;

            /// <summary>
            /// The <see cref="ManiaAction"/> for the special column.
            /// </summary>
            public ManiaAction SpecialAction;

            /// <summary>
            /// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
            /// </summary>
            /// <param name="columns">The number of columns that need to be bound.</param>
            /// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param>
            /// <returns>The keybindings.</returns>
            public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
            {
                ManiaAction currentNormalAction = NormalActionStart;

                var bindings = new List<KeyBinding>();

                for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
                    bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));

                if (columns % 2 == 1)
                    bindings.Add(new KeyBinding(SpecialKey, SpecialAction));

                for (int i = 0; i < columns / 2; i++)
                    bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));

                nextNormalAction = currentNormalAction;
                return bindings;
            }
        }
    }

    public enum PlayfieldType
    {
        /// <summary>
        /// Columns are grouped into a single stage.
        /// Number of columns in this stage lies at (item - Single).
        /// </summary>
        Single = 0,

        /// <summary>
        /// Columns are grouped into two stages.
        /// Overall number of columns lies at (item - Dual), further computation is required for
        /// number of columns in each individual stage.
        /// </summary>
        Dual = 1000,
    }
}