#region Copyright & License Information
/*
 * Copyright (c) The OpenRA Developers and Contributors
 * This file is part of OpenRA, which is free software. It is made
 * available to you under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version. For more
 * information, see COPYING.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Lint;
using OpenRA.Network;
using OpenRA.Primitives;
using OpenRA.Widgets;

namespace OpenRA.Mods.Common.Widgets.Logic
{
	[ChromeLogicArgsHotkeys("CombinedViewKey", "WorldViewKey")]
	public class ObserverShroudSelectorLogic : ChromeLogic
	{
		[FluentReference]
		const string CameraOptionAllPlayers = "options-shroud-selector.all-players";

		[FluentReference]
		const string CameraOptionDisableShroud = "options-shroud-selector.disable-shroud";

		[FluentReference]
		const string CameraOptionOther = "options-shroud-selector.other";

		[FluentReference]
		const string Players = "label-players";

		[FluentReference("team")]
		const string TeamNumber = "label-team-name";

		[FluentReference]
		const string NoTeam = "label-no-team";

		readonly CameraOption combined, disableShroud;
		readonly IGrouping<int, CameraOption>[] teams;
		readonly bool limitViews;

		readonly HotkeyReference combinedViewKey = new();
		readonly HotkeyReference worldViewKey = new();

		readonly World world;

		CameraOption selected;
		readonly LabelWidget shroudLabel;

		sealed class CameraOption
		{
			public readonly Player Player;
			public readonly string Label;
			public readonly Color Color;
			public readonly string Faction;
			public readonly Func<bool> IsSelected;
			public readonly Action OnClick;

			public CameraOption(ObserverShroudSelectorLogic logic, Player p)
			{
				Player = p;
				Label = p.ResolvedPlayerName;
				Color = p.Color;
				Faction = p.Faction.InternalName;
				IsSelected = () => p.World.RenderPlayer == p;
				OnClick = () =>
				{
					p.World.RenderPlayer = p;
					logic.selected = this;
					p.World.Selection.Clear();
					WidgetUtils.BindPlayerNameAndStatus(logic.shroudLabel, p);
				};
			}

			public CameraOption(ObserverShroudSelectorLogic logic, World w, string label, Player p)
			{
				Player = p;
				Label = label;
				Color = Color.White;
				Faction = null;
				IsSelected = () => w.RenderPlayer == p;
				OnClick = () => { w.RenderPlayer = p; logic.selected = this; };
			}
		}

		[ObjectCreator.UseCtor]
		public ObserverShroudSelectorLogic(Widget widget, ModData modData, World world, WorldRenderer worldRenderer, Dictionary<string, MiniYaml> logicArgs)
		{
			this.world = world;

			if (logicArgs.TryGetValue("CombinedViewKey", out var yaml))
				combinedViewKey = modData.Hotkeys[yaml.Value];

			if (logicArgs.TryGetValue("WorldViewKey", out yaml))
				worldViewKey = modData.Hotkeys[yaml.Value];

			limitViews = world.Map.Visibility.HasFlag(MapVisibility.MissionSelector);

			var groups = new Dictionary<string, IEnumerable<CameraOption>>();

			combined = new CameraOption(this, world, FluentProvider.GetMessage(CameraOptionAllPlayers), world.Players.First(p => p.InternalName == "Everyone"));
			disableShroud = new CameraOption(this, world, FluentProvider.GetMessage(CameraOptionDisableShroud), null);
			if (!limitViews)
				groups.Add(FluentProvider.GetMessage(CameraOptionOther), new List<CameraOption>() { combined, disableShroud });

			teams = world.Players.Where(p => !p.NonCombatant && p.Playable)
				.Select(p => new CameraOption(this, p))
				.GroupBy(p => (world.LobbyInfo.ClientWithIndex(p.Player.ClientIndex) ?? new Session.Client()).Team)
				.OrderBy(g => g.Key)
				.ToArray();

			var noTeams = teams.Length == 1;
			var totalPlayers = 0;
			foreach (var t in teams)
			{
				totalPlayers += t.Count();
				var label = noTeams ? FluentProvider.GetMessage(Players) : t.Key > 0
					? FluentProvider.GetMessage(TeamNumber, "team", t.Key)
					: FluentProvider.GetMessage(NoTeam);

				groups.Add(label, t);
			}

			var shroudSelectorDisabled = limitViews && totalPlayers < 2;
			var shroudSelector = widget.Get<DropDownButtonWidget>("SHROUD_SELECTOR");
			shroudSelector.IsDisabled = () => shroudSelectorDisabled;
			shroudSelector.OnMouseDown = _ =>
			{
				ScrollItemWidget SetupItem(CameraOption option, ScrollItemWidget template)
				{
					var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick);
					var showFlag = option.Faction != null;

					var label = item.Get<LabelWidget>("LABEL");
					label.IsVisible = () => showFlag;
					label.GetColor = () => option.Color;

					if (showFlag)
						WidgetUtils.BindPlayerNameAndStatus(label, option.Player);
					else
						label.GetText = () => option.Label;

					var flag = item.Get<ImageWidget>("FLAG");
					flag.IsVisible = () => showFlag;
					flag.GetImageCollection = () => "flags";
					flag.GetImageName = () => option.Faction;

					var labelAlt = item.Get<LabelWidget>("NOFLAG_LABEL");
					labelAlt.IsVisible = () => !showFlag;
					labelAlt.GetText = () => option.Label;
					labelAlt.GetColor = () => option.Color;

					return item;
				}

				shroudSelector.ShowDropDown("SPECTATOR_DROPDOWN_TEMPLATE", 400, groups, SetupItem);
			};

			shroudLabel = shroudSelector.Get<LabelWidget>("LABEL");
			shroudLabel.IsVisible = () => selected.Faction != null;
			shroudLabel.GetText = () => selected.Label;
			shroudLabel.GetColor = () => selected.Color;

			var shroudFlag = shroudSelector.Get<ImageWidget>("FLAG");
			shroudFlag.IsVisible = () => selected.Faction != null;
			shroudFlag.GetImageCollection = () => "flags";
			shroudFlag.GetImageName = () => selected.Faction;

			var shroudLabelAlt = shroudSelector.Get<LabelWidget>("NOFLAG_LABEL");
			shroudLabelAlt.IsVisible = () => selected.Faction == null;
			shroudLabelAlt.GetText = () => selected.Label;
			shroudLabelAlt.GetColor = () => selected.Color;

			var keyhandler = shroudSelector.Get<LogicKeyListenerWidget>("SHROUD_KEYHANDLER");
			keyhandler.AddHandler(HandleKeyPress);

			selected = limitViews ? groups.First().Value.First() : world.WorldActor.Owner.Shroud.ExploreMapEnabled ? combined : disableShroud;
			selected.OnClick();

			// Enable zooming out to fractional zoom levels
			worldRenderer.Viewport.UnlockMinimumZoom(0.5f);
		}

		public bool HandleKeyPress(KeyInput e)
		{
			if (e.Event == KeyInputEvent.Down && !e.IsRepeat)
			{
				if (combinedViewKey.IsActivatedBy(e) && !limitViews)
				{
					selected = combined;
					selected.OnClick();

					return true;
				}

				if (worldViewKey.IsActivatedBy(e) && !limitViews)
				{
					selected = disableShroud;
					selected.OnClick();

					return true;
				}

				if (e.Key >= Keycode.NUMBER_0 && e.Key <= Keycode.NUMBER_9)
				{
					var key = (int)e.Key - (int)Keycode.NUMBER_0;
					var team = teams.SingleOrDefault(t => t.Key == key)?.ToList();
					if (team == null || team.Count == 0)
						return false;

					if (e.Modifiers == Modifiers.Shift)
						team.Reverse();

					selected = team.SkipWhile(t => t.Player != selected.Player).Skip(1).FirstOrDefault() ?? team.FirstOrDefault();
					selected.OnClick();

					return true;
				}
			}

			return false;
		}

		public override void Tick()
		{
			// Fix the selector if something else has changed the render player
			if (selected != null && world.RenderPlayer != selected.Player)
			{
				if (combined.Player == world.RenderPlayer)
					combined.OnClick();
				else if (disableShroud.Player == world.RenderPlayer)
					disableShroud.OnClick();
				else
					foreach (var group in teams)
						foreach (var option in group)
							if (option.Player == world.RenderPlayer)
								option.OnClick();
			}
		}
	}
}
