/// <summary>
        ///  Reads data from the world grid.  Not done in the constructor to allow callers to subscribe to
        ///  the events that will be fired.
        /// </summary>
        /// <param name="position"> The position. </param>
        public void Initialize(GridCoordinate position)
        {
            _currentPosition     = position;
            _viewCurrentPosition = ConvertToGridItemIndex(position);

            InitializeUnits();
        }
        /// <summary>
        ///  Converts the given grid coordinate into a an index that can be used to get the data in
        ///  <see cref="_visibleGridItems"/>.
        /// </summary>
        /// <remarks>
        ///  Multiple GridCoordinates map to the same ArrayIndex, so this is technically an irreversible
        ///  operation. However, provided we always remain within the correct GridCoordinates (e.g. always
        ///  use grid coordinates that we generate or which are validated first), we should never really
        ///  have a situation where a conversion to an array index and back results in a different
        ///  coordinate.
        /// </remarks>
        /// <param name="coordinate"> The coordinate. </param>
        /// <returns> The given data converted to a grid item index. </returns>
        private Array2DIndex ConvertToGridItemIndex(GridCoordinate coordinate)
        {
            var correctedX = MathUtils.PositiveRemainder(coordinate.X, _visibleGridItems.Width);
            var correctedY = MathUtils.PositiveRemainder(coordinate.Y, _visibleGridItems.Height);

            var arrayIndex = new Array2DIndex(correctedX, correctedY);

            return(arrayIndex);
        }
        /// <summary> Changes the position being observed. </summary>
        public void Recenter(GridCoordinate center)
        {
            var originalPosition = _currentPosition;

            _currentPosition     = center;
            _viewCurrentPosition = ConvertToGridItemIndex(_currentPosition);

            UpdateChangedUnits(originalPosition);
        }
        /// <summary> Updates the data at the specified index inside the grid. </summary>
        /// <param name="index"> Zero-based index of the. </param>
        /// <param name="data"> The grid data to store. </param>
        /// <param name="position"> The coordinate of the original GridItem where the data was retrieved
        ///   from. </param>
        /// <param name="chunk"></param>
        private void UpdateData(Array2DIndex index, GridItem data, GridCoordinate position, Chunk chunk)
        {
            var newData  = new SliceUnitData <T>(chunk, position, data, default(T));
            var rawIndex = _visibleGridItems.CalculateRawArrayIndex(index);

            var oldData = _visibleGridItems.Data[rawIndex];

            _visibleGridItems.Data[rawIndex] = newData;

            DataChanged?.Invoke(oldData, ref _visibleGridItems.Data[rawIndex]);
            MarkChunkChanged(oldData.Chunk, chunk);
        }
        /// <summary>
        ///  Converts an array index into the expected grid coordinate for the given index.
        /// </summary>
        /// <param name="arrayIndex"> Zero-based index of the array. </param>
        /// <returns> The given data converted to a grid coordinate. </returns>
        private GridCoordinate ConvertToGridCoordinate(Array2DIndex arrayIndex)
        {
            var difViewX = _viewCurrentPosition.X - arrayIndex.X;
            var signX    = GetSign(difViewX);

            // Normalize the offset from the "current" view position.
            //
            // This explanation just talks about X, the same theory holds for Y. Imagine a number line
            // ranging from 0 to maxViewX).
            //
            // If the current position is on the right (so it's position is maxViewX), then the very left
            // position (position 0) of the view really just represents one world unit to the right of the
            // current position.  If we simply subtracted these two positions, however, it would be
            // reported it as a difference of -ViewWidth units away We want to convert it to a difference
            // of (1).
            //
            // We know we have to do convert values if it's more than half the size away (that's the only
            // time that wrapping occurs when putting the values into the view). The conversion is fairly
            // straight forward when given examples:
            //  - When we're `maxViewX` view units away, we're really only 1 world unit away.
            //  - When we're `maxViewX - 1` units away we're really only 2 world units away.
            //  - When we're `maxViewX - N` units away, we're really only N world units away.
            //
            // The pattern to find N (the world units away) is to take `ViewWidth` and "subtract"
            // `(maxViewX - N)`. `maxViewX - N` happens to be diffViewX, so then we just have to "subtract"
            // it from `ViewWidth`. "subtract" is in quotes because actually, when the difference is
            // negative, we want to add `ViewWidth` (otherwise we end up in very negative territory). So we
            // add when `maxViewX - N` is negative, subtract when `maxViewX - N` is positive.
            var offsetX = Math.Abs(difViewX) > _numUnitsWideHalfThreshold
        ? difViewX + -signX * _visibleGridItems.Width
        : difViewX;

            // all of the above but now for Y
            var difViewY = _viewCurrentPosition.Y - arrayIndex.Y;
            var signY    = difViewY >= 0 ? 1 : -1;

            var offsetY = Math.Abs(difViewY) > _numUnitsHighHalfThreshold
        ? difViewY + -signY * _visibleGridItems.Height
        : difViewY;

            var gridPosition = _currentPosition.OffsetBy(-offsetX, -offsetY);

            ValidateConversion(gridPosition, arrayIndex);

            return(gridPosition);
        }
        /// <summary> Initialize each grid unit. </summary>
        private void InitializeUnits()
        {
            for (var y = 0; y < _visibleGridItems.Height; y++)
            {
                for (var x = 0; x < _visibleGridItems.Width; x++)
                {
                    var index      = new Array2DIndex(x, y);
                    var coordinate = ConvertToGridCoordinate(index);

                    var subCoordinates = coordinate.AsSubCoordinates();

                    if (_worldGrid.IsValid(subCoordinates.ChunkCoordinate))
                    {
                        var(chunk, data) = _worldGrid.GetChunkAndCellData(subCoordinates);

                        UpdateData(index, data, coordinate, chunk, GridItemPropertyChange.All);
                    }
                }
            }
        }
        /// <summary>
        ///  Checks the array index/position to verify that it has the correct position information and if
        ///  it does not, re-reads the data from the world and updates the data.
        /// </summary>
        private void Invalidate(Array2DIndex index, GridCoordinate coordinate, bool bypassSamePositionCheck, GridItemPropertyChange changeType)
        {
            var existingData = _visibleGridItems[index];

            if (!bypassSamePositionCheck && existingData.Position == coordinate)
            {
                return;
            }

            var subCoordinate = coordinate.AsSubCoordinates();

            if (!_worldGrid.IsValid(subCoordinate.ChunkCoordinate))
            {
                return;
            }

            var(chunk, data) = _worldGrid.GetChunkAndCellData(subCoordinate);

            UpdateData(index, data, coordinate, chunk, changeType);
        }
        /// <summary> Initialize each grid unit. </summary>
        private void InitializeUnits()
        {
            for (var y = 0; y < _visibleGridItems.Height; y++)
            {
                for (var x = 0; x < _visibleGridItems.Width; x++)
                {
                    var index      = new Array2DIndex(x, y);
                    var coordinate = ConvertToGridCoordinate(index);

                    var chunkCoordinate = coordinate.ChunkCoordinate;
                    if (_worldGrid.IsValid(chunkCoordinate))
                    {
                        var chunk = _worldGrid[chunkCoordinate];
                        var data  = chunk[coordinate.InnerChunkGridCoordinate];

                        UpdateData(index, data, coordinate, chunk);
                    }
                }
            }
        }
        /// <summary>
        ///  Checks the array index/position to verify that it has the correct position information and if
        ///  it does not, re-reads the data from the world and updates the data.
        /// </summary>
        /// <param name="index"> The index position to update. </param>
        /// <param name="coordinate"> The coordinate representing the position to update. </param>
        private void Invalidate(Array2DIndex index, GridCoordinate coordinate, bool bypassSamePositionCheck = false)
        {
            var existingData = _visibleGridItems[index];

            if (!bypassSamePositionCheck && existingData.Position == coordinate)
            {
                return;
            }

            var chunkCoordinate = coordinate.ChunkCoordinate;

            if (!_worldGrid.IsValid(chunkCoordinate))
            {
                return;
            }

            var chunk = _worldGrid[chunkCoordinate];
            var data  = chunk[coordinate.InnerChunkGridCoordinate];

            UpdateData(index, data, coordinate, chunk);
        }
        private void ValidateConversion(GridCoordinate gridPosition, Array2DIndex arrayIndex)
        {
            var worked = ConvertToGridItemIndex(gridPosition).X == arrayIndex.X &&
                         ConvertToGridItemIndex(gridPosition).Y == arrayIndex.Y;

            if (worked)
            {
                return;
            }

            // to aid in debugging
            worked = ConvertToGridItemIndex(gridPosition).X == arrayIndex.X &&
                     ConvertToGridItemIndex(gridPosition).Y == arrayIndex.Y;

            var message = new StringBuilder();

            message.AppendFormat("Array Index ({0}) was converted into Grid Position ({1})", arrayIndex, gridPosition);
            message.AppendFormat("But we expected ({0},{1})",
                                 ConvertToGridItemIndex(gridPosition).X,
                                 ConvertToGridItemIndex(gridPosition).Y);

            Debug.WriteLine(message);
        }
        /// <summary> Updates the data at the specified index inside the grid. </summary>
        /// <param name="index"> Zero-based index of the. </param>
        /// <param name="data"> The grid data to store. </param>
        /// <param name="position"> The coordinate of the original GridItem where the data was retrieved
        ///   from. </param>
        /// <param name="chunk"></param>
        /// <param name="changeType"></param>
        private void UpdateData(Array2DIndex index, GridItem data, GridCoordinate position, Chunk chunk, GridItemPropertyChange changeType)
        {
            var newData  = new SliceUnitData <T>(chunk, position, data, default(T));
            var rawIndex = _visibleGridItems.CalculateRawArrayIndex(index);

            ref var slot = ref _visibleGridItems.Data[rawIndex];
 private void Invalidate(Array2DIndex index, GridCoordinate coordinate)
 => Invalidate(index, coordinate, bypassSamePositionCheck: false, changeType: GridItemPropertyChange.All);
 /// <summary>
 ///  Checks the array index/position to verify that it has the correct position information and if
 ///  it does not, re-reads the data from the world and updates the data.
 /// </summary>
 /// <param name="index"> The index position to update. </param>
 private void InvalidateIndex(Array2DIndex index)
 {
     Invalidate(index, ConvertToGridCoordinate(index));
 }