Imports System.Drawing
Imports System.Windows.Forms
Imports System.ComponentModel

''' <summary>
''' A base class for constructing markable data grid columns.
''' </summary>
''' <remarks></remarks>
Public MustInherit Class MarkableDataGridColumnBase
    Inherits DataGridTextBoxColumn

    Private WithEvents _owner As DataGrid = Nothing
    Private _columnOrdinal As Integer = -1
    Private _readOnly As Boolean = False
    Private _bounds As Rectangle = Rectangle.Empty

    ' Highlighting
    Private _highlightFlagColumnName As String = String.Empty
    Private _cellHighlightColor As Color

    ' Drawing
    Private _backColor As Color = Color.Empty
    Private _foreColor As Color = Color.Empty
    Private _stringFormat As StringFormat = Nothing

    ' Hosted control
    Private _hostedControl As Control = Nothing

    ''' <summary>
    ''' Initializes a default instance of the <see cref="Mercury.Mobile.Forms.MarkableDataGridColumnBase"/> class.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the <see cref="Mercury.Mobile.Forms.MarkableDataGridColumnBase"/> class with the specified property descriptor.
    ''' </summary>
    ''' <param name="pd">The property descriptor to initialize with.</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal pd As PropertyDescriptor)
        Me.PropertyDescriptor = pd
        If pd IsNot Nothing Then
            Me.HeaderText = pd.Name
            Me.MappingName = pd.Name
        End If
    End Sub

