//------------------------------------------------------------------------------
// <copyright file=”*.cs” company=”EhLib Team”>
//     Copyright (c) 2017 Dmitry V. Bolshakov   
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace EhLib
{
  [System.ComponentModel.DesignerCategory("Code")]
  [ToolboxBitmap(typeof(NumericBoxEh), "ToolboxBitmaps.EhLib_NumericBox.bmp")]
  //[ToolboxItem(false)]
  [Description("Enables the user to edit a single numeric value and to display that numeric value in a specified format")]
  public class NumericBoxEh : TextBoxEh
  {

    #region internal fields
    private int decimalPlaces;
    private Nullable<decimal> maximum;
    private Nullable<decimal> minimum;
    private Nullable<decimal> currentValue;
    private decimal increment = 1;
    private bool thousandsSeparator;

    private EventHandler onValueChanged;
    #endregion internal fields

    #region constructor
    public NumericBoxEh()
    {
    }
    #endregion constructor

    #region design-time properties
    [DefaultValue(0)]
    public int DecimalPlaces
    {
      get
      {
        return decimalPlaces;
      }
      set
      {
        if (value < 0 || value > 99)
        {
          throw new ArgumentOutOfRangeException("value", @"InvalidBoundArgument");
        }
        decimalPlaces = value;
        UpdateEditText();
      }
    }

    [DefaultValue(false)]
    public bool ThousandsSeparator
    {
      get
      {
        return thousandsSeparator;
      }
      set
      {
        if (thousandsSeparator != value)
        {
          thousandsSeparator = value;
          UpdateEditText();
        }
      }
    }

    //public int MinDecimalPlaces
    //public int MaxDecimalPlaces

    public Nullable<decimal> Maximum
    {
      get
      {
        return maximum;
      }
      set
      {
        if (maximum != value)
        {
          maximum = value;
          if (minimum > maximum)
          {
            minimum = maximum;
          }

          Value = Constrain(currentValue);
        }
      }
    }

    public Nullable<decimal> Minimum
    {
      get
      {
        return minimum;
      }
      set
      {
        if (minimum != value)
        {
          minimum = value;
          if (minimum > maximum)
          {
            maximum = value;
          }

          Value = Constrain(currentValue);
        }
      }
    }

    [Bindable(true)]
    public Nullable<decimal> Value
    {
      get
      {
        //if (UserEdit)
        //{
        //  ValidateEditText();
        //}
        return currentValue;
      }
      set
      {
        if (value != currentValue)
        {
          currentValue = value;

          OnValueChanged(EventArgs.Empty);
          //currentValueChanged = true;
          UpdateEditText();
        }
      }
    }

    //[DefaultValue((decimal)1)]
    public decimal Increment
    {

      get
      {
        return increment;
      }

      set
      {
        if (value < (decimal)0.0)
        {
          throw new ArgumentOutOfRangeException("value");
        }
        else
        {
          increment = value;
        }
      }
    }

    #endregion design-time properties

    #region events
    public event EventHandler ValueChanged
    {
      add
      {
        onValueChanged += value;
      }
      remove
      {
        onValueChanged -= value;
      }
    }
    #endregion events

    #region internal methos
    protected override void OnLostFocus(EventArgs e)
    {
      bool userTextChanged = UserTextChanged;

      base.OnLostFocus(e);

      if (userTextChanged)
        ValidateEditText();
      else
        UpdateEditText();
    }

    protected override void OnGotFocus(EventArgs e)
    {
      base.OnGotFocus(e);
      UpdateEditText();
    }

    protected override void OnValidating(CancelEventArgs e)
    {
      if (UserTextChanged)
        ValidateEditText();

      base.OnValidating(e);
    }

    public virtual bool ShouldSerializeIncrement()
    {
      return (Increment != 1);
    }

    public virtual void ResetIncrement()
    {
      Increment = 1;
    }

    public virtual bool ShouldSerializeValue()
    {
      return (Value.HasValue);
    }

    public virtual void ResetValue()
    {
      Value = null;
    }

    public virtual bool ShouldSerializeMinimum()
    {
      return (Minimum.HasValue);
    }

    public virtual void ResetMinimum()
    {
      Minimum = null;
    }

    public virtual bool ShouldSerializeMaximum()
    {
      return (Maximum.HasValue);
    }

    public virtual void ResetMaximum()
    {
      Maximum = null;
    }

    protected override void RealignControls()
    {
      base.RealignControls();
    }

    protected virtual void UpdateEditText()
    {
      if (!currentValue.HasValue)
        Text = "";
      else
      {
        if (ContainsFocus)
        {
          Text = currentValue.ToString();
        }
        else
        {
          if (ThousandsSeparator)
            Text = currentValue.Value.ToString("#,0.####################");
          else
            Text = currentValue.ToString();
        }
      }
    }

    protected virtual void ParseEditText()
    {
      string s = Text;

      if (string.IsNullOrEmpty(s))
        Value = null;
      else
      {
        try
        {
          Value = Constrain(decimal.Parse(s, CultureInfo.CurrentCulture));
        }
        catch (FormatException)
        {
          //Leave old value
        }
      }
    }

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

    protected virtual Nullable<decimal> Constrain(Nullable<decimal> value)
    {
      //Debug.Assert(minimum <= maximum, "minimum > maximum");

      if (!value.HasValue) return null;

      if (minimum.HasValue && value.Value < minimum.Value)
        value = minimum.Value;

      if (maximum.HasValue && value.Value > maximum.Value)
        value = maximum.Value;

      return value;
    }

    protected internal override void OnProcessInEditUpDownButton(UpDownEventArgs e)
    {
      if (e.ButtonId == UpDownButtonId.Up)
        IncrementValue(Increment);
      else
        IncrementValue(-Increment);
    }

    protected override void OnMouseWheel(MouseEventArgs e)
    {
      base.OnMouseWheel(e);
      HandledMouseEventArgs hme = e as HandledMouseEventArgs;
      if (hme != null)
      {
        if (hme.Handled)
        {
          return;
        }
        hme.Handled = true;
      }

      if ((ModifierKeys & (Keys.Shift | Keys.Alt)) != 0 || MouseButtons != MouseButtons.None)
      {
        return; // Do not scroll when Shift or Alt key is down, or when a mouse button is down.
      }

      decimal val = Increment;
      if (e.Delta < 0)
        val = -val;

      if ((ModifierKeys & (Keys.Control)) != 0)
        val = val * 10;

      IncrementValue(val);
    }

    protected override void TextBoxControl_KeyPress(object sender, KeyPressEventArgs e)
    {
      base.TextBoxControl_KeyPress(sender, e);

      NumberFormatInfo numberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
      string decimalSeparator = numberFormatInfo.NumberDecimalSeparator;
      //string groupSeparator = numberFormatInfo.NumberGroupSeparator;
      string negativeSign = numberFormatInfo.NegativeSign;

      string keyInput = e.KeyChar.ToString();

      if (char.IsDigit(e.KeyChar))
      {
        // Digits are OK
      }
      else if (keyInput.Equals(negativeSign))
      {
        if (numberFormatInfo.NumberNegativePattern == 1) //-n
        {
          if (Text.Contains(negativeSign))
            Text = Text.Replace(negativeSign, "");
          else
            Text = negativeSign + Text;
          e.Handled = true;
        }
      }
      else if (keyInput == "." ||
               keyInput == ","||
               keyInput == decimalSeparator)
      {
        e.KeyChar = decimalSeparator[0];
        if (Text.Contains(decimalSeparator))
          e.Handled = true;
      }
      else if (e.KeyChar == (char)Keys.Back)
      {
        // Backspace key is OK
      }
      else if ((ModifierKeys & (Keys.Control | Keys.Alt)) != 0)
      {
        // Let the edit control handle control and alt key combinations
      }
      else
      {
        // Eat this invalid key and beep
        e.Handled = true;
        //SafeNativeMethods.MessageBeep(0);
      }

    }

    protected override bool DoCustomPaste()
    {
      string text = Clipboard.GetText();

      StringBuilder sb = new StringBuilder();
      foreach(char c in text)
      {
        if (ValidChar(c))
          sb.Append(c);
      }

      TextBoxControl.SelectedText = sb.ToString();
      return true;
    }

    protected virtual bool ValidChar(char c)
    {
      return char.IsDigit(c);
    }

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

    protected internal override bool IsInEditControlDefaultVisible(EditItem inEditControl)
    {
      return true;
    }
    #endregion internal methos

    #region public methos
    public override void InEditButtonDownDefaultAction(EditItem inEditControl, DownEventArgs e)
    {
      NumericBoxEhManager.DefaultManager.InEditButtonDownDefaultAction(this, inEditControl, e);
    }

    public void IncrementValue(decimal value)
    {
      decimal newValue;

      if (!Value.HasValue)
        newValue = 0;
      else
        newValue = Value.Value;

      Value = newValue + value;
    }
    #endregion public methos

  }
}
