using System.Collections.Generic;
using System.Drawing;
using CefSharp;
using TweetDuck.Browser.Base;
using TweetDuck.Configuration;
using TweetDuck.Utils;
using TweetLib.Browser.CEF.Data;
using TweetLib.Browser.Contexts;

namespace TweetDuck.Browser.Handling {
	class ContextMenuBase : IContextMenuHandler {
		private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26500;

		private static readonly HashSet<CefMenuCommand> AllowedCefCommands = new HashSet<CefMenuCommand> {
			CefMenuCommand.NotFound,
			CefMenuCommand.Undo,
			CefMenuCommand.Redo,
			CefMenuCommand.Cut,
			CefMenuCommand.Copy,
			CefMenuCommand.Paste,
			CefMenuCommand.Delete,
			CefMenuCommand.SelectAll,
			CefMenuCommand.SpellCheckSuggestion0,
			CefMenuCommand.SpellCheckSuggestion1,
			CefMenuCommand.SpellCheckSuggestion2,
			CefMenuCommand.SpellCheckSuggestion3,
			CefMenuCommand.SpellCheckSuggestion4,
			CefMenuCommand.SpellCheckNoSuggestions,
			CefMenuCommand.AddToDictionary
		};

		protected static UserConfig Config => Program.Config.User;

		private readonly TweetLib.Browser.Interfaces.IContextMenuHandler handler;
		private readonly ContextMenuActionRegistry actionRegistry;

		public ContextMenuBase(TweetLib.Browser.Interfaces.IContextMenuHandler handler) {
			this.handler = handler;
			this.actionRegistry = new ContextMenuActionRegistry();
		}

		private sealed class ContextMenuActionRegistry : ContextMenuActionRegistry<CefMenuCommand> {
			protected override CefMenuCommand NextId(int n) {
				return CefMenuCommand.UserFirst + 500 + n;
			}
		}

		protected virtual Context CreateContext(IContextMenuParams parameters) {
			return CefContextMenuModel.CreateContext(parameters, null, Config.TwitterImageQuality);
		}

		public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
			for (int i = model.Count - 1; i >= 0; i--) {
				CefMenuCommand command = model.GetCommandIdAt(i);

				if (!AllowedCefCommands.Contains(command) && !(command >= CefMenuCommand.CustomFirst && command <= CefMenuCommand.CustomLast)) {
					model.RemoveAt(i);
				}
			}

			for (int i = model.Count - 2; i >= 0; i--) {
				if (model.GetTypeAt(i) == MenuItemType.Separator && model.GetTypeAt(i + 1) == MenuItemType.Separator) {
					model.RemoveAt(i);
				}
			}

			if (model.Count > 0 && model.GetTypeAt(0) == MenuItemType.Separator) {
				model.RemoveAt(0);
			}

			AddSeparator(model);
			handler?.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
			RemoveSeparatorIfLast(model);
			AddLastContextMenuItems(model);
		}

		protected virtual void AddLastContextMenuItems(IMenuModel model) {
			AddDebugMenuItems(model);
		}

		public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
			if (actionRegistry.Execute(commandId)) {
				return true;
			}

			if (commandId == MenuOpenDevTools) {
				browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord));
				return true;
			}

			return false;
		}

		public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
			actionRegistry.Clear();
		}

		public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
			return false;
		}

		protected static void AddDebugMenuItems(IMenuModel model) {
			if (Config.DevToolsInContextMenu) {
				AddSeparator(model);
				model.AddItem(MenuOpenDevTools, "Open dev tools");
			}
		}

		protected static void AddSeparator(IMenuModel model) {
			if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
				model.AddSeparator();
			}
		}

		private static void RemoveSeparatorIfLast(IMenuModel model) {
			if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator) {
				model.RemoveAt(model.Count - 1);
			}
		}
	}
}