using HPJ.Simulation.Enums;
using HPJ.Simulation.Pathing;
using System;
using System.Collections.Generic;
using System.Linq;

namespace HPJ.Simulation.Map
{
    /// <summary>
    /// The set of maps (base and redundant) that represent a specific area
    /// </summary>
    [Serializable]
    public class MapSet
    {
        /// <summary>
        /// The Settings for this Map Set
        /// </summary>
        public MapSetSettings SetSettings = new MapSetSettings();

        /// <summary>
        /// The Instance ID to distinguish each map set apart
        /// </summary>       
        public int InstanceID;

        /// <summary>
        /// Shows if this Map Set is initialized or not
        /// </summary>
        public bool Initialized { get; protected set; }

        /// <summary>
        /// The Base map that comes with each Map Set
        /// </summary>
        public Map BaseMap { get; protected set; }

        /// <summary>
        /// The Redundant Maps that help are used to help reduce queue times for each Navigation Agent
        /// </summary>
        public Map[] RedundantMaps { get; protected set; }

        /// <summary>
        /// The List of Bridges that connect to this Map Set
        /// </summary>
        public List<MapBridge> MapBridges { get; set; }

        /// <summary>
        /// The List of Seams that connect to this Map Set
        /// </summary>
        public List<MapSeam> MapSeams { get; set; }

        /// <summary>
        /// The Callback to links back up to Unity. Cannot use Debug.Log() in the simulation side of the navigation
        /// </summary>
        public Action<string, SimulationDebugLog> DebugLogCallback;

        /// <summary>
        /// The List of Key points around the border of the Map Set
        /// </summary>
        public List<IntVector3> KeyBorderPositions { get; set; }

        /// <summary>
        /// Bottom Left Corner of the Map
        /// </summary>
        public IntVector3 BottomLeftPosition { get; set; }

        /// <summary>
        /// Left Center Position of the Map
        /// </summary>
        public IntVector3 LeftPosition { get; set; }

        /// <summary>
        /// Bottom Center Position of the Map
        /// </summary>
        public IntVector3 BottomPosition { get; set; }

        /// <summary>
        /// Bottom Right Corner of the Map
        /// </summary>
        public IntVector3 BottomRightPosition { get; set; }

        /// <summary>
        /// Top Right Corner of the Map
        /// </summary>
        public IntVector3 TopRightPosition { get; set; }

        /// <summary>
        /// Right Center Position of the Map
        /// </summary>
        public IntVector3 RightPosition { get; set; }

        /// <summary>
        /// Top Center Position of the Map
        /// </summary>
        public IntVector3 TopPosition { get; set; }

        /// <summary>
        /// Top Left Corner of the Map
        /// </summary>
        public IntVector3 TopLeftPosition { get; set; }

        /// <summary>
        /// Middle Position of the Map Position of the Map
        /// </summary>
        public IntVector3 MiddlePosition { get; set; }

        public MapSet()
        {
            MapBridges = new List<MapBridge>();
            MapSeams = new List<MapSeam>();
            OutgoingClaims = new Dictionary<IntVector2, TileOwnershipClaim>();
        }

