/**********************************************************
RunUO 2.0 AoV C# script file
Official Age of Valor Script :: www.uovalor.com
Last modified by 
Filepath: scripts\Multis\HousePlacement.cs
Lines of code: 298
***********************************************************/


using System;
using System.Collections;
using System.Collections.Generic;
using Server;
using Server.Items;
using Server.Regions;
using Server.Commands;

namespace Server.Multis
{
	public enum HousePlacementResult
	{
		Valid,
		BadRegion,
		BadLand,
		BadStatic,
		BadItem,
		NoSurface,
		BadRegionHidden
	}

	public class HousePlacement
	{
		private const int YardSize = 5;

		// Any land tile which matches one of these ID numbers is considered a road and cannot be placed over.
		private static int[] m_RoadIDs = new int[]
			{
				0x071, 0x08C,
				0x0E8, 0x0EB,
				0x14C, 0x14F,
				0x161, 0x174,
				0x1F0, 0x1F3,
				0x26E, 0x279,
				0x27E, 0x281,
				0x324, 0x3AC,
				0x547, 0x556,
				0x597, 0x5A6,
				0x637, 0x63A,
				0x7AE, 0x7B1,
				0x442, 0x479, // Sand stones
				0x501, 0x510, // Sand stones
				0x009, 0x015, // Furrows
				0x150, 0x15C  // Furrows 
			};
			
			
		public static void ShowBadItem(Mobile from, Item item)
		{
			//Console.WriteLine("Bad item  {0}",item);//debug
			RegionBounds.ShowObjectBounds(from,item,0);
		}
		
		public static void ShowBadRegion(Mobile from, Region reg)
		{
			//Console.WriteLine("Bad region  {0}",reg);//debug
			RegionBounds.ShowRegionBounds(reg, from,false,true);
		}		
		
		public static void ShowBadSpot(Mobile from,Point2D point)
		{
			ShowBadSpot(from,new Point3D(point.X,point.Y,from.Z));
		}
		
		public static void ShowBadSpot(Mobile from,Point3D point)
		{
			//Console.WriteLine("Bad spot @ {0}",point);//debug
			RegionBounds.ShowRectBounds(new Rectangle3D(point, new Point3D(point.X+1,point.Y+1,point.Z)), from.Map, from,false, true);
		}

