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

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

namespace EhLib
{
  public interface IComboBoxDataCellHolder
  {
    void OnCustomAreaPaint(ComboBoxCustomAreaPaintEventArgs e);
  }

  [DataAxisGridDataCellDesignTimeVisibleAttribute(true)]
  public class DataAxisGridComboBoxDataCell : DataAxisGridDataCellMan, IObjectCollectionObserver
  {
    //private BindingSource bindingSource;
    private CurrencyManager dataManager;
    private object dataSource;
    private bool dataSourceInitialized;
    private BindingMemberInfo displayMember;
    private BindingMemberInfo valueMember;
    private PropertyDescriptor displayMemberProperty;
    private PropertyDescriptor valueMemberProperty;
    private ObjectCollection items;
    private ComboBoxCompound compound;
    private int customAreaWidth = 16;
    private int dropDownBoxVisibleRowCount = 20;
    private int dropDownBoxWidth = 0;


    //public DataGridComboBoxDataCell(DataGridColumn column) : base(column)
    public DataAxisGridComboBoxDataCell()
    {
      //bindingSource = new BindingSource();
      displayMember = new BindingMemberInfo();
      valueMember = new BindingMemberInfo();
    }

    #region Design-time Properties
    [DefaultValue(ComboBoxCompound.TextEditor)]
    public ComboBoxCompound Compound
    {
      get
      {
        return compound;
      }
      set
      {
        if (value != compound)
        {
          compound = value;
          OnCompoundChanged();
        }
      }
    }

    [DefaultValue(null)]
    [RefreshProperties(RefreshProperties.Repaint)]
    [AttributeProvider(typeof(IListSource))]
    public object DataSource
    {
      get
      {
        return dataSource;
      }
      set
      {

        if (this.DataSource != value)
        {
          this.dataManager = null;
          dataSourceInitialized = false;

          UnwireDataSource();
          dataSource = value;
          WireDataSource(value);

          ResetDataSourceConnection();
        }
      }
    }

    [DefaultValue("")]
    [TypeConverter("System.Windows.Forms.Design.DataMemberFieldConverter ")]
    [Editor("System.Windows.Forms.Design.DataMemberFieldEditor", typeof(System.Drawing.Design.UITypeEditor))]
    public string DisplayMember
    {
      get
      {
        return displayMember.BindingMember;
      }

      set
      {
        if (value != displayMember.BindingMember)
        {
          displayMember = new BindingMemberInfo(value);
          ResetDataSourceConnection();
        }
      }
    }

    [DefaultValue("")]
    [TypeConverter("System.Windows.Forms.Design.DataMemberFieldConverter")]
    [Editor("System.Windows.Forms.Design.DataMemberFieldEditor", typeof(System.Drawing.Design.UITypeEditor))]
    public string ValueMember
    {
      get
      {
        return valueMember.BindingMember;
      }
      set
      {
        if (value != valueMember.BindingMember)
        {
          valueMember = new BindingMemberInfo(value);
          ResetDataSourceConnection();
        }
      }
    }

    [Editor("System.Windows.Forms.Design.StringCollectionEditor", typeof(System.Drawing.Design.UITypeEditor))]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public ObjectCollection Items
    {
      get
      {
        if (items == null)
          items = new ObjectCollection(this);
        return items;
      }
    }

    [DefaultValue(16)]
    public int CustomAreaWidth
    {
      get
      {
        return customAreaWidth;
      }

      set
      {
        if (value != customAreaWidth)
        {
          customAreaWidth = value;
          InvalidateGrid();
        }
      }
    }
    #endregion

    #region run-time properties
    private CurrencyManager DataManager
    {
      get
      {
        return dataManager;
      }
    }

    private PropertyDescriptor DisplayMemberProperty
    {
      get
      {
        return displayMemberProperty;
      }
    }

    private PropertyDescriptor ValueMemberProperty
    {
      get
      {
        return valueMemberProperty;
      }
    }

    public override Type EditType
    {
      get
      {
        return typeof(DataAxisGridComboBoxEditControl);
      }
    }

    //public new DataGridComboBoxColumn Column
    //{
    //  get
    //  {
    //    return (base.Column as DataGridComboBoxColumn);
    //  }
    //}

