using System;
using System.Linq;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

namespace UnityEditor.Tilemaps
{
    /// <summary>
    /// This Brush instances, places and manipulates GameObjects onto the scene.
    /// Use this as an example to create brushes which targets objects other than tiles for manipulation.
    /// </summary>
    [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.tilemap.extras@latest/index.html?subfolder=/manual/GameObjectBrush.html")]
    [CustomGridBrush(true, false, false, "GameObject Brush")]
    public class GameObjectBrush : GridBrushBase
    {
        [Serializable]
        internal class HiddenGridLayout
        {
            public Vector3 cellSize = Vector3.one;
            public Vector3 cellGap = Vector3.zero;
            public GridLayout.CellLayout cellLayout = GridLayout.CellLayout.Rectangle;
            public GridLayout.CellSwizzle cellSwizzle = GridLayout.CellSwizzle.XYZ;
        }

        [SerializeField]
        private BrushCell[] m_Cells;

        [SerializeField]
        private Vector3Int m_Size;

        [SerializeField]
        private Vector3Int m_Pivot;

        [SerializeField]
        [HideInInspector]
        private bool m_CanChangeZPosition;

        [SerializeField] 
        [HideInInspector] 
        internal HiddenGridLayout hiddenGridLayout = new HiddenGridLayout();

        /// <summary>
        /// GameObject used for painting onto the Scene root
        /// </summary>
        [HideInInspector]
        public GameObject hiddenGrid;

        /// <summary>
        /// Anchor Point of the Instantiated GameObject in the cell when painting
        /// </summary>
        public Vector3 m_Anchor = new Vector3(0.5f, 0.5f, 0.0f);
        /// <summary>Size of the brush in cells. </summary>
        public Vector3Int size { get { return m_Size; } set { m_Size = value; SizeUpdated(); } }
        /// <summary>Pivot of the brush. </summary>
        public Vector3Int pivot { get { return m_Pivot; } set { m_Pivot = value; } }
        /// <summary>All the brush cells the brush holds. </summary>
        public BrushCell[] cells { get { return m_Cells; } }
        /// <summary>Number of brush cells in the brush.</summary>
        public int cellCount { get { return m_Cells != null ? m_Cells.Length : 0; } }
        /// <summary>Number of brush cells based on size.</summary>
        public int sizeCount
        {
            get { return m_Size.x * m_Size.y * m_Size.z; }
        }
        /// <summary>Whether the brush can change Z Position</summary>
        public bool canChangeZPosition
        {
            get { return m_CanChangeZPosition; }
            set { m_CanChangeZPosition = value; }
        }
        
        /// <summary>
        /// This Brush instances, places and manipulates GameObjects onto the scene.
        /// </summary>
        public GameObjectBrush()
        {
            Init(Vector3Int.one, Vector3Int.zero);
            SizeUpdated();
        }

        private void OnEnable()
        {
            hiddenGrid = new GameObject();
            hiddenGrid.name = "(Paint on SceneRoot)";
            hiddenGrid.hideFlags = HideFlags.HideAndDontSave;
            hiddenGrid.transform.position = Vector3.zero;
            var grid = hiddenGrid.AddComponent<Grid>();
            grid.cellSize = hiddenGridLayout.cellSize;
            grid.cellGap = hiddenGridLayout.cellGap;
            grid.cellSwizzle = hiddenGridLayout.cellSwizzle;
            grid.cellLayout = hiddenGridLayout.cellLayout;
        }

        private void OnDisable()
        {
            DestroyImmediate(hiddenGrid);
        }

        private void OnValidate()
        {
            if (m_Size.x < 0)
                m_Size.x = 0;
            if (m_Size.y < 0)
                m_Size.y = 0;
            if (m_Size.z < 0)
                m_Size.z = 0;
        }

        /// <summary>
        /// Initializes the content of the GameObjectBrush.
        /// </summary>
        /// <param name="size">Size of the GameObjectBrush.</param>
        public void Init(Vector3Int size)
        {
            Init(size, Vector3Int.zero);
            SizeUpdated();
        }

        /// <summary>Initializes the content of the GameObjectBrush.</summary>
        /// <param name="size">Size of the GameObjectBrush.</param>
        /// <param name="pivot">Pivot point of the GameObjectBrush.</param>
        public void Init(Vector3Int size, Vector3Int pivot)
        {
            m_Size = size;
            m_Pivot = pivot;
            SizeUpdated();
        }

        /// <summary>
        /// Paints GameObjects into a given position within the selected layers.
        /// The GameObjectBrush overrides this to provide GameObject painting functionality.
        /// </summary>
        /// <param name="gridLayout">Grid used for layout.</param>
        /// <param name="brushTarget">Target of the paint operation. By default the currently selected GameObject.</param>
        /// <param name="position">The coordinates of the cell to paint data to.</param>
        public override void Paint(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
        {
            Vector3Int min = position - pivot;
            BoundsInt bounds = new BoundsInt(min, m_Size);

            BoxFill(gridLayout, brushTarget, bounds);
        }

        private void PaintCell(GridLayout grid, Vector3Int position, Transform parent, BrushCell cell)
        {
            if (cell.gameObject == null)
                return;

            var existingGO = GetObjectInCell(grid, parent, position, m_Anchor);
            if (existingGO == null)
            {
                SetSceneCell(grid, parent, position, cell.gameObject, cell.offset, cell.scale, cell.orientation, m_Anchor);
            }
        }

        /// <summary>
        /// Erases GameObjects in a given position within the selected layers.
        /// The GameObjectBrush overrides this to provide GameObject erasing functionality.
        /// </summary>
        /// <param name="gridLayout">Grid used for layout.</param>
        /// <param name="brushTarget">Target of the erase operation. By default the currently selected GameObject.</param>
        /// <param name="position">The coordinates of the cell to erase data from.</param>
        public override void Erase(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
        {
            Vector3Int min = position - pivot;
            BoundsInt bounds = new BoundsInt(min, m_Size);

            GetGrid(ref gridLayout, ref brushTarget);
            BoxErase(gridLayout, brushTarget, bounds);
        }

        private void EraseCell(GridLayout grid, Vector3Int position, Transform parent)
        {
            ClearSceneCell(grid, parent, position);
        }

        /// <summary>
        /// Box fills GameObjects into given bounds within the selected layers.
        /// The GameObjectBrush overrides this to provide GameObject box-filling functionality.
        /// </summary>
        /// <param name="gridLayout">Grid to box fill data to.</param>
        /// <param name="brushTarget">Target of the box fill operation. By default the currently selected GameObject.</param>
        /// <param name="position">The bounds to box fill data into.</param>
        public override void BoxFill(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
        {
            GetGrid(ref gridLayout, ref brushTarget);
            
            foreach (Vector3Int location in position.allPositionsWithin)
            {
                Vector3Int local = location - position.min;
                BrushCell cell = m_Cells[GetCellIndexWrapAround(local.x, local.y, local.z)];
                PaintCell(gridLayout, location, brushTarget != null ? brushTarget.transform : null, cell);
            }
        }

        /// <summary>
        /// Erases GameObjects from given bounds within the selected layers.
        /// The GameObjectBrush overrides this to provide GameObject box-erasing functionality.
        /// </summary>
        /// <param name="gridLayout">Grid to erase data from.</param>
        /// <param name="brushTarget">Target of the erase operation. By default the currently selected GameObject.</param>
        /// <param name="position">The bounds to erase data from.</param>
        public override void BoxErase(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
        {
            GetGrid(ref gridLayout, ref brushTarget);
            
            foreach (Vector3Int location in position.allPositionsWithin)
            {
                EraseCell(gridLayout, location, brushTarget != null ? brushTarget.transform : null);
            }
        }

        /// <summary>
        /// This is not supported but it should floodfill GameObjects starting from a given position within the selected layers.
        /// </summary>
        /// <param name="gridLayout">Grid used for layout.</param>
        /// <param name="brushTarget">Target of the flood fill operation. By default the currently selected GameObject.</param>
        /// <param name="position">Starting position of the flood fill.</param>
        public override void FloodFill(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
        {
            Debug.LogWarning("FloodFill not supported");
        }

        /// <summary>
        /// Rotates the brush by 90 degrees in the given direction.
        /// </summary>
        /// <param name="direction">Direction to rotate by.</param>
        /// <param name="layout">Cell Layout for rotating.</param>
        public override void Rotate(RotationDirection direction, GridLayout.CellLayout layout)
        {
            Vector3Int oldSize = m_Size;
            BrushCell[] oldCells = m_Cells.Clone() as BrushCell[];
            size = new Vector3Int(oldSize.y, oldSize.x, oldSize.z);
            BoundsInt oldBounds = new BoundsInt(Vector3Int.zero, oldSize);

            foreach (Vector3Int oldPos in oldBounds.allPositionsWithin)
            {
                int newX = direction == RotationDirection.Clockwise ? oldSize.y - oldPos.y - 1 : oldPos.y;
                int newY = direction == RotationDirection.Clockwise ? oldPos.x : oldSize.x - oldPos.x - 1;
                int toIndex = GetCellIndex(newX, newY, oldPos.z);
                int fromIndex = GetCellIndex(oldPos.x, oldPos.y, oldPos.z, oldSize.x, oldSize.y, oldSize.z);
                m_Cells[toIndex] = oldCells[fromIndex];
            }

            int newPivotX = direction == RotationDirection.Clockwise ? oldSize.y - pivot.y - 1 : pivot.y;
            int newPivotY = direction == RotationDirection.Clockwise ? pivot.x : oldSize.x - pivot.x - 1;
            pivot = new Vector3Int(newPivotX, newPivotY, pivot.z);

            Quaternion orientation = Quaternion.Euler(0f, 0f, direction != RotationDirection.Clockwise ? 90f : -90f);
            foreach (BrushCell cell in m_Cells)
                cell.orientation = cell.orientation * orientation;
        }

        /// <summary>Flips the brush in the given axis.</summary>
        /// <param name="flip">Axis to flip by.</param>
        /// <param name="layout">Cell Layout for flipping.</param>
        public override void Flip(FlipAxis flip, GridLayout.CellLayout layout)
        {
            if (flip == FlipAxis.X)
                FlipX();
            else
                FlipY();
        }

        /// <summary>
        /// Picks child GameObjects given the coordinates of the cells.
        /// The GameObjectBrush overrides this to provide GameObject picking functionality.
        /// </summary>
        /// <param name="gridLayout">Grid to pick data from.</param>
        /// <param name="brushTarget">Target of the picking operation. By default the currently selected GameObject.</param>
        /// <param name="position">The coordinates of the cells to paint data from.</param>
        /// <param name="pivot">Pivot of the picking brush.</param>
        public override void Pick(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, Vector3Int pivot)
        {
            Reset();
            UpdateSizeAndPivot(new Vector3Int(position.size.x, position.size.y, 1), new Vector3Int(pivot.x, pivot.y, 0));

            GetGrid(ref gridLayout, ref brushTarget);
            
            foreach (Vector3Int pos in position.allPositionsWithin)
            {
                Vector3Int brushPosition = new Vector3Int(pos.x - position.x, pos.y - position.y, 0);
                PickCell(pos, brushPosition, gridLayout, brushTarget != null ? brushTarget.transform : null, true);
            }
        }

        private void PickCell(Vector3Int position, Vector3Int brushPosition, GridLayout grid, Transform parent, bool withoutAnchor = false)
        {
            var go = GetObjectInCell(grid, parent, position, m_Anchor);
            if (go == null)
            {
                go = GetObjectInCell(grid, parent, position, Vector3.zero);
            }
            var anchorRatio = GetAnchorRatio(grid, m_Anchor);
            var cellCenter = grid.LocalToWorld(grid.CellToLocalInterpolated(position) + grid.CellToLocalInterpolated(anchorRatio));

            if (go != null)
            {
                Object prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
                if (prefab)
                {
                    SetGameObject(brushPosition, (GameObject) prefab);
                }
                else
                {
                    GameObject newInstance = Instantiate(go);
                    newInstance.hideFlags = HideFlags.HideAndDontSave;
                    newInstance.SetActive(false);
                    SetGameObject(brushPosition, newInstance);
                }

                SetOffset(brushPosition, go.transform.position - cellCenter);
                SetScale(brushPosition, go.transform.localScale);
                SetOrientation(brushPosition, go.transform.localRotation);
            }
        }

        /// <summary>
        /// MoveStart is called when user starts moving the area previously selected with the selection marquee.
        /// The GameObjectBrush overrides this to provide GameObject moving functionality.
        /// </summary>
        /// <param name="gridLayout">Grid used for layout.</param>
        /// <param name="brushTarget">Target of the move operation. By default the currently selected GameObject.</param>
        /// <param name="position">Position where the move operation has started.</param>
        public override void MoveStart(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
        {
            Reset();
            UpdateSizeAndPivot(new Vector3Int(position.size.x, position.size.y, 1), Vector3Int.zero);

            GetGrid(ref gridLayout, ref brushTarget);

            var targetTransform = brushTarget != null ? brushTarget.transform : null;
            foreach (Vector3Int pos in position.allPositionsWithin)
            {
                Vector3Int brushPosition = new Vector3Int(pos.x - position.x, pos.y - position.y, 0);
                PickCell(pos, brushPosition, gridLayout, targetTransform);
                ClearSceneCell(gridLayout, targetTransform, pos);
            }
        }

        /// <summary>
        /// MoveEnd is called when user has ended the move of the area previously selected with the selection marquee.
        /// The GameObjectBrush overrides this to provide GameObject moving functionality.
        /// </summary>
        /// <param name="gridLayout">Grid used for layout.</param>
        /// <param name="brushTarget">Target of the move operation. By default the currently selected GameObject.</param>
        /// <param name="position">Position where the move operation has ended.</param>
        public override void MoveEnd(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
        {
            GetGrid(ref gridLayout, ref brushTarget);
            Paint(gridLayout, brushTarget, position.min);
            Reset();
        }

        private void GetGrid(ref GridLayout gridLayout, ref GameObject brushTarget)
        {
            if (brushTarget == hiddenGrid)
                brushTarget = null;
            if (brushTarget != null)
            {
                var targetGridLayout = brushTarget.GetComponent<GridLayout>();
                if (targetGridLayout != null)
                    gridLayout = targetGridLayout;
            }
        }
        
        /// <summary>Clears all data of the brush.</summary>
        public void Reset()
        {
            foreach (var cell in m_Cells)
            {
                if (cell.gameObject != null && !EditorUtility.IsPersistent(cell.gameObject))
                {
                    DestroyImmediate(cell.gameObject);
                }
                cell.gameObject = null;
            }
            UpdateSizeAndPivot(Vector3Int.one, Vector3Int.zero);
        }

        private void FlipX()
        {
            BrushCell[] oldCells = m_Cells.Clone() as BrushCell[];
            BoundsInt oldBounds = new BoundsInt(Vector3Int.zero, m_Size);

            foreach (Vector3Int oldPos in oldBounds.allPositionsWithin)
            {
                var newX = m_Size.x - oldPos.x - 1;
                var toIndex = GetCellIndex(newX, oldPos.y, oldPos.z);
                var fromIndex = GetCellIndex(oldPos);
                m_Cells[toIndex] = oldCells[fromIndex];
            }

            var newPivotX = m_Size.x - pivot.x - 1;
            pivot = new Vector3Int(newPivotX, pivot.y, pivot.z);
            
            FlipCells(ref m_Cells, new Vector3(-1f, 1f, 1f));
        }

        private void FlipY()
        {
            BrushCell[] oldCells = m_Cells.Clone() as BrushCell[];
            BoundsInt oldBounds = new BoundsInt(Vector3Int.zero, m_Size);

            foreach (Vector3Int oldPos in oldBounds.allPositionsWithin)
            {
                var newY = m_Size.y - oldPos.y - 1;
                var toIndex = GetCellIndex(oldPos.x, newY, oldPos.z);
                var fromIndex = GetCellIndex(oldPos);
                m_Cells[toIndex] = oldCells[fromIndex];
            }

            var newPivotY = m_Size.y - pivot.y - 1;
            pivot = new Vector3Int(pivot.x, newPivotY, pivot.z);

            FlipCells(ref m_Cells, new Vector3(1f, -1f, 1f));
        }
        
        private static void FlipCells(ref BrushCell[] cells, Vector3 scale)
        {
            foreach (BrushCell cell in cells)
            {
                cell.scale = Vector3.Scale(cell.scale, scale);
            }
        }

        /// <summary>Updates the size, pivot and the number of layers of the brush.</summary>
        /// <param name="size">New size of the brush.</param>
        /// <param name="pivot">New pivot of the brush.</param>
        public void UpdateSizeAndPivot(Vector3Int size, Vector3Int pivot)
        {
            m_Size = size;
            m_Pivot = pivot;
            SizeUpdated();
        }

        /// <summary>
        /// Sets a GameObject at the position in the brush.
        /// </summary>
        /// <param name="position">Position to set the GameObject in the brush.</param>
        /// <param name="go">GameObject to set in the brush.</param>
        public void SetGameObject(Vector3Int position, GameObject go)
        {
            if (ValidateCellPosition(position))
                m_Cells[GetCellIndex(position)].gameObject = go;
        }

        /// <summary>
        /// Sets a position offset at the position in the brush.
        /// </summary>
        /// <param name="position">Position to set the offset in the brush.</param>
        /// <param name="offset">Offset to set in the brush.</param>
        public void SetOffset(Vector3Int position, Vector3 offset)
        {
            if (ValidateCellPosition(position))
                m_Cells[GetCellIndex(position)].offset = offset;
        }

        /// <summary>
        /// Sets an orientation at the position in the brush.
        /// </summary>
        /// <param name="position">Position to set the orientation in the brush.</param>
        /// <param name="orientation">Orientation to set in the brush.</param>
        public void SetOrientation(Vector3Int position, Quaternion orientation)
        {
            if (ValidateCellPosition(position))
                m_Cells[GetCellIndex(position)].orientation = orientation;
        }

        /// <summary>
        /// Sets a scale at the position in the brush.
        /// </summary>
        /// <param name="position">Position to set the scale in the brush.</param>
        /// <param name="scale">Scale to set in the brush.</param>
        public void SetScale(Vector3Int position, Vector3 scale)
        {
            if (ValidateCellPosition(position))
                m_Cells[GetCellIndex(position)].scale = scale;
        }

        /// <summary>Gets the index to the GameObjectBrush::ref::BrushCell based on the position of the BrushCell.</summary>
        /// <param name="brushPosition">Position of the BrushCell.</param>
        /// <returns>The cell index for the position of the BrushCell.</returns>
        public int GetCellIndex(Vector3Int brushPosition)
        {
            return GetCellIndex(brushPosition.x, brushPosition.y, brushPosition.z);
        }

        /// <summary>Gets the index to the GameObjectBrush::ref::BrushCell based on the position of the BrushCell.</summary>
        /// <param name="x">X Position of the BrushCell.</param>
        /// <param name="y">Y Position of the BrushCell.</param>
        /// <param name="z">Z Position of the BrushCell.</param>
        /// <returns>The cell index for the position of the BrushCell.</returns>
        public int GetCellIndex(int x, int y, int z)
        {
            return x + m_Size.x * y + m_Size.x * m_Size.y * z;
        }

        /// <summary>Gets the index to the GameObjectBrush::ref::BrushCell based on the position of the BrushCell.</summary>
        /// <param name="x">X Position of the BrushCell.</param>
        /// <param name="y">Y Position of the BrushCell.</param>
        /// <param name="z">Z Position of the BrushCell.</param>
        /// <param name="sizex">X Size of Brush.</param>
        /// <param name="sizey">Y Size of Brush.</param>
        /// <param name="sizez">Z Size of Brush.</param>
        /// <returns>The cell index for the position of the BrushCell.</returns>
        public int GetCellIndex(int x, int y, int z, int sizex, int sizey, int sizez)
        {
            return x + sizex * y + sizex * sizey * z;
        }

        /// <summary>Gets the index to the GameObjectBrush::ref::BrushCell based on the position of the BrushCell. Wraps each coordinate if it is larger than the size of the GameObjectBrush.</summary>
        /// <param name="x">X Position of the BrushCell.</param>
        /// <param name="y">Y Position of the BrushCell.</param>
        /// <param name="z">Z Position of the BrushCell.</param>
        /// <returns>The cell index for the position of the BrushCell.</returns>
        public int GetCellIndexWrapAround(int x, int y, int z)
        {
            return (x % m_Size.x) + m_Size.x * (y % m_Size.y) + m_Size.x * m_Size.y * (z % m_Size.z);
        }

        private GameObject GetObjectInCell(GridLayout grid, Transform parent, Vector3Int position, Vector3 anchor)
        {
            int childCount;
            GameObject[] sceneChildren = null;
            if (parent == null)
            {
                var scene = SceneManager.GetActiveScene();
                sceneChildren = scene.GetRootGameObjects();
                childCount = scene.rootCount;
            }
            else
            {
                childCount = parent.childCount;
            }

            var anchorRatio = GetAnchorRatio(grid, anchor);
            var anchorWorld = grid.CellToLocalInterpolated(anchorRatio);
            for (var i = 0; i < childCount; i++)
            {
                var child = sceneChildren == null ? parent.GetChild(i) : sceneChildren[i].transform;
                var childCell = grid.LocalToCell(grid.WorldToLocal(child.position) - anchorWorld);
                if (position == childCell)
                    return child.gameObject;
            }
            return null;
        }

        private bool ValidateCellPosition(Vector3Int position)
        {
            var valid =
                position.x >= 0 && position.x < size.x &&
                position.y >= 0 && position.y < size.y &&
                position.z >= 0 && position.z < size.z;
            if (!valid)
                throw new ArgumentException(string.Format("Position {0} is an invalid cell position. Valid range is between [{1}, {2}).", position, Vector3Int.zero, size));
            return true;
        }

        internal void SizeUpdated(bool keepContents = false)
        {
            OnValidate();
            Array.Resize(ref m_Cells, sizeCount);
            var bounds = new BoundsInt(Vector3Int.zero, m_Size);
            foreach (Vector3Int pos in bounds.allPositionsWithin)
            {
                if (keepContents || m_Cells[GetCellIndex(pos)] == null)
                    m_Cells[GetCellIndex(pos)] = new BrushCell();
            }
        }

        private static void SetSceneCell(GridLayout grid, Transform parent, Vector3Int position, GameObject go, Vector3 offset, Vector3 scale, Quaternion orientation, Vector3 anchor)
        {
            if (go == null)
                return;

            GameObject instance;
            if (PrefabUtility.IsPartOfPrefabAsset(go))
            {
                instance = (GameObject) PrefabUtility.InstantiatePrefab(go, parent != null ? parent.root.gameObject.scene : SceneManager.GetActiveScene());
                instance.transform.parent = parent;
            }
            else
            {
                instance = Instantiate(go, parent);
                instance.name = go.name;
                instance.SetActive(true);
                foreach (var renderer in instance.GetComponentsInChildren<Renderer>())
                {
                    renderer.enabled = true;
                }
            }

            Undo.RegisterCreatedObjectUndo(instance, "Paint GameObject");
            var anchorRatio = GetAnchorRatio(grid, anchor);
            
            instance.transform.position = grid.LocalToWorld(grid.CellToLocalInterpolated(position) + grid.CellToLocalInterpolated(anchorRatio));
            instance.transform.localRotation = orientation;
            instance.transform.localScale = scale;
            instance.transform.Translate(offset);
        }

        private static Vector3 GetAnchorRatio(GridLayout grid, Vector3 cellAnchor)
        {
            var cellSize = grid.cellSize;
            var cellStride = cellSize + grid.cellGap;
            cellStride.x = Mathf.Approximately(0f, cellStride.x) ? 1f : cellStride.x;
            cellStride.y = Mathf.Approximately(0f, cellStride.y) ? 1f : cellStride.y;
            cellStride.z = Mathf.Approximately(0f, cellStride.z) ? 1f : cellStride.z;
            var anchorRatio = new Vector3(
                cellAnchor.x * cellSize.x / cellStride.x,
                cellAnchor.y * cellSize.y / cellStride.y,
                cellAnchor.z * cellSize.z / cellStride.z
            );
            return anchorRatio;
        }
        
        private void ClearSceneCell(GridLayout grid, Transform parent, Vector3Int position)
        {
            var erased = GetObjectInCell(grid, parent, position, m_Anchor);
            if (erased != null)
                Undo.DestroyObjectImmediate(erased);
        }

        /// <summary>
        /// Hashes the contents of the brush.
        /// </summary>
        /// <returns>A hash code of the brush</returns>
        public override int GetHashCode()
        {
            int hash = 0;
            unchecked
            {
                foreach (var cell in cells)
                {
                    hash = hash * 33 + cell.GetHashCode();
                }
            }
            return hash;
        }

        internal void UpdateHiddenGridLayout()
        {
            var grid = hiddenGrid.GetComponent<Grid>();
            hiddenGridLayout.cellSize = grid.cellSize;
            hiddenGridLayout.cellGap = grid.cellGap;
            hiddenGridLayout.cellSwizzle = grid.cellSwizzle;
            hiddenGridLayout.cellLayout = grid.cellLayout;
        }

        /// <summary>
        ///Brush Cell stores the data to be painted in a grid cell.
        /// </summary>
        [Serializable]
        public class BrushCell
        {
            /// <summary>
            /// GameObject to be placed when painting.
            /// </summary>
            public GameObject gameObject { get { return m_GameObject; } set { m_GameObject = value; } }
            /// <summary>
            /// Position offset of the GameObject when painted.
            /// </summary>
            public Vector3 offset { get { return m_Offset; } set { m_Offset = value; } }
            /// <summary>
            /// Scale of the GameObject when painted.
            /// </summary>
            public Vector3 scale { get { return m_Scale; } set { m_Scale = value; } }
            /// <summary>
            /// Orientatio of the GameObject when painted.
            /// </summary>
            public Quaternion orientation { get { return m_Orientation; } set { m_Orientation = value; } }
            
            [SerializeField]
            private GameObject m_GameObject;
            [SerializeField]
            Vector3 m_Offset = Vector3.zero;
            [SerializeField]
            Vector3 m_Scale = Vector3.one;
            [SerializeField]
            Quaternion m_Orientation = Quaternion.identity;

            /// <summary>
            /// Hashes the contents of the brush cell.
            /// </summary>
            /// <returns>A hash code of the brush cell.</returns>
            public override int GetHashCode()
            {
                int hash;
                unchecked
                {
                    hash = gameObject != null ? gameObject.GetInstanceID() : 0;
                    hash = hash * 33 + offset.GetHashCode();
                    hash = hash * 33 + scale.GetHashCode();
                    hash = hash * 33 + orientation.GetHashCode();
                }
                return hash;
            }
        }
    }

    /// <summary>
    /// The Brush Editor for a GameObject Brush.
    /// </summary>
    [CustomEditor(typeof(GameObjectBrush))]
    public class GameObjectBrushEditor : GridBrushEditorBase
    {
        private bool hiddenGridFoldout;
        private Editor hiddenGridEditor;

        /// <summary>
        /// The GameObjectBrush for this Editor
        /// </summary>
        public GameObjectBrush brush { get { return target as GameObjectBrush; } }

        /// <summary> Whether the GridBrush can change Z Position. </summary>
        public override bool canChangeZPosition
        {
            get { return brush.canChangeZPosition; }
            set { brush.canChangeZPosition = value; }
        }
        
        /// <summary>
        /// Callback for painting the GUI for the GridBrush in the Scene View.
        /// The GameObjectBrush Editor overrides this to draw the preview of the brush when drawing lines.
        /// </summary>
        /// <param name="gridLayout">Grid that the brush is being used on.</param>
        /// <param name="brushTarget">Target of the GameObjectBrush::ref::Tool operation. By default the currently selected GameObject.</param>
        /// <param name="position">Current selected location of the brush.</param>
        /// <param name="tool">Current GameObjectBrush::ref::Tool selected.</param>
        /// <param name="executing">Whether brush is being used.</param>
        public override void OnPaintSceneGUI(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing)
        {
            BoundsInt gizmoRect = position;

            if (tool == GridBrushBase.Tool.Paint || tool == GridBrushBase.Tool.Erase)
                gizmoRect = new BoundsInt(position.min - brush.pivot, brush.size);
            
            base.OnPaintSceneGUI(gridLayout, brushTarget, gizmoRect, tool, executing);
        }

        /// <summary>
        /// Callback for painting the inspector GUI for the GameObjectBrush in the tilemap palette.
        /// The GameObjectBrush Editor overrides this to show the usage of this Brush.
        /// </summary>
        public override void OnPaintInspectorGUI()
        {
            EditorGUI.BeginChangeCheck();
            base.OnInspectorGUI();
            if (EditorGUI.EndChangeCheck() && brush.cellCount != brush.sizeCount)
            {
                brush.SizeUpdated(true);
            }

            hiddenGridFoldout = EditorGUILayout.Foldout(hiddenGridFoldout, "SceneRoot Grid");
            if (hiddenGridFoldout)
            {
                EditorGUI.indentLevel++;
                using (new EditorGUI.DisabledScope(GridPaintingState.scenePaintTarget != brush.hiddenGrid))
                {
                    if (hiddenGridEditor == null)
                    {
                        hiddenGridEditor = Editor.CreateEditor(brush.hiddenGrid.GetComponent<Grid>());
                    }
                    brush.hiddenGrid.hideFlags = HideFlags.None;
                    EditorGUI.BeginChangeCheck();
                    hiddenGridEditor.OnInspectorGUI();
                    if (EditorGUI.EndChangeCheck())
                    {
                        brush.UpdateHiddenGridLayout();
                        EditorUtility.SetDirty(brush);
                        SceneView.RepaintAll();
                    }
                    brush.hiddenGrid.hideFlags = HideFlags.HideAndDontSave;
                }
                EditorGUI.indentLevel--;
            }
        }

        /// <summary>
        /// The targets that the GameObjectBrush can paint on
        /// </summary>
        public override GameObject[] validTargets
        {
            get
            {
                StageHandle currentStageHandle = StageUtility.GetCurrentStageHandle();
                var results = currentStageHandle.FindComponentsOfType<GridLayout>().Where(x =>
                {
                    GameObject gameObject;
                    return (gameObject = x.gameObject).scene.isLoaded
                           && gameObject.activeInHierarchy;
                }).Select(x => x.gameObject);
                return results.Prepend(brush.hiddenGrid).ToArray();
            }
        }

    }
}