		public static HousePlacementResult Check( Mobile from, int multiID, Point3D center, out ArrayList toMove )
		{
			// If this spot is considered valid, every item and mobile in this list will be moved under the house sign
			toMove = new ArrayList();

			Map map = from.Map;

			if ( map == null || map == Map.Internal )
				return HousePlacementResult.BadLand; // A house cannot go here

		//	if ( from.AccessLevel >= AccessLevel.GameMaster )
		//		return HousePlacementResult.Valid; // Staff can place anywhere

			if ( map == Map.Ilshenar )
				return HousePlacementResult.BadRegion; // No houses in Ilshenar

			NoHousingRegion noHousingRegion = (NoHousingRegion) Region.Find( center, map ).GetRegion( typeof( NoHousingRegion ) );

			if ( noHousingRegion != null )
				return HousePlacementResult.BadRegion;

			// This holds data describing the internal structure of the house
			MultiComponentList mcl = MultiData.GetComponents( multiID );

			if ( multiID >= 0x13EC && multiID < 0x1D00 )
				HouseFoundation.AddStairsTo( ref mcl ); // this is a AOS house, add the stairs

			// Location of the nortwest-most corner of the house
			Point3D start = new Point3D( center.X + mcl.Min.X, center.Y + mcl.Min.Y, center.Z );

			// These are storage lists. They hold items and mobiles found in the map for further processing
			ArrayList items = new ArrayList(), mobiles = new ArrayList();

			// These are also storage lists. They hold location values indicating the yard and border locations.
			List<Point2D> yard = new List<Point2D>(), borders = new List<Point2D>();

			/* RULES:
			 * 
			 * 1) All tiles which are around the -outside- of the foundation must not have anything impassable.
			 * 2) No impassable object or land tile may come in direct contact with any part of the house.
			 * 3) Five tiles from the front and back of the house must be completely clear of all house tiles.
			 * 4) The foundation must rest flatly on a surface. Any bumps around the foundation are not allowed.
			 * 5) No foundation tile may reside over terrain which is viewed as a road.
			 */

			for ( int x = 0; x < mcl.Width; ++x )
			{
				for ( int y = 0; y < mcl.Height; ++y )
				{
					int tileX = start.X + x;
					int tileY = start.Y + y;

					StaticTile[] addTiles = mcl.Tiles[x][y];

					if ( addTiles.Length == 0 )
						continue; // There are no tiles here, continue checking somewhere else

					Point3D testPoint = new Point3D( tileX, tileY, center.Z );

					Region reg = Region.Find( testPoint, map );
					
					if(testPoint.InGreenAcres() && (map==Map.Trammel || map==Map.Felucca))
					{
						from.SendMessage("You cannot put a house in green acres!");
						return HousePlacementResult.BadRegion;
					}

					if ( !reg.AllowHousing( from, testPoint ) ) // Cannot place houses in dungeons, towns, treasure map areas etc
					{
						ShowBadRegion(from,reg);
						
						if ( reg.IsPartOf( typeof( TreasureRegion ) ) )				
							return HousePlacementResult.BadRegionHidden;

						return HousePlacementResult.BadRegion;
					}

					LandTile landTile = map.Tiles.GetLandTile( tileX, tileY );
					int landID = landTile.ID & TileData.MaxLandValue;

					StaticTile[] oldTiles = map.Tiles.GetStaticTiles( tileX, tileY, true );

					Sector sector = map.GetSector( tileX, tileY );

					items.Clear();

					for ( int i = 0; i < sector.Items.Count; ++i )
					{
						Item item = sector.Items[i];

						if ( item.Visible && item.X == tileX && item.Y == tileY )
							items.Add( item );
					}

					mobiles.Clear();

					for ( int i = 0; i < sector.Mobiles.Count; ++i )
					{
						Mobile m = (Mobile)sector.Mobiles[i];

						if ( m.X == tileX && m.Y == tileY )
							mobiles.Add( m );
					}

					int landStartZ = 0, landAvgZ = 0, landTopZ = 0;

					map.GetAverageZ( tileX, tileY, ref landStartZ, ref landAvgZ, ref landTopZ );

					bool hasFoundation = false;

					for ( int i = 0; i < addTiles.Length; ++i )
					{
						StaticTile addTile = addTiles[i];

						if ( addTile.ID == 0x4001 ) // Nodraw
							continue;

						TileFlag addTileFlags = TileData.ItemTable[addTile.ID & TileData.MaxItemValue].Flags;

						bool isFoundation = ( addTile.Z == 0 && (addTileFlags & TileFlag.Wall) != 0 );
						bool hasSurface = false;

						if ( isFoundation )
							hasFoundation = true;

						int addTileZ = center.Z + addTile.Z;
						int addTileTop = addTileZ + addTile.Height;

						if ( (addTileFlags & TileFlag.Surface) != 0 )
							addTileTop += 16;

						if ( addTileTop > landStartZ && landAvgZ > addTileZ )
						{
							ShowBadSpot(from,testPoint);
							return HousePlacementResult.BadLand; // Broke rule #2
						}

						if ( isFoundation && ((TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Impassable) == 0) && landAvgZ == center.Z )
							hasSurface = true;

						for ( int j = 0; j < oldTiles.Length; ++j )
						{
							StaticTile oldTile = oldTiles[j];
							ItemData id = TileData.ItemTable[oldTile.ID & TileData.MaxItemValue];

							if ( (id.Impassable || (id.Surface && (id.Flags & TileFlag.Background) == 0)) && addTileTop > oldTile.Z && (oldTile.Z + id.CalcHeight) > addTileZ )
							{
								ShowBadSpot(from,testPoint);
								return HousePlacementResult.BadStatic; // Broke rule #2	
							}
							/*else if ( isFoundation && !hasSurface && (id.Flags & TileFlag.Surface) != 0 && (oldTile.Z + id.CalcHeight) == center.Z )
								hasSurface = true;*/
						}

						for ( int j = 0; j < items.Count; ++j )
						{
							Item item = (Item)items[j];
							ItemData id = item.ItemData;

							if ( addTileTop > item.Z && (item.Z + id.CalcHeight) > addTileZ || item is HouseRubble)
							{
								if ( item.Movable )
									toMove.Add( item );
								else if(item is HouseRubble)
								{
									HouseRubble hr = (HouseRubble)item;
									
									if(hr==null || hr.Owner==null || from.Account==null || hr.Owner.Account!=from.Account)
									{									
										ShowBadItem(from,item);
										return HousePlacementResult.BadItem;
									}
									
								}
								else if (!(item is HouseRubble) && (item.Movable==false || id.Impassable || (id.Surface && (id.Flags & TileFlag.Background) == 0)) )
								{
									ShowBadItem(from,item);
									return HousePlacementResult.BadItem; // Broke rule #2
								}
							}
							/*else if ( isFoundation && !hasSurface && (id.Flags & TileFlag.Surface) != 0 && (item.Z + id.CalcHeight) == center.Z )
							{
								hasSurface = true;
							}*/
						}

						if ( isFoundation && !hasSurface )
						{
							return HousePlacementResult.NoSurface; // Broke rule #4
						}

						for ( int j = 0; j < mobiles.Count; ++j )
						{
							Mobile m = (Mobile)mobiles[j];

							if ( addTileTop > m.Z && (m.Z + 16) > addTileZ )
								toMove.Add( m );
						}
					}

					for ( int i = 0; i < m_RoadIDs.Length; i += 2 )
					{
						if ( landID >= m_RoadIDs[i] && landID <= m_RoadIDs[i + 1] )
						{
							ShowBadSpot(from,testPoint);
							return HousePlacementResult.BadLand; // Broke rule #5
						}
					}

					if ( hasFoundation )
					{
						for ( int xOffset = -1; xOffset <= 1; ++xOffset )
						{
							for ( int yOffset = -YardSize; yOffset <= YardSize; ++yOffset )
							{
								Point2D yardPoint = new Point2D( tileX + xOffset, tileY + yOffset );

								if ( !yard.Contains( yardPoint ) )
									yard.Add( yardPoint );
							}
						}

						for ( int xOffset = -1; xOffset <= 1; ++xOffset )
						{
							for ( int yOffset = -1; yOffset <= 1; ++yOffset )
							{
								if ( xOffset == 0 && yOffset == 0 )
									continue;

								// To ease this rule, we will not add to the border list if the tile here is under a base floor (z<=8)

								int vx = x + xOffset;
								int vy = y + yOffset;

								if ( vx >= 0 && vx < mcl.Width && vy >= 0 && vy < mcl.Height )
								{
									StaticTile[] breakTiles = mcl.Tiles[vx][vy];
									bool shouldBreak = false;

									for ( int i = 0; !shouldBreak && i < breakTiles.Length; ++i )
									{
										StaticTile breakTile = breakTiles[i];

										if ( breakTile.Height == 0 && breakTile.Z <= 8 && TileData.ItemTable[breakTile.ID & TileData.MaxItemValue].Surface )
											shouldBreak = true;
									}

									if ( shouldBreak )
										continue;
								}

								Point2D borderPoint = new Point2D( tileX + xOffset, tileY + yOffset );

								if ( !borders.Contains( borderPoint ) )
									borders.Add( borderPoint );
							}
						}
					}
				}
			}

			for ( int i = 0; i < borders.Count; ++i )
			{
				Point2D borderPoint = borders[i];

				LandTile landTile = map.Tiles.GetLandTile( borderPoint.X, borderPoint.Y );
				int landID = landTile.ID & TileData.MaxLandValue;

				if ( (TileData.LandTable[landID].Flags & TileFlag.Impassable) != 0 )
				{
					ShowBadSpot(from,borderPoint);
					return HousePlacementResult.BadLand;
				}

				for ( int j = 0; j < m_RoadIDs.Length; j += 2 )
				{
					if ( landID >= m_RoadIDs[j] && landID <= m_RoadIDs[j + 1] )
					{
						ShowBadSpot(from,borderPoint);
						return HousePlacementResult.BadLand; // Broke rule #5
					}
				}

				StaticTile[] tiles = map.Tiles.GetStaticTiles( borderPoint.X, borderPoint.Y, true );

				for ( int j = 0; j < tiles.Length; ++j )
				{
					StaticTile tile = tiles[j];
					ItemData id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];

					if ( id.Impassable || (id.Surface && (id.Flags & TileFlag.Background) == 0 && (tile.Z + id.CalcHeight) > (center.Z + 2)) )
					{
						ShowBadSpot(from,new Point3D(borderPoint.X,borderPoint.Y,tile.Z));
						return HousePlacementResult.BadStatic; // Broke rule #1
					}
				}

				Sector sector = map.GetSector( borderPoint.X, borderPoint.Y );
				List<Item> sectorItems = sector.Items;

				for ( int j = 0; j < sectorItems.Count; ++j )
				{
					Item item = sectorItems[j];

					if ( item.X != borderPoint.X || item.Y != borderPoint.Y || item.Movable )
						continue;

					ItemData id = item.ItemData;

					if ( id.Impassable || (id.Surface && (id.Flags & TileFlag.Background) == 0 && (item.Z + id.CalcHeight) > (center.Z + 2)) )
					{
						ShowBadItem(from,item);
						return HousePlacementResult.BadItem; // Broke rule #1
					}
				}
			}

			for ( int i = 0; i < yard.Count; ++i )
			{
				Point2D yardPoint = (Point2D)yard[i];

				IPooledEnumerable eable = map.GetMultiTilesAt( yardPoint.X, yardPoint.Y );

				foreach ( StaticTile[] tile in eable )
				{
					for ( int j = 0; j < tile.Length; ++j )
					{
						if ( (TileData.ItemTable[tile[j].ID & 0x3FFF].Flags & (TileFlag.Impassable | TileFlag.Surface)) != 0 )
						{
							eable.Free();
							ShowBadSpot(from,new Point2D(yardPoint.X, yardPoint.Y));
							return HousePlacementResult.BadStatic; // Broke rule #3
						}
					}
				}

				eable.Free();
			}

			return HousePlacementResult.Valid;

		}
	}
}