        /// <summary>
        /// Initilizes the Map Set
        /// </summary>
        /// <param name="Parrelism"></param>
        /// <param name="Redundancy"></param>
        /// <param name="CreateAgentMap"></param>
        /// <param name="debugLogCallback"></param>
        public void Initialize(int Parrelism, int Redundancy, bool CreateAgentMap, Action<string, SimulationDebugLog> debugLogCallback)
        {
            try
            {
                DebugLogCallback = debugLogCallback;

                Redundancy++;
                RedundantMaps = new Map[Redundancy];
                for (int i = 0; i < Redundancy; i++)
                {
                    Map NewMap = new Map(SetSettings.MapSettings, $"{SetSettings.Name} - Map ({i})", Parrelism, CreateAgentMap);
                    NewMap.Initialize(debugLogCallback);
                    RedundantMaps[i] = NewMap;
                }
                BaseMap = RedundantMaps[0];

                // Initialize Pathfinding
                if (AStar.Instance == null)
                {
                    AStar.Instance = new AStar();
                }
                if (JumpPointSearch.Instance == null)
                {
                    JumpPointSearch.Instance = new JumpPointSearch();
                }
                KeyBorderPositions = new List<IntVector3>();

                IntVector3 WorldTileMax = new IntVector3();
                WorldTileMax.x = SetSettings.MapSettings.TileSize * SetSettings.MapSettings.MapWidth;
                WorldTileMax.y = 0;
                WorldTileMax.z = SetSettings.MapSettings.TileSize * SetSettings.MapSettings.MapHeight;

                BottomLeftPosition = SetSettings.MapSettings.Offset;
                KeyBorderPositions.Add(BottomLeftPosition);
                BottomPosition = SetSettings.MapSettings.Offset + WorldTileMax.xVector() / 2;
                KeyBorderPositions.Add(BottomPosition);
                BottomRightPosition = SetSettings.MapSettings.Offset + WorldTileMax.xVector();
                KeyBorderPositions.Add(BottomRightPosition);

                LeftPosition = SetSettings.MapSettings.Offset + WorldTileMax.zVector() / 2;
                KeyBorderPositions.Add(LeftPosition);
                MiddlePosition = SetSettings.MapSettings.Offset + WorldTileMax.xzVector() / 2;
                KeyBorderPositions.Add(MiddlePosition);
                RightPosition = SetSettings.MapSettings.Offset + WorldTileMax.xVector() + WorldTileMax.zVector() / 2;
                KeyBorderPositions.Add(RightPosition);

                TopLeftPosition = SetSettings.MapSettings.Offset + WorldTileMax.zVector();
                KeyBorderPositions.Add(TopLeftPosition);
                TopPosition = SetSettings.MapSettings.Offset + WorldTileMax.xVector() / 2 + WorldTileMax.zVector();
                KeyBorderPositions.Add(TopPosition);
                TopRightPosition = SetSettings.MapSettings.Offset + WorldTileMax.xzVector();
                KeyBorderPositions.Add(TopRightPosition);

                Initialized = true;
            }
            catch (Exception ex)
            {
                debugLogCallback?.Invoke($"{ex.Message}: {ex.StackTrace}", SimulationDebugLog.Error);
            }
        }

        public void OnDestroy()
        {
            for (int i = 0; i < RedundantMaps.Length; i++)
            {
                RedundantMaps[i].OnDestroy();
            }
        }

        /// <summary>
        /// Logs the debugs occured in the simulation
        /// </summary>
        /// <param name="Log"></param>
        public void Log(string Log)
        {
            DebugLogCallback?.Invoke(Log, SimulationDebugLog.Log);
        }


        /// <summary>
        /// Logs the warning occured in the simulation
        /// </summary>
        /// <param name="Log"></param>
        public void LogWarning(string Log)
        {
            DebugLogCallback?.Invoke(Log, SimulationDebugLog.Warning);
        }

        /// <summary>
        /// Logs the errors occured in the simulation
        /// </summary>
        /// <param name="Log"></param>
        public void LogError(string Log)
        {
            DebugLogCallback?.Invoke(Log, SimulationDebugLog.Error);
        }

        #region Pathfinding

        private int _redundantMapCounter = -1;
        /// <summary>
        /// The Call a navigation job sends to the map to be calculated
        /// </summary>
        /// <param name="Job"></param>
        public void SetDestination(NavigationJob Job)
        {
            _redundantMapCounter++;
            if (_redundantMapCounter >= RedundantMaps.Length)
            {
                _redundantMapCounter = 0;
            }

            if (RedundantMaps.Length == 0)
            {
                LogWarning("Warning, No Maps to cast 'Set Destination On'");
                return;
            }

            RedundantMaps[_redundantMapCounter].AddNavigationJob(Job);
        }

        /// <summary>
        /// Checks to see if this job is compatible with this map set. For example a job that has its start in another map is not compatible with this map set
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        public bool JobCompatible(NavigationJob job)
        {
            if (BaseMap == null)
            {
                LogError($"Map {SetSettings.Name} Close Range is Null");
                return false;
            }
            return BaseMap.JobCompatible(job);
        }

        /// <summary>
        /// Whether this index is out of range of the map
        /// </summary>
        /// <param name="newTilePosition"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public bool OutOfRange(IntVector2 newTilePosition)
        {
            if (newTilePosition.x < 0 || newTilePosition.y < 0)
            {
                return true;
            }

            if (newTilePosition.x >= SetSettings.MapSettings.MapWidth || newTilePosition.y >= SetSettings.MapSettings.MapHeight)
            {
                return true;
            }

            return false;
        }

        #endregion

        #region Map Changes