    public bool LookupMode
    {
      get
      {
        if (ValueMemberProperty != null)
          return true;
        else
          return false;
      }
    }
    #endregion

    #region methods
    public override string GetDisplayText(PropertyAxisBar propAxisBar, object listItemBar)
    {
      if (propAxisBar != null && listItemBar != null)
      {
        if (LookupMode)
        {
          object displayValue;
          object keyValue = propAxisBar.GetValue(listItemBar);
          if (LookupDisplayValue(keyValue, out displayValue))
          {
            if (displayValue == null)
              return "";
            else
              return displayValue.ToString();
          }
          else
          {
            return null;
          }
        }
        else
        {
          return base.GetDisplayText(propAxisBar, listItemBar);
        }
      }
      else
        return base.GetDisplayText(propAxisBar, listItemBar);
    }

    public virtual string GetValueDisplayText(object value)
    {
      if (LookupMode)
      {
        object displayValue;
        if (LookupDisplayValue(value, out displayValue))
        {
          if (displayValue == null)
            return "";
          else
            return displayValue.ToString();
        }
        else
        {
          return null;
        }
      }
      else
      {
        if (value != null && !(value is string))
          return value.ToString();
        else
          return "";
      }
    }

    private bool LookupDisplayValue(object keyValue, out object displayValue)
    {
      bool result = false;
      object rowItem;

      displayValue = null;

      IBindingList list = this.DataManager.List as IBindingList;
      if ((list != null) && list.SupportsSearching)
      {
        int index = list.Find(ValueMemberProperty, keyValue);
        if (index != -1)
        {
          rowItem = this.DataManager.List[index];
          displayValue = DisplayMemberProperty.GetValue(rowItem);
          result = true;
        }
      }
      else
      {
        foreach (object itemTmp in this.DataManager.List)
        {
          object value = ValueMemberProperty.GetValue(itemTmp);
          if (EhLibUtils.DBValueEqual(keyValue, value))
          {
            displayValue = DisplayMemberProperty.GetValue(itemTmp);
            result = true;
            break;
          }
        }
      }
      return result;
    }

    protected override void PaintContent(DataAxisGridDataCellContentPaintEventArgs e)
    {
      Rectangle cellContentRect = e.CellContentRect;
      object val = null;
      if (e.PropAxisBar != null && e.ListItemBar != null)
        val = e.PropAxisBar.GetValue(e.ListItemBar);

      if (Compound == ComboBoxCompound.CustomAreaAndTextEditor)
      {
        Rectangle customAreaRect = e.CellContentRect;

        customAreaRect.Width = CustomAreaWidth;
        cellContentRect.Width = cellContentRect.Width - CustomAreaWidth;
        cellContentRect.X = cellContentRect.X + CustomAreaWidth;

        var ce = new ComboBoxCustomAreaPaintEventArgs(e.Graphics, customAreaRect, val);
        OnCustomAreaPaint(ce);
      }

      var te = new DataAxisGridDataCellContentPaintEventArgs(e.DataCellPaintEventArgs, cellContentRect);
      PaintTextContent(te);
    }

    internal protected virtual void OnCustomAreaPaint(ComboBoxCustomAreaPaintEventArgs e)
    {
      var holder = PropAxisBar as IComboBoxDataCellHolder;
      if (holder != null)
        holder.OnCustomAreaPaint(e);
    }

    protected virtual void PaintTextContent(DataAxisGridDataCellContentPaintEventArgs e)
    {
      Color fForeColor;
      Rectangle textRect;
      //bool wordWrap;

      if (((BasePaintCellStates.Current & e.State) != 0) ||
          ((BasePaintCellStates.RowSelected & e.State) != 0)
      )
        fForeColor = SystemColors.HighlightText;
      else
        fForeColor = Grid.ForeColor;

      string text = GetDisplayText(e.DataColIndex, e.DataRowIndex);
      if (text != null)
      {

        //TextFormatFlagsEh flags = TextFormatFlagsEh.Default;
        textRect = EhLibUtils.TrimPadding(e.CellContentRect, Padding);

        //wordWrap = false;
        //if (wordWrap)
        //  flags = flags | TextFormatFlagsEh.WordBreak;

        Grid.PaintingDrawText(e.Graphics, text, Font, textRect, fForeColor, HorzAlign, VertAlign, false);

      }

      CharacterRange[] charRanges;
      string searchingText = Grid.GetHighlightSearchingData(e.PropAxisBar, e.ListItemBar, text, out charRanges);
      if (String.IsNullOrEmpty(searchingText))
      {
        PaintHighlightedText(e, searchingText, charRanges);
      }
    }

