/*
 * Copyright (C) 2013 Colin Mackie.
 * This software is distributed under the terms of the GNU General Public License.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

using WinAuth.Resources;
using System.Diagnostics;

namespace WinAuth
{
	/// <summary>
	/// An item within the OwnerDraw list that represents an authenticator
	/// </summary>
	public class AuthenticatorListitem
	{
		/// <summary>
		/// Create a new AuthenticatorListitem
		/// </summary>
		/// <param name="auth">authenticator</param>
		/// <param name="index">index of item</param>
		public AuthenticatorListitem(WinAuthAuthenticator auth, int index)
		{
			Authenticator = auth;
			LastUpdate = DateTime.MinValue;
			Index = index;
			DisplayUntil = DateTime.MinValue;
		}

		/// <summary>
		/// Index of item in list
		/// </summary>
		public int Index { get; set; }

		/// <summary>
		/// Authenticator wrapper
		/// </summary>
		public WinAuthAuthenticator Authenticator { get; set; }

		/// <summary>
		/// When the item was last updated
		/// </summary>
		public DateTime LastUpdate { get; set;}

		/// <summary>
		/// When the code should be displayed to if not AutoRefresh
		/// </summary>
		public DateTime DisplayUntil { get; set; }

		/// <summary>
		/// The last code to be displayed
		/// </summary>
		public string LastCode { get; set; }

		/// <summary>
		/// If this item is being dragged
		/// </summary>
		public bool Dragging { get; set; }

		/// <summary>
		/// A count for password protected to allow multipel unprotect operations
		/// </summary>
		public int UnprotectCount { get; set; }

		/// <summary>
		/// Width of item for autosizing
		/// </summary>
		public int AutoWidth { get; set; }
	}

	/// <summary>
	/// Delegate for event when item is removed
	/// </summary>
	/// <param name="source"></param>
	/// <param name="args"></param>
	public delegate void AuthenticatorListItemRemovedHandler(object source, AuthenticatorListItemRemovedEventArgs args);

	/// <summary>
	/// Delegate for Redordered event
	/// </summary>
	/// <param name="source"></param>
	/// <param name="args"></param>
	public delegate void AuthenticatorListReorderedHandler(object source, AuthenticatorListReorderedEventArgs args);

	/// <summary>
	/// Event arguments for removing an item
	/// </summary>
	public class AuthenticatorListItemRemovedEventArgs : EventArgs
	{
		/// <summary>
		/// The item that was removed
		/// </summary>
		public AuthenticatorListitem Item { get; private set; }

		/// <summary>
		/// Default constructor
		/// </summary>
		public AuthenticatorListItemRemovedEventArgs(AuthenticatorListitem item)
			: base()
		{
			Item = item;
		}
	}

	/// <summary>
	/// Event arguments for reordering the list
	/// </summary>
	public class AuthenticatorListReorderedEventArgs : EventArgs
	{
		/// <summary>
		/// Default constructor
		/// </summary>
		public AuthenticatorListReorderedEventArgs()
			: base()
		{
		}
	}

	/// <summary>
	/// Delegate for event when double click
	/// </summary>
	/// <param name="source"></param>
	/// <param name="args"></param>
	public delegate void AuthenticatorListDoubleClickHandler(object source, AuthenticatorListDoubleClickEventArgs args);

	/// <summary>
	/// Event arguments for double click
	/// </summary>
	public class AuthenticatorListDoubleClickEventArgs : EventArgs
	{
		public WinAuthAuthenticator Authenticator;

		/// <summary>
		/// Default constructor
		/// </summary>
		public AuthenticatorListDoubleClickEventArgs(WinAuthAuthenticator auth)
			: base()
		{
			Authenticator = auth;
		}
	}

	/// <summary>
	/// An owner draw listbox that displays wrapped authenticators
	/// </summary>
	public class AuthenticatorListBox : ListBox
  {
		private const int MARGIN_LEFT = 4;
		private const int MARGIN_TOP = 8;
		private const int MARGIN_RIGHT = 8;
		private const int ICON_WIDTH = 48;
		private const int ICON_HEIGHT = 48;
		private const int ICON_MARGIN_RIGHT = 12;

		private const int LABEL_MARGIN_BOTTOM = 4;

		private const int LABEL_WIDTH = 250;

		private const int FONT_SIZE = 12;

		private const int PIE_WIDTH = 46;
		private const int PIE_HEIGHT = 46;
		private const int PIE_MARGIN = 2;
		private const int PIE_STARTANGLE = 270;
		private const int PIE_SWEEPANGLE = 360;

		/// <summary>
		/// Event handler for ItemRemoved
		/// </summary>
		public event AuthenticatorListItemRemovedHandler ItemRemoved;

		/// <summary>
		/// Event handler for Reordered
		/// </summary>
		public event AuthenticatorListReorderedHandler Reordered;

		/// <summary>
		/// Scrolled event handler
		/// </summary>
		[Category("Action")]
		public event ScrollEventHandler Scrolled = null;

		/// <summary>
		/// Event handler for double click
		/// </summary>
		public new event AuthenticatorListDoubleClickHandler DoubleClick;

		/// <summary>
		/// Rename textbox
		/// </summary>
		private TextBox _renameTextbox;

		/// <summary>
		/// Current item
		/// </summary>
		private AuthenticatorListitem _currentItem;

		/// <summary>
		/// Saved point of mouse down
		/// </summary>
		private Point _mouseDownLocation = Point.Empty;

		/// <summary>
		/// Saved pont of mouse move
		/// </summary>
		private Point _mouseMoveLocation = Point.Empty;

		/// <summary>
		/// Bitmap of cloned item we are dragging
		/// </summary>
		private Bitmap _draggedBitmap;

		/// <summary>
		/// Item that is being dragged
		/// </summary>
		private AuthenticatorListitem _draggedItem;

		/// <summary>
		/// Offset of last position of dragged bitmap so we can paint behind it
		/// </summary>
		private Rectangle _draggedBitmapRect;

		/// <summary>
		/// Offset of mouseY and top of item
		/// </summary>
		private int _draggedBitmapOffsetY;

		/// <summary>
		/// Last TopIndex if we scrolled while dragging
		/// </summary>
		private int? _lastDragTopIndex;

		/// <summary>
		/// When we last changed the TopIndex
		/// </summary>
		private DateTime _lastDragScroll;

		/// <summary>
		/// Default constructor for our authenticator list box
		/// </summary>
		public AuthenticatorListBox()
    {
			// set owner draw stlying
      this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
			this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
			this.ReadOnly = true;
			this.AllowReorder = true;
			this.AllowDrop = true;
			this.DoubleBuffered = true;

			// hook the scroll event
			this.Scrolled += AuthenticatorListBox_Scrolled;

			// hook the context menu
			this.ContextMenuStrip = new ContextMenuStrip();
			this.ContextMenuStrip.Opening += ContextMenuStrip_Opening;

			// preload the content menu
			loadContextMenuStrip();
    }

		/// <summary>
		/// Capture the mouse wheel events and scroll to appropriate position
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseWheel(MouseEventArgs e)
		{
			base.OnMouseWheel(e);

			int y = (e.Delta * this.ItemHeight) + MARGIN_TOP;
			ScrollEventArgs sargs = new ScrollEventArgs(ScrollEventType.ThumbPosition, y, ScrollOrientation.VerticalScroll);
			Scrolled(this, sargs);
		}

		/// <summary>
		/// Copy the current code when double-clicking
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseDoubleClick(MouseEventArgs e)
		{
			var item = this.CurrentItem;
			if (item != null)
			{
				DoubleClick(this, new AuthenticatorListDoubleClickEventArgs(item.Authenticator));
			}
		}

		#region Control Events

		/// <summary>
		/// Resize event to resize base and fix rename box location
		/// </summary>
		/// <param name="e"></param>
		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);
			setRenameTextboxLocation();
		}

		/// <summary>
		/// Fix position of rename box when scrolling
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void AuthenticatorListBox_Scrolled(object sender, ScrollEventArgs e)
		{
			if (e.Type == ScrollEventType.EndScroll || e.Type == ScrollEventType.ThumbPosition)
			{
				setRenameTextboxLocation();
			}
		}

		/// <summary>
		/// Click an item in the context menu
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void ContextMenu_Click(object sender, EventArgs e)
		{
			ProcessMenu((ToolStripItem)sender);
		}

		/// <summary>
		/// Click to open the contxet menu and set the state
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void ContextMenuStrip_Opening(object sender, CancelEventArgs e)
		{
			SetContextMenuItems();
		}

		/// <summary>
		/// Tick event used to update the pie, codes and relock authenticators
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Tick(object sender, EventArgs e)
		{
			for (int index = 0; index < this.Items.Count; index++)
			{
				// get the item
				AuthenticatorListitem item = this.Items[index] as AuthenticatorListitem;
				WinAuthAuthenticator auth = item.Authenticator;

				int y = (this.ItemHeight * index) - (this.TopIndex * this.ItemHeight);
				if (auth.AutoRefresh == true)
				{
          // for autorefresh we repaint the pie or the code too
          //int tillUpdate = (int)((auth.AuthenticatorData.ServerTime % ((long)auth.AuthenticatorData.Period * 1000L)) / 1000L);
          int tillUpdate = (int)Math.Round((decimal)((auth.AuthenticatorData.ServerTime % ((long)auth.AuthenticatorData.Period * 1000L)) / 1000L) * (360M / (decimal)auth.AuthenticatorData.Period));
          if (item.LastUpdate == DateTime.MinValue || tillUpdate == 0)
					{
						this.Invalidate(new Rectangle(0, y, this.Width, this.ItemHeight), false);
						item.LastUpdate = DateTime.Now;
					}
					else
					{
						this.Invalidate(new Rectangle(0, y, this.Width, this.ItemHeight), false);
						item.LastUpdate = DateTime.Now;
					}
				}
				else
				{
					// check if we need to redraw
					if (item.DisplayUntil != DateTime.MinValue)
					{
						// clear the item
						if (item.DisplayUntil <= DateTime.Now)
						{
							item.DisplayUntil = DateTime.MinValue;
							item.LastUpdate = DateTime.MinValue;
							item.LastCode = null;

							if (item.Authenticator.AuthenticatorData != null && item.Authenticator.AuthenticatorData.PasswordType == Authenticator.PasswordTypes.Explicit)
							{
								ProtectAuthenticator(item);
							}

							SetCursor(this.PointToClient(Control.MousePosition));
						}
						this.Invalidate(new Rectangle(0, y, this.Width, this.ItemHeight), false);
					}
				}
			}
		}

		/// <summary>
		/// Handle mouse down event
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseDown(MouseEventArgs e)
		{
			// set the current item based on position
			SetCurrentItem(e.Location);
			_mouseDownLocation = e.Location;

			base.OnMouseDown(e);
		}

		/// <summary>
		/// Handle the mouse up and check for click in the refresh icon
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseUp(MouseEventArgs e)
		{
			base.OnMouseUp(e);

			if ((e.Button & System.Windows.Forms.MouseButtons.Left) != 0)
			{
				// if this was in a refresh icon, we do a refresh
				int index = this.IndexFromPoint(e.Location);
				if (index >= 0 && index < this.Items.Count)
				{
					var item = this.Items[index] as AuthenticatorListitem;
					int x = 0;
					int y = (this.ItemHeight * index) - (this.ItemHeight * this.TopIndex);
					bool hasvscroll = (this.Height < (this.Items.Count * this.ItemHeight));
					if (item.Authenticator.AutoRefresh == false && item.DisplayUntil < DateTime.Now
						&& new Rectangle(x + this.Width - (ICON_WIDTH + MARGIN_RIGHT) - (hasvscroll ? SystemInformation.VerticalScrollBarWidth : 0), y + MARGIN_TOP, ICON_WIDTH, ICON_HEIGHT).Contains(e.Location))
					{
						if (UnprotectAuthenticator(item) == DialogResult.Cancel)
						{
							return;
						}

						item.LastCode = item.Authenticator.CurrentCode;
						item.LastUpdate = DateTime.Now;
						item.DisplayUntil = DateTime.Now.AddSeconds(10);

						if (item.Authenticator.CopyOnCode == true)
						{
							// copy to clipboard
							item.Authenticator.CopyCodeToClipboard(this.Parent as Form);
						}

						RefreshCurrentItem();
					}
				}
			}

			// dispose and reset the dragging
			_mouseDownLocation = Point.Empty;
			if (_draggedBitmap != null)
			{
				_draggedBitmap.Dispose();
				_draggedBitmap = null;
				this.Invalidate(_draggedBitmapRect);
			}
			if (_draggedItem != null)
			{
				_draggedItem = null;
			}
		}

		/// <summary>
		/// Handle the mouse move event to capture cursor position
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseMove(MouseEventArgs e)
		{
			_mouseMoveLocation = e.Location;

			// if we are moving with LeftMouse down and moved more than 2 pixles then we are dragging
			if (e.Button == System.Windows.Forms.MouseButtons.Left && _mouseDownLocation != Point.Empty && this.Items.Count > 1 && this.AllowReorder == true)
			{
				int dx = Math.Abs(_mouseDownLocation.X - e.Location.X);
				int dy = Math.Abs(_mouseDownLocation.Y - e.Location.Y);
				if (dx > 2 || dy > 2)
				{
					_draggedItem = this.CurrentItem;

					// get a snapshot of the current item for the drag
					bool hasvscroll = (this.Height < (this.Items.Count * this.ItemHeight));
					_draggedBitmap = new Bitmap(this.Width - (hasvscroll ? SystemInformation.VerticalScrollBarWidth : 0), this.ItemHeight);
					_draggedBitmapRect = Rectangle.Empty;
					using (Graphics g = Graphics.FromImage(_draggedBitmap))
					{
						int y = (this.ItemHeight * this.CurrentItem.Index) - (this.ItemHeight * this.TopIndex);

						Point screen = this.Parent.PointToScreen(new Point(this.Location.X, this.Location.Y + y));
						g.CopyFromScreen(screen.X, screen.Y, 0, 0, new Size(this.Width - (hasvscroll ? SystemInformation.VerticalScrollBarWidth : 0), this.ItemHeight), CopyPixelOperation.SourceCopy);

						// save offset in Y from top of item
						_draggedBitmapOffsetY = e.Y - y;
					}

					// make the bitmap darker
					Lighten(_draggedBitmap, -10);

					_draggedItem.Dragging = true;
					this.RefreshItem(_draggedItem);

					// moved enough so start drag
					this.DoDragDrop(_draggedItem, DragDropEffects.Move);
				}
			}
			else if (e.Button == System.Windows.Forms.MouseButtons.None)
			{
				SetCursor(e.Location);
			}

			base.OnMouseMove(e);
		}

		/// <summary>
		/// When we are dragging over
		/// </summary>
		/// <param name="e"></param>
		protected override void OnDragOver(DragEventArgs e)
		{
			if (this.AllowReorder == false)
			{
				e.Effect = DragDropEffects.None;
				return;
			}

			Rectangle screen = this.Parent.RectangleToScreen(new Rectangle(this.Location.X, this.Location.Y, this.Width, this.Height));
			Point mousePoint = new Point(e.X, e.Y);
			if (screen.Contains(mousePoint) == false)
			{
				e.Effect = DragDropEffects.None;
			}
			else if (_draggedBitmap != null)
			{
				e.Effect = DragDropEffects.Move;
				using (Graphics g = this.CreateGraphics())
				{
					Point mouseClientPoint = this.PointToClient(mousePoint);
					int x = 0;
					int y = Math.Max(mouseClientPoint.Y - _draggedBitmapOffsetY, 0);
					Rectangle rect = new Rectangle(x, y, _draggedBitmap.Width, _draggedBitmap.Height);
					g.DrawImageUnscaled(_draggedBitmap, rect);

					if (_draggedBitmapRect != Rectangle.Empty)
					{
						// invalidate the extent between the old rect and this one
						Region region = new Region(rect);
						region.Union(_draggedBitmapRect);
						region.Exclude(rect);
						this.Invalidate(region);
					}

					_draggedBitmapRect = rect;
				}
			}
		}

		/// <summary>
		/// Check if we should continue dragging
		/// </summary>
		/// <param name="e"></param>
		protected override void OnQueryContinueDrag(QueryContinueDragEventArgs e)
		{
			Rectangle screen = this.Parent.RectangleToScreen(new Rectangle(this.Location.X, this.Location.Y, this.Width, this.Height));
			Point mousePoint = Cursor.Position;

			// if ESC is pressed, always stop
			if (e.EscapePressed == true || ((e.KeyState & 1) == 0 && screen.Contains(mousePoint) == false))
			{
				e.Action = DragAction.Cancel;

				if (_draggedItem != null)
				{
					_draggedItem.Dragging = false;
					this.RefreshItem(_draggedItem);
					_draggedItem = null;
				}
				if (_draggedBitmap != null)
				{
					_draggedBitmap.Dispose();
					_draggedBitmap = null;
					this.Invalidate(_draggedBitmapRect);
				}
				_mouseDownLocation = Point.Empty;
			}
			else
			{
				DateTime now = DateTime.Now;

				// if we are at the top or bottom, scroll every 150ms
				if (mousePoint.Y >= screen.Bottom)
				{
					int visible = this.ClientSize.Height / this.ItemHeight;
					int maxtopindex = Math.Max(this.Items.Count - visible + 1, 0);
					if (this.TopIndex < maxtopindex && now.Subtract(_lastDragScroll).TotalMilliseconds >= 150)
					{
						this.TopIndex++;
						_lastDragScroll = now;
						this.Refresh();
					}
				}
				else if (mousePoint.Y <= screen.Top)
				{
					int visible = this.ClientSize.Height / this.ItemHeight;
					if (this.TopIndex > 0 && now.Subtract(_lastDragScroll).TotalMilliseconds >= 150)
					{
						this.TopIndex--;
						_lastDragScroll = now;
						this.Refresh();
					}
				}
				_lastDragTopIndex = this.TopIndex;

				base.OnQueryContinueDrag(e);
			}
		}

		/// <summary>
		/// When a dragdrop operation has completed
		/// </summary>
		/// <param name="e"></param>
		protected override void OnDragDrop(DragEventArgs e)
		{
			if (this.AllowReorder == false)
			{
				return;
			}

			AuthenticatorListitem item = e.Data.GetData(typeof(AuthenticatorListitem)) as AuthenticatorListitem;
			if (item != null)
			{
				// stop paiting as we reorder to reduce flicker
				WinAPI.SendMessage(this.Handle, WinAPI.WM_SETREDRAW, 0, IntPtr.Zero);
				try
				{
					// get the new index
					Point point = this.PointToClient(new Point(e.X, e.Y));
					int index = this.IndexFromPoint(point);
					if (index < 0)
					{
						index = this.Items.Count - 1;
					}
					// move the item
					this.Items.Remove(item);
					this.Items.Insert(index, item);

					// set the correct indexes of our items
					for (int i = 0; i < this.Items.Count; i++)
					{
						(this.Items[i] as AuthenticatorListitem).Index = i;
					}

					// fire the reordered event
					Reordered(this, new AuthenticatorListReorderedEventArgs());

					// clear state
					item.Dragging = false;
					_draggedItem = null;
					if (_draggedBitmap != null)
					{
						_draggedBitmap.Dispose();
						_draggedBitmap = null;
					}

					if (_lastDragTopIndex != null)
					{
						this.TopIndex = _lastDragTopIndex.Value;
					}
				}
				finally
				{
					// resume painting
					WinAPI.SendMessage(this.Handle, WinAPI.WM_SETREDRAW, 1, IntPtr.Zero);
				}
				this.Refresh();
			}
			else
			{
				base.OnDragDrop(e);
			}
		}

		/// <summary>
		/// Main WndProc handler to capture scroll events
		/// </summary>
		/// <param name="msg"></param>
		protected override void WndProc(ref System.Windows.Forms.Message msg)
		{
			if (msg.Msg == WinAPI.WM_VSCROLL)
			{
				if (Scrolled != null)
				{
					WinAPI.ScrollInfoStruct si = new WinAPI.ScrollInfoStruct();
					si.fMask = WinAPI.SIF_ALL;
					si.cbSize = Marshal.SizeOf(si);
					WinAPI.GetScrollInfo(msg.HWnd, 0, ref si);

					if (msg.WParam.ToInt32() == WinAPI.SB_ENDSCROLL)
					{
						ScrollEventArgs sargs = new ScrollEventArgs(ScrollEventType.EndScroll, si.nPos);
						Scrolled(this, sargs);
					}
					else if (msg.WParam.ToInt32() == WinAPI.SB_THUMBTRACK)
					{
						ScrollEventArgs sargs = new ScrollEventArgs(ScrollEventType.ThumbTrack, si.nPos);
						Scrolled(this, sargs);
					}
				}
			}

			base.WndProc(ref msg);
		}

		#endregion

		#region Item renaming

		/// <summary>
		/// Set the position of the rename textbox
		/// </summary>
		private void setRenameTextboxLocation()
		{
			if (_renameTextbox != null && _renameTextbox.Visible == true)
			{
				AuthenticatorListitem item = _renameTextbox.Tag as AuthenticatorListitem;
				if (item != null)
				{
					int y = (this.ItemHeight * item.Index) - (this.TopIndex * this.ItemHeight) + MARGIN_TOP;
					if (RenameTextbox.Location.Y != y)
					{
						RenameTextbox.Location = new Point(RenameTextbox.Location.X, y);
					}
					Refresh();
				}
			}
		}

		/// <summary>
		/// Get or create the rename textbox
		/// </summary>
		public TextBox RenameTextbox
		{
			get
			{
				bool hasvscroll = (this.Height < (this.Items.Count * this.ItemHeight));
				int labelMaxWidth = GetMaxAvailableLabelWidth(this.Width - this.Margin.Horizontal - this.DefaultPadding.Horizontal - (hasvscroll ? SystemInformation.VerticalScrollBarWidth : 0));
				if (_renameTextbox == null)
				{
					_renameTextbox = new TextBox();
					_renameTextbox.Name = "renameTextBox";
					_renameTextbox.AllowDrop = true;
					_renameTextbox.CausesValidation = false;
					_renameTextbox.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
					_renameTextbox.Location = new System.Drawing.Point(0, 0);
					_renameTextbox.Multiline = false;
					_renameTextbox.Name = "secretCodeField";
					_renameTextbox.Size = new System.Drawing.Size(labelMaxWidth, 22);
					_renameTextbox.TabIndex = 0;
					_renameTextbox.Visible = false;
					_renameTextbox.Leave += RenameTextbox_Leave;
					_renameTextbox.KeyPress += _renameTextbox_KeyPress;

					this.Controls.Add(_renameTextbox);
				}
				else
				{
					_renameTextbox.Width = labelMaxWidth;
				}

				return _renameTextbox;
			}
		}

		/// <summary>
		/// Get flag is we are renaming
		/// </summary>
		public bool IsRenaming
		{
			get
			{
				return (RenameTextbox.Visible == true);
			}
		}

		/// <summary>
		/// End the renaming and decide to save
		/// </summary>
		/// <param name="save"></param>
		public void EndRenaming(bool save = true)
		{
			if (save == false)
			{
				RenameTextbox.Tag = null;
			}
			RenameTextbox.Visible = false;
		}

		/// <summary>
		/// Keypress event to cancel or commit renaming
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _renameTextbox_KeyPress(object sender, KeyPressEventArgs e)
		{
			if (e.KeyChar == 27)
			{
				RenameTextbox.Tag = null;
				RenameTextbox.Visible = false;
				e.Handled = true;
			}
			else if (e.KeyChar == 13 || e.KeyChar == 9)
			{
				RenameTextbox.Visible = false;
				e.Handled = true;
			}
		}

		/// <summary>
		/// Handle the focus leave for rename box
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void RenameTextbox_Leave(object sender, EventArgs e)
		{
			RenameTextbox.Visible = false;
			AuthenticatorListitem item = RenameTextbox.Tag as AuthenticatorListitem;
			if (item != null)
			{
				string newname = RenameTextbox.Text.Trim();
				if (newname.Length != 0)
				{
					// force the autowidth to be recaculated when we set the name
					item.AutoWidth = 0;
					item.Authenticator.Name = newname;
					RefreshItem(item.Index);
				}
			}
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Readonly flag
		/// </summary>
		public bool ReadOnly { get; set; }

		/// <summary>
		/// Allow items to be dragged to reorder the list.
		/// </summary>
		public bool AllowReorder { get; set; }

		/// <summary>
		/// The current (selected) authenticator
		/// </summary>
		public AuthenticatorListitem CurrentItem
		{
			get
			{
				if (_currentItem == null && this.Items.Count != 0)
				{
					_currentItem = (AuthenticatorListitem)this.Items[0];
				}
				return _currentItem;
			}
			set
			{
				_currentItem = value;
			}
		}

		#endregion

		#region Private methods

		/// <summary>
		/// Set the current item based on the mouse position
		/// </summary>
		/// <param name="mouseLocation">mouse position</param>
		private void SetCurrentItem(Point mouseLocation)
		{
			int index = this.IndexFromPoint(mouseLocation);
			if (index < 0)
			{
				index = 0;
			}
			else if (index >= this.Items.Count)
			{
				index = this.Items.Count - 1;
			}

			if (index >= this.Items.Count)
			{
				CurrentItem = null;
			}
			else
			{
				CurrentItem = this.Items[index] as AuthenticatorListitem;
			}
		}

		/// <summary>
		/// Set the current cursor based on position
		/// </summary>
		/// <param name="mouseLocation">current mouse position</param>
		private void SetCursor(Point mouseLocation, Cursor force = null)
		{
			// set cursor if we are over a refresh icon
			var cursor = Cursor.Current;
			if (force == null)
			{
				int index = this.IndexFromPoint(mouseLocation);
				if (index >= 0 && index < this.Items.Count)
				{
					var item = this.Items[index] as AuthenticatorListitem;
					int x = 0;
					int y = (this.ItemHeight * index) - (this.TopIndex * this.ItemHeight);
					bool hasvscroll = (this.Height < (this.Items.Count * this.ItemHeight));
					if (item.Authenticator.AutoRefresh == false && item.DisplayUntil < DateTime.Now
						&& new Rectangle(x + this.Width - (ICON_WIDTH + MARGIN_RIGHT) - (hasvscroll ? SystemInformation.VerticalScrollBarWidth : 0), y + MARGIN_TOP, ICON_WIDTH, ICON_HEIGHT).Contains(mouseLocation))
					{
						cursor = Cursors.Hand;
					}
				}
			}
			else
			{
				cursor = force;
			}
			if (Cursor.Current != cursor)
			{
				Cursor.Current = cursor;
			}
		}

		/// <summary>
		/// Unprotected an authenticator (if possible)
		/// </summary>
		/// <param name="item">item to unprotect</param>
		/// <param name="screen">screen to display dialog for multi-monitors</param>
		/// <returns></returns>
		private DialogResult UnprotectAuthenticator(AuthenticatorListitem item, Screen screen = null)
		{
			// keep a count so we can have multiples
			if (item.UnprotectCount > 0)
			{
				item.UnprotectCount++;
				return DialogResult.OK;
			}

			// if there is no protection return None
			WinAuthAuthenticator auth = item.Authenticator;
			if (auth.AuthenticatorData == null || auth.AuthenticatorData.RequiresPassword == false)
			{
				return DialogResult.None;
			}

			// request the password
			UnprotectPasswordForm getPassForm = new UnprotectPasswordForm();
			getPassForm.Authenticator = auth;
			if (screen != null)
			{
				// center on the current windows screen (in case of multiple monitors)
				getPassForm.StartPosition = FormStartPosition.Manual;
				int left = (screen.Bounds.Width / 2) - (getPassForm.Width / 2) + screen.Bounds.Left;
				int top = (screen.Bounds.Height / 2) - (getPassForm.Height / 2) + screen.Bounds.Top;
				getPassForm.Location = new Point(left, top);
			}
			else
			{
				getPassForm.StartPosition = FormStartPosition.CenterScreen;
			}
			DialogResult result = getPassForm.ShowDialog(this.Parent as Form);
			if (result == DialogResult.OK)
			{
				item.UnprotectCount++;
			}

			return result;
		}

		/// <summary>
		/// Reprotect the authenticator and each action or when the 30second window has expired
		/// </summary>
		/// <param name="item"></param>
		private void ProtectAuthenticator(AuthenticatorListitem item)
		{
			// if already protected just decrement counter
			item.UnprotectCount--;
			if (item.UnprotectCount > 0)
			{
				return;
			}

			// reprotect the authenticator
			WinAuthAuthenticator auth = item.Authenticator;
			if (auth.AuthenticatorData == null)
			{
				return;
			}
			auth.AuthenticatorData.Protect();
			item.UnprotectCount = 0;
		}

		/// <summary>
		/// Preload the context menu items
		/// </summary>
		private void loadContextMenuStrip()
		{
			this.ContextMenuStrip.Items.Clear();

			ToolStripLabel label = new ToolStripLabel();
			label.Name = "contextMenuItemName";
			label.ForeColor = SystemColors.HotTrack;
			this.ContextMenuStrip.Items.Add(label);
			this.ContextMenuStrip.Items.Add(new ToolStripSeparator());
			//
			EventHandler onclick = new EventHandler(ContextMenu_Click);
			//
			ToolStripMenuItem menuitem;
			ToolStripMenuItem subitem;
			//
			menuitem = new ToolStripMenuItem(strings.SetPassword + "...");
			menuitem.Name = "setPasswordMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			this.ContextMenuStrip.Items.Add(new ToolStripSeparator());
			//
			menuitem = new ToolStripMenuItem(strings.ShowCode);
			menuitem.Name = "showCodeMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			menuitem = new ToolStripMenuItem(strings.CopyCode);
			menuitem.Name = "copyCodeMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			this.ContextMenuStrip.Items.Add(new ToolStripSeparator());
			//
			menuitem = new ToolStripMenuItem(strings.ShowSerialAndRestoreCode + "...");
			menuitem.Name = "showRestoreCodeMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			menuitem = new ToolStripMenuItem(strings.ShowSecretKey + "...");
			menuitem.Name = "showGoogleSecretMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			menuitem = new ToolStripMenuItem(strings.ShowSerialKeyAndDeviceId + "...");
			menuitem.Name = "showTrionSecretMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			menuitem = new ToolStripMenuItem(strings.ShowRevocation + "...");
			menuitem.Name = "showSteamSecretMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			this.ContextMenuStrip.Items.Add(new ToolStripSeparator { Name = "steamSeperator" });
			//
			menuitem = new ToolStripMenuItem(strings.ConfirmTrades + "...");
			menuitem.Name = "showSteamTradesMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			this.ContextMenuStrip.Items.Add(new ToolStripSeparator());
			//
			menuitem = new ToolStripMenuItem(strings.Delete);
			menuitem.Name = "deleteMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			menuitem = new ToolStripMenuItem(strings.Rename);
			menuitem.Name = "renameMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			this.ContextMenuStrip.Items.Add(new ToolStripSeparator());
			//
			menuitem = new ToolStripMenuItem(strings.AutoRefresh);
			menuitem.Name = "autoRefreshMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			menuitem = new ToolStripMenuItem(strings.CopyOnNewCode);
			menuitem.Name = "copyOnCodeMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			menuitem = new ToolStripMenuItem(strings.Icon);
			menuitem.Name = "iconMenuItem";
			subitem = new ToolStripMenuItem();
			subitem.Text = strings.IconAuto;
			subitem.Name = "iconMenuItem_default";
			subitem.Tag = string.Empty;
			subitem.Click += ContextMenu_Click;
			menuitem.DropDownItems.Add(subitem);
			menuitem.DropDownItems.Add("-");
			this.ContextMenuStrip.Items.Add(menuitem);
			int iconindex = 1;
			var parentItem = menuitem;
			foreach (Tuple<string,string> entry in WinAuthMain.AUTHENTICATOR_ICONS)
			{
				string icon = entry.Item1;
				string iconfile = entry.Item2;
				if (iconfile.Length == 0)
				{
					parentItem.DropDownItems.Add(new ToolStripSeparator());
				}
				else if (icon.StartsWith("+") == true)
				{
					if (parentItem.Tag is ToolStripMenuItem)
					{
						parentItem = parentItem.Tag as ToolStripMenuItem;
					}

					subitem = new ToolStripMenuItem();
					subitem.Text = icon.Substring(1);
					//subitem.Name = "iconMenuItem_" + iconindex++;
					subitem.Tag = parentItem;
					subitem.Image = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("WinAuth.Resources." + iconfile));
					subitem.ImageAlign = ContentAlignment.MiddleLeft;
					subitem.ImageScaling = ToolStripItemImageScaling.SizeToFit;
					//subitem.Click += ContextMenu_Click;
					parentItem.DropDownItems.Add(subitem);
					parentItem = subitem;
				}
				else
				{
					subitem = new ToolStripMenuItem();
					subitem.Text = icon;
					subitem.Name = "iconMenuItem_" + iconindex++;
					subitem.Tag = iconfile;
					subitem.Image = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("WinAuth.Resources." + iconfile));
					subitem.ImageAlign = ContentAlignment.MiddleLeft;
					subitem.ImageScaling = ToolStripItemImageScaling.SizeToFit;
					subitem.Click += ContextMenu_Click;
					parentItem.DropDownItems.Add(subitem);
				}
			}
			menuitem.DropDownItems.Add("-");
			subitem = new ToolStripMenuItem();
			subitem.Text = strings.Other + "...";
			subitem.Name = "iconMenuItem_0";
			subitem.Tag = "OTHER";
			subitem.Click += ContextMenu_Click;
			menuitem.DropDownItems.Add(subitem);
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			this.ContextMenuStrip.Items.Add(new ToolStripSeparator());
			//
			menuitem = new ToolStripMenuItem(strings.ShortcutKey + "...");
			menuitem.Name = "shortcutKeyMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
			//
			ToolStripSeparator sepitem = new ToolStripSeparator();
			sepitem.Name = "syncMenuSep";
			this.ContextMenuStrip.Items.Add(sepitem);
			//
			menuitem = new ToolStripMenuItem(strings.SyncTime);
			menuitem.Name = "syncMenuItem";
			menuitem.Click += ContextMenu_Click;
			this.ContextMenuStrip.Items.Add(menuitem);
		}

		/// <summary>
		/// Set the options for the context menu items based on the currnet authenticator and state
		/// </summary>
		private void SetContextMenuItems()
		{
			var menu = this.ContextMenuStrip;
			var item = this.CurrentItem;
			WinAuthAuthenticator auth = null;
			if (item == null || (auth = item.Authenticator) == null || auth.AuthenticatorData == null)
			{
				return;
			}

			ToolStripLabel labelitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "contextMenuItemName").FirstOrDefault() as ToolStripLabel;
			labelitem.Text = item.Authenticator.Name;
			if (auth.HotKey != null)
			{
				labelitem.Text += " (" + auth.HotKey.ToString() + ")";
			}

			ToolStripItem sepitem;
			ToolStripMenuItem menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "setPasswordMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.Text = (item.Authenticator.AuthenticatorData.PasswordType == Authenticator.PasswordTypes.Explicit ? strings.ChangeOrRemovePassword + "..." : strings.SetPassword + "...");

			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "showCodeMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.Visible = !auth.AutoRefresh;
			//
			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "showRestoreCodeMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.Visible = (auth.AuthenticatorData is BattleNetAuthenticator);
			//
			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "showGoogleSecretMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.Visible = (auth.AuthenticatorData is GoogleAuthenticator || auth.AuthenticatorData is HOTPAuthenticator);
			//
			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "showTrionSecretMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.Visible = (auth.AuthenticatorData is TrionAuthenticator);
			//
			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "showSteamSecretMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.Visible = (auth.AuthenticatorData is SteamAuthenticator);
			menuitem.Enabled = (auth.AuthenticatorData is SteamAuthenticator && string.IsNullOrEmpty(((SteamAuthenticator)auth.AuthenticatorData).SteamData) == false);
			//
			sepitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "steamSeperator").FirstOrDefault() as ToolStripItem;
			sepitem.Visible = (auth.AuthenticatorData is SteamAuthenticator);
			//
			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "showSteamTradesMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.Visible = (auth.AuthenticatorData is SteamAuthenticator);
			menuitem.Enabled = (auth.AuthenticatorData is SteamAuthenticator && string.IsNullOrEmpty(((SteamAuthenticator)auth.AuthenticatorData).SteamData) == false);
			//
			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "autoRefreshMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.Visible = !(auth.AuthenticatorData is HOTPAuthenticator);
			menuitem.CheckState = (auth.AutoRefresh == true ? CheckState.Checked : CheckState.Unchecked);
			menuitem.Enabled = (auth.AuthenticatorData.RequiresPassword == false && auth.AuthenticatorData.PasswordType != Authenticator.PasswordTypes.Explicit);
			//
			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "copyOnCodeMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.CheckState = (auth.CopyOnCode == true ? CheckState.Checked : CheckState.Unchecked);
			//
			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "iconMenuItem").FirstOrDefault() as ToolStripMenuItem;
			ToolStripMenuItem subitem = menuitem.DropDownItems.Cast<ToolStripItem>().Where(i => i.Name == "iconMenuItem_default").FirstOrDefault() as ToolStripMenuItem;
			subitem.CheckState = CheckState.Checked;
			foreach (ToolStripItem iconitem in menuitem.DropDownItems)
			{
				if (iconitem is ToolStripMenuItem && iconitem.Tag is string)
				{
					ToolStripMenuItem iconmenuitem = (ToolStripMenuItem)iconitem;
					if (string.IsNullOrEmpty((string)iconmenuitem.Tag) && string.IsNullOrEmpty(auth.Skin) == true)
					{
						iconmenuitem.CheckState = CheckState.Checked;
					}
					else if (string.Compare((string)iconmenuitem.Tag, auth.Skin) == 0)
					{
						iconmenuitem.CheckState = CheckState.Checked;
					}
					else
					{
						iconmenuitem.CheckState = CheckState.Unchecked;
					}
				}
			}
			//
			sepitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "syncMenuSep").FirstOrDefault() as ToolStripItem;
			sepitem.Visible = !(auth.AuthenticatorData is HOTPAuthenticator);
			menuitem = menu.Items.Cast<ToolStripItem>().Where(i => i.Name == "syncMenuItem").FirstOrDefault() as ToolStripMenuItem;
			menuitem.Visible = !(auth.AuthenticatorData is HOTPAuthenticator);
		}

		/// <summary>
		/// Process the context menu item
		/// </summary>
		/// <param name="menuitem"></param>
		private void ProcessMenu(ToolStripItem menuitem)
		{
			var item = this.CurrentItem;
			var auth = item.Authenticator;

			// check and perform each menu
			if (menuitem.Name == "setPasswordMenuItem")
			{
				// check if the authentcated is still protected
				DialogResult wasprotected = UnprotectAuthenticator(item);
				if (wasprotected == DialogResult.Cancel)
				{
					return;
				}
				try
				{
					// show the new password form
					SetPasswordForm form = new SetPasswordForm();
					if (form.ShowDialog(this.Parent as Form) != DialogResult.OK)
					{
						return;
					}

					// set the encrpytion
					string password = form.Password;
					if (string.IsNullOrEmpty(password) == false)
					{
						auth.AuthenticatorData.SetEncryption(Authenticator.PasswordTypes.Explicit, password);
						// can't have autorefresh on
						auth.AutoRefresh = false;

						item.UnprotectCount = 0;
						item.DisplayUntil = DateTime.MinValue;
						item.LastUpdate = DateTime.MinValue;
						item.LastCode = null;
					}
					else
					{
						auth.AuthenticatorData.SetEncryption(Authenticator.PasswordTypes.None);
					}
					// make sure authenticator is saved
					auth.MarkChanged();
					RefreshCurrentItem();
				}
				finally
				{
					if (wasprotected == DialogResult.OK)
					{
						ProtectAuthenticator(item);
					}
				}
			}
			else if (menuitem.Name == "showCodeMenuItem")
			{
				// check if the authentcated is still protected
				if (UnprotectAuthenticator(item) == DialogResult.Cancel)
				{
					return;
				}

				// reduce unprotect count if already displayed
				if (item.DisplayUntil != DateTime.MinValue)
				{
					ProtectAuthenticator(item);
				}

				item.LastCode = auth.CurrentCode;
				item.LastUpdate = DateTime.Now;
				item.DisplayUntil = DateTime.Now.AddSeconds(10);
				RefreshCurrentItem();
			}
			else if (menuitem.Name == "copyCodeMenuItem")
			{
				// check if the authentcated is still protected
				DialogResult wasprotected = UnprotectAuthenticator(item);
				if (wasprotected == DialogResult.Cancel)
				{
					return;
				}
				try
				{
					auth.CopyCodeToClipboard(this.Parent as Form, item.LastCode, true);
				}
				finally
				{
					if (wasprotected == DialogResult.OK)
					{
						ProtectAuthenticator(item);
					}
				}
			}
			else if (menuitem.Name == "autoRefreshMenuItem")
			{
				auth.AutoRefresh = !auth.AutoRefresh;
				item.LastUpdate = DateTime.Now;
				item.DisplayUntil = DateTime.MinValue;
				RefreshCurrentItem();
			}
			else if (menuitem.Name == "shortcutKeyMenuItem")
			{
				DialogResult wasprotected = UnprotectAuthenticator(item);
				if (wasprotected == DialogResult.Cancel)
				{
					return;
				}
				try
				{
					SetShortcutKeyForm form = new SetShortcutKeyForm();
					form.Hotkey = auth.HotKey;
					if (form.ShowDialog(this.Parent as Form) != DialogResult.OK)
					{
						return;
					}
					auth.HotKey = form.Hotkey;
				}
				finally
				{
					if (wasprotected == DialogResult.OK)
					{
						ProtectAuthenticator(item);
					}
				}
			}
			else if (menuitem.Name == "copyOnCodeMenuItem")
			{
				auth.CopyOnCode = !auth.CopyOnCode;
			}
			else if (menuitem.Name == "showRestoreCodeMenuItem")
			{
				// check if the authentcated is still protected
				DialogResult wasprotected = UnprotectAuthenticator(item);
				if (wasprotected == DialogResult.Cancel)
				{
					return;
				}
				try
				{
					if (wasprotected != DialogResult.OK)
					{
						// confirm current main password
						var mainform = this.Parent as WinAuthForm;
						if ((mainform.Config.PasswordType & Authenticator.PasswordTypes.Explicit) != 0)
						{
							bool invalidPassword = false;
							while (true)
							{
								GetPasswordForm checkform = new GetPasswordForm();
								checkform.InvalidPassword = invalidPassword;
								var result = checkform.ShowDialog(this);
								if (result == DialogResult.Cancel)
								{
									return;
								}
								if (mainform.Config.IsPassword(checkform.Password) == true)
								{
									break;
								}
								invalidPassword = true;
							}
						}
					}

					// show the serial and restore code for Battle.net authenticator				
					ShowRestoreCodeForm form = new ShowRestoreCodeForm();
					form.CurrentAuthenticator = auth;
					form.ShowDialog(this.Parent as Form);
				}
				finally
				{
					if (wasprotected == DialogResult.OK)
					{
						ProtectAuthenticator(item);
					}
				}
			}
			else if (menuitem.Name == "showGoogleSecretMenuItem")
			{
				// check if the authentcated is still protected
				DialogResult wasprotected = UnprotectAuthenticator(item);
				if (wasprotected == DialogResult.Cancel)
				{
					return;
				}
				try
				{
					if (wasprotected != DialogResult.OK)
					{
						// confirm current main password
						var mainform = this.Parent as WinAuthForm;
						if ((mainform.Config.PasswordType & Authenticator.PasswordTypes.Explicit) != 0)
						{
							bool invalidPassword = false;
							while (true)
							{
								GetPasswordForm checkform = new GetPasswordForm();
								checkform.InvalidPassword = invalidPassword;
								var result = checkform.ShowDialog(this);
								if (result == DialogResult.Cancel)
								{
									return;
								}
								if (mainform.Config.IsPassword(checkform.Password) == true)
								{
									break;
								}
								invalidPassword = true;
							}
						}
					}

					// show the secret key for Google authenticator				
					ShowSecretKeyForm form = new ShowSecretKeyForm();
					form.CurrentAuthenticator = auth;
					form.ShowDialog(this.Parent as Form);
				}
				finally
				{
					if (wasprotected == DialogResult.OK)
					{
						ProtectAuthenticator(item);
					}
				}
			}
			else if (menuitem.Name == "showTrionSecretMenuItem")
			{
				// check if the authenticator is still protected
				DialogResult wasprotected = UnprotectAuthenticator(item);
				if (wasprotected == DialogResult.Cancel)
				{
					return;
				}
				try
				{
					// show the secret key for Trion authenticator				
					ShowTrionSecretForm form = new ShowTrionSecretForm();
					form.CurrentAuthenticator = auth;
					form.ShowDialog(this.Parent as Form);
				}
				finally
				{
					if (wasprotected == DialogResult.OK)
					{
						ProtectAuthenticator(item);
					}
				}
			}
			else if (menuitem.Name == "showSteamSecretMenuItem")
			{
				// check if the authenticator is still protected
				DialogResult wasprotected = UnprotectAuthenticator(item);
				if (wasprotected == DialogResult.Cancel)
				{
					return;
				}

				try
				{
					if (wasprotected != DialogResult.OK)
					{
						// confirm current main password
						var mainform = this.Parent as WinAuthForm;
						if ((mainform.Config.PasswordType & Authenticator.PasswordTypes.Explicit) != 0)
						{
							bool invalidPassword = false;
							while (true)
							{
								GetPasswordForm checkform = new GetPasswordForm();
								checkform.InvalidPassword = invalidPassword;
								var result = checkform.ShowDialog(this);
								if (result == DialogResult.Cancel)
								{
									return;
								}
								if (mainform.Config.IsPassword(checkform.Password) == true)
								{
									break;
								}
								invalidPassword = true;
							}
						}
					}

					// show the secret key for Google authenticator				
					ShowSteamSecretForm form = new ShowSteamSecretForm();
					form.CurrentAuthenticator = auth;
					form.ShowDialog(this.Parent as Form);
				}
				finally
				{
					if (wasprotected == DialogResult.OK)
					{
						ProtectAuthenticator(item);
					}
				}
			}
			else if (menuitem.Name == "showSteamTradesMenuItem")
			{
				// check if the authenticator is still protected
				DialogResult wasprotected = UnprotectAuthenticator(item);
				if (wasprotected == DialogResult.Cancel)
				{
					return;
				}

				try
				{
					// show the Steam trades dialog
					ShowSteamTradesForm form = new ShowSteamTradesForm();
					form.Authenticator = auth;
					form.ShowDialog(this.Parent as Form);
				}
				finally
				{
					if (wasprotected == DialogResult.OK)
					{
						ProtectAuthenticator(item);
					}
				}
			}
			else if (menuitem.Name == "deleteMenuItem")
			{
				if (WinAuthForm.ConfirmDialog(this.Parent as Form, strings.DeleteAuthenticatorWarning, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
				{
					int index = item.Index;
					this.Items.Remove(item);
					ItemRemoved(this, new AuthenticatorListItemRemovedEventArgs(item));
					if (index >= this.Items.Count)
					{
						index = this.Items.Count - 1;
					}
					this.CurrentItem = (this.Items.Count != 0 ? this.Items[index] as AuthenticatorListitem : null);
					// reset the correct indexes of our items
					for (int i = 0; i < this.Items.Count; i++)
					{
						(this.Items[i] as AuthenticatorListitem).Index = i;
					}
				}
			}
			else if (menuitem.Name == "renameMenuItem")
			{
				int y = (this.ItemHeight * item.Index) - (this.TopIndex * this.ItemHeight) + 8;
				RenameTextbox.Location = new Point(64, y);
				RenameTextbox.Text = auth.Name;
				RenameTextbox.Tag = item;
				RenameTextbox.Visible = true;
				RenameTextbox.Focus();
			}
			else if (menuitem.Name == "syncMenuItem")
			{
				// check if the authentcated is still protected
				DialogResult wasprotected = UnprotectAuthenticator(item);
				if (wasprotected == DialogResult.Cancel)
				{
					return;
				}
				Cursor cursor = Cursor.Current;
				try {
					Cursor.Current = Cursors.WaitCursor;
					auth.Sync();
					RefreshItem(item);
				}
				finally
				{
					Cursor.Current = cursor;
					if (wasprotected == DialogResult.OK)
					{
						ProtectAuthenticator(item);
					}
				}
			}
			else if (menuitem.Name.StartsWith("iconMenuItem_") == true)
			{
				if (menuitem.Tag is string && string.Compare((string)menuitem.Tag, "OTHER") == 0)
				{
					do
					{
						// other..choose an image file
						OpenFileDialog ofd = new OpenFileDialog();
						ofd.AddExtension = true;
						ofd.CheckFileExists = true;
						ofd.DefaultExt = "png";
						ofd.InitialDirectory = Directory.GetCurrentDirectory();
						ofd.FileName = string.Empty;
						ofd.Filter = "PNG Image Files (*.png)|*.png|GIF Image Files (*.gif)|*.gif|All Files (*.*)|*.*";
						ofd.RestoreDirectory = true;
						ofd.ShowReadOnly = false;
						ofd.Title = strings.LoadIconImage + " (png or gif @ 48x48)";
						DialogResult result = ofd.ShowDialog(this);
						if (result != System.Windows.Forms.DialogResult.OK)
						{
							return;
						}
						try
						{
							// get the image and create an icon if not already the right size
							using (Bitmap iconimage = (Bitmap)Image.FromFile(ofd.FileName))
							{
								if (iconimage.Width != ICON_WIDTH || iconimage.Height != ICON_HEIGHT)
								{
                  using (Bitmap scaled = new Bitmap(ICON_WIDTH, ICON_HEIGHT))
                  {
                    using (Graphics g = Graphics.FromImage(scaled))
                    {
                      g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                      g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                      g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                      g.DrawImage(iconimage, new Rectangle(0, 0, ICON_WIDTH, ICON_HEIGHT));
                    }
                    auth.Icon = scaled;
                  }
								}
								else
								{
									auth.Icon = iconimage;
								}
								RefreshCurrentItem();
							}
						}
						catch (Exception ex)
						{
							if (MessageBox.Show(this.Parent as Form,
								string.Format(strings.ErrorLoadingIconFile, ex.Message),
								WinAuthMain.APPLICATION_NAME,
								MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
							{
								continue;
							}
						}
						break;
					} while (true);
				}
				else 
				{
					auth.Skin = (((string)menuitem.Tag).Length != 0 ? (string)menuitem.Tag : null);
					RefreshCurrentItem();
				}
			}
		}

		/// <summary>
		/// Get the current code for an item in the list, unprotecting and reprotecting the authenticator if necessary
		/// </summary>
		/// <param name="item">List item to get code</param>
		/// <returns>code or null if failed (i.e. bad password)</returns>
		public string GetItemCode(AuthenticatorListitem item, Screen screen = null)
		{
			WinAuthAuthenticator auth = item.Authenticator;

			// check if the authentcated is still protected
			DialogResult wasprotected = UnprotectAuthenticator(item, screen);
			if (wasprotected == DialogResult.Cancel)
			{
				return null;
			}

			try {
				return auth.CurrentCode;
			}
			finally
			{
				if (wasprotected == DialogResult.OK)
				{
					ProtectAuthenticator(item);
				}
			}
		}

		/// <summary>
		/// Refresh the current item by invalidating it
		/// </summary>
		private void RefreshCurrentItem()
		{
			RefreshItem(this.CurrentItem);
		}

		/// <summary>
		/// Refresh the item by invalidating it
		/// </summary>
		private void RefreshItem(AuthenticatorListitem item)
		{
			bool hasvscroll = (this.Height < (this.Items.Count * this.ItemHeight));
			int y = (this.ItemHeight * item.Index) - (this.ItemHeight * this.TopIndex);
			Rectangle rect = new Rectangle(0, y, this.Width, this.Height);
			this.Invalidate(rect, false);
		}

		/// <summary>
		/// Convert a Bitmap into grayscale
		/// http://stackoverflow.com/questions/4669317/how-to-convert-a-bitmap-image-to-black-and-white-in-c
		/// </summary>
		/// <param name="bmp">Bitmap to convert</param>
		/// <returns>Original bitmap but grayscale</returns>
		private static Bitmap GrayScale(Bitmap bmp)
		{
			int rgb;
			Color c;

			for (int y = 0; y < bmp.Height; y++)
			{
				for (int x = 0; x < bmp.Width; x++)
				{
					c = bmp.GetPixel(x, y);
					rgb = (int)((c.R * 0.3) + (c.G * 0.59) + (c.B * 0.11));
					bmp.SetPixel(x, y, Color.FromArgb(rgb, rgb, rgb));
				}
			}
			return bmp;
		}

		/// <summary>
		/// Lighten or darken a bitmap
		/// </summary>
		/// <param name="bmp">Bitmap</param>
		/// <param name="amount">amount to change 0-255</param>
		/// <returns>Original bitmap but darkened</returns>
		private static Bitmap Lighten(Bitmap bmp, int amount)
		{
			Color c;
			int r, g, b;

			for (int y = 0; y < bmp.Height; y++)
			{
				for (int x = 0; x < bmp.Width; x++)
				{
					c = bmp.GetPixel(x, y);
					r = Math.Max(Math.Min(c.R + amount, 255), 0);
					g = Math.Max(Math.Min(c.G + amount, 255), 0);
					b = Math.Max(Math.Min(c.B + amount, 255), 0);
					bmp.SetPixel(x, y, Color.FromArgb(r, g, b));
				}
			}
			return bmp;
		}

#endregion

#region Owner Draw

		/// <summary>
		/// Calculate the maximum available label with based on the currnet control size
		/// </summary>
		/// <param name="totalWidth">control size</param>
		/// <returns>maximum possible width for label</returns>
		protected int GetMaxAvailableLabelWidth(int totalWidth)
		{
			return totalWidth - MARGIN_LEFT - ICON_WIDTH - ICON_MARGIN_RIGHT - PIE_WIDTH - MARGIN_RIGHT;
		}

		/// <summary>
		/// Calculate the maximum label width based on the currnet names
		/// </summary>
		/// <param name="totalWidth">control size</param>
		/// <returns>maximum possible width for label</returns>
		public int GetMaxItemWidth()
		{
			var items = this.Items.Cast<AuthenticatorListitem>();

			if (items.Where(i => i.AutoWidth == 0).Count() != 0)
			{
				using (Graphics g = this.CreateGraphics())
				{
					using (var font = new Font(this.Font.FontFamily, FONT_SIZE, FontStyle.Regular))
					{
						foreach (AuthenticatorListitem item in items.Where(i => i.AutoWidth == 0))
						{
							WinAuthAuthenticator auth = item.Authenticator;

							SizeF labelsize = g.MeasureString(auth.Name, font);
							item.AutoWidth = MARGIN_LEFT + ICON_WIDTH + ICON_MARGIN_RIGHT + (int)Math.Ceiling(labelsize.Width) + PIE_WIDTH + MARGIN_RIGHT;
						}
					}
				}
			}

			int maxWidth = this.Items.Cast<AuthenticatorListitem>().Max(i => i.AutoWidth);
			return maxWidth;
		}

		/// <summary>
		/// Hook into the default WndProc to make sure we get our redraw messages
		/// </summary>
		/// <param name="m"></param>
		protected override void DefWndProc(ref Message m)
		{
			if (ReadOnly == false || ((m.Msg <= 0x0200 || m.Msg >= 0x020E)
				&& (m.Msg <= 0x0100 || m.Msg >= 0x0109)
				&& m.Msg != 0x2111
				&& m.Msg != 0x87))
			{
				base.DefWndProc(ref m);
			}
		}

		/// <summary>
		/// Event called when an item requires drawing
		/// </summary>
		/// <param name="e"></param>
		/// <param name="cliprect"></param>
		protected void OnDrawItem(DrawItemEventArgs e, Rectangle cliprect)
		{
			// no need to draw nothing
			if (this.Items.Count == 0 || e.Index < 0)
			{
				return;
			}

			AuthenticatorListitem item = this.Items[e.Index] as AuthenticatorListitem;
			WinAuthAuthenticator auth = item.Authenticator;

			// if the item is being dragged, we draw a blank placeholder
			if (item.Dragging == true)
			{
				if (cliprect.IntersectsWith(e.Bounds) == true)
				{
					using (var brush = new SolidBrush(SystemColors.ControlLightLight))
					{
						using (Pen pen = new Pen(SystemColors.Control))
						{
							e.Graphics.DrawRectangle(pen, e.Bounds);
							e.Graphics.FillRectangle(brush, e.Bounds);
						}
					}
				}
				return;
			}

			// draw the requested item
			using (var brush = new SolidBrush(e.ForeColor))
			{
				bool showCode = (auth.AutoRefresh == true || item.DisplayUntil > DateTime.Now);

				e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

				Rectangle rect = new Rectangle(e.Bounds.X + MARGIN_LEFT, e.Bounds.Y + MARGIN_TOP, ICON_WIDTH, ICON_HEIGHT);
				if (cliprect.IntersectsWith(rect) == true)
				{
					using (var icon = auth.Icon)
					{
						if (icon != null)
						{
							e.Graphics.DrawImage(icon, new PointF(e.Bounds.X + MARGIN_LEFT, e.Bounds.Y + MARGIN_TOP));
						}
					}
				}

				using (var font = new Font(e.Font.FontFamily, FONT_SIZE, FontStyle.Regular))
				{
					string label = auth.Name;
					SizeF labelsize = e.Graphics.MeasureString(label.ToString(), font);
					int labelMaxWidth = GetMaxAvailableLabelWidth(e.Bounds.Width);
					if (labelsize.Width > labelMaxWidth)
					{
						StringBuilder newlabel = new StringBuilder(label + "...");
						while ((labelsize = e.Graphics.MeasureString(newlabel.ToString(), font)).Width > labelMaxWidth)
						{
							newlabel.Remove(newlabel.Length - 4, 1);
						}
						label = newlabel.ToString();
					}
					rect = new Rectangle(e.Bounds.X + 64, e.Bounds.Y + MARGIN_TOP, (int)labelsize.Height, (int)labelsize.Width);
					if (cliprect.IntersectsWith(rect) == true)
					{
						e.Graphics.DrawString(label, font, brush, new RectangleF(e.Bounds.X + MARGIN_LEFT + ICON_WIDTH + ICON_MARGIN_RIGHT, e.Bounds.Y + MARGIN_TOP, labelMaxWidth, labelsize.Height));
					}

					string code;
					if (showCode == true)
					{
						try
						{
							// we we aren't autorefresh we just keep the same code up for the 10 seconds so it doesn't change even crossing the 30s boundary
							if (auth.AutoRefresh == false)
							{
								if (item.LastCode == null)
								{
									code = auth.CurrentCode;
								}
								else
								{
									code = item.LastCode;
								}
							}
							else
							{
								code = auth.CurrentCode;
								if (code != item.LastCode && auth.CopyOnCode == true)
								{
									// code has changed - copy to clipboard
									auth.CopyCodeToClipboard(this.Parent as Form, code);
								}
							}
							item.LastCode = code;
							if (code != null && code.Length > 5)
							{
								code = code.Insert(code.Length / 2, " ");
							}
						}
						catch (EncryptedSecretDataException )
						{
							code = "- - - - - -";
						}
					}
					else
					{
						code = "- - - - - -";
					}
					SizeF codesize = e.Graphics.MeasureString(code, e.Font);
					rect = new Rectangle(e.Bounds.X + MARGIN_LEFT + ICON_WIDTH + ICON_MARGIN_RIGHT, e.Bounds.Y + MARGIN_TOP + (int)labelsize.Height + LABEL_MARGIN_BOTTOM, (int)codesize.Width, (int)codesize.Height);
					if (cliprect.IntersectsWith(rect) == true)
					{
						e.Graphics.DrawString(code, e.Font, brush, new PointF(e.Bounds.X + MARGIN_LEFT + ICON_WIDTH + ICON_MARGIN_RIGHT, e.Bounds.Y + MARGIN_TOP + labelsize.Height + LABEL_MARGIN_BOTTOM));
					}
				}

				// draw the refresh image or pie
				rect = new Rectangle(e.Bounds.X + e.Bounds.Width - (MARGIN_RIGHT + ICON_WIDTH), e.Bounds.Y + MARGIN_TOP, ICON_WIDTH, ICON_HEIGHT);
				if (cliprect.IntersectsWith(rect) == true)
				{
					if (auth.AutoRefresh == true)
					{
						using (var piebrush = new SolidBrush(SystemColors.ActiveCaption))
						{
							using (var piepen = new Pen(SystemColors.ActiveCaption))
							{
                //int y = (this.TopIndex * this.ItemHeight) + e.Bounds.y
                //int tillUpdate = ((int)((auth.AuthenticatorData.ServerTime % 30000) / 1000L) + 1) * 12;
                int tillUpdate = (int)Math.Round((decimal)((auth.AuthenticatorData.ServerTime % ((long)auth.AuthenticatorData.Period * 1000L)) / 1000L) * (360M / (decimal)auth.AuthenticatorData.Period));
								e.Graphics.DrawPie(piepen, e.Bounds.X + e.Bounds.Width - (MARGIN_RIGHT + ICON_WIDTH), e.Bounds.Y + MARGIN_TOP + PIE_MARGIN, PIE_WIDTH, PIE_HEIGHT, PIE_STARTANGLE, PIE_SWEEPANGLE);
								e.Graphics.FillPie(piebrush, e.Bounds.X + e.Bounds.Width - (MARGIN_RIGHT + ICON_WIDTH), e.Bounds.Y + MARGIN_TOP + PIE_MARGIN, PIE_WIDTH, PIE_HEIGHT, PIE_STARTANGLE, tillUpdate);
							}
						}
					}
					else
					{
						if (showCode == true)
						{
							using (var piebrush = new SolidBrush(SystemColors.ActiveCaption))
							{
								using (var piepen = new Pen(SystemColors.ActiveCaption))
								{
									int tillUpdate = (int)((item.DisplayUntil.Subtract(DateTime.Now).TotalSeconds * (double)360) / item.DisplayUntil.Subtract(item.LastUpdate).TotalSeconds);
									e.Graphics.DrawPie(piepen, e.Bounds.X + e.Bounds.Width - (MARGIN_RIGHT + ICON_WIDTH), e.Bounds.Y + MARGIN_TOP + PIE_MARGIN, PIE_WIDTH, PIE_HEIGHT, PIE_STARTANGLE, PIE_SWEEPANGLE);
									e.Graphics.FillPie(piebrush, e.Bounds.X + e.Bounds.Width - (MARGIN_RIGHT + ICON_WIDTH), e.Bounds.Y + MARGIN_TOP + PIE_MARGIN, PIE_WIDTH, PIE_HEIGHT, PIE_STARTANGLE, tillUpdate);
								}
							}
						}
						else if (auth.AuthenticatorData != null && auth.AuthenticatorData.RequiresPassword == true)
						{
							e.Graphics.DrawImage(WinAuth.Properties.Resources.RefreshIconWithLock, rect);
						}
						else
						{
							e.Graphics.DrawImage(WinAuth.Properties.Resources.RefreshIcon, rect);
						}
					}
				}

				// draw the separating line
				rect = new Rectangle(e.Bounds.X, e.Bounds.Y + this.ItemHeight - 1, 1, 1);
				if (cliprect.IntersectsWith(rect) == true)
				{
					using (Pen pen = new Pen(SystemColors.Control))
					{
						e.Graphics.DrawLine(pen, e.Bounds.X, e.Bounds.Y + this.ItemHeight - 1, e.Bounds.X + e.Bounds.Width, e.Bounds.Y + this.ItemHeight - 1);
					}
				}
			}
		}

		/// <summary>
		/// Handle the paint event and work out if item needs redrawing
		/// </summary>
		/// <param name="e"></param>
    protected override void OnPaint(PaintEventArgs e)
    {
      using (var brush = new SolidBrush(this.BackColor))
      {
        Region region = new Region(e.ClipRectangle);

        e.Graphics.FillRegion(brush, region);
        if (this.Items.Count > 0)
        {
          for (int i = 0; i < this.Items.Count; ++i)
          {
            Rectangle irect = this.GetItemRectangle(i);
            if (e.ClipRectangle.IntersectsWith(irect))
            {
							if ((this.SelectionMode == SelectionMode.One && this.SelectedIndex == i)
							|| (this.SelectionMode == SelectionMode.MultiSimple && this.SelectedIndices.Contains(i))
							|| (this.SelectionMode == SelectionMode.MultiExtended && this.SelectedIndices.Contains(i)))
							{
								DrawItemEventArgs diea = new DrawItemEventArgs(e.Graphics, this.Font,
										irect, i,
										DrawItemState.Selected, this.ForeColor,
										this.BackColor);
								OnDrawItem(diea, e.ClipRectangle);
								base.OnDrawItem(diea);
							}
							else
							{
								DrawItemEventArgs diea = new DrawItemEventArgs(e.Graphics, this.Font,
										irect, i,
										DrawItemState.Default, this.ForeColor,
										this.BackColor);
								OnDrawItem(diea, e.ClipRectangle);
								base.OnDrawItem(diea);
              }
              region.Complement(irect);
            }
          }
        }
      }

      base.OnPaint(e);
		}

#endregion
	}
}