        /// <summary>
        /// The Call that changes the tiles on each map in the Map Set
        /// </summary>
        /// <param name="MapJob"></param>
        public void ChangeMap(ChangeMapJob MapJob)
        {
            foreach (Map map in RedundantMaps)
            {
                map.AddMapChange(MapJob);
            }
        }

        /// <summary>
        /// A dictionary to keep track of the outgoing claims
        /// </summary>
        public Dictionary<IntVector2, TileOwnershipClaim> OutgoingClaims { get; protected set; }

        /// <summary>
        /// Requests to claim a tile
        /// </summary>
        /// <param name="Tile"></param>
        /// <param name="AgentID"></param>
        /// <returns></returns>
        public bool AttemptClaim(IntVector2 Tile, ushort AgentID)
        {
            if (BaseMap.AgentOwnershipMap.Tiles[Tile.x, Tile.y] != 0)
            {
                return false;
            }

            if (OutgoingClaims.ContainsKey(Tile))
            {
                return false;
            }

            TileOwnershipClaim NewClaim = new TileOwnershipClaim(AgentID, Tile);
            OutgoingClaims.Add(Tile, NewClaim);
            Claim(NewClaim);
            return true;
        }

        /// <summary>
        /// Disowns a claim to a tile
        /// </summary>
        /// <param name="Tile"></param>
        /// <param name="AgentID"></param>
        /// <returns></returns>
        public bool DisownClaim(IntVector2 Tile, ushort AgentID)
        {
            if (BaseMap.AgentOwnershipMap.Tiles[Tile.x, Tile.y] != AgentID)
            {
                return false;
            }

            DisownTileOwnership NewClaim = new DisownTileOwnership(AgentID, Tile);
            foreach (Map map in RedundantMaps)
            {
                map.AddDisownmentClaim(NewClaim);
            }
            return true;
        }

        /// <summary>
        /// Clears the list of claims
        /// </summary>
        public void ClearClaims()
        {
            OutgoingClaims.Clear();
        }

        /// <summary>
        /// Adds a agent tile request claim for this map set
        /// </summary>
        /// <param name="Claim"></param>
        protected void Claim(TileOwnershipClaim Claim)
        {
            foreach (Map map in RedundantMaps)
            {
                map.AddClaim(Claim);
            }
        }

        #endregion

        #region Helpers

        /// <summary>
        /// This Changed the DATA the Default data and makes all the tiles to the default tile type (TileTypes.Standard)
        /// </summary>
        public void CleanMapTiles()
        {
            SetSettings.MapSettings.MapInfo.CleanMap();
        }

        /// <summary>
        /// The Closest point on the map to the reach point
        /// </summary>
        /// <param name="reachPoint"></param>
        /// <returns></returns>
        public IntVector3 GetClosestPoint(IntVector3 reachPoint)
        {
            IntVector3 ClosestPoint = new IntVector3();
            IntVector2 TileIndex = GetMapTileIndex(reachPoint);

            ClosestPoint.y = reachPoint.y;

            if (TileIndex.x < SetSettings.MapSettings.Offset.x)
            {
                ClosestPoint.x = SetSettings.MapSettings.Offset.x;
            }
            else if (TileIndex.x >= SetSettings.MapSettings.Offset.x + SetSettings.MapSettings.MapWidth)
            {
                ClosestPoint.x = SetSettings.MapSettings.Offset.x + SetSettings.MapSettings.MapWidth * SetSettings.MapSettings.TileSize - 1;
            }
            else
            {
                ClosestPoint.x = reachPoint.x;
            }

            if (TileIndex.y < SetSettings.MapSettings.Offset.z)
            {
                ClosestPoint.z = SetSettings.MapSettings.Offset.z;
            }
            else if (TileIndex.y >= SetSettings.MapSettings.Offset.z + SetSettings.MapSettings.MapHeight)
            {
                ClosestPoint.z = SetSettings.MapSettings.Offset.z + SetSettings.MapSettings.MapHeight * SetSettings.MapSettings.TileSize - 1;
            }
            else
            {
                ClosestPoint.z = reachPoint.z;
            }

            return ClosestPoint;
        }

        /// <summary>
        /// The Closest valid point to the reach point
        /// </summary>
        /// <param name="ClosestPoint"></param>
        /// <param name="ValidTypes"></param>
        /// <returns></returns>
        public IntVector3 GetClosestValidPoint(IntVector3 ClosestPoint, TileTypes[] ValidTypes)
        {
            IntVector2 TileIndex = GetMapTileIndex(ClosestPoint);

            int Radius = 1;
            while (Radius <= 50)
            {
                for(int x = -Radius; x <= Radius; x++)
                {
                    IntVector2 NewTileIndex = TileIndex + new IntVector2(x, Radius);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }

                    NewTileIndex = TileIndex + new IntVector2(x, -Radius);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }
                }