    protected internal virtual void PaintHighlightedText(DataAxisGridDataCellContentPaintEventArgs e, string text, CharacterRange[] charRanges)
    {
      string cellText;
      string hiS;
      Rectangle textRect;
      bool wordWrap;
      Region oldClip;
      int outOfBounds = 0;
      //CharacterRange[] charRanges;
      Rectangle paintRect = e.CellContentRect;

      cellText = GetDisplayText(e.PropAxisBar, e.ListItemBar);
      hiS = text;
      if (string.IsNullOrEmpty(hiS) == true) return;

      //if (!Grid.SearchBox.CheckTextHit(e.Column, cellText, Grid.SearchBox.SearchingText)) return;

      //charRanges = Grid.SearchBox.GetHitRanges(e.Column, e.Row, cellText);

      textRect = EhLibUtils.TrimPadding(e.CellContentRect, Padding);

      wordWrap = CheckWordWrap(paintRect);

      Rectangle[] stringRegions = EhLibUtils.MeasureCharacterRanges(e.Graphics, cellText, Font,
        textRect, HorzAlign, VertAlign, wordWrap, charRanges);
      oldClip = e.Graphics.Clip;
      e.Graphics.SetClip(paintRect, CombineMode.Intersect);

      for (int i = 0; i < stringRegions.Length; i++)
      {
        Rectangle regRect = stringRegions[i];

        if (textRect.IntersectsWith(regRect))
        {
          e.Graphics.FillRectangle(new SolidBrush(Color.Yellow), regRect);

          string drawText = cellText.Substring(charRanges[i].First, charRanges[i].Length);

          EhLibUtils.DrawText(e.Graphics, drawText, Font, regRect, ForeColor, HorizontalAlignment.Left, VerticalAlignment.Top, TextFormatFlagsEh.None);
        }
        else
        {
          outOfBounds = outOfBounds + 1;
        }
      }

      if (outOfBounds > 0)
      {
        string outOfBoundsStr = outOfBounds.ToString();
        Font ofbFont = new Font(Font.FontFamily, 6);
        Size ofbSize = EhLibUtils.MeasureText(e.Graphics, outOfBoundsStr, ofbFont, new Size(1, 1), DataGridTextWrapMode.NoWrap);
        Rectangle ofbRect = new Rectangle(textRect.Right - ofbSize.Width - 2, textRect.Top, ofbSize.Width + 2, ofbSize.Height + 2);

        e.Graphics.FillRectangle(new SolidBrush(Color.Yellow), ofbRect);
        EhLibUtils.DrawText(e.Graphics, outOfBoundsStr, ofbFont, ofbRect, ForeColor, HorizontalAlignment.Center, VerticalAlignment.Center, TextFormatFlagsEh.None);
      }

      e.Graphics.Clip = oldClip;
    }

    protected internal override bool CheckWordWrap(Rectangle paintRect)
    {
      bool wordWrap = false;
      //TODO: Postpone. Add WrapMode mode. Inherite ComboboxCellMan from TextCellMan. So WrapMode already present
      //int oneLineHeight;
      //if (WrapMode == DataGridTextWrapMode.Auto)
      //{
      //  oneLineHeight = GetOneLineHeight();
      //  if (paintRect.Height > oneLineHeight)
      //    wordWrap = true;
      //}
      //else if (WrapMode == DataGridTextWrapMode.WordWrap)
      //{
      //  wordWrap = true;
      //}
      return wordWrap;
    }

