/// <summary>
        /// Calculates the texture co-ordinates for the selection.
        /// </summary>
        /// <param name="preview">
        /// A reference to a <see cref="TileSelectionControl"/> type.
        /// </param>
        /// <param name="material">
        /// A reference to a <see cref="GenericMaterialCreationControl"/> type.
        /// </param>
        /// <param name="orientationIndex">
        /// <p>The orientation Index.</p>
        /// <p>
        /// <list type="number">
        ///     <item>
        ///         <term>0 Zero</term>
        ///         <description>None.</description>
        ///     </item>
        ///     <item>
        ///         <term>1 Vertically</term>
        ///         <description>Coordinates will be flipped vertically.</description>
        ///     </item>
        ///     <item>
        ///         <term>2 Horizontally</term>
        ///         <description>Coordinates will be flipped horizontally.</description>
        ///     </item>
        ///     <item>
        ///         <term>3 Both</term>
        ///         <description>Coordinates will be flipped both horizontally and vertically.</description>
        ///     </item>
        /// </list>
        /// </p> 
        /// </param>
        /// <returns>
        /// Returns the textures co-ordinates for the selection in UV space.
        /// </returns>
        /// <exception cref="ArgumentNullException">If the <see cref="preview"/> or <see cref="material"/> parameter is null.</exception>
        /// <exception cref="ArgumentNullException">If the <see cref="TileSelectionControl.TextureAsset"/> or <see cref="GenericMaterialCreationControl.TextureAsset"/> properties are null.</exception>
        public static Rect GetTextureCoordinates(this TileSelectionControl preview, GenericMaterialCreationControl material, int orientationIndex)
        {    
            var startOffset = material.StartSpacing ? material.Spacing : 0;

            var tileWidth = material.TileWidth;
            var tileHeight = material.TileHeight;
            var spacing = material.Spacing;
            var textureAsset = material.TextureAsset;
            var inset = material.Inset;
            var textureCoordinates = new Rect();

            // if in free form mode return the freeform selection rectangle
            if (material.FreeForm)
            {
                var rect = preview.FreeFormRectangle;
                textureCoordinates.xMin = rect.xMin / preview.TextureAsset.width;
                textureCoordinates.yMin = rect.yMin / preview.TextureAsset.height;
                textureCoordinates.width = rect.width / preview.TextureAsset.width;
                textureCoordinates.height = rect.height / preview.TextureAsset.height;
            }
            else
            {
                // calculate selection rectangle size
                var size = preview.GetSelectionSize();

                // get min X/Y position of the selection rectangle
                var minX = float.MaxValue;
                var minY = float.MaxValue;

                foreach (var tile in preview.SelectedTiles)
                {
                    if (tile.X < minX)
                    {
                        minX = tile.X;
                    }

                    if (tile.Y < minY)
                    {
                        minY = tile.Y;
                    }
                }

                // setup a rect containing the US co-ordinates
                textureCoordinates = new Rect(
                    (startOffset + (minX * (tileWidth + spacing))) / textureAsset.width,
                    (startOffset + (minY * (tileHeight + spacing))) / textureAsset.height,
                    ((size.X * (tileWidth + spacing)) - spacing - startOffset) / textureAsset.width,
                    ((size.Y * (tileHeight + spacing)) - spacing - startOffset) / textureAsset.height);

                // apply inset
                textureCoordinates.x += inset;
                textureCoordinates.y += inset;
                textureCoordinates.width -= inset * 2;
                textureCoordinates.height -= inset * 2;
            }

            // textures co-ordinates originate from the lower left corner of the texture so adjust y to accommodate
            textureCoordinates.y = 1 - (textureCoordinates.y / 1) - textureCoordinates.height;

            // check to flip vertically
            if (orientationIndex == 1 || orientationIndex == 3)
            {
                var min = textureCoordinates.yMin;
                textureCoordinates.yMin = textureCoordinates.yMax;
                textureCoordinates.yMax = min;
            }

            // check to flip horizontally
            if (orientationIndex == 2 || orientationIndex == 3)
            {
                var min = textureCoordinates.xMin;
                textureCoordinates.xMin = textureCoordinates.xMax;
                textureCoordinates.xMax = min;
            }

            return textureCoordinates;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="AutomaticMaterialCreationWindow"/> class.
        /// </summary>
        public AutomaticMaterialCreationWindow()
        {
            // setup controls and variables
            this.tileMaterials = new Dictionary<string, List<TileSelectionModel>>();
            this.images = new Dictionary<string, GenericImage<Color>>();
            this.materialControls = new GenericMaterialCreationControl();
            this.mainPreview = new TileSelectionControl();

            // hook into the before spacing drawn event to allow us to customize the look
            this.materialControls.BeforeSpacingDrawn = () =>
                {
                    GUILayout.BeginHorizontal();
                    GUILayout.BeginVertical();
                };

            // hook into the after spacing drawn event to allow us to customize the look
            this.materialControls.AfterSpacingDrawn = () =>
            {
                GUILayout.EndVertical();

                GUILayout.FlexibleSpace();
                GUILayout.EndHorizontal();
            };

            // draw a show materials toggle button after the texture controls have been drawn
            this.materialControls.AfterTextureDrawn = () =>
            {
                this.showMaterials = GUILayout.Toggle(this.showMaterials, "Show\r\nMaterials", GUI.skin.button);
            };

            // hook into the tile selection event on the main preview control
            this.mainPreview.TileSelection += this.MainPreviewTileSelection;

            // Hook into the texture changed event and update the texture and selected texture id's
            this.materialControls.TextureChanged += (s, e) =>
            {
                this.mainPreview.TextureAsset = this.materialControls.TextureAsset;
                this.selectedTextureId = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetOrScenePath(this.materialControls.TextureAsset));
            };

            // ensure preview texture size is updated
            this.materialControls.TileSizeChanged += (s, e) =>
            {
                this.mainPreview.TileHeight = this.materialControls.TileHeight;
                this.mainPreview.TileWidth = this.materialControls.TileWidth;
            };

            // hook into events to sync control properties
            this.materialControls.StartSpacingChanged += (s, e) => { this.mainPreview.StartSpacing = this.materialControls.StartSpacing; };
            this.materialControls.SpacingChanged += (s, e) => { this.mainPreview.Spacing = this.materialControls.Spacing; };
            this.materialControls.FreeformChanged += (s, e) => { this.mainPreview.FreeForm = this.materialControls.FreeForm; };

            // sync control properties
            this.mainPreview.Refresh += (s, e) => this.Repaint();
            this.mainPreview.StartSpacing = this.materialControls.StartSpacing;
            this.mainPreview.Spacing = this.materialControls.Spacing;
            this.mainPreview.FreeForm = this.materialControls.FreeForm;

            // get grid mapping service and hook into the prefab drawn event
            var gridMappingService = GridMappingService.Instance;
            gridMappingService.PrefabDrawn += this.GridMappingService_PrefabDrawn;
        }
        /// <summary>
        /// Calculates the texture co-ordinates for the selection.
        /// </summary>
        /// <param name="preview">
        /// A reference to a <see cref="TileSelectionControl"/> type.
        /// </param>
        /// <param name="material">
        /// A reference to a <see cref="GenericMaterialCreationControl"/> type.
        /// </param>
        /// <param name="orientationIndex">
        /// <p>The orientation Index.</p>
        /// <p>
        /// <list type="number">
        ///     <item>
        ///         <term>0 Zero</term>
        ///         <description>None.</description>
        ///     </item>
        ///     <item>
        ///         <term>1 Vertically</term>
        ///         <description>Coordinates will be flipped vertically.</description>
        ///     </item>
        ///     <item>
        ///         <term>2 Horizontally</term>
        ///         <description>Coordinates will be flipped horizontally.</description>
        ///     </item>
        ///     <item>
        ///         <term>3 Both</term>
        ///         <description>Coordinates will be flipped both horizontally and vertically.</description>
        ///     </item>
        /// </list>
        /// </p>
        /// </param>
        /// <returns>
        /// Returns the textures co-ordinates for the selection in UV space.
        /// </returns>
        /// <exception cref="ArgumentNullException">If the <see cref="preview"/> or <see cref="material"/> parameter is null.</exception>
        /// <exception cref="ArgumentNullException">If the <see cref="TileSelectionControl.TextureAsset"/> or <see cref="GenericMaterialCreationControl.TextureAsset"/> properties are null.</exception>
        public static Rect GetTextureCoordinates(this TileSelectionControl preview, GenericMaterialCreationControl material, int orientationIndex)
        {
            var startOffset = material.StartSpacing ? material.Spacing : 0;

            var tileWidth          = material.TileWidth;
            var tileHeight         = material.TileHeight;
            var spacing            = material.Spacing;
            var textureAsset       = material.TextureAsset;
            var inset              = material.Inset;
            var textureCoordinates = new Rect();

            // if in free form mode return the freeform selection rectangle
            if (material.FreeForm)
            {
                var rect = preview.FreeFormRectangle;
                textureCoordinates.xMin   = rect.xMin / preview.TextureAsset.width;
                textureCoordinates.yMin   = rect.yMin / preview.TextureAsset.height;
                textureCoordinates.width  = rect.width / preview.TextureAsset.width;
                textureCoordinates.height = rect.height / preview.TextureAsset.height;
            }
            else
            {
                // calculate selection rectangle size
                var size = preview.GetSelectionSize();

                // get min X/Y position of the selection rectangle
                var minX = float.MaxValue;
                var minY = float.MaxValue;

                foreach (var tile in preview.SelectedTiles)
                {
                    if (tile.X < minX)
                    {
                        minX = tile.X;
                    }

                    if (tile.Y < minY)
                    {
                        minY = tile.Y;
                    }
                }

                // setup a rect containing the US co-ordinates
                textureCoordinates = new Rect(
                    (startOffset + (minX * (tileWidth + spacing))) / textureAsset.width,
                    (startOffset + (minY * (tileHeight + spacing))) / textureAsset.height,
                    ((size.X * (tileWidth + spacing)) - spacing - startOffset) / textureAsset.width,
                    ((size.Y * (tileHeight + spacing)) - spacing - startOffset) / textureAsset.height);

                // apply inset
                textureCoordinates.x      += inset;
                textureCoordinates.y      += inset;
                textureCoordinates.width  -= inset * 2;
                textureCoordinates.height -= inset * 2;
            }

            // textures co-ordinates originate from the lower left corner of the texture so adjust y to accommodate
            textureCoordinates.y = 1 - (textureCoordinates.y / 1) - textureCoordinates.height;

            // check to flip vertically
            if (orientationIndex == 1 || orientationIndex == 3)
            {
                var min = textureCoordinates.yMin;
                textureCoordinates.yMin = textureCoordinates.yMax;
                textureCoordinates.yMax = min;
            }

            // check to flip horizontally
            if (orientationIndex == 2 || orientationIndex == 3)
            {
                var min = textureCoordinates.xMin;
                textureCoordinates.xMin = textureCoordinates.xMax;
                textureCoordinates.xMax = min;
            }

            return(textureCoordinates);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="Map2DTileSelection"/> class.
        /// </summary>
        public Map2DTileSelection()
        {
            // setup controls and variables
            this.materialControls = new GenericMaterialCreationControl();
            this.mainPreview = new TileSelectionControl();
            this.materialControls.ShowFreeform = false;
            this.materialControls.ShowColor = false;
            this.materialControls.ShowShader = false;
            this.mainPreview.MultipleTileSelection = false;
            this.mainPreview.ShowHoverRectangle = false;

            // hook into the before spacing drawn event to allow us to customize the look
            this.materialControls.BeforeSpacingDrawn = () => GUILayout.BeginVertical();

            // hook into the after spacing drawn event to allow us to customize the look
            this.materialControls.AfterSpacingDrawn = () =>
                {
                    GUILayout.BeginHorizontal(GUILayout.ExpandWidth(false));

                    var items = new bool[4];
                    items[this.mainPreview.Zoom - 1] = true;
                    ControlGrid.DrawGenericGrid(
                        (data, index, style, options) =>
                        {
                            var result = GUILayout.Toggle(data[index], (index + 1).ToString(CultureInfo.InvariantCulture), style, options);
                            if (result != data[index])
                            {
                                this.mainPreview.Zoom = index + 1;
                            }

                            return result;
                        },
                        items,
                        items.Length,
                        GUI.skin.button);

                    GUILayout.EndHorizontal();
                    GUILayout.EndVertical();
                };

            // hook into the tile selection event on the main preview control
            this.mainPreview.TileSelection += this.MainPreviewTileSelection;

            // hook into the main preview texture changed so we can generate a generic image for the selected texture
            this.mainPreview.TextureChanged += this.MainPreviewTextureChanged;

            // Hook into the texture changed event and update the texture and selected texture id's
            this.materialControls.TextureChanged += (s, e) =>
            {
                this.mainPreview.TextureAsset = this.materialControls.TextureAsset;
            };

            // ensure preview texture size is updated
            this.materialControls.TileSizeChanged += (s, e) =>
            {
                this.mainPreview.TileHeight = this.materialControls.TileHeight;
                this.mainPreview.TileWidth = this.materialControls.TileWidth;
            };

            // hook into events to sync control properties
            this.materialControls.StartSpacingChanged += (s, e) => { this.mainPreview.StartSpacing = this.materialControls.StartSpacing; };
            this.materialControls.SpacingChanged += (s, e) => { this.mainPreview.Spacing = this.materialControls.Spacing; };
            this.materialControls.FreeformChanged += (s, e) => { this.mainPreview.FreeForm = this.materialControls.FreeForm; };

            // sync control properties
            this.mainPreview.Refresh += (s, e) => this.Repaint();
            this.mainPreview.StartSpacing = this.materialControls.StartSpacing;
            this.mainPreview.Spacing = this.materialControls.Spacing;
            this.mainPreview.FreeForm = this.materialControls.FreeForm;
        }