/*
  KeePass Password Safe - The Open-Source Password Manager
  Copyright (C) 2003-2024 Dominik Reichl <dominik.reichl@t-online.de>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using KeePass.App.Configuration;
using KeePass.Forms;
using KeePass.Resources;
using KeePass.Util;
using KeePass.Util.MultipleValues;

using KeePassLib;
using KeePassLib.Cryptography.PasswordGenerator;
using KeePassLib.Delegates;
using KeePassLib.Security;
using KeePassLib.Utility;

namespace KeePass.UI
{
	internal sealed class PwGeneratorMenu : IDisposable
	{
		private Button m_btnHost = null;
		private readonly object m_oTarget;
		private readonly GFunc<PwEntry> m_fGetContextEntry;
		private readonly PwDatabase m_pdContext;
		private readonly bool m_bMultipleValues;

		private Image m_imgHost = null;
		private CustomContextMenuStripEx m_ctx = null;

		private readonly string GenDeriveFromPrevious = "(" +
			KPRes.GenPwBasedOnPrevious + ")";
		private readonly string GenAuto = "(" +
			KPRes.AutoGeneratedPasswordSettings + ")";

		public PwGeneratorMenu(Button btnHost, ToolTip tt, object oTarget,
			GFunc<PwEntry> fGetContextEntry, PwDatabase pdContext, bool bMultipleValues)
		{
			if(btnHost == null) { Debug.Assert(false); return; }
			Debug.Assert(btnHost.Image == null);
			Debug.Assert(btnHost.Text == string.Empty);
			Debug.Assert(btnHost.TextImageRelation == TextImageRelation.Overlay);
			if(oTarget == null) { Debug.Assert(false); return; }
			Debug.Assert((oTarget is PwInputControlGroup) || (oTarget is TextBoxBase));

			m_imgHost = UIUtil.CreateDropDownImage(Properties.Resources.B16x16_Key_New);
			Image imgSc = UIUtil.SetButtonImage(btnHost, m_imgHost, true);
			UIUtil.OverwriteIfNotEqual(ref m_imgHost, imgSc);

			UIUtil.SetToolTip(tt, btnHost, KPRes.GeneratePassword, true);

			btnHost.Click += this.OnHostBtnClick;

			m_btnHost = btnHost;
			m_oTarget = oTarget;
			m_fGetContextEntry = fGetContextEntry;
			m_pdContext = pdContext;
			m_bMultipleValues = bMultipleValues;
		}

		public void Dispose()
		{
			DisposeContextMenu();

			if(m_btnHost != null)
			{
				UIUtil.DisposeButtonImage(m_btnHost, ref m_imgHost);

				m_btnHost.Click -= this.OnHostBtnClick;
				m_btnHost = null;
			}
			else { Debug.Assert(false); }
		}

		private bool IsMultipleValues(ProtectedString ps)
		{
			if(!m_bMultipleValues) return false;

			if(ps == null) { Debug.Assert(false); return false; }
			return ps.Equals(MultipleValuesEx.CueProtectedString, false);
		}

		private List<ToolStripItem> ConstructMenuItems()
		{
			List<ToolStripItem> l = new List<ToolStripItem>();
			AccessKeyManagerEx ak = new AccessKeyManagerEx();
			ProtectedString ps = GetPassword();
			bool bProfiles = Program.Config.PasswordGenerator.ProfilesEnabled;

			GAction<string, Image, EventHandler, object, bool> fAdd =
				delegate(string strText, Image img, EventHandler ehClick,
				object oTag, bool bEnabled)
			{
				string str = ak.CreateText(strText, true);

				ToolStripMenuItem tsmi = new ToolStripMenuItem(str);
				if(img != null) tsmi.Image = img;
				if(ehClick != null) tsmi.Click += ehClick;
				if(oTag != null) tsmi.Tag = oTag;
				if(!bEnabled) tsmi.Enabled = false;

				l.Add(tsmi);
			};

			fAdd(KPRes.PwGenOpen, Properties.Resources.B16x16_Key_New,
				this.OnGenOpen, null, true);

			l.Add(new ToolStripSeparator());

			fAdd(GenDeriveFromPrevious, Properties.Resources.B16x16_CompFile,
				this.OnGenDeriveFromPrevious, null, !IsMultipleValues(ps));
			fAdd(GenAuto, Properties.Resources.B16x16_FileNew,
				this.OnGenAuto, null, bProfiles);

			bool bHideBuiltIn = ((Program.Config.UI.UIFlags &
				(ulong)AceUIFlags.HideBuiltInPwGenPrfInEntryDlg) != 0);
			bool bFirst = true;

			foreach(PwProfile prf in PwGeneratorUtil.GetAllProfiles(true))
			{
				if(prf == null) { Debug.Assert(false); continue; }

				if(bHideBuiltIn && PwGeneratorUtil.IsBuiltInProfile(prf.Name))
					continue;

				if(bFirst)
				{
					l.Add(new ToolStripSeparator());
					bFirst = false;
				}

				fAdd(prf.Name, Properties.Resources.B16x16_KOrganizer,
					this.OnGenProfile, prf, bProfiles);
			}

			return l;
		}

		private void ConstructContextMenu()
		{
			DisposeContextMenu();

			m_ctx = new CustomContextMenuStripEx();
			m_ctx.SuspendLayout();
			m_ctx.Items.AddRange(ConstructMenuItems().ToArray());
			m_ctx.ResumeLayout(true);
		}

		private void DisposeContextMenu()
		{
			if(m_ctx != null)
			{
				m_ctx.Dispose();
				m_ctx = null;
			}
		}

		private void OnHostBtnClick(object sender, EventArgs e)
		{
			if(m_btnHost == null) { Debug.Assert(false); return; }

			ConstructContextMenu();
			m_ctx.ShowEx(m_btnHost);
		}

		private ProtectedString GetPassword()
		{
			PwInputControlGroup icg = (m_oTarget as PwInputControlGroup);
			if(icg != null) return icg.GetPasswordEx();

			TextBoxBase tb = (m_oTarget as TextBoxBase);
			if(tb != null) return new ProtectedString(false, tb.Text);

			Debug.Assert(false); // Unknown target type
			return null;
		}

		private void SetPassword(ProtectedString ps)
		{
			if(ps == null) { Debug.Assert(false); return; }

			PwInputControlGroup icg = (m_oTarget as PwInputControlGroup);
			if(icg != null)
			{
				icg.SetPassword(ps, true);
				return;
			}

			TextBoxBase tb = (m_oTarget as TextBoxBase);
			if(tb != null)
			{
				tb.Text = ps.ReadString();
				return;
			}

			Debug.Assert(false); // Unknown target type
		}

		private void GenerateAndSetPassword(PwProfile prf)
		{
			if(prf == null) { Debug.Assert(false); return; }

			byte[] pbEntropy = EntropyForm.CollectEntropyIfEnabled(prf);
			PwEntry pe = ((m_fGetContextEntry != null) ? m_fGetContextEntry() : null);

			SetPassword(PwGeneratorUtil.GenerateAcceptable(prf,
				pbEntropy, pe, m_pdContext, true));
		}

		private void OnGenOpen(object sender, EventArgs e)
		{
			PwProfile prf = null;
			ProtectedString ps = (GetPassword() ?? ProtectedString.Empty);
			if(!ps.IsEmpty && !IsMultipleValues(ps))
				prf = PwProfile.DeriveFromPassword(ps);

			PwGeneratorForm pgf = new PwGeneratorForm();
			pgf.InitEx(prf, true, false);

			if(pgf.ShowDialog() == DialogResult.OK)
				GenerateAndSetPassword(pgf.SelectedProfile);

			UIUtil.DestroyForm(pgf);
		}

		private void OnGenDeriveFromPrevious(object sender, EventArgs e)
		{
			ProtectedString ps = GetPassword();
			if(ps == null) { Debug.Assert(false); return; }

			GenerateAndSetPassword(PwProfile.DeriveFromPassword(ps));
		}

		private void OnGenAuto(object sender, EventArgs e)
		{
			GenerateAndSetPassword(Program.Config.PasswordGenerator.AutoGeneratedPasswordsProfile);
		}

		private void OnGenProfile(object sender, EventArgs e)
		{
			ToolStripItem tsi = (sender as ToolStripItem);
			if(tsi == null) { Debug.Assert(false); return; }

			GenerateAndSetPassword(tsi.Tag as PwProfile);
		}
	}
}