    public override bool IsCharToEnterEditMode(KeyPressEventArgs e)
    {
      UnicodeCategory uc = char.GetUnicodeCategory(e.KeyChar);
      if (uc != UnicodeCategory.Control)
        return true;

      return base.IsCharToEnterEditMode(e);
    }

    public override void OccupyEditControl(Control cellEditControl, int areaColIndex, int areaRowIndex, bool selectAll)
    {
      DataAxisGridComboBoxEditControl editor = (DataAxisGridComboBoxEditControl)cellEditControl;

      editor.Font = Font;
      editor.ReadOnly = !CanModify(areaColIndex, areaRowIndex);
      editor.PrepareEditorForEdit(selectAll);
      editor.Padding = Padding;
      //editor.TextAlign = HorzAlign;

      editor.DataSource = DataSource;
      editor.DisplayMember = DisplayMember;
      editor.ValueMember = ValueMember;

      editor.SetExternalEditButton(EditButton);
      editor.Compound = Compound;
      editor.Items.Clear();
      editor.Items.AddRange(Items.ToArray());

      editor.DropDownBox.HeightInRows = dropDownBoxVisibleRowCount;
      editor.DropDownBox.Width = dropDownBoxWidth;

      if (LookupMode)
      {
        PropertyAxisBar propAxisBar;
        object listItemBar;
        AxisObjectsByDataColRowIndex(areaColIndex, areaRowIndex, out propAxisBar, out listItemBar);

        editor.SelectedValue = propAxisBar.GetValue(listItemBar);
      }
      else
        editor.EditorEditValue = GetDisplayText(areaColIndex, areaRowIndex);

      ((IDataAxisGridCellInPlaceEditor)editor).EditorValueChanged = false;
      editor.CellManager = this;
    }

    public override void ReleaseEditControl(Control cellEditControl)
    {
      DataAxisGridComboBoxEditControl editor = (DataAxisGridComboBoxEditControl)cellEditControl;

      editor.DataSource = null;
      editor.DisplayMember = null;
      editor.ValueMember = "";
      editor.SetExternalEditButton(null);

      dropDownBoxVisibleRowCount = editor.DropDownBox.HeightInRows;
      dropDownBoxWidth = editor.DropDownBox.Width;
    }

    public virtual string GetCellNonfitToolTipText(BaseGridCellEventArgs e)
    {
      SizeF sf;
      Graphics g = EhLibUtils.DisplayGraphicsCash;
      string cellText = GetDisplayText(e.AreaColIndex, e.AreaRowIndex);

      sf = g.MeasureString(cellText, Grid.Font, new PointF(0, 0), StringFormat.GenericTypographic);

      if (sf.Width > e.CellRect.Width)
        return cellText;
      else
        return "";
    }

    protected internal override void OnMouseEnter(BaseGridCellEnterEventArgs e)
    {
      base.OnMouseEnter(e);

      string cellText = GetCellNonfitToolTipText(e);

      if (!string.IsNullOrEmpty(cellText))
      {
        Grid.ShowCellNonFitToolTip(cellText, e);
      }
    }

    protected internal override void OnMouseLeave(BaseGridCellLeaveEventArgs e)
    {
      base.OnMouseLeave(e);
      Grid.HideCellNonFitToolTip();
    }

    public virtual int GetOneLineHeight()
    {
      return CalcDefaultRowHeight();
    }

    void IObjectCollectionObserver.OnCollectionChangeAction(ObjectCollection collection, CollectionExtendedChangeAction action, int index, object oldItem, object newItem)
    {
      if (action == CollectionExtendedChangeAction.CollectionChanging ||
          action == CollectionExtendedChangeAction.ItemAdding ||
          action == CollectionExtendedChangeAction.ItemChanging ||
          action == CollectionExtendedChangeAction.ItemRemoving)
      {
        CheckNoDataSource();
      }
      else
      {
        ItemsChanged();
      }
    }

    int IObjectCollectionObserver.OnCompareCollectionItems(object item1, object item2)
    {
      string itemName1 = item1.ToString();
      string itemName2 = item2.ToString();

      CompareInfo compInfo = (Application.CurrentCulture).CompareInfo;
      return compInfo.Compare(itemName1, itemName2, CompareOptions.StringSort);
    }

