/// <summary>
 /// Moves an event to a new start date (preserving duration) and updates labels.
 /// </summary>
 /// <param name="ev">Event to move</param>
 /// <param name="newStartDate">New start date for event</param>
 private void MoveEvent(Event ev, DateTime newStartDate)
 {
     var newRange = new TimeRange(ev.Start, ev.End);
     newRange.MoveToDate(newStartDate);
     UpdateEvent(ev, newRange);
 }
        /// <summary>
        /// Highlights destination cells during drag
        /// </summary>
        private void HandleDragging(object sender, DragEventArgs args)
        {
            var label = sender as DraggableView;

            if (label == null)
            {
                return;
            }

            var ev = label.BindingContext as Event;

            if (ev == null)
            {
                return;
            }

            var destinationPoint = GetDragDestinationPoint(args.Center, label);

            var newDate = GetDateAtPoint(destinationPoint);

            if (newDate.HasValue)
            {
                // Offset for this not being the first label of the event
                newDate = newDate.Value.AddDays(-_dragLabelDayOffset);

                var newRange = new TimeRange(ev.Start, ev.End);
                newRange.MoveToDate(newDate.Value);

                // Highlight target cells, un-highlight previously-targeted cells
                //
                HighlightCells(newRange.StartDate, newRange.EndDate);
            }
        }
        /// <summary>
        /// Remove resize mode views
        /// </summary>
        /// <param name="overlay">The resize mode overlay that triggered this, so we can remove it</param>
        private void EndResizeMode(View overlay)
        {
            _grid.Children.Remove(overlay);
            _grid.Children.Remove(_rightResizeHandle);
            _grid.Children.Remove(_rightResizeHandleDragger);
            _grid.Children.Remove(_leftResizeHandle);
            _grid.Children.Remove(_leftResizeHandleDragger);

            ((TappableView)overlay).Tapped -= HandleOverlayTapped;
            _rightResizeHandleDragger.Dragging -= HandleResizeDragging;
            _rightResizeHandleDragger.Dragged -= HandleResizeDragged;
            _rightResizeHandleDragger.DragCanceled -= HandleResizeCanceled;
            _leftResizeHandleDragger.Dragging -= HandleResizeDragging;
            _leftResizeHandleDragger.Dragged -= HandleResizeDragged;
            _leftResizeHandleDragger.DragCanceled -= HandleResizeCanceled;

            // Reset opacity
            foreach (var label in _labels)
            {
                label.Opacity = 1.0;
            }

            UnhighlightCells();

            _resizingEvent = null;
            _rightResizeHandle = null;
            _rightResizeHandleDragger = null;
            _leftResizeHandle = null;
            _leftResizeHandleDragger = null;
            _resizeRange = null;
        }
        /// <summary>
        /// Creates overlay and tracking handles, highlights cells, and dims other events.
        /// </summary>
        /// <param name="ev">Event to resize</param>
        private void BeginResizeMode(Event ev)
        {
            _resizingEvent = ev;

            _resizeRange = new TimeRange(ev.Start, ev.End);

            // Create overlay to capture taps for dismissing resize mode
            //
            var overlay = new TappableView();
            Grid.SetRowSpan(overlay, _rows + 1);
            Grid.SetColumnSpan(overlay, _cols);
            overlay.BackgroundColor = Color.Transparent;

            overlay.Tapped += HandleOverlayTapped;

            _grid.Children.Add(overlay);

            // Dim everything but this event
            //
            foreach (var label in _labels.Where(l => l.BindingContext != _resizingEvent))
            {
                label.Opacity = 0.5;
            }

            HighlightCells(_resizingEvent.StartDate, _resizingEvent.EndDate);

            var cells = GetCellsForRange(_resizingEvent.StartDate, _resizingEvent.EndDate);
            var firstCell = cells.First();
            var lastCell = cells.Last();

            _leftResizeHandle = CreateResizeHandleVisual(Grid.GetColumn(firstCell), Grid.GetRow(firstCell), false);
            _grid.Children.Add(_leftResizeHandle);
            _rightResizeHandle = CreateResizeHandleVisual(Grid.GetColumn(lastCell), Grid.GetRow(lastCell), true);
            _grid.Children.Add(_rightResizeHandle);

            _leftResizeHandleDragger = CreateResizeHandleDraggerForVisual(_leftResizeHandle);
            _grid.Children.Add(_leftResizeHandleDragger);
            _rightResizeHandleDragger = CreateResizeHandleDraggerForVisual(_rightResizeHandle);
            _grid.Children.Add(_rightResizeHandleDragger);
        }
        /// <summary>
        /// Applies the new range to the event and updates its label(s)
        /// </summary>
        /// <param name="ev">Event to update</param>
        /// <param name="newRange">Desired time range for the event</param>
        private void UpdateEvent(Event ev, TimeRange newRange)
        {
            // Q: Why don't we just update the properties on the Event and let the property change events handle this?
            // A: Mainly to avoid having to deal with multiple notifications firing for a single change (e.g., moving an event)
            //    Updating the calendar labels is a bit more expensive than just updating a text box.

            if (newRange.Start == ev.Start && newRange.End == ev.End)
            {
                // no change
                return;
            }

            var newCells = GetCellsForRange(newRange.StartDate, newRange.EndDate);

            if (!newCells.Any())
            {
                // Don't allow moving off the calendar entirely
                //
                return;
            }

            _updatingEvent = true;
            ev.Start = newRange.Start;
            ev.End = newRange.End;
            _updatingEvent = false;

            var labels = _labels.Where(l => l.BindingContext == ev).ToList();

            System.Diagnostics.Debug.Assert(labels.Any());

            var label = labels.First();

            bool wasMultiRow = labels.Count > 1;

            var newRows = newCells.Select(cell => Grid.GetRow(cell)).Distinct().ToList();

            bool willBeMultiRow = newRows.Count > 1;

            // If the event fit on a single row before and will still fit on a single row,
            // then we can "simply" move the existing label. Otherwise, we need to recreate,
            // because multiple labels are involved with changing widths etc.
            //
            if (!wasMultiRow && !willBeMultiRow && GetEventContainerForRow(newRows.First()) == label.ParentView)
            {
                var container = label.ParentView as AutoStackGrid;

                // So basically all the previous complexity is being turned into:
                // AutoStackGrids span whole week (row). If the event is being moved
                // within a row, we just reposition it in the stack grid. Otherwise
                // re-create.

                // Note that even with the stack-per-row model, moving items between
                // layout containers is problematic on WinPhone (hence re-creating instead)

                var newCols = newCells.Select(cell => Grid.GetColumn(cell)).OrderBy(col => col).ToList();

                // Defer layout until we're done updating properties
                //
                container.DeferLayout = true;

                AutoStackGrid.SetColumn(label, newCols.First());
                AutoStackGrid.SetColumnSpan(label, newCols.Last() - newCols.First() + 1);

                // Update margin. Even though we're staying on one row, there's the possibility that
                // the event actually stretches before and/or after that row and those parts just aren't visible
                //
                bool truncatedStart = ev.Start < (newCells.First().BindingContext as DateTime?).Value;

                // Subtracting 1 second because an end time of midnight is exclusive and should only
                // label the previous day
                //
                bool truncatedEnd = ev.End.AddSeconds(-1).Date > (newCells.Last().BindingContext as DateTime?).Value;

                var margin = new Thickness(!truncatedStart ? _defaultMargin : 0,
                    _defaultMargin,
                    !truncatedEnd ? _defaultMargin : 0,
                    _defaultMargin);

                AutoStackGrid.SetMargin(label, margin);

                container.DeferLayout = false;
                container.ForceLayout();
            }
            else
            {
                // Recreate labels

                // Removing a label just to create a new one in its place caused movement flicker
                // on WinPhone if we rely on adding/removing to automatically update layout.
                // So that behavior is temporarily disabled on AutoStackGrid, and instead we
                // explicitly call ForceLayout on grids that have been modified.
                //
                var oldContainers = labels.Select(l => l.ParentView).OfType<AutoStackGrid>().ToList();

                foreach (var container in oldContainers)
                {
                    container.DeferLayout = true;
                }

                ClearEventLabels(labels);
                _labels = _labels.Except(labels).ToList();
                var newLabels = PlaceEvent(ev);

                foreach (var container in oldContainers.Union(newLabels.Select(l => l.ParentView)).OfType<AutoStackGrid>())
                {
                    container.ForceLayout();
                    container.DeferLayout = false;
                }
            }
        }
        /// <summary>
        /// Don't exit resize mode, but reset any pending resize visuals
        /// </summary>
        private void ResetResizeMode()
        {
            _resizeRange = new TimeRange(_resizingEvent.Start, _resizingEvent.End);

            HighlightCells(_resizingEvent.StartDate, _resizingEvent.EndDate);

            var cells = GetCellsForRange(_resizingEvent.StartDate, _resizingEvent.EndDate);
            var firstCell = cells.First();
            var lastCell = cells.Last();

            Grid.SetColumn(_leftResizeHandle, Grid.GetColumn(firstCell));
            Grid.SetRow(_leftResizeHandle, Grid.GetRow(firstCell));
            Grid.SetColumn(_rightResizeHandle, Grid.GetColumn(lastCell));
            Grid.SetRow(_rightResizeHandle, Grid.GetRow(lastCell));

            SynchronizeResizeHandleDraggersWithVisuals();
        }