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

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;


//DONE: MouseDown when scrollPos > 0
//DONE: Check how it works when Empty Value

//DONE: Validate value in OnValidate OnLeaveFocus
//TODO: Postpone. Validate value OnKeyPress = Enter.
//TODO: Postpone. Ctrl+A do SelectAll then Copy, Copy, Paste, Clear;

/*TODO: Postpone. Add TimePart: TimeMaskSeparator, TimeMask, HideWhenTimeIsZero properties
        New todos
 */

namespace EhLib
{

  [System.ComponentModel.DesignerCategory("Code")]
  [ToolboxBitmap(typeof(DateTimeBoxEh), "ToolboxBitmaps.EhLib_DateTimeBox.bmp")]
  [ToolboxItem(true)]
  [Description("Enables the user to select a date and time, and to display that date and time in a specified format")]
  public class DateTimeBoxEh : BaseEditBoxEh
  {

    private EventHandler valueChanged;
    private EventHandler editItemChanged;
    private EventHandler editItemDisplayTextChanged;

    internal bool IgnoreMouseScreenRectPressed;

    #region constructor
    public DateTimeBoxEh()
    {
      SetStyle(ControlStyles.FixedHeight, true);
    }
    #endregion constructor

    #region design-time properties
    [Bindable(true)]
    public Nullable<DateTime> Value
    {
      get
      {
        return EditControl.Value;
      }
      set
      {
        if (value != EditControl.Value)
        {
          EditControl.Value = value;

          OnValueChanged(EventArgs.Empty);
          Invalidate();
        }
      }
    }

    [DefaultValue("d")]
    public string DateFormatString
    {
      get
      {
        return EditControl.DateFormatString;
      }
      set
      {
        EditControl.DateFormatString = value;
      }
    }

    [DefaultValue("t")]
    public string TimeFormatString
    {
      get
      {
        return EditControl.TimeFormatString;
      }
      set
      {
        EditControl.TimeFormatString = value;
      }
    }

    [DefaultValue(true)]
    public bool HideTimeWhenZero
    {
      get
      {
        return EditControl.HideTimeWhenZero;
      }
      set
      {
        EditControl.HideTimeWhenZero = value;
      }
    }

    [DefaultValue(" ")]
    public string DateTimePartSeparator
    {
      get
      {
        return EditControl.DateTimePartSeparator;
      }
      set
      {
        EditControl.DateTimePartSeparator = value;
      }
    }

    [DefaultValue(HorizontalAlignment.Left)]
    [Localizable(true)]
    public HorizontalAlignment HorzAlign
    {
      get
      {
        return EditControl.HorzAlign;
      }
      set
      {
        EditControl.HorzAlign = value;
      }
    }
    #endregion

    #region run-time properties
    [Bindable(false)]
    new DateTimeEditControl EditControl
    {
      get
      {
        return (DateTimeEditControl)base.EditControl;
      }
    }

    [Bindable(false)]
    public override int PreferredHeight
    {
      get
      {
        int result = EditControl.PreferredHeight;
        if (Border.Style != ControlBorderStyle.None)
        {
          result = result + SystemInformation.BorderSize.Height * 4 + 3;
        }
        return result;
      }
    }

    [Bindable(false)]
    public int SelectedPatternItemIndex
    {
      get
      {
        return EditControl.SelectedPatternItemIndex;
      }
      set
      {
        EditControl.SelectedPatternItemIndex = value;
      }
    }

    [Bindable(false)]
    new public string Text
    {
      get { return EditControl.Text; }
    }

    protected override Padding DefaultPadding
    {
      get
      {
        if (Border.Style == ControlBorderStyle.None)
          return Padding.Empty;
        else
          return new Padding(1, 1, 1, 2);
      }
    }
    #endregion run-time properties

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

    public event EventHandler EditItemChanged
    {
      add
      {
        editItemChanged += value;
      }
      remove
      {
        editItemChanged -= value;
      }
    }
    #endregion events

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

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

    public override void InEditButtonDownDefaultAction(EditItem inEditControl, DownEventArgs e)
    {
      if (!IgnoreMouseScreenRectPressed) 
        DateTimeBoxEhManager.DefaultManager.InEditButtonDownDefaultAction(this, inEditControl, e);
      IgnoreMouseScreenRectPressed = false;
    }

    public void SelectAll()
    {
      EditControl.SelectAll();
    }

    public void Validate()
    {
      EditControl.Validate();
    }
    #endregion public methods

    #region internal methos
    protected override Control CreateEditControl()
    {
      DateTimeEditControl result = new DateTimeEditControl(this);

      result.ValueChanged += EditControl_ValueChanged;
      result.EditItemChagned += EditControl_EditItemChagned;
      //result.EditItem Chagned += EditControl_EditItemChagned;
      //result.BorderStyle = BorderStyle.None;
      result.GotFocus += EditControl_GotFocus;
      result.LostFocus += EditControl_LostFocus;
      result.MouseEnter += EditControl_MouseEnter;
      result.MouseLeave += EditControl_MouseLeave;
      //result.KeyDown += TextBoxControl_KeyDown;
      //result.KeyUp += TextBoxControl_KeyUp;
      //result.KeyPress += TextBoxControl_KeyPress;
      result.TextChanged += EditControl_TextChanged;

      return result;
    }

    private void EditControl_TextChanged(object sender, EventArgs e)
    {
      OnTextChanged(e);
    }

    protected virtual void EditControl_MouseEnter(object sender, EventArgs e)
    {
      MouseOver = true;
      Invalidate(true);
    }

    protected virtual void EditControl_MouseLeave(object sender, EventArgs e)
    {
      if (this.ClientRectangle.Contains(this.PointToClient(MousePosition)))
      {
        EhLibUtils.DoNothing();
      }
      else
      {
        MouseOver = false;
      }
    }

    protected virtual void EditControl_LostFocus(object sender, EventArgs e)
    {
      Invalidate(true);
    }

    protected virtual void EditControl_GotFocus(object sender, EventArgs e)
    {
      Invalidate(true);
    }

    protected virtual void EditControl_ValueChanged(object sender, EventArgs e)
    {
      OnValueChanged(e);
    }

    protected virtual void EditControl_EditItemChagned(object sender, EventArgs e)
    {
      OnEditItemChagned(e);
    }

    protected virtual void EditControl_EditItemDisplayTextChagned(object sender, EventArgs e)
    {
      OnEditItemDisplayTextChagned(e);
    }

    protected internal virtual bool EditControl_ProcessCmdKey(ref Message msg, Keys keyData)
    {
      if (InEditControl != null)
        return InEditControl.ProcessCmdKey(ref msg, keyData);
      else
        return false;
    }

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

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

    protected virtual void OnEditItemDisplayTextChagned(EventArgs e)
    {
      if (editItemDisplayTextChanged != null)
      {
        editItemDisplayTextChanged(this, e);
      }
    }
    //DisplayText

    protected virtual void UpdateEditText()
    {
    }

    protected override bool AutoHeight()
    {
      if (AutoSize)
        return true;
      else
        return false;
    }

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

    protected override bool EditButtonDefaultVisible()
    {
      return true;
    }
    #endregion internal methos

  }

  [ToolboxItem(false)]
  //public class DateTimeEditControl : TextBox
  public class DateTimeEditControl : Control
  {
    private Nullable<DateTime> _value;
    private readonly DateTimeBoxEh dateTimeBox;
    private readonly DateTimeDisplayPattern datePaintPattern;
    private readonly DateTimeDisplayPattern timePaintPattern;

    private string dateFormatString;
    private string timeFormatString;
    private bool hideTimeWhenZero;
    private string dateTimePartSeparator;
    private Rectangle datePartBounds;
    private Rectangle dateTimePartSeparatorBounds;
    private Rectangle timePartBounds;

    private int selectedPatternItemIndex;
    readonly List<DateTimePatternItemPosition> mutableDatePatternItems;
    //readonly List<DateTimePatternItem> mutableTimePatternItems;
    private bool internalPatternSet;
    private int scrollPos;
    private bool allSeleted;

    private EventHandler onValueChanged;
    private EventHandler onEditItemChagned;
    private EventHandler onEditItemDisplayTextChanged;
    private HorizontalAlignment horzAlign;
    private int alignShift;

    #region constructor
    public DateTimeEditControl(DateTimeBoxEh dateTimeBox)
    {
      this.dateTimeBox = dateTimeBox;
      //SetStyle(ControlStyles.Selectable, true);
      SetStyle(ControlStyles.UserMouse, true);

      datePaintPattern = new DateTimeDisplayPattern();
      datePaintPattern.DisplayTextChanged += PatternDisplayTextChanged;
      datePaintPattern.DigitalValueChanged += PatternDigitalValueChanged;

      timePaintPattern = new DateTimeDisplayPattern();
      timePaintPattern.DisplayTextChanged += PatternDisplayTextChanged;
      timePaintPattern.DigitalValueChanged += PatternDigitalValueChanged;

      mutableDatePatternItems = new List<DateTimePatternItemPosition>();
      //mutableTimePatternItems = new List<DateTimePatternItem>();

      dateFormatString = "d";
      timeFormatString = "t";
      hideTimeWhenZero = true;
      dateTimePartSeparator = " ";
      selectedPatternItemIndex = -1;
      horzAlign = HorizontalAlignment.Left;
      InitData();
    }