    private void CheckNoDataSource()
    {
      if (DataSource != null)
      {
        throw new ArgumentException("DataSourceLocksItems");
      }
    }

    private void ItemsChanged()
    {
      ResetItemsConnection();
    }

    private void ResetItemsConnection()
    {
      if (DataSource == null)
      {
        if (Items.Count > 0)
        {
          //TODO: Postpone. Fibish support of Items property
          //bindingSource.DataSource = Items;
          //bindingSource.DataMember = "";
        }
        else
        {
          //bindingSource.DataSource = null;
          //bindingSource.DataMember = "";
        }
      }
    }

    private void WireDataSource(object dataSource)
    {
      IComponent component = dataSource as IComponent;
      if (component != null)
      {
        component.Disposed += this.DataSource_Disposed;
      }

      ISupportInitializeNotification dsInit = this.DataSource as ISupportInitializeNotification;
      if (dsInit != null)
      {
        dsInit.Initialized += DataSource_Initialized;
        dataSourceInitialized = dsInit.IsInitialized;
      }
      else if (dataSource != null)
        dataSourceInitialized = true;

    }

    private void UnwireDataSource()
    {
      IComponent component = this.DataSource as IComponent;
      if (component != null)
      {
        component.Disposed -= DataSource_Disposed;
      }

      ISupportInitializeNotification dsInit = this.DataSource as ISupportInitializeNotification;
      if (dsInit != null)
      {
        dsInit.Initialized -= DataSource_Initialized;
      }
    }

    private void DataSource_Disposed(object sender, EventArgs e)
    {
      this.DataSource = null;
    }

    private void DataSource_Initialized(object sender, EventArgs e)
    {
      dataSourceInitialized = true;
      ResetDataSourceConnection();
    }

    private void ResetDataSourceConnection()
    {
      if (!dataSourceInitialized) return;
      InitializeDisplayMemberPropertyDescriptor();
      InitializeValueMemberPropertyDescriptor();
      InvalidateGrid();
    }

    private void InitializeDisplayMemberPropertyDescriptor()
    {
      if (DataSource != null && /*displayMember != null &&*/ Grid != null)
      {
        if (string.IsNullOrEmpty(displayMember.BindingMember))
        {
          displayMemberProperty = null;
        }
        else
        {
          dataManager = (CurrencyManager)Grid.BindingContext[DataSource, displayMember.BindingPath];
          displayMemberProperty = dataManager.GetItemProperties().Find(displayMember.BindingField, true);
        }
      }
      else
      {
        displayMemberProperty = null;
        dataManager = null;
      }
    }

    private void InitializeValueMemberPropertyDescriptor()
    {
      PropertyDescriptor oldValueMemberProperty = ValueMemberProperty;

      if (DataManager != null)
      {
        if (string.IsNullOrEmpty(valueMember.BindingMember))
        {
          valueMemberProperty = null;
        }
        else
        {
          valueMemberProperty = this.DataManager.GetItemProperties().Find(valueMember.BindingField, true);
        }
      }
      else
      {
        valueMemberProperty = null;
      }

      if (!ReferenceEquals(oldValueMemberProperty, valueMemberProperty))
        ValueMemberPropertyChanged();
    }

    protected virtual void ValueMemberPropertyChanged()
    {
      //if (Column != null)
      //  Column.ValueMemberPropertyChanged();
      ////Update;
    }

    protected override bool EditButtonDefaultVisible()
    {
      return true;
    }

    public override HorizontalAlignment DefaultHorzAlign()
    {
      if (LookupMode)
        return HorizontalAlignment.Left;
      else
        return base.DefaultHorzAlign();
    }

    protected virtual void OnCompoundChanged()
    {
      InvalidateGrid();
    }

    protected internal override int GetOptimalCellContentWidth(int colIndex, int rowIndex, int dataColIndex, int dataRowIndex, object listItemBar)
    {
      int result = base.GetOptimalCellContentWidth(colIndex, rowIndex, dataColIndex, dataRowIndex, listItemBar);
      result = result + CustomAreaWidth;
      return result;
    }
    #endregion

  }

