// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2005 Novell, Inc.
//
// Authors:
//	Jonathan Gilbert	<logic@deltaq.org>
//
// Integration into MWF:
//	Peter Bartok		<pbartok@novell.com>
//

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace System.Windows.Forms {
	[DefaultEvent("ValueChanged")]
	[DefaultProperty("Value")]
	[DefaultBindingProperty ("Value")]
	[ClassInterface (ClassInterfaceType.AutoDispatch)]
	[ComVisible (true)]
	public class NumericUpDown : UpDownBase, ISupportInitialize {
		#region Local Variables
		private bool	suppress_validation;
		private int	decimal_places;
		private bool	hexadecimal;
		private decimal	increment;
		private decimal	maximum;
		private decimal	minimum;
		private bool	thousands_separator;
		private decimal	dvalue;
		

		NumericUpDownAccelerationCollection accelerations;
//		private long buttonPressedTicks;
		//private bool isSpinning;
		#endregion	// Local Variables

		#region UIA FrameWork Events
		static object UIAMinimumChangedEvent = new object ();

		internal event EventHandler UIAMinimumChanged {
			add { Events.AddHandler (UIAMinimumChangedEvent, value); }
			remove { Events.RemoveHandler (UIAMinimumChangedEvent, value); }
		}

		internal void OnUIAMinimumChanged (EventArgs e)
		{
			EventHandler eh = (EventHandler) Events [UIAMinimumChangedEvent];
			if (eh != null)
				eh (this, e);
		}

		static object UIAMaximumChangedEvent = new object ();

		internal event EventHandler UIAMaximumChanged {
			add { Events.AddHandler (UIAMaximumChangedEvent, value); }
			remove { Events.RemoveHandler (UIAMaximumChangedEvent, value); }
		}

		internal void OnUIAMaximumChanged (EventArgs e)
		{
			EventHandler eh = (EventHandler) Events [UIAMaximumChangedEvent];
			if (eh != null)
				eh (this, e);
		}

		static object UIASmallChangeChangedEvent = new object ();

		internal event EventHandler UIASmallChangeChanged {
			add { Events.AddHandler (UIASmallChangeChangedEvent, value); }
			remove { Events.RemoveHandler (UIASmallChangeChangedEvent, value); }
		}

		internal void OnUIASmallChangeChanged (EventArgs e)
		{
			EventHandler eh = (EventHandler) Events [UIASmallChangeChangedEvent];
			if (eh != null)
				eh (this, e);
		}
		#endregion

		#region Public Constructors
		public NumericUpDown() {
			suppress_validation = false;
			decimal_places = 0;
			hexadecimal = false;
			increment = 1M;
			maximum = 100M;
			minimum = 0.0M;
			thousands_separator = false;

			Text = "0";
		}
		#endregion	// Public Constructors

		#region Public Instance Properties

		[Browsable (false)]
		[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
		public NumericUpDownAccelerationCollection Accelerations {
			get {
				if (accelerations == null)
					accelerations = new NumericUpDownAccelerationCollection ();
				return accelerations;
			}
		}

		[DefaultValue(0)]
		public int DecimalPlaces {
			get {
				return decimal_places;
			}

			set {
				decimal_places = value;
				UpdateEditText();
			}
		}

		[DefaultValue(false)]
		public bool Hexadecimal {
			get {
				return hexadecimal;
			}

			set {
				hexadecimal = value;
				UpdateEditText();
			}
		}

		public decimal Increment {
			get {

				return increment;
			}

			set {
				if (value < 0) {
					throw new ArgumentOutOfRangeException("value", value, "NumericUpDown increment cannot be negative");
				}

				increment = value;

				// UIA Framework Event: SmallChange Changed
				OnUIASmallChangeChanged (EventArgs.Empty);
			}
		}

		[RefreshProperties(RefreshProperties.All)]
		public decimal Maximum {
			get {
				return maximum;
			}

			set {
				maximum = value;

				if (minimum > maximum)
					minimum = maximum;

				if (dvalue > maximum)
					Value = maximum;

				// UIA Framework Event: Maximum Changed
				OnUIAMaximumChanged (EventArgs.Empty);
			}
		}

		[RefreshProperties(RefreshProperties.All)]
		public decimal Minimum {
			get {
				return minimum;
			}

			set {
				minimum = value;

				if (maximum < minimum)
					maximum = minimum;

				if (dvalue < minimum)
					Value = minimum;

				// UIA Framework Event: Minimum Changed
				OnUIAMinimumChanged (EventArgs.Empty);
			}
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
		public new Padding Padding {
			get { return Padding.Empty; }
			set { }
		}

		[Bindable(false)]
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public override string Text {
			get {
				return base.Text;
			}

			set {
				base.Text = value;
			}
		}

		[DefaultValue(false)]
		[Localizable(true)]
		public bool ThousandsSeparator {
			get {
				return thousands_separator;
			}

			set {
				thousands_separator = value;
				UpdateEditText();
			}
		}

		[Bindable(true)]
		public decimal Value {
			get {
				if (UserEdit)
					ValidateEditText ();
				return dvalue;
			}

			set {
				if (value != dvalue) {
					if (!suppress_validation && ((value < minimum) || (value > maximum))) {
						throw new ArgumentOutOfRangeException ("value", "NumericUpDown.Value must be within the specified Minimum and Maximum values");
					}

					dvalue = value;
					OnValueChanged (EventArgs.Empty);
					UpdateEditText ();
				}
			}
		}
		#endregion	// Public Instance Properties

		#region Public Instance Methods
		public void BeginInit() {
			suppress_validation = true;
		}

		public void EndInit() {
			suppress_validation = false;
			Value = Check (dvalue);
			UpdateEditText ();
		}

		public override string ToString() {
			return string.Format("{0}, Minimum = {1}, Maximum = {2}", base.ToString(), minimum, maximum);
		}

		public override void DownButton() {
			if (UserEdit) {
				ParseEditText ();
			}

			Value = Math.Max (minimum, unchecked (dvalue - increment));

			// UIA Framework Event: DownButton Click
			OnUIADownButtonClick (EventArgs.Empty);
		}

		public override void UpButton() {
			if (UserEdit) {
				ParseEditText ();
			}

			Value = Math.Min (maximum, unchecked (dvalue + increment));

			// UIA Framework Event: UpButton Click
			OnUIAUpButtonClick (EventArgs.Empty);
		}
		#endregion	// Public Instance Methods

		#region Protected Instance Methods
		protected override AccessibleObject CreateAccessibilityInstance() {
			AccessibleObject	acc;

			acc = new AccessibleObject(this);
			acc.role = AccessibleRole.SpinButton;

			return acc;
		}

		protected override void OnTextBoxKeyPress(object source, KeyPressEventArgs e) {
			if ((ModifierKeys & ~Keys.Shift) != Keys.None) {
				return;
			}

			NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat;
			string pressedKey = e.KeyChar.ToString ();

			if ((pressedKey != nfi.NegativeSign) && (pressedKey != nfi.NumberDecimalSeparator) && 
				(pressedKey != nfi.NumberGroupSeparator)) {
				string acceptable = hexadecimal ? "\b0123456789abcdefABCDEF" : "\b0123456789";
				if (acceptable.IndexOf (e.KeyChar) == -1) {
					// FIXME: produce beep to signal that "invalid" key was pressed
					// prevent the key from reaching the text box
					e.Handled = true;
				}
			}

			base.OnTextBoxKeyPress(source, e);
		}

		protected virtual void OnValueChanged(EventArgs e) {
			EventHandler eh = (EventHandler)(Events [ValueChangedEvent]);
			if (eh != null)
				eh (this, e);
		}

		protected void ParseEditText () {
			try {
				if (!hexadecimal) {
					Value = Check (decimal.Parse (Text, CultureInfo.CurrentCulture));
				} else {
					Value = Check (Convert.ToDecimal (Convert.ToInt32 (Text, 16)));
				}
			}
			catch { }
			finally {
				UserEdit = false;
			}
		}

		private decimal Check (decimal val)
		{
			decimal ret = val;
			if (ret < minimum) {
				ret = minimum;
			}

			if (ret > maximum) {
				ret = maximum;
			}

			return ret;
		}

		protected override void UpdateEditText () {
			if (suppress_validation)
				return;

			if (UserEdit)
				ParseEditText ();

			ChangingText = true;
			if (!hexadecimal) {
				// "N" and "F" differ only in that "N" includes commas
				// every 3 digits to the left of the decimal and "F"
				// does not.

				string format_string;

				if (thousands_separator) {
					format_string = "N";
				} else {
					format_string = "F";
				}

				format_string += decimal_places;

				Text = dvalue.ToString (format_string, CultureInfo.CurrentCulture);

			} else {
				// Cast to Int64 to be able to use the "X" formatter.
				// If the value is below Int64.MinValue or above Int64.MaxValue,
				// then an OverflowException will be thrown, as with .NET.
				Text = ((Int64)dvalue).ToString("X", CultureInfo.CurrentCulture);
			}
		}


		protected override void ValidateEditText() {
			ParseEditText ();
			UpdateEditText ();
		}

		protected override void OnLostFocus(EventArgs e) {
			base.OnLostFocus(e);
			if (UserEdit)
				UpdateEditText();
		}

		protected override void OnKeyUp (KeyEventArgs e)
		{
//			isSpinning = false;
			base.OnKeyUp (e);
		}

		protected override void OnKeyDown (KeyEventArgs e)
		{
//			buttonPressedTicks = DateTime.Now.Ticks;
//			isSpinning = true;
			base.OnKeyDown (e);
		}
		#endregion	// Protected Instance Methods

		#region Events
		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public new event EventHandler PaddingChanged {
			add { base.PaddingChanged += value; }
			remove { base.PaddingChanged -= value; }
		}

		static object ValueChangedEvent = new object ();

		public event EventHandler ValueChanged {
			add { Events.AddHandler (ValueChangedEvent, value); }
			remove { Events.RemoveHandler (ValueChangedEvent, value); }
		}

		[Browsable(false)]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public new event EventHandler TextChanged {
			add { base.TextChanged += value; }
			remove { base.TextChanged -= value; }
		}
		#endregion	// Events
	}
}