                for (int y = -Radius; y <= Radius; y++)
                {
                    IntVector2 NewTileIndex = TileIndex + new IntVector2(Radius, y);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }

                    NewTileIndex = TileIndex + new IntVector2(Radius, y);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }
                }
                Radius++;
            }

            return ClosestPoint;
        }


        /// <summary>
        /// The Closest valid point to the reach point
        /// </summary>
        /// <param name="ClosestPoint"></param>
        /// <param name="ValidTypes"></param>
        /// <returns></returns>
        public IntVector3 GetClosestValidPoint(IntVector3 ClosestPoint, List<TileTypes> ValidTypes)
        {
            IntVector2 TileIndex = GetMapTileIndex(ClosestPoint);

            int Radius = 1;
            while (Radius <= 50)
            {
                for (int x = -Radius; x <= Radius; x++)
                {
                    IntVector2 NewTileIndex = TileIndex + new IntVector2(x, Radius);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }

                    NewTileIndex = TileIndex + new IntVector2(x, -Radius);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }
                }

                for (int y = -Radius; y <= Radius; y++)
                {
                    IntVector2 NewTileIndex = TileIndex + new IntVector2(Radius, y);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }

                    NewTileIndex = TileIndex + new IntVector2(Radius, y);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }
                }
                Radius++;
            }

            return ClosestPoint;
        }


        /// <summary>
        /// The Closest valid point to the current tile index
        /// </summary>
        /// <param name="ClosestPoint"></param>
        /// <param name="ValidTypes"></param>
        /// <returns></returns>
        public IntVector3 GetClosestValidPoint(IntVector2 CurrentTileIndex, TileTypes[] ValidTypes)
        {
            int Radius = 1;
            while (Radius <= 50)
            {
                for (int x = -Radius; x <= Radius; x++)
                {
                    IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(x, Radius);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(x, -Radius);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }
                }

                for (int y = -Radius; y <= Radius; y++)
                {
                    IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(Radius, y);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(Radius, y);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }
                }
                Radius++;
            }

            return GetWorldTileIndex(CurrentTileIndex); 
        }

        /// <summary>
        /// The Closest valid point to the current tile index
        /// </summary>
        /// <param name="ClosestPoint"></param>
        /// <param name="ValidTypes"></param>
        /// <returns></returns>
        public IntVector3 GetClosestValidPoint(IntVector2 CurrentTileIndex, List<TileTypes> ValidTypes)
        {
            int Radius = 1;
            while (Radius <= 50)
            {
                for (int x = -Radius; x <= Radius; x++)
                {
                    IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(x, Radius);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(x, -Radius);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }
                }

                for (int y = -Radius; y <= Radius; y++)
                {
                    IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(Radius, y);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(Radius, y);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return GetWorldTileIndex(NewTileIndex);
                    }
                }
                Radius++;
            }

            return GetWorldTileIndex(CurrentTileIndex);
        }

        /// <summary>
        /// The Closest valid tile index to the current tile index
        /// </summary>
        /// <param name="ClosestPoint"></param>
        /// <param name="ValidTypes"></param>
        /// <returns></returns>
        public IntVector2 GetClosestValidIndex(IntVector2 CurrentTileIndex, TileTypes[] ValidTypes)
        {
            int Radius = 1;
            while (Radius <= 50)
            {
                for (int x = -Radius; x <= Radius; x++)
                {
                    IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(x, Radius);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(x, -Radius);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }
                }

                for (int y = -Radius; y <= Radius; y++)
                {
                    IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(Radius, y);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(Radius, y);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }
                }
                Radius++;
            }

            return CurrentTileIndex;
        }

        /// <summary>
        /// The Closest valid tile index to the current tile index
        /// </summary>
        /// <param name="CurrentTileIndex"></param>
        /// <param name="ValidTypes"></param>
        /// <returns></returns>
        public IntVector2 GetClosestValidIndex(IntVector2 CurrentTileIndex, List<TileTypes> ValidTypes)
        {
            int Radius = 1;
            while (Radius <= 50)
            {
                for (int x = 0; x <= Radius; x++)
                {
                    IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(x, Radius);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(x, -Radius);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(-x, Radius);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(-x, -Radius);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }
                }

                for (int y = 0; y <= Radius; y++)
                {
                    IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(Radius, y);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(-Radius, y);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(Radius, -y);

                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }

                    NewTileIndex = CurrentTileIndex + new IntVector2(-Radius, -y);
                    if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                    {
                        return NewTileIndex;
                    }
                }
                Radius++;
            }

            return CurrentTileIndex;
        }
        /// <summary>
        /// The Closest valid tile index to the current tile index
        /// </summary>
        /// <param name="CurrentTileIndex"></param>
        /// <param name="ValidTypes"></param>
        /// <param name="SimAgentID"></param>
        /// <returns></returns>
        public IntVector2 GetClosestValidIndex(IntVector2 CurrentTileIndex, List<TileTypes> ValidTypes, ushort SimAgentID)
        {
            int Radius = 1;
            if (BaseMap.AgentAvoidence)
            {
                while (Radius <= 50)
                {
                    for (int x = 0; x <= Radius; x++)
                    {
                        IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(x, Radius);

                        if (ValidTypes.Contains(GetTileType(NewTileIndex)) && BaseMap.AgentOwnershipMap.ValidTileForAgent(SimAgentID, NewTileIndex.x, NewTileIndex.y))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(x, -Radius);
                        if (ValidTypes.Contains(GetTileType(NewTileIndex)) && BaseMap.AgentOwnershipMap.ValidTileForAgent(SimAgentID, NewTileIndex.x, NewTileIndex.y))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(-x, Radius);

                        if (ValidTypes.Contains(GetTileType(NewTileIndex)) && BaseMap.AgentOwnershipMap.ValidTileForAgent(SimAgentID, NewTileIndex.x, NewTileIndex.y))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(-x, -Radius);
                        if (ValidTypes.Contains(GetTileType(NewTileIndex)) && BaseMap.AgentOwnershipMap.ValidTileForAgent(SimAgentID, NewTileIndex.x, NewTileIndex.y))
                        {
                            return NewTileIndex;
                        }
                    }

                    for (int y = 0; y <= Radius; y++)
                    {
                        IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(Radius, y);

                        if (ValidTypes.Contains(GetTileType(NewTileIndex)) && BaseMap.AgentOwnershipMap.ValidTileForAgent(SimAgentID, NewTileIndex.x, NewTileIndex.y))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(-Radius, y);
                        if (ValidTypes.Contains(GetTileType(NewTileIndex)) && BaseMap.AgentOwnershipMap.ValidTileForAgent(SimAgentID, NewTileIndex.x, NewTileIndex.y))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(Radius, -y);

                        if (ValidTypes.Contains(GetTileType(NewTileIndex)) && BaseMap.AgentOwnershipMap.ValidTileForAgent(SimAgentID, NewTileIndex.x, NewTileIndex.y))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(-Radius, -y);
                        if (ValidTypes.Contains(GetTileType(NewTileIndex)) && BaseMap.AgentOwnershipMap.ValidTileForAgent(SimAgentID, NewTileIndex.x, NewTileIndex.y))
                        {
                            return NewTileIndex;
                        }
                    }
                    Radius++;
                }
            }
            else
            {
                while (Radius <= 50)
                {
                    for (int x = 0; x <= Radius; x++)
                    {
                        IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(x, Radius);

                        if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(x, -Radius);
                        if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(-x, Radius);

                        if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(-x, -Radius);
                        if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                        {
                            return NewTileIndex;
                        }
                    }

                    for (int y = 0; y <= Radius; y++)
                    {
                        IntVector2 NewTileIndex = CurrentTileIndex + new IntVector2(Radius, y);

                        if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(-Radius, y);
                        if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(Radius, -y);

                        if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                        {
                            return NewTileIndex;
                        }

                        NewTileIndex = CurrentTileIndex + new IntVector2(-Radius, -y);
                        if (ValidTypes.Contains(GetTileType(NewTileIndex)))
                        {
                            return NewTileIndex;
                        }
                    }
                    Radius++;
                }
            }

            return CurrentTileIndex;
        }

        /// <summary>
        /// Gets the world tile index relative to the tile index of the map set
        /// </summary>
        /// <param name="newTileIndex"></param>
        /// <param name="GetTileCenter"></param>
        /// <returns></returns>
        public IntVector3 GetWorldTileIndex(IntVector2 newTileIndex, bool GetTileCenter = true)
        {
            IntVector3 WorldTilePosition = new IntVector3();

            WorldTilePosition.x = SetSettings.MapSettings.Offset.x + newTileIndex.x * SetSettings.MapSettings.TileSize;
            WorldTilePosition.y = SetSettings.MapSettings.Offset.y;
            WorldTilePosition.z = SetSettings.MapSettings.Offset.z + newTileIndex.y * SetSettings.MapSettings.TileSize;

            if (GetTileCenter)
            {
                WorldTilePosition.x += SetSettings.MapSettings.TileSize / 2;
                WorldTilePosition.z += SetSettings.MapSettings.TileSize / 2;
            }

            return WorldTilePosition;
        }

        /// <summary>
        /// Gets the world tile index relative to the tile index of the map set
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="GetTileCenter"></param>
        /// <returns></returns>
        public IntVector3 GetWorldTileIndex(int x, int y, bool GetTileCenter = true)
        {
            IntVector3 WorldTilePosition = new IntVector3();

            WorldTilePosition.x = SetSettings.MapSettings.Offset.x + x * SetSettings.MapSettings.TileSize;
            WorldTilePosition.y = SetSettings.MapSettings.Offset.y;
            WorldTilePosition.z = SetSettings.MapSettings.Offset.z + y * SetSettings.MapSettings.TileSize;

            if (GetTileCenter)
            {
                WorldTilePosition.x += SetSettings.MapSettings.TileSize / 2;
                WorldTilePosition.z += SetSettings.MapSettings.TileSize / 2;
            }

            return WorldTilePosition;
        }

        /// <summary>
        /// This gets you the Tile Index of the Default Map
        /// </summary>
        /// <param name="WorldPosition"></param>
        /// <returns></returns>
        public IntVector2 GetMapTileIndex(IntVector3 WorldTileIndexPosition)
        {
            // Offset the Position
            IntVector2 TileIndex = new IntVector2();
            TileIndex.x = (int)Math.Floor((WorldTileIndexPosition.x - SetSettings.MapSettings.Offset.x) / (float)SetSettings.MapSettings.TileSize);
            TileIndex.y = (int)Math.Floor((WorldTileIndexPosition.z - SetSettings.MapSettings.Offset.z) / (float)SetSettings.MapSettings.TileSize);

            return TileIndex;
        }

        /// <summary>
        /// Checks to see if this point is on the Map 
        /// </summary>
        /// <param name="hitTileIndexPosition"></param>
        /// <returns></returns>
        public bool PointInMap(IntVector3 hitTileIndexPosition)
        {
            if (hitTileIndexPosition.x < SetSettings.MapSettings.Offset.x || hitTileIndexPosition.z < SetSettings.MapSettings.Offset.z)
            {
                return false;
            }

            if (hitTileIndexPosition.x >= SetSettings.MapSettings.Offset.x + SetSettings.MapSettings.MapWidth * SetSettings.MapSettings.TileSize || hitTileIndexPosition.z >= SetSettings.MapSettings.Offset.z + SetSettings.MapSettings.MapHeight * SetSettings.MapSettings.TileSize)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Checks to see if this point is on the Map 
        /// </summary>
        /// <param name="hitTileIndexPosition"></param>
        /// <returns></returns>
        public bool PointInMap(IntVector2 TileIndex)
        {
            if (TileIndex.x < 0 || TileIndex.y < 0)
            {
                return false;
            }

            if (TileIndex.x >= SetSettings.MapSettings.MapWidth || TileIndex.y >= SetSettings.MapSettings.MapHeight)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// This Changed the DATA the Default data, the maps that are generated on awake are not effected by this
        /// </summary>
        /// <param name="WorldPosition"></param>
        /// <param name="NewType"></param>
        public void ChangeDefaultTileType(IntVector3 WorldTileIndexPosition, TileTypes NewType, int Radius, PainterTypes painterType)
        {
            IntVector2 TileIndex = GetMapTileIndex(WorldTileIndexPosition);

            if (painterType == PainterTypes.Square)
            {
                for (int x = -Radius; x <= Radius; x++)
                {
                    for (int y = -Radius; y <= Radius; y++)
                    {
                        if (GetDefaultTileType(TileIndex.x + x, TileIndex.y + y) == TileTypes.OutOfBounds)
                        {
                            continue;
                        }

                        SetSettings.MapSettings.MapInfo.SavedTiles[TileIndex.x + x, TileIndex.y + y] = (byte)NewType;
                    }
                }
            }
            else
            {
                for (int x = -Radius; x <= Radius; x++)
                {
                    for (int y = -Radius; y <= Radius; y++)
                    {
                        if (GetDefaultTileType(TileIndex.x + x, TileIndex.y + y) == TileTypes.OutOfBounds)
                        {
                            continue;
                        }

                        if (x * x + y * y > Radius * Radius)
                        {
                            continue;
                        }

                        SetSettings.MapSettings.MapInfo.SavedTiles[TileIndex.x + x, TileIndex.y + y] = (byte)NewType;
                    }
                }
            }
        }

        /// <summary>
        /// This Changed the DATA the Default data, the maps that are generated on awake are not effected by this
        /// </summary>
        /// <param name="WorldPosition"></param>
        /// <param name="NewType"></param>
        public void ChangeDefaultTileType(int x, int y, TileTypes NewType)
        {
            if (GetDefaultTileType(x, y) == TileTypes.OutOfBounds)
            {
                return;
            }

            SetSettings.MapSettings.MapInfo.SavedTiles[x, y] = (byte)NewType;
        }

        /// <summary>
        /// This gets you the Tile Type at a Index from the Default Map
        /// </summary>
        /// <param name="TileIndex"></param>
        /// <returns></returns>
        public TileTypes GetDefaultTileType(IntVector2 TileIndex)
        {
            if (TileIndex.x < 0 || TileIndex.y < 0 || TileIndex.x >= SetSettings.MapSettings.MapWidth || TileIndex.y >= SetSettings.MapSettings.MapHeight)
            {
                return TileTypes.OutOfBounds;
            }

            return (TileTypes)SetSettings.MapSettings.MapInfo.SavedTiles[TileIndex.x, TileIndex.y];
        }

        /// <summary>
        /// This gets you the Tile Type at a Index from the Default Map
        /// </summary>
        /// <param name="TileIndex"></param>
        /// <returns></returns>
        public TileTypes GetDefaultTileType(int x, int y)
        {
            if (x < 0 || y < 0 || x >= SetSettings.MapSettings.MapWidth || y >= SetSettings.MapSettings.MapHeight)
            {
                return TileTypes.OutOfBounds;
            }

            return (TileTypes)SetSettings.MapSettings.MapInfo.SavedTiles[x, y];
        }

        /// <summary>
        /// This gets you the Tile Type at a Index from the Map
        /// </summary>
        /// <param name="TileIndex"></param>
        /// <returns></returns>
        public TileTypes GetTileType(IntVector2 TileIndex)
        {
            if (TileIndex.x < 0 || TileIndex.y < 0 || TileIndex.x >= SetSettings.MapSettings.MapWidth || TileIndex.y >= SetSettings.MapSettings.MapHeight)
            {
                return TileTypes.OutOfBounds;
            }

            if (BaseMap == null)
            {
                return TileTypes.OutOfBounds;
            }

            return BaseMap.GetTileType(TileIndex);
        }

        /// <summary>
        /// This gets you the Tile Type at a World Position Index from the Map
        /// </summary>
        /// <param name="TileIndex"></param>
        /// <returns></returns>
        public TileTypes GetTileType(IntVector3 worldTileIndex)
        {
            IntVector2 MapIndex = GetMapTileIndex(worldTileIndex);
            return GetTileType(MapIndex);
        }

        /// <summary>
        /// This gets you the Tile Type at a Index from the Default Map
        /// </summary>
        /// <param name="TileIndex"></param>
        /// <returns></returns>
        public TileTypes GetTileType(int x, int y)
        {
            if (x < 0 || y < 0 || x >= SetSettings.MapSettings.MapWidth || y >= SetSettings.MapSettings.MapHeight)
            {
                return TileTypes.OutOfBounds;
            }

            return BaseMap.GetTileType(x, y);
        }

        /// <summary>
        /// Adds the bridge if this bridge is connected to this Map
        /// </summary>
        /// <param name="bridge"></param>
        public void AddBridge(MapBridge bridge)
        {
            if (RedundantMaps != null)
            {
                foreach (Map map in RedundantMaps)
                {
                    if (!map.Bridges.Contains(bridge))
                    {
                        map.Bridges.Add(bridge);
                    }
                }
            }

            if (!MapBridges.Contains(bridge))
            {
                MapBridges.Add(bridge);
            }
        }

        /// <summary>
        /// Adds the seam if this seam is connected to this Map
        /// </summary>
        /// <param name="bridge"></param>
        public void AddSeam(MapSeam seam)
        {
            if (RedundantMaps != null)
            {
                foreach (Map map in RedundantMaps)
                {
                    if (!map.Seams.Contains(seam))
                    {
                        map.Seams.Add(seam);
                    }
                }
            }

            if (!MapSeams.Contains(seam))
            {
                MapSeams.Add(seam);
            }
        }

        /// <summary>
        /// Clears the Maps and the Bridges connected to this Map
        /// </summary>
        public void ClearMaps()
        {
            if (RedundantMaps != null)
            {
                foreach(Map map in RedundantMaps)
                {
                    map.Bridges.Clear();
                    map.Seams.Clear();
                }
            }

            MapBridges.Clear();
        }

        /// <summary>
        /// Finds the closest bridge that connects the destination Map
        /// </summary>
        /// <param name="nextMap"></param>
        /// <param name="start"></param>
        /// <returns></returns>
        public MapBridge GetClosestBridge(MapSet nextMap, IntVector3 start)
        {
            MapBridge ClosestBridge = null;
            int ClosestScore = int.MaxValue;
            // Check for direct bridges first
            foreach (MapBridge Bridge in MapBridges)
            {
                if (!Bridge.ValidBridge)
                {
                    continue;
                }

                if (Bridge.CanWalkInDirection(this, nextMap))
                {
                    IntVector3 BridgePoint = Bridge.GetPoint(this);
                    int Distance = IntVector3.DistanceSquared(BridgePoint, start);
                    if (Distance < ClosestScore)
                    {
                        ClosestBridge = Bridge;
                        ClosestScore = Distance;
                    }
                }
            }

            return ClosestBridge;
        }

        /// <summary>
        /// Finds the closest seam that connects the destination Map
        /// </summary>
        /// <param name="nextMap"></param>
        /// <param name="start"></param>
        /// <returns></returns>
        public MapSeam GetClosestSeam(MapSet nextMap, IntVector3 start)
        {
            MapSeam ClosestSeam = null;
            int ClosestScore = int.MaxValue;
            // Check for direct bridges first
            foreach (MapSeam Seam in MapSeams)
            {
                if (!Seam.ValidSeam)
                {
                    continue;
                }

                if (Seam.DirectConnects(this, nextMap))
                {
                    IntVector3 BridgePoint = Seam.GetPoint(this, start);
                    int Distance = IntVector3.DistanceSquared(BridgePoint, start);
                    if (Distance < ClosestScore)
                    {
                        ClosestSeam = Seam;
                        ClosestScore = Distance;
                    }
                }
            }

            return ClosestSeam;
        }

        /// <summary>
        /// Gets the closest Map Tile Index based on a tile index relative to this Map
        /// </summary>
        /// <param name="tileIndex"></param>
        /// <returns></returns>
        public IntVector2 GetClosestMapTileIndex(IntVector2 tileIndex)
        {
            if (tileIndex.x < 0)
            {
                tileIndex.x = 0;
            }
            else if (tileIndex.x >= SetSettings.MapSettings.MapWidth)
            {
                tileIndex.x = SetSettings.MapSettings.MapWidth - 1;
            }

            if (tileIndex.y < 0)
            {
                tileIndex.y = 0;
            }
            else if (tileIndex.y >= SetSettings.MapSettings.MapHeight)
            {
                tileIndex.y = SetSettings.MapSettings.MapHeight - 1;
            }

            return tileIndex;
        }

        /// <summary>
        /// Gets the closest Map Tile Index based on a tile index relative to this Map
        /// </summary>
        /// <param name="worldIndex"></param>
        /// <returns></returns>
        public IntVector2 GetClosestMapTileIndex(IntVector3 worldIndex)
        {
            IntVector2 tileIndex = GetMapTileIndex(worldIndex);

            return GetClosestMapTileIndex(tileIndex);
        }

        #endregion
    }

    /// <summary>
    /// The settings of the Map Set
    /// </summary>
    [Serializable]
    public class MapSetSettings
    {
        /// <summary>
        /// The Name of the Map
        /// </summary>
        public string Name;
        /// <summary>
        /// The Settings for each Map on the Map Set
        /// </summary>
        public MapSettings MapSettings = new MapSettings();

        public MapSetSettings()
        {
            Name = "No Name";
        }
    }
}