  [ToolboxItem(false)]
  [System.ComponentModel.DesignerCategory("Code")]
  class DataAxisGridComboBoxEditControl : ComboBoxEh, IDataAxisGridCellInPlaceEditor
  {
    private DataAxisGrid ownerGrid;
    private DataAxisGridComboBoxDataCell cellManager;
    private int rowIndex;
    private bool valueChanged;

    public DataAxisGridComboBoxEditControl()
    {
      this.TabStop = false;
      this.Border.Style = ControlBorderStyle.None;
      InitData();
    }

    private void InitData()
    {
      this.AutoSize = false;
    }

    public object EditorEditValue
    {
      get
      {
        if (LookupMode)
          return SelectedValue;
        else
          return Text;
      }

      set
      {
        this.Text = (string)value;
        this.valueChanged = false;
      }
    }

    public DataAxisGrid EditorOwnerGrid
    {
      get { return this.ownerGrid; }
      set { this.ownerGrid = value; }
    }

    public DataAxisGridComboBoxDataCell CellManager
    {
      get { return this.cellManager; }
      internal set { this.cellManager = value; }
    }

    public int EditorTableRowIndex
    {
      get { return this.rowIndex; }
      set { this.rowIndex = value; }
    }

    public bool EditorValueChanged
    {
      get { return this.valueChanged; }
      set { this.valueChanged = value; }
    }

    public bool EditorWantsInputKey(Keys keyData, bool dataGridWantsInputKey)
    {
      switch (keyData & Keys.KeyCode)
      {
        case Keys.Right:
          if ((this.RightToLeft == RightToLeft.No && !(this.SelectionLength == 0 && this.SelectionStart == this.Text.Length)) ||
              (this.RightToLeft == RightToLeft.Yes && !(this.SelectionLength == 0 && this.SelectionStart == 0)))
          {
            return true;
          }
          break;

        case Keys.Left:
          if ((this.RightToLeft == RightToLeft.No && !(this.SelectionLength == 0 && this.SelectionStart == 0)) ||
              (this.RightToLeft == RightToLeft.Yes && !(this.SelectionLength == 0 && this.SelectionStart == this.Text.Length)))
          {
            return true;
          }
          break;

        case Keys.Down:
          int end = this.SelectionStart + this.SelectionLength;
          if (this.Text.IndexOf("\r\n", end, StringComparison.Ordinal) != -1)
          {
            return true;
          }
          break;

        case Keys.Up:
          if (!(Text.IndexOf("\r\n", StringComparison.Ordinal) < 0 ||
                SelectionStart + SelectionLength < Text.IndexOf("\r\n", StringComparison.Ordinal)))
          {
            return true;
          }
          break;

        case Keys.Home:
        case Keys.End:
          if (this.SelectionLength != this.Text.Length)
          {
            return true;
          }
          break;

        case Keys.Prior:
        case Keys.Next:
          if (this.valueChanged)
          {
            return true;
          }
          break;

        case Keys.Delete:
          if (this.SelectionLength > 0 ||
              this.SelectionStart < this.Text.Length)
          {
            return true;
          }
          break;

        //case Keys.Enter:
        //  if ((keyData & (Keys.Control | Keys.Shift | Keys.Alt)) == Keys.Shift && this.Multiline && this.AcceptsReturn)
        //  {
        //    return true;
        //  }
        //  break;

        case Keys.Escape:
          if (ListVisible &&
              ((keyData & (Keys.Control | Keys.Shift | Keys.Alt)) == 0))
          {
            return true;
          }
          break;

      }
      return !dataGridWantsInputKey;
    }

    public void PrepareEditorForEdit(bool selectAll)
    {
      if (selectAll)
        SelectAll();
      else
        this.SelectionStart = this.Text.Length;
    }

    protected override void OnTextChanged(EventArgs e)
    {
      base.OnTextChanged(e);
      this.valueChanged = true;
      ownerGrid.EditorTextChanged(e);
    }

    internal protected override void OnCustomAreaPaint(ComboBoxCustomAreaPaintEventArgs e)
    {
      base.OnCustomAreaPaint(e);
      if (!e.Handled)
      {
        CellManager.OnCustomAreaPaint(e);
      }
    }
  }
}