    private void InitData()
    {
      ReparseFormat();
    }

    #endregion constructor

    #region properties
    public DateTimeBoxEh DateTimeBox { get { return dateTimeBox; } }

    public Nullable<DateTime> Value
    {
      get
      {
        if (Focused)
          CheckFinishUpdateItemValueFromText();
        return _value;
      }
      set
      {
        if (value != this._value)
        {
          this._value = value;

          OnValueChanged(EventArgs.Empty);
        }
      }
    }

    public int PreferredHeight
    {
      get
      {
        return FontHeight;
      }
    }

    [DefaultValue("d")]
    public string DateFormatString
    {
      get
      {
        return dateFormatString;
      }
      set
      {
        if (value != dateFormatString)
        {
          dateFormatString = value;
          ReparseFormat();
        }
      }
    }

    [DefaultValue("t")]
    public string TimeFormatString
    {
      get
      {
        return timeFormatString;
      }
      set
      {
        if (value != timeFormatString)
        {
          timeFormatString = value;
          ReparseFormat();
        }
      }
    }

    [DefaultValue(true)]
    public bool HideTimeWhenZero
    {
      get
      {
        return hideTimeWhenZero;
      }
      set
      {
        if (value != hideTimeWhenZero)
        {
          hideTimeWhenZero = value;
          ReparseFormat();
        }
      }
    }

    [DefaultValue(" ")]
    public string DateTimePartSeparator
    {
      get
      {
        return dateTimePartSeparator;
      }
      set
      {
        if (value != dateTimePartSeparator)
        {
          dateTimePartSeparator = value;
          ReparseFormat();
        }
      }
    }

    public int SelectedPatternItemIndex
    {
      get
      {
        return selectedPatternItemIndex;
      }
      set
      {
        if (value != selectedPatternItemIndex)
        {
          if (value < 0 || value >= mutableDatePatternItems.Count)
            throw new InvalidOperationException("SelectedPatternItemIndex is out of range");
          selectedPatternItemIndex = value;
          ClampSelectedItemInView();
          allSeleted = false;
          Invalidate();
        }
      }
    }

    [DefaultValue(HorizontalAlignment.Left)]
    [Localizable(true)]
    public HorizontalAlignment HorzAlign
    {
      get
      {
        return horzAlign;
      }
      set
      {
        if (value != horzAlign)
        {
          horzAlign = value;
          ReparseFormat();
        }
      }
    }
    #endregion properties

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

    public event EventHandler EditItemChagned
    {
      add
      {
        onEditItemChagned += value;
      }
      remove
      {
        onEditItemChagned -= value;
      }
    }

    public event EventHandler EditItemDisplayTextChanged
    {
      add
      {
        onEditItemDisplayTextChanged += value;
      }
      remove
      {
        onEditItemDisplayTextChanged -= value;
      }
    }
    #endregion events

    #region internal methods
    protected virtual void OnValueChanged(EventArgs e)
    {
      UpdatePatternValue();
      UpdateText();
      if (onValueChanged != null)
      {
        onValueChanged(this, e);
      }
    }

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

    protected virtual void OnEditItemDisplayTextChagned(EventArgs e)
    {
      DeselectAll();
      if (onEditItemDisplayTextChanged != null)
      {
        onEditItemDisplayTextChanged(this, e);
      }
    }

    protected virtual void UpdateText()
    {
      if (!Focused && !Value.HasValue)
      {
        Text = "";
        return;
      }

      string text = "";

      if (!String.IsNullOrEmpty(DateFormatString))
        text = datePaintPattern.DisplayText;

      if (HideTimeWhenZero && !Focused && Value.HasValue && Value.Value.TimeOfDay.Ticks == 0)
      {
        EhLibUtils.DoNothing();
      }
      else
      {
        text = text + DateTimePartSeparator;
        text = text + timePaintPattern.DisplayText;
      }

      if (String.Compare(Text, text) != 0)
        Text = text;

      UpdateAlignShift();
    }

    private void UpdateAlignShift()
    {
      if (!IsHandleCreated)
        alignShift = 0;
      else
      {
        int textWidth = EhLibUtils.MeasureText(EhLibUtils.DisplayGraphicsCash, Text, Font).Width;
        HorizontalAlignment checkingAlign = HorzAlign;
        if (textWidth > ClientSize.Width)
          checkingAlign = HorizontalAlignment.Left;
        switch (checkingAlign)
        {
          case HorizontalAlignment.Left:
            alignShift = 0;
            break;
          case HorizontalAlignment.Right:
            alignShift = ClientSize.Width - textWidth;
            break;
          case HorizontalAlignment.Center:
            alignShift = (ClientSize.Width - textWidth) / 2;
            break;
        }
      }
    }

    protected virtual void UpdatePatternValue()
    {
      if (internalPatternSet) return;

      internalPatternSet = true;
      try
      {
        datePaintPattern.ParseValue(Value);
        datePaintPattern.FillDataMetric(EhLibUtils.DisplayGraphicsCash, Font);
        timePaintPattern.ParseValue(Value);
        timePaintPattern.FillDataMetric(EhLibUtils.DisplayGraphicsCash, Font);

        datePartBounds.X = 0;
        datePartBounds.Width = datePaintPattern.CalcWidth();

        dateTimePartSeparatorBounds.X = datePartBounds.Right;
        dateTimePartSeparatorBounds.Width = EhLibUtils.MeasureText(EhLibUtils.DisplayGraphicsCash, DateTimePartSeparator, Font).Width;

        timePartBounds.X = dateTimePartSeparatorBounds.Right;
        timePartBounds.Width = datePaintPattern.CalcWidth();

        UpdateMutableItemsBounds();
        Invalidate();
      }
      finally
      {
        internalPatternSet = false;
      }
    }

    protected virtual void ReparseFormat()
    {
      Debug.Assert(internalPatternSet == false, "internalPatternSet is true");

      internalPatternSet = true;
      try
      {
        datePaintPattern.ParseFormat(dateFormatString);
        timePaintPattern.ParseFormat(timeFormatString);
        RefillMutableItems();
      }
      finally
      {
        internalPatternSet = false;
      }

      UpdateText();
      UpdatePatternValue();
    }

    protected virtual void RefillMutableItems()
    {

      mutableDatePatternItems.Clear();
      foreach (DateTimePatternItem pi in datePaintPattern.PatternItems)
      {
        if (pi.IsMutable())
        {
          var pip = new DateTimePatternItemPosition();
          pip.Pattern = datePaintPattern;
          pip.PatternItem = pi;
          pip.StartPos = pi.StartPos + datePartBounds.Left;
          pip.Width = pi.Width;

          mutableDatePatternItems.Add(pip);
        }
      }

      foreach (DateTimePatternItem pi in timePaintPattern.PatternItems)
      {
        if (pi.IsMutable())
        {
          var pip = new DateTimePatternItemPosition();
          pip.Pattern = timePaintPattern;
          pip.PatternItem = pi;
          pip.StartPos = pi.StartPos + timePartBounds.Left;
          pip.Width = pi.Width;

          mutableDatePatternItems.Add(pip);
        }
      }

      if (mutableDatePatternItems.Count >= 0)
        selectedPatternItemIndex = 0;
      else
        selectedPatternItemIndex = -1;
    }

    protected virtual void UpdateMutableItemsBounds()
    {
      foreach (DateTimePatternItemPosition pip in mutableDatePatternItems)
      {
        Rectangle patternBounds;
        if (pip.Pattern == datePaintPattern)
          patternBounds = datePartBounds;
        else
          patternBounds = timePartBounds;
        pip.StartPos = pip.PatternItem.StartPos + patternBounds.Left;
        pip.Width = pip.PatternItem.Width;
      }
      UpdateText();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
      string text;
      Rectangle textRect;
      Brush backBrush;
      Color frontColor;

      base.OnPaint(e);
      Font drawFont;

      drawFont = Font;
      if (Focused && allSeleted)
      {
        frontColor = SystemColors.HighlightText;
        backBrush = new SolidBrush(SystemColors.Highlight);
      }
      else
      {
        frontColor = SystemColors.WindowText;
        backBrush = new SolidBrush(SystemColors.Window);
      }

      e.Graphics.FillRectangle(backBrush, ClientRectangle);

      if (!Focused && !Value.HasValue) return;

      text = Text;
      textRect = ClientRectangle;
      textRect.X = -scrollPos + alignShift;
      textRect.Width = ClientSize.Width + scrollPos;
      EhLibUtils.DrawText(e.Graphics, text, drawFont, textRect, frontColor,
        HorizontalAlignment.Left, VerticalAlignment.Top, TextFormatFlagsEh.None);

      if (Focused && SelectedPatternItemIndex >= 0)
      {
        DateTimePatternItemPosition pip = mutableDatePatternItems[SelectedPatternItemIndex];
        textRect.X = pip.StartPos - scrollPos + alignShift;
        textRect.Width = pip.Width;
        frontColor = SystemColors.HighlightText;
        backBrush = new SolidBrush(SystemColors.Highlight);

        e.Graphics.FillRectangle(backBrush, textRect);
        EhLibUtils.DrawText(e.Graphics, pip.PatternItem.TextValue, Font, textRect, frontColor,
          HorizontalAlignment.Left, VerticalAlignment.Top, TextFormatFlagsEh.None);
      }
    }

