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

using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace EhLib
{
  [ToolboxItem(false)]
  public class PopupDataGrid : DataGridEh
  {

    #region privates
    private readonly PopupDataGridForm gridForm;
    private string displayMember;
    private string valueMember;
    private int selectedIndex;
    #endregion privates

    #region constructor
    public PopupDataGrid(PopupDataGridForm gridForm)
    {
      this.gridForm = gridForm;
      SetStyle(ControlStyles.Selectable, false);
      IndicatorColumn.Visible = false;
      Selection.RowSelect = true;
      Title.Visible = false;
      AutoSizeColumnOptions.FitToClient = true;
      SizeGripAlwaysShow = true;
      LineOptions.HorzLines = false;
      LineOptions.VertLines = false;
      Selection.AllowedSelection.SetAllowedSelections(true, false, false, false);
      ReadOnly = true;
      EmptyDataInfo.Active = true;
      EmptyDataInfo.Text = "The list is empty";
    }
    #endregion

    #region properties
    public string DisplayMember
    {
      get
      {
        return displayMember;
      }

      set
      {
        if (value != displayMember)
        {
          displayMember = value;
          UpdateColumnsList();
        }
      }
    }

    public string ValueMember
    {
      get
      {
        return valueMember;
      }

      set
      {
        valueMember = value;
        //if (value != valueMember)
        //{
        //  valueMember = value;
        //}
      }
    }

    public int SelectedIndex
    {
      get
      {
        return selectedIndex;
      }
      set
      {
        if (value != selectedIndex)
        {
          if (value >= 0 && VisibleRows.Count > 0)
          {
            int inVisibleRowsIndex = Rows[value].VisibleIndex;
            if (inVisibleRowsIndex >= 0)
              CurrentRowIndex = inVisibleRowsIndex;
          }
          else
          {
            if (VisibleRows.Count > 0)
              CurrentRowIndex = 0;
          }
          selectedIndex = value;
          Invalidate();
        }
      }
    }

    public object SelectedValue
    {
      get
      {
        if (CurrentRow != null)
        {
          PropertyDescriptorCollection pc = CurrencyManager.GetItemProperties();
          PropertyDescriptor prop = pc.Find(DisplayMember, true);
          object val = prop.GetValue(CurrentRow.SourceItem);
          return val;
        }
        else
        {
          return null;
        }
      }
      set
      {
        throw new NotSupportedException(@"set SelectedValue is called");
        //if (dataManager != null)
        //{
        //  string propertyName = valueMember.BindingField;
        //  if (string.IsNullOrEmpty(propertyName))
        //    throw new InvalidOperationException("ListControlEmptyValueMemberInSettingSelectedValue");
        //  PropertyDescriptorCollection props = dataManager.GetItemProperties();
        //  PropertyDescriptor property = props.Find(propertyName, true);
        //  int index = FindInDataManager(property, value, true);
        //  this.SelectedIndex = index;
        //}
      }
    }

    public new int DefaultRowHeight
    {
      get
      {
        return CalcDefaultRowHeight();
      }
    }

    protected override CreateParams CreateParams
    {
      get
      {
        CreateParams cp = base.CreateParams;
        //cp.Style |= WS_BORDER;
        //cp.ExStyle |= WS_EX_CLIENTEDGE;
        return cp;
      }
    }

    public override bool Focused
    {
      get { return true; }
    }

    public new SizeGripPosition SizeGripPosition
    {
      get { return base.SizeGripPosition; }
      set { base.SizeGripPosition = value; }
    }

    public ComboBoxEh ComboBox
    {
      get
      {
        return gridForm.comboBox;
      }
    }
    #endregion properties

    #region methods
    protected override PropertyAxisBar[] CreateDynamicColumnsForDataProperty(PropertyDescriptor prop)
    {
      if (OnCheckIfCreateDynaColumnForDataProperty(prop))
        return base.CreateDynamicColumnsForDataProperty(prop);
      else
        return null;
    }

    protected bool OnCheckIfCreateDynaColumnForDataProperty(PropertyDescriptor prop)
    {
      bool result = true;
      if (DataSource != null)
      {
        if (result)
        {
          if (prop.Name == DisplayMember)
            // ReSharper disable once RedundantAssignment
            result = true;
          else
            result = false;
        }
      }
      return result;
    }

    protected override PropertyAxisBar CreatePropBarForDataProperty(PropertyDescriptor prop)
    {
      if (DataSource != null && prop.Name == DisplayMember)
      {
        return CreatePropBarForListItemType(prop.PropertyType);
      }
      else
      {
        return base.CreatePropBarForDataProperty(prop);
      }
    }

    protected override PropertyAxisBar CreatePropBarForListItemType(Type type)
    {
      Type columnType = ComboBoxManager.DefaultManager.GetMainColumnType();
      DataGridColumn column = (DataGridColumn)Activator.CreateInstance(columnType);
      return column;
    }

    protected internal override void FocusGrid()
    {
      //Nothing to-do
    }

    protected internal override bool IsActiveControl()
    {
      return true;
    }

    protected override GripActiveStatus CornerSizeGripActiveStatus(SizeGripControl sizeGrip)
    {
      return GripActiveStatus.Always;
    }

    internal void ProcessKeyDown(KeyEventArgs e)
    {
      if (e.Alt) return;

      switch (e.KeyCode)
      {
        case Keys.Up:
        case Keys.Down:
          if (SelectedIndex == -1 && VisibleRows.Count > 0)
            SelectedIndex = VisibleRows[0].Index;
          else
            OnKeyDown(e);
          e.Handled = true;
          break;
        //case Keys.Home:
        //case Keys.End:
        case Keys.PageDown:
        case Keys.PageUp:
          OnKeyDown(e);
          e.Handled = true;
          break;
        case Keys.Return:
          gridForm.comboBox.CloseUp(true);
          e.Handled = true;
          break;
      }
    }

    protected override void OnCellMouseClick(BaseGridCellManager cell, BaseGridCellMouseEventArgs e)
    {
      base.OnCellMouseClick(cell, e);
      CellAreaType areaType = GetCellAreaTypeByColRow(e.ColIndex, e.RowIndex);
      if (areaType.HorzType == DataGridCellAreaHorzType.Data &&
          areaType.VertType == DataGridCellAreaVertType.Data)
      {
        gridForm.comboBox.CloseUp(true);
      }
    }

    protected override void OnCellMouseMove(BaseGridCellManager cell, BaseGridCellMouseEventArgs e)
    {
      base.OnCellMouseMove(cell, e);
      CellAreaType areaType = GetCellAreaTypeByColRow(e.ColIndex, e.RowIndex);
      if (VisibleRows.Count > 0 &&
          areaType.HorzType == DataGridCellAreaHorzType.Data &&
          areaType.VertType == DataGridCellAreaVertType.Data)
      {
        SelectedIndex = VisibleRows[e.RowIndex].Index;
      }
    }

    protected override void OnHandleCreated(EventArgs e)
    {
      base.OnHandleCreated(e);
      gridForm.UpdateHeight(false);
    }

    protected override void PaintCell(Graphics g, int colIndex, int rowIndex, Rectangle rect, BasePaintCellStates state)
    {
      if (SelectedIndex == -1)
        state = state &
               (~BasePaintCellStates.Selected) &
               (~BasePaintCellStates.Current) &
               (~BasePaintCellStates.Focused) &
               (~BasePaintCellStates.CurrentRow) &
               (~BasePaintCellStates.RowSelected);

      base.PaintCell(g, colIndex, rowIndex, rect, state);
    }

    protected override void CurrentCellMoved(GridCoord oldCurCell)
    {
      base.CurrentCellMoved(oldCurCell);
      if (CurrentRowIndex < VisibleRows.Count)
        selectedIndex = VisibleRows[CurrentRowIndex].Index;
      else
        selectedIndex = -1;
    }
    #endregion
  }

  public class PopupDataGridForm : Form
  {
    #region privates
    readonly BindingSource bindingSource;
    readonly internal ComboBoxEh comboBox;
    public PopupDataGrid Grid;
    private int visibleRowCount;
    private int oldWidth, oldHeight;
    private bool widthChanged, heightChanged;
    private string filterText;
    private bool internalSizeSet;
    #endregion

    #region constructor
    public PopupDataGridForm(ComboBoxEh comboBox)
    {
      FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
      StartPosition = FormStartPosition.Manual;
      //BackColor = Color.Black;
      //TransparencyKey = Color.Black;
      ShowInTaskbar = false;
      this.comboBox = comboBox;

      bindingSource = new BindingSource();

      Grid = new PopupDataGrid(this);
      Grid.Dock = DockStyle.Fill;
      Grid.Parent = this;
      Grid.DataSource = bindingSource;
    }
    #endregion

    #region properties
    public object DataSource
    {
      get
      {
        return bindingSource.DataSource;
      }

      set
      {
        bindingSource.DataSource = value;
      }
    }

    public string DisplayMember
    {
      get
      {
        return Grid.DisplayMember;
      }

      set
      {
        Grid.DisplayMember = value;
      }
    }

    public string ValueMember
    {
      get
      {
        return Grid.ValueMember;
      }

      set
      {
        Grid.ValueMember = value;
      }
    }

    public int SelectedIndex
    {
      get
      {
        return Grid.SelectedIndex;
      }

      set
      {
        Grid.SelectedIndex = value;
      }
    }

    public object SelectedValue
    {
      get
      {
        return Grid.SelectedValue;
      }

      set
      {
        Grid.SelectedValue = value;
      }
    }

    public bool WidthChanged
    {
      get { return widthChanged; }
    }

    public bool HeightChanged
    {
      get { return heightChanged; }
    }

    protected override bool ShowWithoutActivation
    {
      get { return true; }
    }

    protected override CreateParams CreateParams
    {
      //[EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
      get
      {

        CreateParams baseParams = base.CreateParams;
        baseParams.ExStyle |= (NativeMethods.WS_EX_TOPMOST);
        baseParams.ClassStyle |= NativeMethods.CS_DROPSHADOW;

        return baseParams;
      }
    }

    public int VisibleRowCount
    {
      get
      {
        return visibleRowCount;
      }
      set
      {
        if (value != visibleRowCount)
        {
          visibleRowCount = value;
          UpdateHeight(false);
        }
      }
    }

    public SizeGripPosition SizeGripPosition
    {
      get
      {
        return Grid.SizeGripPosition;
      }
      set
      {
        Grid.SizeGripPosition = value;
      }
    }

    public string FilterText
    {
      get
      {
        return filterText;
      }
      set
      {
        if (value != filterText)
        {
          filterText = value;
          if (FilterType == ComboBoxFilterType.StartOfText)
          {
            Grid.SearchBox.CellBeginsWithMode = true;
            Grid.SearchBox.HighlightSearchingText = false;
          }
          else
          {
            Grid.SearchBox.CellBeginsWithMode = false;
            Grid.SearchBox.HighlightSearchingText = true;
          }
          Grid.SearchBox.SearchingText = filterText;
          Grid.SearchBox.ApplyFilter(filterText);
          internalSizeSet = true;
          try
          {
            UpdateHeight(false);
          }
          finally
          {
            internalSizeSet = false;
          }
        }
      }
    }

    public ComboBoxFilterType FilterType { get; set; }
    #endregion

    #region methods
    public void ResetOldSizeInfo()
    {
      oldWidth = Width;
      widthChanged = false;
      oldHeight = Height;
      heightChanged = false;
    }

    protected override void DefWndProc(ref Message m)
    {
      switch (m.Msg)
      {
        case NativeMethods.WM_MOUSEACTIVATE:
          m.Result = (IntPtr)NativeMethods.MA_NOACTIVATE;
          return;
      }
      base.DefWndProc(ref m);
    }

    protected internal virtual void ProcessKeyDown(KeyEventArgs e)
    {
      Grid.ProcessKeyDown(e);
    }

    protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
    {
      height = CalcAutoHeightForOfferedHeight(height);
      width = Math.Max(SystemInformation.VerticalScrollBarWidth * 2, width);
      base.SetBoundsCore(x, y, width, height, specified);
    }

    protected override void OnHandleCreated(EventArgs e)
    {
      base.OnHandleCreated(e);
      UpdateHeight(false);
    }

    protected override void OnSizeChanged(EventArgs e)
    {
      base.OnSizeChanged(e);
      if (internalSizeSet) return;
      if (oldWidth != Width)
      {
        widthChanged = true;
        oldWidth = Width;
      }
      if (oldHeight != Height)
      {
        heightChanged = true;
        int defRowHeight = Grid.DefaultRowHeight;
        int gridBordersHeight = Grid.Height - Grid.ClientBounds.Height;
        visibleRowCount = (Height - gridBordersHeight) / defRowHeight;
        oldHeight = Height;
      }
    }

    internal void UpdateHeight(bool forceHandle)
    {
      //if (Grid == null || !Grid.IsHandleCreated) return;
      if (Grid == null) return;
      if (!forceHandle && !Grid.IsHandleCreated) return;

      Grid.CreateControl();
      int defRowHeight = Grid.DefaultRowHeight;
      int gridBordersHeight = Grid.Height - Grid.ClientBounds.Height;
      Height = CalcAutoHeightForOfferedHeight(VisibleRowCount * defRowHeight + gridBordersHeight);
    }

    //internal void UpdateHeightFromVisibleRowCount()
    //{

    //}

    private int CalcAutoHeightForOfferedHeight(int offeredHeight)
    {
      if (!Grid.IsHandleCreated) return offeredHeight;

      int result;

      int gridBordersHeight = Grid.Height - Grid.ClientBounds.Height;
      int gridNewClientHeight = offeredHeight - gridBordersHeight;
      int defRowHeight = Grid.DefaultRowHeight;
      int rows = gridNewClientHeight / defRowHeight;
      if (rows < 1) rows = 1;

      int heightLow = rows * defRowHeight;
      int heightHigh = (rows + 1) * defRowHeight;

      int fixedVisibleRowCount;
      if (Grid.VisibleRows.Count > 0)
        fixedVisibleRowCount = Grid.VisibleRows.Count;
      else
        fixedVisibleRowCount = VisibleRowCount;

      int maxRowsHeight = fixedVisibleRowCount * defRowHeight;
      if (heightLow > maxRowsHeight) heightLow = maxRowsHeight;
      if (heightHigh > maxRowsHeight) heightHigh = maxRowsHeight;

      int deltaToLow = Math.Abs(heightLow - gridNewClientHeight);
      int deltaToHigh = Math.Abs(heightHigh - gridNewClientHeight);

      if (deltaToLow < deltaToHigh)
        result = heightLow + gridBordersHeight;
      else
        result = heightHigh + gridBordersHeight;

      return result;
    }
    #endregion
  }

  [ToolboxItem(false)]
  public class ComboBoxCustomAreaControl : Control
  {
    private ComboBoxEh comboBox;

    public ComboBoxCustomAreaControl(ComboBoxEh comboBox)
    {
      this.comboBox = comboBox;
      SetStyle(ControlStyles.Selectable, false);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
      var pe = new ComboBoxCustomAreaPaintEventArgs(e.Graphics, ClientRectangle, comboBox.SelectedValue);
      comboBox.OnCustomAreaPaint(pe);
    }
  }

  [DataAxisGridPropBarDesignTimeVisibleAttribute(false)]
  public class ComboBoxTextColumn : DataGridTextColumn
  {
    public ComboBoxTextColumn()
    {

    }

    #region Properties
    #endregion

    #region methods
    protected override DataAxisGridDataCellMan CreateTemplateCell()
    {
      return new ComboBoxTextDataCell();
    }
    #endregion
  }

  [DataAxisGridDataCellDesignTimeVisibleAttribute(false)]
  public class ComboBoxTextDataCell : DataAxisGridTextDataCell
  {

    #region privates
    #endregion

    public ComboBoxTextDataCell()
    {
    }

    #region properties
    public ComboBoxEh ComboBox
    {
      get
      {
        return ((PopupDataGrid)Grid).ComboBox;
      }
    }
    #endregion

    #region methods
    #endregion
    protected override void PaintContent(DataAxisGridDataCellContentPaintEventArgs e)
    {
      Rectangle cellContentRect = e.CellContentRect;
      object val = ComboBox.GetValueByIndex(((DataGridRow)e.ListItemBar).Index);

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

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

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

      var te = new ComboBoxTextAreaPaintEventArgs(e, this, cellContentRect, val);
      ComboBox.OnTextAreaPaint(te);
    }

    protected internal virtual void InternalPaintTextContent(ComboBoxTextAreaPaintEventArgs e)
    {
      var ne = new DataAxisGridDataCellContentPaintEventArgs(e.CellPaintEventArgs.DataCellPaintEventArgs, e.AreaRect);
      base.PaintContent(ne);
    }

  }

  public class ComboBoxCustomAreaPaintEventArgs : EventArgs
  {
    private Graphics graphics;
    private Rectangle areaRect;
    private object comboValue;
    private bool handled;

    public ComboBoxCustomAreaPaintEventArgs(Graphics graphics, Rectangle areaRect, object comboValue)
    {
      this.graphics = graphics;
      this.areaRect = areaRect;
      this.comboValue = comboValue;
    }

    public Graphics Graphics
    {
      get { return this.graphics; }
    }

    public Rectangle AreaRect
    {
      get { return this.areaRect; }
    }

    public object ComboValue
    {
      get { return this.comboValue; }
    }

    public bool Handled
    {
      get
      {
        return handled;
      }
      set
      {
        handled = value;
      }
    }

  }

  public class ComboBoxTextAreaPaintEventArgs : EventArgs
  {
    private Rectangle areaRect;
    private object comboValue;
    private DataAxisGridDataCellContentPaintEventArgs cellPaintEventArgs;
    private bool handled;
    private BaseGridCellManager cellManager;

    public ComboBoxTextAreaPaintEventArgs(DataAxisGridDataCellContentPaintEventArgs cellPaintEventArgs, 
      BaseGridCellManager cellManager, Rectangle areaRect, object comboValue)
    {
      this.cellPaintEventArgs = cellPaintEventArgs;
      this.cellManager = cellManager;
      this.areaRect = areaRect;
      this.comboValue = comboValue;
    }

    public DataAxisGridDataCellContentPaintEventArgs CellPaintEventArgs
    {
      get { return this.cellPaintEventArgs; }
    }

    public Graphics Graphics
    {
      get { return this.cellPaintEventArgs.Graphics; }
    }

    public Rectangle AreaRect
    {
      get { return this.areaRect; }
    }

    public object ComboValue
    {
      get { return this.comboValue; }
    }

    public BaseGridCellManager CellManager
    {
      get { return this.cellManager; }
    }

    public bool Handled
    {
      get
      {
        return handled;
      }
      set
      {
        handled = value;
      }
    }

    public void DrawContent(ComboBoxTextAreaPaintEventArgs e)
    {
      ((ComboBoxTextDataCell)CellManager).InternalPaintTextContent(e);
    }

  }
}
