// 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>
//

// COMPLETE

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

namespace System.Windows.Forms {
	[DefaultEvent("ValueChanged")]
	[DefaultProperty("Value")]
	public class NumericUpDown : UpDownBase, ISupportInitialize {
		#region Local Variables
		private int	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;
		#endregion	// Local Variables

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

		#region Private Methods
		void wide_number_multiply_by_10(int[] number) {
			long carry = 0;

			for (int i=0; i < number.Length; i++) {
				long multiplication = unchecked(carry + 10 * (long)(uint)number[i]);

				carry = multiplication >> 32;

				number[i] = unchecked((int)multiplication);
			}
		}

		void wide_number_multiply_by_16(int[] number) {
			int carry = 0;

			for (int i=0; i < number.Length; i++) {
				int multiplication = unchecked(carry | (number[i] << 4));

				carry = (number[i] >> 28) & 0x0F;

				number[i] = multiplication;
			}
		}

		void wide_number_divide_by_16(int[] number) {
			int carry = 0;

			for (int i=number.Length - 1; i >= 0; i--) {
				int division = unchecked(carry | ((number[i] >> 4) & 0x0FFFFFFF));

				carry = (number[i] << 28);

				number[i] = division;
			}
		}

		bool wide_number_less_than(int[] left, int[] right) {
			unchecked {
				for (int i=left.Length - 1; i >= 0; i--) {
					uint leftvalue = (uint)left[i];
					uint rightvalue = (uint)right[i];

					if (leftvalue > rightvalue)
						return false;
					if (leftvalue < rightvalue)
						return true;
				}
			}

			// equal
			return false;
		}

		void wide_number_subtract(int[] subtrahend, int[] minuend) {
			long carry = 0;

			unchecked {
				for (int i=0; i < subtrahend.Length; i++) {
					long subtrahendvalue = (uint)subtrahend[i];
					long minuendvalue = (uint)minuend[i];

					long result = subtrahendvalue - minuendvalue + carry;

					if (result < 0) {
						carry = -1;
						result -= int.MinValue;
						result -= int.MinValue;
					}
					else
						carry = 0;

					subtrahend[i] = unchecked((int)result);
				}
			}
		}
		#endregion	// Private Methods

		#region Public Instance Properties
		[DefaultValue(0)]
		public int DecimalPlaces {
			get {
				return decimal_places;
			}

			set {
				decimal_places = value;
				if (!UserEdit) {
					UpdateEditText();
				}
			}
		}

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

			set {
				hexadecimal = value;
				if (!UserEdit) {
					UpdateEditText();
				}
			}
		}

		public decimal Increment {
			get {
				return increment;
			}

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

				increment = value;
			}
		}

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

			set {
				maximum = value;

				if (minimum > maximum)
					minimum = maximum;

				if (dvalue > maximum)
					Value = maximum;
			}
		}

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

			set {
				minimum = value;

				if (maximum < minimum)
					maximum = minimum;

				if (dvalue < minimum)
					Value = minimum;
			}
		}

		[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;

				if (!UserEdit) {
					UpdateEditText();
				}
			}
		}

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

			set {
				if (suppress_validation <= 0) {
					if ((value < minimum) || (value > maximum)) {
						throw new ArgumentException("NumericUpDown.Value must be within the specified Minimum and Maximum values", "value");
					}
				}
				if (value != dvalue) {
					dvalue = value;
					OnValueChanged(EventArgs.Empty);
					UpdateEditText();
				}
			}
		}
		#endregion	// Public Instance Properties

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

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

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

		public void EndInit() {
			suppress_validation--;
			if (suppress_validation == 0)
				UpdateEditText ();
		}

		public override string ToString() {
			return string.Format("{0}: value {1} in range [{2}, {3}]", base.ToString(), dvalue, minimum, maximum);
		}

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

			Value = Math.Min(maximum, unchecked(dvalue + increment));
		}
		#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;
			}

			string acceptable = hexadecimal ? "\b-.,0123456789ABCDEF" : "\b-.,0123456789";

			if (acceptable.IndexOf(e.KeyChar) < 0) {
				// prevent the key from reaching the text box
				e.Handled = true;
			}

			base.OnTextBoxKeyPress(source, e);
		}

		protected virtual void OnValueChanged(EventArgs e) {
			if (ValueChanged != null) {
				ValueChanged(this, e);
			}
		}

		protected void ParseEditText() {
			UserEdit = false;

			try {
				string user_edit_text = Text.Replace(",", "").Trim();

				if (!hexadecimal) {
					dvalue = decimal.Parse(user_edit_text);
				} else {
					dvalue = 0M;

					for (int i=0; i < user_edit_text.Length; i++) {
						int hex_digit = Convert.ToInt32(user_edit_text.Substring(i, 1), 16);

						dvalue = unchecked(dvalue * 16M + (decimal)hex_digit);
					}
				}

				if (dvalue < minimum) {
					dvalue = minimum;
				}

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

				OnValueChanged(EventArgs.Empty);
			}
			catch {}
		}

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

			if (UserEdit)
				ParseEditText(); // validate user input

			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;

				ChangingText = true;
				Text = dvalue.ToString(format_string);
			}
			else {
				// Decimal.ToString doesn't know the "X" formatter, and
				// converting it to an int is narrowing, so do it
				// manually...

				int[] bits = decimal.GetBits(dvalue);

				bool negative = (bits[3] < 0);

				int scale = (bits[3] >> 16) & 0x1F;

				bits[3] = 0;

				int[] radix = new int[4];

				radix[0] = 1;

				for (int i=0; i < scale; i++)
					wide_number_multiply_by_10(radix);

				int num_chars = 0;

				while (!wide_number_less_than(bits, radix)) {
					num_chars++;
					wide_number_multiply_by_16(radix);
				}

				if (num_chars == 0) {
					ChangingText = true;
					Text = "0";
					return;
				}

				StringBuilder chars = new StringBuilder();

				if (negative)
					chars.Append('-');

				for (int i=0; i < num_chars; i++) {
					int digit = 0;

					wide_number_divide_by_16(radix);

					while (!wide_number_less_than(bits, radix)) { // greater than or equals
						digit++;
						wide_number_subtract(bits, radix);
					}

					if (digit < 10) {
						chars.Append((char)('0' + digit));
					} else {
						chars.Append((char)('A' + digit - 10));
					}
				}

				ChangingText = true;
				Text = chars.ToString();
			}
		}

		protected override void ValidateEditText() {
			ParseEditText();
			UpdateEditText();
		}
		#endregion	// Protected Instance Methods

		#region Events
		public event EventHandler ValueChanged;

		[Browsable(false)]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public event EventHandler TextChanged;
		#endregion	// Events
	}
}