    protected override void OnGotFocus(EventArgs e)
    {
      base.OnGotFocus(e);
      UpdateText();
      Invalidate(true);
    }

    protected override void OnLostFocus(EventArgs e)
    {
      base.OnLostFocus(e);
      UpdateText();
      Invalidate(true);
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
      base.OnMouseDown(e);
      DateTimePatternItemPosition piPos = GetMutablePatternItemAtPos(e.Location);
      if (piPos != null)
      {
        int piIndex = mutableDatePatternItems.IndexOf(piPos);
        if (piIndex >= 0 && piIndex != SelectedPatternItemIndex)
        {
          CheckFinishUpdateItemValueFromText();
          SelectedPatternItemIndex = piIndex;
          Invalidate();
        }
      }
    }

    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 (!Focused || 
          (ModifierKeys & (Keys.Shift | Keys.Alt)) != 0 || 
          (MouseButtons != MouseButtons.None))
      {
        return;
      }

      if (e.Delta > 0)
        IncrementPatternItemValue();
      else if (e.Delta < 0)
        DecrementPatternItemValue();
    }

    protected override bool IsInputKey(Keys keyData)
    {
      switch (keyData & Keys.KeyCode)
      {
        case Keys.Right:
        case Keys.Left:
        case Keys.Up:
        case Keys.Down:
          return true;
      }
      return base.IsInputKey(keyData);
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
      base.OnKeyDown(e);
      switch (e.KeyData & Keys.KeyCode)
      {
        case Keys.Right:
          {
            SelectNextPatternItem();
            break;
          }
        case Keys.Left:
          {
            SelectPriorPatternItem();
            break;
          }
        case Keys.Home:
          {
            SelectFirstPatternItem();
            break;
          }
        case Keys.End:
          {
            SelectLastPatternItem();
            break;
          }
        case Keys.Up:
          {
            IncrementPatternItemValue();
            Invalidate();
            break;
          }
        case Keys.Down:
          {
            if (e.Alt && !e.Shift && !e.Control)
            {
              DateTimeBox.InEditButtonDownDefaultAction(DateTimeBox.EditButton, new DownEventArgs(false, 0));
            }
            else
            {
              DecrementPatternItemValue();
              Invalidate();
            }
            break;
          }
        case Keys.A:
          {
            if (e.Control)
              SelectAll();
            break;
          }
        case Keys.Delete:
          {
            if (allSeleted)
              Clear();
            else
              ResetSelectedItem();
            break;
          }
        case Keys.T:
          {
            if (allSeleted)
              Value = DateTime.Today;
            break;
          }
        case Keys.Return:
          {
            CheckFinishUpdateItemValueFromText();
            UpdatePatternValue();
            break;
          }
      }
    }