#Region "Properties"

    ''' <summary>
    ''' Indicates whether the column is ReadOnly.
    ''' </summary>
    ''' <value>Boolean.</value>
    ''' <returns>True, if the column is ReadOnly; otherwise, false.</returns>
    ''' <remarks></remarks>
    Public Overridable Property IsReadOnly() As Boolean
        Get
            Return Me._readOnly
        End Get
        Set(ByVal value As Boolean)
            Me._readOnly = value
            Me.Invalidate()
        End Set
    End Property

    ''' <summary>
    ''' Gets/sets the background color of the column.
    ''' </summary>
    ''' <value>Color.</value>
    ''' <returns>Background color.</returns>
    ''' <remarks></remarks>
    Public Property BackColor() As Color
        Get
            Return Me._backColor
        End Get
        Set(ByVal value As Color)
            Me._backColor = value
            Me.Invalidate()
        End Set
    End Property

    ''' <summary>
    ''' Gets/sets the foreground color of the column.
    ''' </summary>
    ''' <value>Color.</value>
    ''' <returns>Foreground color.</returns>
    ''' <remarks></remarks>
    Public Property ForeColor() As Color
        Get
            Return Me._foreColor
        End Get
        Set(ByVal value As Color)
            Me._foreColor = value
            Me.Invalidate()
        End Set
    End Property

    ''' <summary>
    ''' Gets/sets the cell highlight color.
    ''' </summary>
    ''' <value>Color.</value>
    ''' <returns>Cell highlight color.</returns>
    ''' <remarks></remarks>
    Public Property HighlightFlagCellColor() As Color
        Get
            Return Me._cellHighlightColor
        End Get
        Set(ByVal value As Color)
            Me._cellHighlightColor = value
            Me.Invalidate()
        End Set
    End Property

    ''' <summary>
    ''' Gets/sets the name of the highlight flag column.
    ''' </summary>
    ''' <value>String.</value>
    ''' <returns>Highlight flag column name.</returns>
    ''' <remarks></remarks>
    Public Property HighlightFlagColumnName() As String
        Get
            Return Me._highlightFlagColumnName
        End Get
        Set(ByVal value As String)
            Me._highlightFlagColumnName = value
            Me.Invalidate()
        End Set
    End Property

    ''' <summary>
    ''' Gets/sets the DataGrid owner of this ColumnStyle.
    ''' </summary>
    ''' <value>DataGrid.</value>
    ''' <returns>DataGrid owner.</returns>
    ''' <remarks></remarks>
    Public Property Owner() As DataGrid
        Get
            If Me._owner Is Nothing Then
                Throw New InvalidOperationException("DataGrid owner of this ColumnStyle must be set prior to " + _
                                                    "this operation.")
            End If
            Return Me._owner
        End Get
        Set(ByVal value As DataGrid)
            If Me._owner IsNot Nothing Then
                Throw New InvalidOperationException("DataGrid owner of this ColumnStyle is already set.")
            End If
            Me._owner = value
        End Set
    End Property

    ''' <summary>
    ''' Sets horisontal aligment in the cell.
    ''' </summary>
    ''' <value>HorizontalAlignment.</value>
    ''' <returns>StingAlignment expressed in terms of HorizontalAlignment.</returns>
    ''' <remarks></remarks>
    Public Overridable Property Alignment() As HorizontalAlignment
        Get
            Return If(Me._stringFormat.Alignment = StringAlignment.Center, HorizontalAlignment.Center, _
                      If(Me._stringFormat.Alignment = StringAlignment.Far, HorizontalAlignment.Right, _
                         HorizontalAlignment.Left))
        End Get
        Set(ByVal value As HorizontalAlignment)
            Me._stringFormat.Alignment = If(value = HorizontalAlignment.Center, StringAlignment.Center, _
                                            If(value = HorizontalAlignment.Right, StringAlignment.Far, _
                                               StringAlignment.Near))
            Me.Invalidate()
        End Set
    End Property

    ''' <summary>
    ''' Gets the column's ordinal in the grid.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Ordinal() As Integer
        Get
            ' Ordinal not set and parent is not null
            If Me._columnOrdinal = -1 AndAlso Me._owner IsNot Nothing Then
                For Each table As DataGridTableStyle In Me._owner.TableStyles
                    Me._columnOrdinal = table.GridColumnStyles.IndexOf(Me)
                    If Me._columnOrdinal <> -1 Then
                        Exit For
                    End If
                Next
            End If

            ' Return
            Return Me._columnOrdinal
        End Get
    End Property

    ''' <summary>
    ''' Gets or sets null value. This values would be shown if data in the data source is null.
    ''' If data in the grid is set to this value, null would be pushed to data source.  This 
    ''' base class is text oriented, so NullText will be used.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overridable Property NullValue() As Object
        Get
            Return Me.NullText
        End Get
        Set(ByVal value As Object)
            Me.NullText = value.ToString()
        End Set
    End Property

    ''' <summary>
    ''' Gets the hosted control.
    ''' </summary>
    ''' <value>Control.</value>
    ''' <returns>Hosted control for the column.</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property HostedControl() As Control
        Get
            ' If hosted control is null and owner exists
            If Me._hostedControl Is Nothing And Me._owner IsNot Nothing Then
                ' Create hosted control
                Me._hostedControl = CreateHostedControl()

                ' Set defaults
                Me._hostedControl.Visible = False
                Me._hostedControl.Name = Me.HeaderText
                Me._hostedControl.Font = Me._owner.Font
                Me.Owner.Controls.Add(Me._hostedControl)
                ' Add data binding
                Me._hostedControl.DataBindings.Add(GetBoundPropertyName, Me._owner.DataSource, Me.MappingName, True, DataSourceUpdateMode.OnValidation, NullValue)

                ' Now we need to hook into grid's horisontal scroll event so we can move 
                ' hosted control as user scrolls.  To do so we'll look for HScrollBar 
                ' control owned by the grid.  The first found is used.
                Dim horizontal As HScrollBar = Nothing
                For Each c As Control In Me._owner.Controls
                    ' If HScrollBar
                    If TypeOf (c) Is HScrollBar Then
                        AddHandler horizontal.ValueChanged, AddressOf OnHorizontalGridScroll
                        Exit For
                    End If
                Next
            End If

            ' Return
            Return _hostedControl
        End Get
    End Property

#End Region

#Region "Events"

    Private Sub OnHorizontalGridScroll(ByVal sender As Object, ByVal e As System.EventArgs)
        UpdateHostedControl()
    End Sub

#End Region

#Region "Methods"

    Protected MustOverride Function GetBoundPropertyName() As String

    Protected MustOverride Function CreateHostedControl() As Control

    Protected Sub UpdateHostedControl()
        Dim selectedBounds As Rectangle = Me._owner.GetCellBounds(Me._owner.CurrentCell.RowNumber, Me._owner.CurrentCell.ColumnNumber)

        ' If not readonly AND column ordinal = column number AND hit test corresponds to cell
        If Not (Me._readOnly) AndAlso (Me.Ordinal = Me._owner.CurrentCell.ColumnNumber) AndAlso _
            Me._owner.HitTest(selectedBounds.Left, selectedBounds.Top).Type = DataGrid.HitTestType.Cell AndAlso _
            Me._owner.HitTest(selectedBounds.Right, selectedBounds.Bottom).Type = DataGrid.HitTestType.Cell Then
            ' If selected bounds not equal
            If selectedBounds <> Me._bounds Then
                ' Set current column bounds
                Me._bounds = selectedBounds

                ' Set hosted control bounds
                Me.HostedControl.Bounds = selectedBounds

                ' Set focus and update
                Me.HostedControl.Focus()
                Me.HostedControl.Update()
            End If

            ' If hosted control is not visible
            If Not (Me.HostedControl.Visible) Then
                Me.HostedControl.Show()
                Me.HostedControl.Focus()
            End If
        ElseIf Me.HostedControl.Visible Then
            Me.HostedControl.Hide()
        End If
    End Sub

    'Public Overridable Function IsSelected(ByVal rowNum As Integer) As Boolean
    '    ' If DataGrid owner exists
    '    If Me._owner IsNot Nothing Then
    '        ' If current cell matches
    '        Return Me.Owner.IsSelected(rowNum)
    '    End If
    'End Function

#End Region

#Region "Drawing Methods"

    Private _cacheBackColorBrush As Brush
    Private _cacheForeColorBrush As Brush
    Private _cacheCellHighlightBrush As Brush
    Private _cacheSelectionHighlightBrush As Brush

    Protected Overrides Sub Paint(ByVal g As System.Drawing.Graphics, ByVal bounds As System.Drawing.Rectangle, ByVal source As System.Windows.Forms.CurrencyManager, ByVal rowNum As Integer, ByVal backBrush As System.Drawing.Brush, ByVal foreBrush As System.Drawing.Brush, ByVal alignToRight As Boolean)
        Dim isSelectedItem As Boolean = False

        ' Setup brushes
        If Me._cacheBackColorBrush Is Nothing AndAlso Me._backColor <> Color.Empty Then
            Me._cacheBackColorBrush = New SolidBrush(Me._backColor)
        Else
            Me._cacheBackColorBrush = backBrush
        End If
        If Me._cacheForeColorBrush Is Nothing AndAlso Me._foreColor <> Color.Empty Then
            Me._cacheForeColorBrush = New SolidBrush(Me._foreColor)
        Else
            Me._cacheForeColorBrush = foreBrush
            Dim checkHighlightBrush As SolidBrush = CType(foreBrush, SolidBrush)
            If checkHighlightBrush.Color.R = SystemColors.HighlightText.R AndAlso _
                checkHighlightBrush.Color.G = SystemColors.HighlightText.G AndAlso _
                checkHighlightBrush.Color.B = SystemColors.HighlightText.B Then
                isSelectedItem = True
            End If
        End If
        If Me._cacheCellHighlightBrush Is Nothing Then
            Me._cacheCellHighlightBrush = New SolidBrush(Me._cellHighlightColor)
        End If
        If Me._cacheSelectionHighlightBrush Is Nothing Then
            Me._cacheSelectionHighlightBrush = New SolidBrush(System.Drawing.SystemColors.Highlight)
        End If

        ' Declare private vars
        Dim textBounds As RectangleF
        Dim cellData As Object

        ' Draw background
        DrawBackground(g, bounds, rowNum, Me._cacheBackColorBrush, isSelectedItem)

        ' Prepare text bounds
        Dim newBounds = bounds
        newBounds.Inflate(-2, -2)
        textBounds = New RectangleF(newBounds.X, newBounds.Y, newBounds.Width, newBounds.Height)

        ' Get text data
        cellData = Me.PropertyDescriptor.GetValue(source.List(rowNum))

        ' Get font
        Dim currentFont As Font = New Font(FontFamily.GenericSansSerif, 8.0, FontStyle.Regular)
        If Me.Owner IsNot Nothing Then
            currentFont = Me.Owner.Font
        End If
        If IsHighlightRow(rowNum) Then
            currentFont = New Font(currentFont.Name, currentFont.Size, FontStyle.Bold)
        End If

        ' Draw
        g.DrawString(FormatText(cellData), currentFont, Me._cacheForeColorBrush, textBounds, GridStringFormat)

        ' If host control, update hosted control.
        UpdateHostedControl()
    End Sub

    Protected Overridable Sub DrawBackground(ByVal gx As Graphics, ByVal bounds As Rectangle, ByVal rowNum As Integer, ByVal backBrush As Brush, ByVal isSelectedItem As Boolean)
        ' Determine background color
        Dim backGroundBrush As Brush = backBrush
        If backGroundBrush IsNot Nothing AndAlso IsHighlightRow(rowNum) AndAlso _
            Not (isSelectedItem) Then
            backGroundBrush = Me._cacheCellHighlightBrush
        End If

        ' Fill background
        gx.FillRectangle(backGroundBrush, bounds)
    End Sub

    ''' <summary>
    ''' Converts data from data source to string according to formatting set.
    ''' </summary>
    ''' <param name="cellData">The data from which to derive the text.</param>
    ''' <returns>Textual representation of the data object.</returns>
    ''' <remarks></remarks>
    Protected Overridable Function FormatText(ByVal cellData As Object) As String
        Dim cellText As String

        ' Determine proper text output
        If cellData Is Nothing OrElse TypeOf (cellData) Is DBNull Then                      ' If data is null
            cellText = Me.NullText                                                          ' Set null text
        ElseIf TypeOf (cellData) Is IFormattable Then                                       ' Else if formattable
            cellText = CType(cellData, IFormattable).ToString(Me.Format, Me.FormatInfo)     ' Format
        ElseIf TypeOf (cellData) Is IConvertible Then                                       ' Else if convertible
            cellText = CType(cellData, IConvertible).ToString(Me.FormatInfo)                ' Convert
        Else                                                                                ' Else
            cellText = cellData.ToString()                                                  ' Call ToString()
        End If

        ' Return text
        Return cellText
    End Function

    ''' <summary>
    ''' Gets the text formatting in the grid.
    ''' </summary>
    ''' <value>StringFormat.</value>
    ''' <returns>StringFormat for grid text.</returns>
    ''' <remarks>Grid needs to be invalidated if format is changed.</remarks>
    Public ReadOnly Property GridStringFormat() As StringFormat
        Get
            ' If string format currently nothing
            If Me._stringFormat Is Nothing Then
                Me._stringFormat = New StringFormat()
                Me.Alignment = HorizontalAlignment.Left
            End If

            ' Return
            Return Me._stringFormat
        End Get
    End Property

    ''' <summary>
    ''' Invalidates the control and repaints column.
    ''' </summary>
    ''' <remarks></remarks>
    Protected Overridable Sub Invalidate()
        ' Check for parent
        If Me._owner IsNot Nothing Then
            Me.Owner.Invalidate()
        End If
    End Sub

    Public Function IsHighlightRow(ByVal rowNum As Integer) As Boolean
        ' If flag column name and DataGrid owner exists
        If Not (String.IsNullOrEmpty(Me._highlightFlagColumnName)) AndAlso Me._owner IsNot Nothing Then
            ' Get currency manager
            Dim manager As CurrencyManager
            Try
                manager = CType(Me._owner.BindingContext(Me._owner.DataSource), CurrencyManager)
            Catch ex As System.ArgumentException
                Return False
            End Try

            ' Get item properties
            If manager.GetItemProperties.Count > 0 Then
                ' Get row
                Dim row As Object = manager.List(rowNum)
                ' Get property
                Dim managerProperty As PropertyDescriptor
                managerProperty = manager.GetItemProperties.Item(Me._highlightFlagColumnName)

                Try
                    Dim boolObj = managerProperty.GetValue(row)
                    If boolObj Is Nothing Then _
                        Return False
                    If TypeOf boolObj Is Boolean Then _
                        Return CType(boolObj, Boolean)
                    Return Boolean.Parse(boolObj.ToString())
                Catch ex As ArgumentNullException
                    Return False
                Catch ex As FormatException
                    Return False
                End Try
            End If
        End If
    End Function

#End Region

End Class
