Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Values
Imports SkiaSharp
Imports Nexamas.UI.Rendering

Namespace Nexamas.UI.Components

    Partial Public NotInheritable Class MASAgendaView

#Region "Timed Layout"

        Private NotInheritable Class AgendaEventLayoutCandidate
            Friend Sub New(eventIndex As Integer,
                           startMinute As Integer,
                           endMinute As Integer)
                Me.EventIndex = eventIndex
                Me.StartMinute = startMinute
                Me.EndMinute = endMinute
            End Sub

            Friend ReadOnly Property EventIndex As Integer
            Friend ReadOnly Property StartMinute As Integer
            Friend ReadOnly Property EndMinute As Integer
        End Class

        Private NotInheritable Class AgendaEventLayoutSlot
            Friend Sub New(eventIndex As Integer,
                           rect As SKRect,
                           laneIndex As Integer,
                           laneCount As Integer,
                           overlapGroupIndex As Integer,
                           startMinute As Integer,
                           endMinute As Integer,
                           clippedStart As Boolean,
                           clippedEnd As Boolean)
                Me.EventIndex = eventIndex
                Me.Rect = rect
                Me.LaneIndex = laneIndex
                Me.LaneCount = laneCount
                Me.OverlapGroupIndex = overlapGroupIndex
                Me.StartMinute = startMinute
                Me.EndMinute = endMinute
                Me.ClippedStart = clippedStart
                Me.ClippedEnd = clippedEnd
            End Sub

            Friend ReadOnly Property EventIndex As Integer
            Friend ReadOnly Property Rect As SKRect
            Friend ReadOnly Property LaneIndex As Integer
            Friend ReadOnly Property LaneCount As Integer
            Friend ReadOnly Property OverlapGroupIndex As Integer
            Friend ReadOnly Property StartMinute As Integer
            Friend ReadOnly Property EndMinute As Integer
            Friend ReadOnly Property ClippedStart As Boolean
            Friend ReadOnly Property ClippedEnd As Boolean
        End Class

        Friend NotInheritable Class AgendaEventLayoutEvidence
            Friend Sub New(eventKey As String,
                           rect As SKRect,
                           laneIndex As Integer,
                           laneCount As Integer,
                           overlapGroupIndex As Integer,
                           startMinute As Integer,
                           endMinute As Integer,
                           clippedStart As Boolean,
                           clippedEnd As Boolean)
                Me.EventKey = If(eventKey, String.Empty)
                Me.Rect = rect
                Me.LaneIndex = laneIndex
                Me.LaneCount = laneCount
                Me.OverlapGroupIndex = overlapGroupIndex
                Me.StartMinute = startMinute
                Me.EndMinute = endMinute
                Me.ClippedStart = clippedStart
                Me.ClippedEnd = clippedEnd
            End Sub

            Friend ReadOnly Property EventKey As String
            Friend ReadOnly Property Rect As SKRect
            Friend ReadOnly Property LaneIndex As Integer
            Friend ReadOnly Property LaneCount As Integer
            Friend ReadOnly Property OverlapGroupIndex As Integer
            Friend ReadOnly Property StartMinute As Integer
            Friend ReadOnly Property EndMinute As Integer
            Friend ReadOnly Property ClippedStart As Boolean
            Friend ReadOnly Property ClippedEnd As Boolean
        End Class

        Private Function BuildTimedEventLayout(body As SKRect,
                                               dpi As Single,
                                               isRtl As Boolean,
                                               plan As MASAgendaViewLayoutPlan) As List(Of AgendaEventLayoutSlot)
            Dim slots As New List(Of AgendaEventLayoutSlot)()
            If body.Width <= 1.0F OrElse body.Height <= 1.0F Then Return slots
            If _events.Count = 0 Then Return slots

            Dim timelineRect As SKRect = ResolveTimelineEventsRect(body, isRtl, plan)
            If timelineRect.Width <= 1.0F OrElse timelineRect.Height <= 1.0F Then Return slots

            Dim startMinute As Integer = _startHour * 60
            Dim endMinute As Integer = _endHour * 60
            If endMinute <= startMinute Then Return slots

            Dim candidates As New List(Of AgendaEventLayoutCandidate)()
            For i As Integer = 0 To _events.Count - 1
                Dim item As AgendaEventState = _events(i)
                If MASAgendaViewPolicy.NormalizeDay(item.EventDay) <> _day Then Continue For
                Dim clippedStart As Integer = Math.Max(startMinute, item.StartMinute)
                Dim clippedEnd As Integer = Math.Min(endMinute, item.EndMinute)
                If clippedEnd <= clippedStart Then Continue For
                candidates.Add(New AgendaEventLayoutCandidate(i, clippedStart, clippedEnd))
            Next
            If candidates.Count = 0 Then Return slots

            candidates.Sort(Function(left As AgendaEventLayoutCandidate, right As AgendaEventLayoutCandidate)
                                Dim c As Integer = left.StartMinute.CompareTo(right.StartMinute)
                                If c <> 0 Then Return c
                                c = left.EndMinute.CompareTo(right.EndMinute)
                                If c <> 0 Then Return c
                                Return StringComparer.OrdinalIgnoreCase.Compare(_events(left.EventIndex).Title, _events(right.EventIndex).Title)
                            End Function)

            Dim group As New List(Of AgendaEventLayoutCandidate)()
            Dim groupEndMinute As Integer = -1
            Dim groupIndex As Integer
            For Each candidate As AgendaEventLayoutCandidate In candidates
                If group.Count > 0 AndAlso candidate.StartMinute >= groupEndMinute Then
                    AddTimedLayoutGroup(slots, group, groupIndex, timelineRect, dpi, isRtl, startMinute, endMinute)
                    group.Clear()
                    groupIndex += 1
                    groupEndMinute = -1
                End If
                group.Add(candidate)
                groupEndMinute = Math.Max(groupEndMinute, candidate.EndMinute)
            Next
            If group.Count > 0 Then
                AddTimedLayoutGroup(slots, group, groupIndex, timelineRect, dpi, isRtl, startMinute, endMinute)
            End If

            slots.Sort(Function(left As AgendaEventLayoutSlot, right As AgendaEventLayoutSlot)
                           Dim c As Integer = left.StartMinute.CompareTo(right.StartMinute)
                           If c <> 0 Then Return c
                           c = left.LaneIndex.CompareTo(right.LaneIndex)
                           If c <> 0 Then Return c
                           Return left.EventIndex.CompareTo(right.EventIndex)
                       End Function)
            Return slots
        End Function

        Private Sub AddTimedLayoutGroup(slots As List(Of AgendaEventLayoutSlot),
                                        group As List(Of AgendaEventLayoutCandidate),
                                        overlapGroupIndex As Integer,
                                        timelineRect As SKRect,
                                        dpi As Single,
                                        isRtl As Boolean,
                                        startMinute As Integer,
                                        endMinute As Integer)
            If slots Is Nothing OrElse group Is Nothing OrElse group.Count = 0 Then Return
            Dim laneEndMinutes As New List(Of Integer)()
            Dim laneByEvent As New Dictionary(Of Integer, Integer)()

            For Each candidate As AgendaEventLayoutCandidate In group
                Dim laneIndex As Integer = -1
                For lane As Integer = 0 To laneEndMinutes.Count - 1
                    If laneEndMinutes(lane) <= candidate.StartMinute Then
                        laneIndex = lane
                        Exit For
                    End If
                Next
                If laneIndex < 0 Then
                    laneIndex = laneEndMinutes.Count
                    laneEndMinutes.Add(candidate.EndMinute)
                Else
                    laneEndMinutes(laneIndex) = candidate.EndMinute
                End If
                laneByEvent(candidate.EventIndex) = laneIndex
            Next

            Dim laneCount As Integer = Math.Max(1, laneEndMinutes.Count)
            Dim laneWidth As Single = timelineRect.Width / CSng(laneCount)
            Dim laneGap As Single = Math.Min(6.0F * Math.Max(0.64F, dpi), Math.Max(0.0F, laneWidth * 0.2F))
            Dim minHeight As Single = Math.Max(28.0F * Math.Max(0.64F, dpi), AgendaViewTokens.TimePillHeightDip * Math.Max(0.64F, dpi) + 12.0F * Math.Max(0.64F, dpi))
            Dim totalMinutes As Single = CSng(Math.Max(1, endMinute - startMinute))

            For Each candidate As AgendaEventLayoutCandidate In group
                Dim laneIndex As Integer = 0
                If laneByEvent.ContainsKey(candidate.EventIndex) Then laneIndex = laneByEvent(candidate.EventIndex)
                Dim visualLane As Integer = If(isRtl, laneCount - laneIndex - 1, laneIndex)
                Dim laneLeft As Single = timelineRect.Left + CSng(visualLane) * laneWidth
                Dim rawTop As Single = timelineRect.Top + (CSng(candidate.StartMinute - startMinute) / totalMinutes) * timelineRect.Height
                Dim rawBottom As Single = timelineRect.Top + (CSng(candidate.EndMinute - startMinute) / totalMinutes) * timelineRect.Height
                If rawBottom - rawTop < minHeight Then rawBottom = Math.Min(timelineRect.Bottom, rawTop + minHeight)

                Dim rect As SKRect = PixelSnap.SnapRect(New SKRect(
                    laneLeft + laneGap * 0.5F,
                    rawTop + 2.0F * dpi,
                    laneLeft + laneWidth - laneGap * 0.5F,
                    Math.Max(rawTop + 4.0F * dpi, rawBottom - 2.0F * dpi)), dpi)

                If rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Continue For
                Dim item As AgendaEventState = _events(candidate.EventIndex)
                slots.Add(New AgendaEventLayoutSlot(
                    eventIndex:=candidate.EventIndex,
                    rect:=rect,
                    laneIndex:=laneIndex,
                    laneCount:=laneCount,
                    overlapGroupIndex:=overlapGroupIndex,
                    startMinute:=candidate.StartMinute,
                    endMinute:=candidate.EndMinute,
                    clippedStart:=item.StartMinute < startMinute,
                    clippedEnd:=item.EndMinute > endMinute))
            Next
        End Sub

        Private Function ResolveTimelineEventsRect(body As SKRect,
                                                   isRtl As Boolean,
                                                   plan As MASAgendaViewLayoutPlan) As SKRect
            Dim railWidth As Single = If(plan Is Nothing OrElse Not plan.ShowTimeRail, 0.0F, plan.TimeRailWidthPx)
            If isRtl Then
                Return New SKRect(body.Left, body.Top, Math.Max(body.Left, body.Right - railWidth), body.Bottom)
            End If
            Return New SKRect(Math.Min(body.Right, body.Left + railWidth), body.Top, body.Right, body.Bottom)
        End Function

        Friend Function CreateTimedLayoutEvidenceForTests(widthPx As Single,
                                                          heightPx As Single,
                                                          Optional dpi As Single = 1.0F,
                                                          Optional isRtl As Boolean = False) As IReadOnlyList(Of AgendaEventLayoutEvidence)
            Dim safeDpi As Single = Math.Max(0.64F, dpi)
            Dim body As New SKRect(0.0F, 0.0F, Math.Max(1.0F, widthPx), Math.Max(1.0F, heightPx))
            Dim profile As MASResponsiveLayoutProfile = MASResponsiveLayoutResolver.FromLogicalSize(
                MASSizeLayoutIntegrationContext.Empty,
                New SKSize(Math.Max(1.0F, widthPx), Math.Max(1.0F, heightPx)),
                MASSize.Default,
                MASAgendaViewLayoutPlan.Thresholds)
            Dim plan As MASAgendaViewLayoutPlan = MASAgendaViewLayoutPlan.Create(profile)
            Dim slots As List(Of AgendaEventLayoutSlot) = BuildTimedEventLayout(body, safeDpi, isRtl, plan)
            Dim evidence As New List(Of AgendaEventLayoutEvidence)()
            For Each slot As AgendaEventLayoutSlot In slots
                Dim key As String = String.Empty
                If slot.EventIndex >= 0 AndAlso slot.EventIndex < _events.Count Then key = _events(slot.EventIndex).Key
                evidence.Add(New AgendaEventLayoutEvidence(
                    eventKey:=key,
                    rect:=slot.Rect,
                    laneIndex:=slot.LaneIndex,
                    laneCount:=slot.LaneCount,
                    overlapGroupIndex:=slot.OverlapGroupIndex,
                    startMinute:=slot.StartMinute,
                    endMinute:=slot.EndMinute,
                    clippedStart:=slot.ClippedStart,
                    clippedEnd:=slot.ClippedEnd))
            Next
            Return New ReadOnlyCollection(Of AgendaEventLayoutEvidence)(evidence)
        End Function

#End Region

    End Class

End Namespace