    protected override void OnKeyPress(KeyPressEventArgs e)
    {
      DateTimePatternItemPosition pip = null;
      int selItemIndex;

      base.OnKeyPress(e);
      if (allSeleted)
        selItemIndex = 0;
      else
        selItemIndex = SelectedPatternItemIndex;

      if (selItemIndex >= 0 &&
          selItemIndex < mutableDatePatternItems.Count)
      {
        pip = mutableDatePatternItems[selItemIndex];
      }

      if (pip != null && pip.PatternItem.ValidKey(e.KeyChar))
      { 
        if (allSeleted)
        {
          Value = null;
          SelectedPatternItemIndex = selItemIndex;
        }
        pip.PatternItem.ProcessKey(e.KeyChar);
      }
      else if (e.KeyChar == ' ' || char.IsPunctuation(e.KeyChar))
      {
        SelectNextPatternItem();
      }
    }

    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
      bool result = base.ProcessCmdKey(ref msg, keyData);
      if (!result)
        result = dateTimeBox.EditControl_ProcessCmdKey(ref msg, keyData);
      return result;
    }

    protected override void OnHandleCreated(EventArgs e)
    {
      base.OnHandleCreated(e);
      datePaintPattern.FillDataMetric(EhLibUtils.DisplayGraphicsCash, Font);
      UpdateAlignShift();
    }

    protected override void OnResize(EventArgs e)
    {
      base.OnResize(e);
      scrollPos = 0;
      UpdateAlignShift();
      Invalidate();
    }

    protected override void OnValidating(CancelEventArgs e)
    {
      CheckFinishUpdateItemValueFromText();
      UpdatePatternValue();
      base.OnValidating(e);
    }

    protected DateTimePatternItemPosition GetMutablePatternItemAtPos(Point location)
    {
      DateTimePatternItemPosition lastPi = null;
      foreach (DateTimePatternItemPosition pip in mutableDatePatternItems)
      {
        if (pip.StartPos - scrollPos + alignShift < location.X)
          lastPi = pip;
      }
      return lastPi;
    }

    protected void PatternDisplayTextChanged(object sender, EventArgs e)
    {
      if (IsHandleCreated && !internalPatternSet)
      {
        OnEditItemDisplayTextChagned(EventArgs.Empty);
        datePaintPattern.FillDataMetric(EhLibUtils.DisplayGraphicsCash, Font);
        timePaintPattern.FillDataMetric(EhLibUtils.DisplayGraphicsCash, Font);
        UpdateMutableItemsBounds();
        Invalidate();
      }
    }

    protected void PatternDigitalValueChanged(object sender, EventArgs e)
    {
      if (IsHandleCreated && !internalPatternSet)
      {
        OnEditItemChagned(EventArgs.Empty);
        UpdateValueFromPattern();
        Invalidate();
      }
    }

    protected virtual void UpdateValueFromPattern()
    {
      int y, m, d, h, mi, s, ms;

      if (Value.HasValue)
      {
        y = Value.Value.Year;
        m = Value.Value.Month;
        d = Value.Value.Day;
        h = Value.Value.Hour;
        mi = Value.Value.Minute;
        s = Value.Value.Second;
        ms = Value.Value.Millisecond;
      }
      else
      {
        y = -1;
        m = -1;
        d = -1;
        h = -1;
        mi = -1;
        s = -1;
        ms = -1;
      }

      datePaintPattern.GetDateTimeElements(ref y, ref m, ref d, ref h, ref mi, ref s, ref ms);
      timePaintPattern.GetDateTimeElements(ref y, ref m, ref d, ref h, ref mi, ref s, ref ms);

      if (y == -1 || m == -1 || d == -1)
        Value = null;
      else
      {
        if (h == -1) h = 0;
        if (mi == -1) mi = 0;
        if (s == -1) s = 0;
        if (ms == -1) ms = 0;

        if (y < 1 || y > 9999)
        {
          if (Value.HasValue)
            y = Value.Value.Year;
          else
            y = DateTime.Now.Year;
        }

        if (m < 1 || m > 12)
        {
          if (Value.HasValue)
            m = Value.Value.Month;
          else
            m = 1;
        }

        if (d < 1 || d > 31)
        {
          if (Value.HasValue)
            d = Value.Value.Day;
          else
            d = 1;
        }

        FixDayForMonth(y, m, ref d);
        Value = new DateTime(y, m, d, h, mi, s, ms);
      }
    }

    private void FixDayForMonth(int y, int m, ref int d)
    {
      int daysInMonth = System.DateTime.DaysInMonth(y, m);
      if (d > daysInMonth)
        d = daysInMonth;
    }

    protected virtual void OnScrollPosChanged()
    {
    }
    #endregion internal methods

    #region public methods
    public void SelectNextPatternItem()
    {
      CheckFinishUpdateItemValueFromText();

      if (mutableDatePatternItems.Count < 0)
        selectedPatternItemIndex = -1;
      else if (selectedPatternItemIndex < mutableDatePatternItems.Count - 1)
        SelectedPatternItemIndex = SelectedPatternItemIndex + 1;
      else
        SelectedPatternItemIndex = 0;
    }

    public void SelectPriorPatternItem()
    {
      CheckFinishUpdateItemValueFromText();

      if (mutableDatePatternItems.Count < 0)
        selectedPatternItemIndex = -1;
      else if (selectedPatternItemIndex > 0 )
        SelectedPatternItemIndex = SelectedPatternItemIndex - 1;
      else
        SelectedPatternItemIndex = mutableDatePatternItems.Count - 1;
    }

    public void SelectFirstPatternItem()
    {
      CheckFinishUpdateItemValueFromText();

      if (mutableDatePatternItems.Count < 0)
        selectedPatternItemIndex = -1;
      else
        SelectedPatternItemIndex = 0;
    }

    public void SelectLastPatternItem()
    {
      CheckFinishUpdateItemValueFromText();

      if (mutableDatePatternItems.Count < 0)
        selectedPatternItemIndex = -1;
      else
        SelectedPatternItemIndex = mutableDatePatternItems.Count - 1;
    }

    public void CheckFinishUpdateItemValueFromText()
    {
      //string text;
      if (selectedPatternItemIndex >= 0 && selectedPatternItemIndex < mutableDatePatternItems.Count)
      {
        DateTimePatternItemPosition pip = mutableDatePatternItems[SelectedPatternItemIndex];
        if (pip.PatternItem.ValueTextTypeChanged)
        {
          pip.PatternItem.UpdateValueFromDigitalText(true, true);
          pip.PatternItem.ValueTextTypeChanged = false;
          //if (finishChanges)
          datePaintPattern.UpdateDisplayText();
        }
      }
    }

    public void IncrementPatternItemValue()
    {
      if (selectedPatternItemIndex >= 0 && selectedPatternItemIndex < mutableDatePatternItems.Count)
      {
        DateTimePatternItemPosition pip = mutableDatePatternItems[SelectedPatternItemIndex];
        pip.PatternItem.IncrementValue();
        datePaintPattern.UpdateDisplayText();
        datePaintPattern.FillDataMetric(EhLibUtils.DisplayGraphicsCash, Font);
      }
    }

    public void DecrementPatternItemValue()
    {
      if (selectedPatternItemIndex >= 0 && selectedPatternItemIndex < mutableDatePatternItems.Count)
      {
        DateTimePatternItemPosition pip = mutableDatePatternItems[SelectedPatternItemIndex];
        pip.PatternItem.DecrementValue();
        datePaintPattern.UpdateDisplayText();
        datePaintPattern.FillDataMetric(EhLibUtils.DisplayGraphicsCash, Font);
      }
    }

    public void ClampSelectedItemInView()
    {
      int newScrollPos = scrollPos;
      DateTimePatternItemPosition pip = mutableDatePatternItems[SelectedPatternItemIndex];

      if (pip.StartPos + pip.Width - scrollPos > ClientSize.Width)
      {
        newScrollPos = pip.StartPos + pip.Width - ClientSize.Width;
        if (scrollPos > pip.StartPos)
          newScrollPos = pip.StartPos;
      }
      else if (pip.StartPos - scrollPos < 0)
      {
        newScrollPos = pip.StartPos;
      }

      if (newScrollPos != scrollPos)
      {
        scrollPos = newScrollPos;
        Invalidate();
        OnScrollPosChanged();
      }
    }

    public void SelectAll()
    {
      allSeleted = true;
      selectedPatternItemIndex = -1;
      Invalidate();
    }

    public void DeselectAll()
    {
      if (allSeleted)
      {
        allSeleted = false;
        if (mutableDatePatternItems.Count > 0)
          selectedPatternItemIndex = 0;
        else
          selectedPatternItemIndex = -1;
        Invalidate();
      }
    }
    
    public void Clear()
    {
      Value = null;
    }

    public void ResetSelectedItem()
    {
      if (selectedPatternItemIndex >= 0 && selectedPatternItemIndex < mutableDatePatternItems.Count)
      {
        DateTimePatternItemPosition pip = mutableDatePatternItems[SelectedPatternItemIndex];
        pip.PatternItem.Reset();
        datePaintPattern.UpdateDisplayText();
        datePaintPattern.FillDataMetric(EhLibUtils.DisplayGraphicsCash, Font);
      }
    }

    public void Validate()
    {
      CheckFinishUpdateItemValueFromText();
      UpdatePatternValue();
    }
    #endregion public methods

  }

  [SuppressMessage("ReSharper", "InconsistentNaming")]
  public enum DateTimePatternItemKind
  {
    StaticText,
    //Day
    FormatItem_d, FormatItem_dd, FormatItem_ddd, FormatItem_dddd,
    //Month
    FormatItem_M, FormatItem_MM, FormatItem_MMM, FormatItem_MMMM,
    //Year
    FormatItem_y, FormatItem_yy, FormatItem_yyy, FormatItem_yyyy,

    //Hours h, hh, H, HH
    FormatItem_hlc, FormatItem_hhlc, FormatItem_huc, FormatItem_hhuc,
    //Minute
    FormatItem_mlc, FormatItem_mmlc,
    //Second
    FormatItem_s, FormatItem_ss,
    //Fraction of a second
    FormatItem_f, FormatItem_ff, FormatItem_fff, FormatItem_ffff, FormatItem_fffff, FormatItem_ffffff, FormatItem_fffffff,

    //AM/PM designator
    FormatItem_t, FormatItem_tt,

    //Separators
    FormatItem_DateSeparator, FormatItem_TimeSeparator
  }

  public class DateTimePatternItem
  {
    #region privates
    private DateTimePatternItemKind itemKind;
    private string valueText;
    private readonly DateTimeDisplayPattern displayPattern;
    private bool valueTextChanged;
    private int _digitalValue;
    #endregion privates

    public DateTimePatternItem(DateTimeDisplayPattern displayPattern)
    {
      this.displayPattern = displayPattern;
      DigitalTextValue = "";
    }

    #region properties
    public DateTimePatternItemKind ItemKind
    {
      get
      {
        return itemKind;
      }

      set
      {
        if (value != itemKind)
        {
          itemKind = value;
          UpdateDigitalValueFormat();
        }
      }

    }

    public string StaticText { get; set; }
    public string TextValue
    {
      get
      {
        return valueText;
      }
      set
      {
        if (value != valueText)
        {
          valueText = value;
          displayPattern.ValueTextChanged(this);
        }
      }
    }

    public string DigitalTextValue { get; set; }

    public int DigitalValue
    {
      get
      {
        return _digitalValue;
      }
      set
      {
        if (value != _digitalValue)
        {
          _digitalValue = value;
          displayPattern.OnDigitalValueChanged(this);
        }
      }
    }

    public int StartPos { get; set; }
    public int Width { get; set; }

    public string DigitalValueFormat { get; set; }

    public int MaxDigits
    {
      get
      {
        switch (ItemKind)
        {
          case DateTimePatternItemKind.FormatItem_d: return 2;
          case DateTimePatternItemKind.FormatItem_dd: return 2;
          case DateTimePatternItemKind.FormatItem_ddd: return 1;
          case DateTimePatternItemKind.FormatItem_dddd: return 1;

          case DateTimePatternItemKind.FormatItem_M:
          case DateTimePatternItemKind.FormatItem_MM:
          case DateTimePatternItemKind.FormatItem_MMM:
          case DateTimePatternItemKind.FormatItem_MMMM:
            return 2;

          case DateTimePatternItemKind.FormatItem_y: return 2;
          case DateTimePatternItemKind.FormatItem_yy: return 2;
          case DateTimePatternItemKind.FormatItem_yyy: return 3;
          case DateTimePatternItemKind.FormatItem_yyyy: return 4;

          case DateTimePatternItemKind.FormatItem_hlc: 
          case DateTimePatternItemKind.FormatItem_hhlc:
          case DateTimePatternItemKind.FormatItem_huc:
          case DateTimePatternItemKind.FormatItem_hhuc:
            return 2;

          case DateTimePatternItemKind.FormatItem_mlc:
          case DateTimePatternItemKind.FormatItem_mmlc:
            return 2;

          case DateTimePatternItemKind.FormatItem_s:
          case DateTimePatternItemKind.FormatItem_ss:
            return 2;

          case DateTimePatternItemKind.FormatItem_f: return 1;
          case DateTimePatternItemKind.FormatItem_ff: return 2;
          case DateTimePatternItemKind.FormatItem_fff: return 3;
          case DateTimePatternItemKind.FormatItem_ffff: return 4;
          case DateTimePatternItemKind.FormatItem_fffff: return 5;
          case DateTimePatternItemKind.FormatItem_ffffff: return 6;
          case DateTimePatternItemKind.FormatItem_fffffff: return 7;

          //case DateTimePatternItemKind.FormatItem_t: return "t";
          //case DateTimePatternItemKind.FormatItem_tt: return "tt";

          //case DateTimePatternItemKind.FormatItem_DateSeparator: return "/";
          default: return 0;
        }
      }
    }
    public bool TextValueIsDigital
    {
      get
      {
        if (ItemKind == DateTimePatternItemKind.FormatItem_ddd ||
            ItemKind == DateTimePatternItemKind.FormatItem_dddd ||
            ItemKind == DateTimePatternItemKind.FormatItem_MMM ||
            ItemKind == DateTimePatternItemKind.FormatItem_MMMM ||
            ItemKind == DateTimePatternItemKind.FormatItem_t ||
            ItemKind == DateTimePatternItemKind.FormatItem_t)
          return false;
        else
          return true;
      }
    }

    public bool UserEnterComplete
    {
      get
      {
        return !TextValue.Contains(" ");
      }
    }
    public bool ValueTextTypeChanged
    {
      get
      {
        return valueTextChanged;
      } 
      
      set
      {
        if (value != valueTextChanged)
        {
          valueTextChanged = value;
          if (!value)
          {
            DigitalTextValue = "";
            displayPattern.ValueTextTypingCompete(this);
          }
        }
      }
    }
    #endregion properties

    //public int PressedDigits { get; set; }
    //public string EnteredDigits { get; set; }

    public int MaxValue()
    {
      switch (ItemKind)
      {
        case DateTimePatternItemKind.FormatItem_d: return 31;
        case DateTimePatternItemKind.FormatItem_dd: return 31;
        case DateTimePatternItemKind.FormatItem_ddd: return 31;
        case DateTimePatternItemKind.FormatItem_dddd: return 31;

        case DateTimePatternItemKind.FormatItem_M: return 12;
        case DateTimePatternItemKind.FormatItem_MM: return 12;
        case DateTimePatternItemKind.FormatItem_MMM: return 12;
        case DateTimePatternItemKind.FormatItem_MMMM: return 12;

        case DateTimePatternItemKind.FormatItem_y: return 9999;
        case DateTimePatternItemKind.FormatItem_yy: return 9999;
        case DateTimePatternItemKind.FormatItem_yyy: return 9999;
        case DateTimePatternItemKind.FormatItem_yyyy: return 9999;

        case DateTimePatternItemKind.FormatItem_hlc: return 23;
        case DateTimePatternItemKind.FormatItem_hhlc: return 23;
        case DateTimePatternItemKind.FormatItem_huc: return 23;
        case DateTimePatternItemKind.FormatItem_hhuc: return 23;

        case DateTimePatternItemKind.FormatItem_mlc: return 59;
        case DateTimePatternItemKind.FormatItem_mmlc: return 59;

        case DateTimePatternItemKind.FormatItem_s: return 59;
        case DateTimePatternItemKind.FormatItem_ss: return 59;

        case DateTimePatternItemKind.FormatItem_f: return 9;
        case DateTimePatternItemKind.FormatItem_ff: return 99;
        case DateTimePatternItemKind.FormatItem_fff: return 999;
        case DateTimePatternItemKind.FormatItem_ffff: return 9999;
        case DateTimePatternItemKind.FormatItem_fffff: return 99999;
        case DateTimePatternItemKind.FormatItem_ffffff: return 999999;
        case DateTimePatternItemKind.FormatItem_fffffff: return 9999999;

        case DateTimePatternItemKind.FormatItem_t: return 1;
        case DateTimePatternItemKind.FormatItem_tt: return 1;

        //case DateTimePatternItemKind.FormatItem_DateSeparator: return "/";
        default: return -1;
      }
    }

    public int MinValue()
    {
      switch (ItemKind)
      {
        case DateTimePatternItemKind.FormatItem_d: return 1;
        case DateTimePatternItemKind.FormatItem_dd: return 1;
        case DateTimePatternItemKind.FormatItem_ddd: return 1;
        case DateTimePatternItemKind.FormatItem_dddd: return 1;

        case DateTimePatternItemKind.FormatItem_M: return 1;
        case DateTimePatternItemKind.FormatItem_MM: return 1;
        case DateTimePatternItemKind.FormatItem_MMM: return 1;
        case DateTimePatternItemKind.FormatItem_MMMM: return 1;

        case DateTimePatternItemKind.FormatItem_y: return 1;
        case DateTimePatternItemKind.FormatItem_yy: return 1;
        case DateTimePatternItemKind.FormatItem_yyy: return 1;
        case DateTimePatternItemKind.FormatItem_yyyy: return 1;

        case DateTimePatternItemKind.FormatItem_hlc: return 0;
        case DateTimePatternItemKind.FormatItem_hhlc: return 0;
        case DateTimePatternItemKind.FormatItem_huc: return 0;
        case DateTimePatternItemKind.FormatItem_hhuc: return 0;

        case DateTimePatternItemKind.FormatItem_mlc: return 0;
        case DateTimePatternItemKind.FormatItem_mmlc: return 0;

        case DateTimePatternItemKind.FormatItem_s: return 0;
        case DateTimePatternItemKind.FormatItem_ss: return 0;

        case DateTimePatternItemKind.FormatItem_f: return 0;
        case DateTimePatternItemKind.FormatItem_ff: return 0;
        case DateTimePatternItemKind.FormatItem_fff: return 0;
        case DateTimePatternItemKind.FormatItem_ffff: return 0;
        case DateTimePatternItemKind.FormatItem_fffff: return 0;
        case DateTimePatternItemKind.FormatItem_ffffff: return 0;
        case DateTimePatternItemKind.FormatItem_fffffff: return 0;

        case DateTimePatternItemKind.FormatItem_t: return 0;
        case DateTimePatternItemKind.FormatItem_tt: return 0;

        //case DateTimePatternItemKind.FormatItem_DateSeparator: return "/";
        default: return -1;
      }
    }

    private void UpdateDigitalValueFormat()
    {
      switch (ItemKind)
      {
        case DateTimePatternItemKind.FormatItem_d:
          DigitalValueFormat = "#0";
          break;
        case DateTimePatternItemKind.FormatItem_dd:
          DigitalValueFormat = "00";
          break;
        case DateTimePatternItemKind.FormatItem_ddd:
        case DateTimePatternItemKind.FormatItem_dddd:
          DigitalValueFormat = "";
          break;

        case DateTimePatternItemKind.FormatItem_M:
          DigitalValueFormat = "#0";
          break;
        case DateTimePatternItemKind.FormatItem_MM:
          DigitalValueFormat = "00";
          break;
        case DateTimePatternItemKind.FormatItem_MMM:
        case DateTimePatternItemKind.FormatItem_MMMM:
          DigitalValueFormat = "";
          break;

        case DateTimePatternItemKind.FormatItem_y:
          DigitalValueFormat = "0";
          break;
        case DateTimePatternItemKind.FormatItem_yy:
          DigitalValueFormat = "00";
          break;
        case DateTimePatternItemKind.FormatItem_yyy:
          DigitalValueFormat = "000";
          break;
        case DateTimePatternItemKind.FormatItem_yyyy:
          DigitalValueFormat = "0000";
          break;

        case DateTimePatternItemKind.FormatItem_hlc:
          DigitalValueFormat = "#0";
          break;
        case DateTimePatternItemKind.FormatItem_hhlc:
          DigitalValueFormat = "00";
          break;
        case DateTimePatternItemKind.FormatItem_huc:
          DigitalValueFormat = "#0";
          break;
        case DateTimePatternItemKind.FormatItem_hhuc:
          DigitalValueFormat = "00";
          break;

        case DateTimePatternItemKind.FormatItem_mlc:
          DigitalValueFormat = "#0";
          break;
        case DateTimePatternItemKind.FormatItem_mmlc:
          DigitalValueFormat = "00";
          break;

        case DateTimePatternItemKind.FormatItem_s:
          DigitalValueFormat = "#0";
          break;
        case DateTimePatternItemKind.FormatItem_ss:
          DigitalValueFormat = "00";
          break;

        case DateTimePatternItemKind.FormatItem_f:
          DigitalValueFormat = "0";
          break;
        case DateTimePatternItemKind.FormatItem_ff:
          DigitalValueFormat = "00";
          break;
        case DateTimePatternItemKind.FormatItem_fff:
          DigitalValueFormat = "000";
          break;
        case DateTimePatternItemKind.FormatItem_ffff:
          DigitalValueFormat = "0000";
          break;
        case DateTimePatternItemKind.FormatItem_fffff:
          DigitalValueFormat = "00000";
          break;
        case DateTimePatternItemKind.FormatItem_ffffff:
          DigitalValueFormat = "000000";
          break;
        case DateTimePatternItemKind.FormatItem_fffffff:
          DigitalValueFormat = "0000000";
          break;

        //case DateTimePatternItemKind.FormatItem_DateSeparator: return "/";
        default:
          DigitalValueFormat = "";
          break;
      }
    }

    public string GetTextFromDateTime(Nullable<DateTime> dateTime)
    {
      if (ItemKind == DateTimePatternItemKind.StaticText)
      {
        return StaticText;
      }
      else if (ItemKind == DateTimePatternItemKind.FormatItem_DateSeparator)
      {
        return CultureInfo.CurrentCulture.DateTimeFormat.DateSeparator;
      }
      else if (ItemKind == DateTimePatternItemKind.FormatItem_TimeSeparator)
      {
        return CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator;
      }
      else
      {
        string formatStr = GetFormatString();
        if (formatStr.Length == 1) formatStr = '%' + formatStr;
        if (dateTime.HasValue)
          return dateTime.Value.ToString(formatStr);
        else
        {
          string s = MinValue().ToString(formatStr);
          return new string(' ', s.Length);
        }
      }
    }

    public int GetDigitalValueFromDateTime(Nullable<DateTime> value)
    {
      if (!value.HasValue) return -1;

      DateTime dateTime = value.Value;

      switch (ItemKind)
      {
        case DateTimePatternItemKind.FormatItem_d: return dateTime.Day;
        case DateTimePatternItemKind.FormatItem_dd: return dateTime.Day;
        case DateTimePatternItemKind.FormatItem_ddd: return (int)dateTime.DayOfWeek;
        case DateTimePatternItemKind.FormatItem_dddd: return (int)dateTime.DayOfWeek;

        case DateTimePatternItemKind.FormatItem_M: return dateTime.Month;
        case DateTimePatternItemKind.FormatItem_MM: return dateTime.Month;
        case DateTimePatternItemKind.FormatItem_MMM: return dateTime.Month;
        case DateTimePatternItemKind.FormatItem_MMMM: return dateTime.Month;

        case DateTimePatternItemKind.FormatItem_y: return dateTime.Year;
        case DateTimePatternItemKind.FormatItem_yy: return dateTime.Year;
        case DateTimePatternItemKind.FormatItem_yyy: return dateTime.Year;
        case DateTimePatternItemKind.FormatItem_yyyy: return dateTime.Year;

        case DateTimePatternItemKind.FormatItem_hlc: return dateTime.Hour;
        case DateTimePatternItemKind.FormatItem_hhlc: return dateTime.Hour;
        case DateTimePatternItemKind.FormatItem_huc: return dateTime.Hour;
        case DateTimePatternItemKind.FormatItem_hhuc: return dateTime.Hour;

        case DateTimePatternItemKind.FormatItem_mlc: return dateTime.Minute;
        case DateTimePatternItemKind.FormatItem_mmlc: return dateTime.Minute;

        case DateTimePatternItemKind.FormatItem_s: return dateTime.Second;
        case DateTimePatternItemKind.FormatItem_ss: return dateTime.Second;

        case DateTimePatternItemKind.FormatItem_f: return dateTime.Millisecond;
        case DateTimePatternItemKind.FormatItem_ff: return dateTime.Millisecond;
        case DateTimePatternItemKind.FormatItem_fff: return dateTime.Millisecond;
        case DateTimePatternItemKind.FormatItem_ffff: return dateTime.Millisecond;
        case DateTimePatternItemKind.FormatItem_fffff: return dateTime.Millisecond;
        case DateTimePatternItemKind.FormatItem_ffffff: return dateTime.Millisecond;
        case DateTimePatternItemKind.FormatItem_fffffff: return dateTime.Millisecond;

        case DateTimePatternItemKind.FormatItem_t:
        case DateTimePatternItemKind.FormatItem_tt:
          if (dateTime.Hour >= 0 && dateTime.Hour <= 11)
            return 0;
          else
            return 1;

        //case DateTimePatternItemKind.FormatItem_DateSeparator: return "/";
        default: return 0;
      }
    }

    public string GetFormatString()
    {
      switch (ItemKind)
      {
        case DateTimePatternItemKind.FormatItem_d: return "d";
        case DateTimePatternItemKind.FormatItem_dd: return "dd";
        case DateTimePatternItemKind.FormatItem_ddd: return "ddd";
        case DateTimePatternItemKind.FormatItem_dddd: return "dddd";

        case DateTimePatternItemKind.FormatItem_M: return "M";
        case DateTimePatternItemKind.FormatItem_MM: return "MM";
        case DateTimePatternItemKind.FormatItem_MMM: return "MMM";
        case DateTimePatternItemKind.FormatItem_MMMM: return "MMMM";

        case DateTimePatternItemKind.FormatItem_y: return "y";
        case DateTimePatternItemKind.FormatItem_yy: return "yy";
        case DateTimePatternItemKind.FormatItem_yyy: return "yyy";
        case DateTimePatternItemKind.FormatItem_yyyy: return "yyyy";

        case DateTimePatternItemKind.FormatItem_hlc: return "h";
        case DateTimePatternItemKind.FormatItem_hhlc: return "hh";
        case DateTimePatternItemKind.FormatItem_huc: return "H";
        case DateTimePatternItemKind.FormatItem_hhuc: return "HH";

        case DateTimePatternItemKind.FormatItem_mlc: return "m";
        case DateTimePatternItemKind.FormatItem_mmlc: return "mm";

        case DateTimePatternItemKind.FormatItem_s: return "s";
        case DateTimePatternItemKind.FormatItem_ss: return "ss";

        case DateTimePatternItemKind.FormatItem_f: return "f";
        case DateTimePatternItemKind.FormatItem_ff: return "ff";
        case DateTimePatternItemKind.FormatItem_fff: return "fff";
        case DateTimePatternItemKind.FormatItem_ffff: return "ffff";
        case DateTimePatternItemKind.FormatItem_fffff: return "fffff";
        case DateTimePatternItemKind.FormatItem_ffffff: return "ffffff";
        case DateTimePatternItemKind.FormatItem_fffffff: return "fffffff";

        case DateTimePatternItemKind.FormatItem_t: return "t";
        case DateTimePatternItemKind.FormatItem_tt: return "tt";

        //case DateTimePatternItemKind.FormatItem_DateSeparator: return "/";
        default: return "";
      }
    }

    public override string ToString()
    {
      return string.Format("ItemKind = {0} StaticText = {1}", ItemKind, StaticText);
    }

    public bool IsMutable()
    {
      switch (ItemKind)
      {
        case DateTimePatternItemKind.FormatItem_d:
        case DateTimePatternItemKind.FormatItem_dd:
        //case DateTimePatternItemKind.FormatItem_ddd:
        //case DateTimePatternItemKind.FormatItem_dddd:
        case DateTimePatternItemKind.FormatItem_M:
        case DateTimePatternItemKind.FormatItem_MM:
        case DateTimePatternItemKind.FormatItem_MMM:
        case DateTimePatternItemKind.FormatItem_MMMM:
        case DateTimePatternItemKind.FormatItem_y:
        case DateTimePatternItemKind.FormatItem_yy:
        case DateTimePatternItemKind.FormatItem_yyy:
        case DateTimePatternItemKind.FormatItem_yyyy:
        case DateTimePatternItemKind.FormatItem_hlc:
        case DateTimePatternItemKind.FormatItem_hhlc:
        case DateTimePatternItemKind.FormatItem_huc:
        case DateTimePatternItemKind.FormatItem_hhuc:
        case DateTimePatternItemKind.FormatItem_mlc:
        case DateTimePatternItemKind.FormatItem_mmlc:
        case DateTimePatternItemKind.FormatItem_s:
        case DateTimePatternItemKind.FormatItem_ss:
        case DateTimePatternItemKind.FormatItem_f:
        case DateTimePatternItemKind.FormatItem_ff:
        case DateTimePatternItemKind.FormatItem_fff:
        case DateTimePatternItemKind.FormatItem_ffff:
        case DateTimePatternItemKind.FormatItem_fffff:
        case DateTimePatternItemKind.FormatItem_ffffff:
        case DateTimePatternItemKind.FormatItem_fffffff:
        case DateTimePatternItemKind.FormatItem_t:
        case DateTimePatternItemKind.FormatItem_tt:
          return true;
        default:
          return false;
      }
    }

    public void IncrementValue()
    {
      int nextValue = DigitalValue + 1;
      if (nextValue > MaxValue() || nextValue < MinValue())
        DigitalValue = MinValue();
      else
        DigitalValue = nextValue;
      TextValue = DigitalValueToString(DigitalValue);
    }

    public void DecrementValue()
    {
      int nextValue = DigitalValue - 1;
      if (nextValue < MinValue())
        DigitalValue = MaxValue();
      else
        DigitalValue = nextValue;
      TextValue = DigitalValueToString(DigitalValue);
    }

    public void Reset()
    {
      DigitalValue = MinValue();
      TextValue = DigitalValueToString(DigitalValue);
    }

    internal bool ValidKey(char keyChar)
    {
      switch (ItemKind)
      {
        case DateTimePatternItemKind.FormatItem_d:
        case DateTimePatternItemKind.FormatItem_dd:
        //case DateTimePatternItemKind.FormatItem_ddd:
        //case DateTimePatternItemKind.FormatItem_dddd:
        case DateTimePatternItemKind.FormatItem_M:
        case DateTimePatternItemKind.FormatItem_MM:
        case DateTimePatternItemKind.FormatItem_MMM:
        case DateTimePatternItemKind.FormatItem_MMMM:
        case DateTimePatternItemKind.FormatItem_y:
        case DateTimePatternItemKind.FormatItem_yy:
        case DateTimePatternItemKind.FormatItem_yyy:
        case DateTimePatternItemKind.FormatItem_yyyy:
        case DateTimePatternItemKind.FormatItem_hlc:
        case DateTimePatternItemKind.FormatItem_hhlc:
        case DateTimePatternItemKind.FormatItem_huc:
        case DateTimePatternItemKind.FormatItem_hhuc:
        case DateTimePatternItemKind.FormatItem_mlc:
        case DateTimePatternItemKind.FormatItem_mmlc:
        case DateTimePatternItemKind.FormatItem_s:
        case DateTimePatternItemKind.FormatItem_ss:
        case DateTimePatternItemKind.FormatItem_f:
        case DateTimePatternItemKind.FormatItem_ff:
        case DateTimePatternItemKind.FormatItem_fff:
        case DateTimePatternItemKind.FormatItem_ffff:
        case DateTimePatternItemKind.FormatItem_fffff:
        case DateTimePatternItemKind.FormatItem_ffffff:
        case DateTimePatternItemKind.FormatItem_fffffff:
          if ("0987654321".Contains(keyChar.ToString()))
            return true;
          else
            return false;

        case DateTimePatternItemKind.FormatItem_t:
        case DateTimePatternItemKind.FormatItem_tt:
          string am1 = CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator.Substring(0, 1).ToUpper();
          string pm1 = CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator.Substring(0, 1).ToUpper();
          string key = keyChar.ToString().ToUpper();

          if (key == am1 || key == pm1)
            return true;
          else
            return false;
        default:
          return false;
      }
    }

    internal void ProcessKey(char keyChar)
    {
      if (ItemKind == DateTimePatternItemKind.FormatItem_t ||
          ItemKind == DateTimePatternItemKind.FormatItem_tt)
        ProcessAMPMKey(keyChar);
      else
        ProcessDigitKey(keyChar);
    }

    // ReSharper disable once InconsistentNaming
    internal void ProcessAMPMKey(char keyChar)
    {
      string am1 = CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator.Substring(0, 1).ToUpper();
      string pm1 = CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator.Substring(0, 1).ToUpper();
      string key = keyChar.ToString().ToUpper();

      if (key == am1)
        DigitalValue = 0;
      else if (key == pm1)
        DigitalValue = 1;
    }

    internal void ProcessDigitKey(char d)
    {
      string s;
      int v;
      int enteredDigits;

      s = DigitalTextValue.Trim();
      enteredDigits = s.Length;

      if (!("0987654321".Contains(d.ToString()))) return;
      if (MaxDigits == 0) return;

      if (enteredDigits == 0 || enteredDigits >= MaxDigits)
      {
        s = d.ToString();
      }
      else
      {
        s = DigitalTextValue + d.ToString();
        s = s.Trim();
      }

      if (!int.TryParse(s, out v)) return;

      if ((v > MaxValue()) ||
          (s.Length == MaxDigits && v < MinValue()))
      {
        s = d.ToString();
      }

      //s = v.ToString();
      DigitalTextValue = s.PadLeft(MaxDigits);
      ValueTextTypeChanged = true;
      UpdateTextValueFromDigitalTextValue();
    }

    internal void UpdateTextValueFromDigitalTextValue()
    {
      bool updateDigitalValue;
      if (TextValueIsDigital)
      {
        TextValue = DigitalTextValue;
        if (UserEnterComplete)
          UpdateValueFromDigitalText(true, true);
      }
      else
      {
        if (DigitalTextValue.Contains(" "))
          updateDigitalValue = false;
        else
          updateDigitalValue = true;

        UpdateValueFromDigitalText(true, updateDigitalValue);
      }
    }

    internal void UpdateValueFromDigitalText(bool updateText, bool updateDigitalValue)
    {
      int newDigitalValue;
      if (String.IsNullOrEmpty(DigitalTextValue.Trim())) return;

      newDigitalValue = int.Parse(DigitalTextValue);
      if (ItemKind == DateTimePatternItemKind.FormatItem_y ||
          ItemKind == DateTimePatternItemKind.FormatItem_yy ||
          ItemKind == DateTimePatternItemKind.FormatItem_yyy ||
          ItemKind == DateTimePatternItemKind.FormatItem_yyyy)
      {
        if (DigitalTextValue.Trim().Length <= 2)
        {
          newDigitalValue = MakeYearFromTwoDegitesYear(newDigitalValue);
        }  
      }

      if (updateDigitalValue)
        DigitalValue = newDigitalValue;
      if (updateText)
      {
        TextValue = DigitalValueToString(newDigitalValue);
        //ValueTextChanged = false;
      }
    }

    // ReSharper disable once InconsistentNaming
    private int MakeYearFromTwoDegitesYear(int year2d)
    {
      int year = DateTime.Today.Year;
      year = year / 100 * 100;
      year = year + year2d;
      if (year > DateTime.Today.Year + 20)
        year = year - 100;
      return year;
    }

    public string DigitalValueToString(int digitalValue)
    {
      int v;

      switch (ItemKind)
      {
        case DateTimePatternItemKind.FormatItem_ddd:
          return CultureInfo.CurrentCulture.DateTimeFormat.ShortestDayNames[digitalValue];

        case DateTimePatternItemKind.FormatItem_dddd:
          return CultureInfo.CurrentCulture.DateTimeFormat.DayNames[digitalValue];

        case DateTimePatternItemKind.FormatItem_MMM:
          if (digitalValue >= 1 && digitalValue <= 12)
            return CultureInfo.CurrentCulture.DateTimeFormat.AbbreviatedMonthNames[digitalValue - 1];
          else
            return TextValue;

        case DateTimePatternItemKind.FormatItem_MMMM:
          if (digitalValue >= 1 && digitalValue <= 12)
            return CultureInfo.CurrentCulture.DateTimeFormat.MonthNames[digitalValue - 1];
          else
            return TextValue;

        case DateTimePatternItemKind.FormatItem_y:
          v = digitalValue % 100;
          return v.ToString("#0");

        case DateTimePatternItemKind.FormatItem_yy:
          v = digitalValue % 100;
          return v.ToString("00");

        case DateTimePatternItemKind.FormatItem_hlc:
        case DateTimePatternItemKind.FormatItem_hhlc:
          if (digitalValue == 0)
            return 12.ToString(DigitalValueFormat);
          else if (digitalValue > 12)
            return (digitalValue-12).ToString(DigitalValueFormat);
          else
            return (digitalValue).ToString(DigitalValueFormat);

        case DateTimePatternItemKind.FormatItem_t:
          if (digitalValue == 0)
            return CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator.Substring(1, 1);
          else
            return CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator.Substring(1, 1);

        case DateTimePatternItemKind.FormatItem_tt:
          if (digitalValue == 0)
            return CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
          else
            return CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator;

        default:
          return digitalValue.ToString(DigitalValueFormat);
      }
    }

    public bool DigitalTextValueIsComplete()
    {
      if (DigitalTextValue.Trim().Length < MaxDigits)
        return false;
      else
        return true;
    }

  }

  public class DateTimeDisplayPattern
  {
    private string displayText;
    private EventHandler displayTextChanged;
    private EventHandler digitalValueChanged;

    private readonly List<DateTimePatternItem> patternItems;
    private readonly ReadOnlyCollection<DateTimePatternItem> roPatternItems;
    private bool _hourIsAM;
    private bool internalValueSet;

    public DateTimeDisplayPattern()
    {
      patternItems = new List<DateTimePatternItem>();
      roPatternItems = patternItems.AsReadOnly();
    }

    #region properties
    public string DisplayText
    {
      get
      {
        return displayText;
      }
    }

    public bool HourIsAM
    {
      get { return _hourIsAM; }
    }
    
    public ReadOnlyCollection<DateTimePatternItem> PatternItems
    {
      get
      {
        return roPatternItems;
      }
    }
    #endregion properties

    #region events
    public event EventHandler DisplayTextChanged
    {
      add
      {
        displayTextChanged += value;
      }
      remove
      {
        displayTextChanged -= value;
      }
    }

    public event EventHandler DigitalValueChanged
    {
      add
      {
        digitalValueChanged += value;
      }
      remove
      {
        digitalValueChanged -= value;
      }
    }
    #endregion events

    #region methods
    public void MakeItem(string formatItem, bool forceLiteralConst, ref int pos, ref int len)
    {
      if (String.IsNullOrEmpty(formatItem)) return;

      DateTimePatternItem pi = new DateTimePatternItem(this);

      if (forceLiteralConst)
      {
        pi.ItemKind = DateTimePatternItemKind.StaticText;
        pi.StaticText = formatItem;
      }
      else if (formatItem == "/")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_DateSeparator;
      else if (formatItem == ":")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_TimeSeparator;

      else if (formatItem == "d")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_d;
      else if (formatItem == "dd")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_dd;
      else if (formatItem == "ddd")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_ddd;
      else if (formatItem == "dddd")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_dddd;
      else if (formatItem[0] == 'd')
        pi.ItemKind = DateTimePatternItemKind.FormatItem_dd;

      else if (formatItem == "M")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_M;
      else if (formatItem == "MM")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_MM;
      else if (formatItem == "MMM")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_MMM;
      else if (formatItem == "MMMM")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_MMMM;
      else if (formatItem[0] == 'M')
        pi.ItemKind = DateTimePatternItemKind.FormatItem_MMMM;

      else if (formatItem == "y")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_y;
      else if (formatItem == "yy")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_yy;
      else if (formatItem == "yyy")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_yyy;
      else if (formatItem == "yyyy")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_yyyy;
      else if (formatItem[0] == 'y')
        pi.ItemKind = DateTimePatternItemKind.FormatItem_yyyy;

      else if (formatItem == "h")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_hlc;
      else if (formatItem == "hh")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_hhlc;
      else if (formatItem == "H")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_huc;
      else if (formatItem == "HH")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_hhuc;

      else if (formatItem == "m")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_mlc;
      else if (formatItem == "mm")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_mmlc;
      else if (formatItem == "s")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_s;
      else if (formatItem == "ss")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_ss;

      else if (formatItem == "f")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_f;
      else if (formatItem == "ff")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_ff;
      else if (formatItem == "fff")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_fff;
      else if (formatItem == "ffff")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_ffff;
      else if (formatItem == "fffff")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_fffff;
      else if (formatItem == "ffffff")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_ffffff;
      else if (formatItem == "fffffff")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_fffffff;

      else if (formatItem == "t")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_t;
      else if (formatItem == "tt")
        pi.ItemKind = DateTimePatternItemKind.FormatItem_tt;

      else
      {
        pi.ItemKind = DateTimePatternItemKind.StaticText;
        pi.StaticText = formatItem;
      }

      patternItems.Add(pi);

      pos = pos + len;
      len = 0;
    }

    public void ParseFormat(string format)
    {
      char lastChar = '\x0000';
      int sameCharCount = 0;
      bool inQuotes = false;
      bool inDoubleQuote = false;
      int cPos = 0;
      int cLen = 0;
      bool formatLiteral = false;

      format = FormatToCustomFormat(format);

      patternItems.Clear();

      foreach (char c in format)
      {
        if ((c == '\'') && (!inDoubleQuote))
        {
          if (inQuotes)
          {
            inQuotes = false;
            MakeItem(format.Substring(cPos, cLen), true, ref cPos, ref cLen);
            cPos = cPos + 1;
          }
          else
          {
            MakeItem(format.Substring(cPos, cLen), false, ref cPos, ref cLen);
            inQuotes = true;
            cPos = cPos + 1;
          }
        }
        else if ((c == '"') && (!inQuotes))
        {
          if (inDoubleQuote)
          {
            inDoubleQuote = false;
            MakeItem(format.Substring(cPos, cLen), true, ref cPos, ref cLen);
            cPos = cPos + 1;
          }
          else
          {
            MakeItem(format.Substring(cPos, cLen), false, ref cPos, ref cLen);
            inDoubleQuote = true;
            cPos = cPos + 1;
          }
        }
        else if (!inQuotes &&
                 !inDoubleQuote &&
                 "dMyhHmsft".Contains(c.ToString()))
        {
          if (lastChar == c)
          {
            cLen = cLen + 1;
            sameCharCount = sameCharCount + 1;
          }
          else
          {
            MakeItem(format.Substring(cPos, cLen), false, ref cPos, ref cLen);
            cLen = cLen + 1;
          }
          formatLiteral = true;
        }
        else if (!inQuotes &&
                 !inDoubleQuote &&
                 c == '/')
        {
          MakeItem(format.Substring(cPos, cLen), false, ref cPos, ref cLen);
          cLen = cLen + 1;
          MakeItem(format.Substring(cPos, cLen), false, ref cPos, ref cLen);
        }
        else
        {
          if (formatLiteral)
            MakeItem(format.Substring(cPos, cLen), false, ref cPos, ref cLen);
          cLen = cLen + 1;
          formatLiteral = false;
        }
        lastChar = c;
      }
      if (cLen != 0)
        MakeItem(format.Substring(cPos, cLen), false, ref cPos, ref cLen);
    }

    public string FormatToCustomFormat(string format)
    {
      DateTimeFormatInfo fi = CultureInfo.CurrentCulture.DateTimeFormat;

      if (format == "d")
        return fi.ShortDatePattern;
      else if (format == "D")
        return fi.LongDatePattern;
      else if (format == "f")
        return fi.LongDatePattern + " " + fi.ShortTimePattern;
      else if (format == "F")
        return fi.FullDateTimePattern;
      else if (format == "g")
        return fi.ShortDatePattern + " " + fi.ShortTimePattern;
      else if (format == "G")
        return fi.ShortDatePattern + " " + fi.LongTimePattern;
      else if (format == "m")
        return fi.MonthDayPattern;
      else if (format == "M")
        return fi.MonthDayPattern;
      else if (format == "o")
        return "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffK";
      else if (format == "O")
        return "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffK";
      else if (format == "r")
        return "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
      else if (format == "R")
        return "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
      else if (format == "s")
        return fi.SortableDateTimePattern;
      else if (format == "t")
        return fi.ShortTimePattern;
      else if (format == "T")
        return fi.LongTimePattern;
      else if (format == "u")
        return fi.UniversalSortableDateTimePattern;
      else if (format == "U")
        return fi.FullDateTimePattern;
      else if (format == "y")
        return fi.YearMonthPattern;
      else if (format == "Y")
        return fi.YearMonthPattern;
      else
        return format;
    }
                                                 
    public void ParseValue(Nullable<DateTime> value)
    {
      internalValueSet = true;
      try
      {
        foreach (DateTimePatternItem pi in PatternItems)
        {
          pi.TextValue = pi.GetTextFromDateTime(value);
          pi.DigitalValue = pi.GetDigitalValueFromDateTime(value);
          pi.ValueTextTypeChanged = false;
          //pi.EnteredDigits = "";
        }
        if (value.HasValue && value.Value.Hour >= 0 && value.Value.Hour <= 11)
          _hourIsAM = true;
        else
          _hourIsAM = false;
      }
      finally
      {
        internalValueSet = false;
      }
      UpdateDisplayText();
    }

    //public string FormatValue(DateTime value)
    //{
    //  StringBuilder sb = new StringBuilder();
    //  foreach (DateTimePatternItem pi in PatternItems)
    //    sb.Append(pi.GetTextFromDateTime(value));
    //  return sb.ToString();
    //}

    public void UpdateDisplayText()
    {
      string newDisplayText;
      StringBuilder sb = new StringBuilder();
      foreach (DateTimePatternItem pi in PatternItems)
        sb.Append(pi.TextValue);
      newDisplayText = sb.ToString();
      if (newDisplayText != displayText)
      {
        displayText = newDisplayText;
        OnDisplayTextChanged();
      }
    }

    protected internal virtual void OnDisplayTextChanged()
    {
      if (displayTextChanged != null)
      {
        displayTextChanged(this, new EventArgs());
      }
    }

    protected internal virtual void OnDigitalValueChanged(DateTimePatternItem patternItem)
    {
      if (internalValueSet) return;

      if (patternItem.ItemKind == DateTimePatternItemKind.FormatItem_t ||
          patternItem.ItemKind == DateTimePatternItemKind.FormatItem_tt)
      {
        internalValueSet = true;
        try
        {
          foreach (DateTimePatternItem pi in PatternItems)
          {
            if (pi.ItemKind == DateTimePatternItemKind.FormatItem_hlc ||
                pi.ItemKind == DateTimePatternItemKind.FormatItem_hhlc ||
                pi.ItemKind == DateTimePatternItemKind.FormatItem_huc ||
                pi.ItemKind == DateTimePatternItemKind.FormatItem_hhuc)
            {
              if (patternItem.DigitalValue == 0)
              {
                if (pi.DigitalValue == 0)
                  pi.DigitalValue = 12;
                else
                  pi.DigitalValue = pi.DigitalValue - 12;
              }
              else
              {
                if (pi.DigitalValue == 12)
                  pi.DigitalValue = 0;
                else
                  pi.DigitalValue = pi.DigitalValue + 12;
              }
            }
          }
        }
        finally
        {
          internalValueSet = false;
        }
      }

      if (digitalValueChanged != null)
      {
        digitalValueChanged(this, new EventArgs());
      }
    }

    public void FillDataMetric(Graphics g, Font font)
    {
      int pos = 0;
      foreach (DateTimePatternItem pi in PatternItems)
      {
        Size sz = EhLibUtils.MeasureText(g, pi.TextValue, font, new Size(1, 1), DataGridTextWrapMode.NoWrap);
        pi.Width = sz.Width;
        pi.StartPos = pos;
        pos = pos + pi.Width;
      }
    }

    public int CalcWidth()
    {
      int result = 0;
      foreach (DateTimePatternItem pi in PatternItems)
        result = result + pi.Width;
      return result;
    }

    internal void ValueTextChanged(DateTimePatternItem dateTimePatternItem)
    {
      UpdateDisplayText();
    }

    internal void ValueTextTypingCompete(DateTimePatternItem dateTimePatternItem)
    {
      OnDisplayTextChanged();
    }

    internal void GetDateTimeElements(ref int y, ref int m, ref int d, ref int h, ref int mi, ref int s, ref int ms)
    {
      foreach (DateTimePatternItem pi in PatternItems)
      {
        if (pi.IsMutable())
        {
          switch(pi.ItemKind)
          {
            case DateTimePatternItemKind.FormatItem_d:
            case DateTimePatternItemKind.FormatItem_dd:
            case DateTimePatternItemKind.FormatItem_ddd:
            case DateTimePatternItemKind.FormatItem_dddd:
              d = pi.DigitalValue;
              break;

            case DateTimePatternItemKind.FormatItem_M:
            case DateTimePatternItemKind.FormatItem_MM:
            case DateTimePatternItemKind.FormatItem_MMM:
            case DateTimePatternItemKind.FormatItem_MMMM:
              m = pi.DigitalValue;
              break;

            case DateTimePatternItemKind.FormatItem_y:
            case DateTimePatternItemKind.FormatItem_yy: 
            case DateTimePatternItemKind.FormatItem_yyy:
            case DateTimePatternItemKind.FormatItem_yyyy:
              y = pi.DigitalValue;
              break;

            case DateTimePatternItemKind.FormatItem_hlc:
            case DateTimePatternItemKind.FormatItem_hhlc:
            case DateTimePatternItemKind.FormatItem_huc:
            case DateTimePatternItemKind.FormatItem_hhuc:
              h = pi.DigitalValue;
              break;

            case DateTimePatternItemKind.FormatItem_mlc: 
            case DateTimePatternItemKind.FormatItem_mmlc:
              mi = pi.DigitalValue;
              break;

            case DateTimePatternItemKind.FormatItem_s:
            case DateTimePatternItemKind.FormatItem_ss:
              s = pi.DigitalValue;
              break;

            case DateTimePatternItemKind.FormatItem_f:
            case DateTimePatternItemKind.FormatItem_ff: 
            case DateTimePatternItemKind.FormatItem_fff:
              ms = pi.DigitalValue;
              break;
          }
        }
      }
    }

    #endregion methods
  }

  public class DateTimePatternItemPosition
  {
    public DateTimePatternItem PatternItem { get; internal set; }
    public DateTimeDisplayPattern Pattern { get; internal set; }
    public int StartPos { get; internal set; }
    public int Width { get; internal set; }
  }

}
