using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using Server;
using Server.Commands;
using Server.Items;
using Server.Network;
using CPA = Server.CommandPropertyAttribute;

namespace Server.Mobiles
{
	public class Spawner : Item, ISpawner
	{
		private int m_Team;
		private int m_HomeRange;
		private int m_WalkingRange;
		private int m_Count;
		private TimeSpan m_MinDelay;
		private TimeSpan m_MaxDelay;
		private List<string> m_SpawnNames;
		private List<ISpawnable> m_Spawned;
		private DateTime m_End;
		private InternalTimer m_Timer;
		private bool m_Running;
		private bool m_Group;
		private WayPoint m_WayPoint;

		public bool IsFull{ get{ return ( m_Spawned.Count >= m_Count ); } }
		public bool IsEmpty{ get{ return ( m_Spawned.Count == 0 ); } }

		public List<string> SpawnNames
		{
			get { return m_SpawnNames; }
			set
			{
				m_SpawnNames = value;
				if ( m_SpawnNames.Count < 1 )
					Stop();

				InvalidateProperties();
			}
		}

		public List<ISpawnable> Spawned
		{
			get { return m_Spawned; }
		}

		public virtual int SpawnNamesCount { get { return m_SpawnNames.Count; } }

		public override void OnAfterDuped( Item newItem )
		{
			Spawner s = newItem as Spawner;

			if ( s == null )
				return;

			s.m_SpawnNames = new List<string>( m_SpawnNames );
			s.m_Spawned = new List<ISpawnable>();
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public int Count
		{
			get { return m_Count; }
			set { m_Count = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public WayPoint WayPoint
		{
			get
			{
				return m_WayPoint;
			}
			set
			{
				m_WayPoint = value;
			}
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public bool Running
		{
			get { return m_Running; }
			set
			{
				if ( value )
					Start();
				else
					Stop();

				InvalidateProperties();
			}
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public int HomeRange
		{
			get { return m_HomeRange; }
			set { m_HomeRange = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public int WalkingRange
		{
			get { return m_WalkingRange; }
			set { m_WalkingRange = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public int Team
		{
			get { return m_Team; }
			set { m_Team = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan MinDelay
		{
			get { return m_MinDelay; }
			set { m_MinDelay = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan MaxDelay
		{
			get { return m_MaxDelay; }
			set { m_MaxDelay = value; InvalidateProperties(); }
		}

		public DateTime End
		{
			get { return m_End; }
			set { m_End = value; }
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan NextSpawn
		{
			get
			{
				if ( m_Running )
					return m_End - DateTime.Now;
				else
					return TimeSpan.FromSeconds( 0 );
			}
			set
			{
				Start();
				DoTimer( value );
			}
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public bool Group
		{
			get { return m_Group; }
			set { m_Group = value; InvalidateProperties(); }
		}

		[Constructable]
		public Spawner()
			: this(null)
		{
		}

		[Constructable]
		public Spawner( string spawnName )
			: this( 1, 5, 10, 0, 4, spawnName)
		{
		}

		[Constructable]
		public Spawner(int amount, int minDelay, int maxDelay, int team, int homeRange, string spawnName)
			: base(0x1f13)
		{
			List<string> spawnNames = new List<string>();

			if (!String.IsNullOrEmpty(spawnName))
				spawnNames.Add(spawnName);

			InitSpawner(amount, TimeSpan.FromMinutes(minDelay), TimeSpan.FromMinutes(maxDelay), team, homeRange, spawnNames);
		}

		public Spawner(int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, List<string> spawnNames)
			: base(0x1f13)
		{
			InitSpawner(amount, minDelay, maxDelay, team, homeRange, spawnNames);
		}

		public override string DefaultName
		{
			get { return "Spawner"; }
		}

		private void InitSpawner( int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, List<string> spawnNames )
		{
			Visible = false;
			Movable = false;
			m_Running = true;
			m_Group = false;
			m_MinDelay = minDelay;
			m_MaxDelay = maxDelay;
			m_Count = amount;
			m_Team = team;
			m_HomeRange = homeRange;
			m_WalkingRange = -1;
			m_SpawnNames = spawnNames;
			m_Spawned = new List<ISpawnable>();
			DoTimer( TimeSpan.FromSeconds( 1 ) );
		}

		public Spawner( Serial serial ) : base( serial )
		{
		}

		public override void OnDoubleClick( Mobile from )
		{
			if ( from.AccessLevel < AccessLevel.GameMaster )
				return;

			SpawnerGump g = new SpawnerGump( this );
			from.SendGump( g );
		}

		public override void GetProperties( ObjectPropertyList list )
		{
			base.GetProperties( list );

			if ( m_Running )
			{
				list.Add( 1060742 ); // active

				list.Add( 1060656, m_Count.ToString() ); // amount to make: ~1_val~
				list.Add( 1061169, m_HomeRange.ToString() ); // range ~1_val~
				list.Add( 1060658, "walking range\t{0}", m_WalkingRange ); // ~1_val~: ~2_val~

				list.Add( 1060659, "group\t{0}", m_Group ); // ~1_val~: ~2_val~
				list.Add( 1060660, "team\t{0}", m_Team ); // ~1_val~: ~2_val~
				list.Add( 1060661, "speed\t{0} to {1}", m_MinDelay, m_MaxDelay ); // ~1_val~: ~2_val~

				if ( m_SpawnNames.Count != 0 )
					list.Add( SpawnedStats() );
			}
			else
			{
				list.Add( 1060743 ); // inactive
			}
		}

		public override void OnSingleClick( Mobile from )
		{
			base.OnSingleClick( from );

			if ( m_Running )
				LabelTo( from, "[Running]" );
			else
				LabelTo( from, "[Off]" );
		}

		public void Start()
		{
			if ( !m_Running )
			{
				if ( SpawnNamesCount > 0 )
				{
					m_Running = true;
					DoTimer();
				}
			}
		}

		public void Stop()
		{
			if ( m_Running )
			{
				if ( m_Timer != null )
					m_Timer.Stop();

				m_Running = false;
			}
		}

		public static string ParseType( string s )
		{
			return s.Split( null, 2 )[0];
		}

		public void Defrag()
		{
			bool removed = false;

			for ( int i = 0; i < m_Spawned.Count; ++i )
			{
				ISpawnable e = m_Spawned[i];

				bool toRemove = false;

				if( e is Item )
				{
					Item item = (Item)e;

					if (item.Deleted || item.Parent != null)
						toRemove = true;
				}
				else if (e is Mobile)
				{
					Mobile m = (Mobile)e;

					if (m.Deleted)
					{
						toRemove = true;
					}
					else if (m is BaseCreature)
					{
						BaseCreature bc = (BaseCreature)m;

						if (bc.Controlled || bc.IsStabled)
						{
							toRemove = true;
						}
					}
				}

				if (toRemove)
				{
					m_Spawned.RemoveAt(i);
					--i;
					removed = true;
				}
			}

			if ( removed )
				InvalidateProperties();
		}

		bool ISpawner.UnlinkOnTaming { get { return true; } }

		void ISpawner.Remove( ISpawnable spawn )
		{
			m_Spawned.Remove( spawn );

			InvalidateProperties();
		}

		public void OnTick()
		{
			DoTimer();

			if ( m_Group )
			{
				Defrag();

				if  ( m_Spawned.Count == 0 )
				{
					Respawn();
				}
				else
				{
					return;
				}
			}
			else
			{
				Spawn();
			}
		}

		public virtual void Respawn()
		{
			RemoveSpawned();

			for ( int i = 0; i < m_Count; i++ )
				Spawn();
		}

		public virtual void Spawn()
		{
			if ( SpawnNamesCount > 0 )
				Spawn( Utility.Random( SpawnNamesCount ) );
		}

		public void Spawn( string creatureName )
		{
			for ( int i = 0; i < m_SpawnNames.Count; i++ )
			{
				if ( m_SpawnNames[i] == creatureName )
				{
					Spawn( i );
					break;
				}
			}
		}

		protected virtual ISpawnable CreateSpawnedObject( int index )
		{
			if ( index >= m_SpawnNames.Count )
				return null;

			Type type = ScriptCompiler.FindTypeByName( ParseType( m_SpawnNames[index] ) );

			if ( type != null )
			{
				try
				{
					return Build( type, CommandSystem.Split( m_SpawnNames[index] ) );
				}
				catch
				{
				}
			}

			return null;
		}

		public static ISpawnable Build( Type type, string[] args)
		{
			bool isISpawnable = typeof(ISpawnable).IsAssignableFrom( type );

			if (!isISpawnable)
			{
				return null;
			}

			Add.FixArgs( ref args );

			string[,] props = null;

			for ( int i = 0; i < args.Length; ++i )
			{
				if ( Insensitive.Equals( args[i], "set" ) )
				{
					int remains = args.Length - i - 1;

					if ( remains >= 2 )
					{
						props = new string[remains / 2, 2];

						remains /= 2;

						for ( int j = 0; j < remains; ++j )
						{
							props[j, 0] = args[i + (j * 2) + 1];
							props[j, 1] = args[i + (j * 2) + 2];
						}

						Add.FixSetString( ref args, i );
					}

					break;
				}
			}

			PropertyInfo[] realProps = null;

			if ( props != null )
			{
				realProps = new PropertyInfo[props.GetLength( 0 )];

				PropertyInfo[] allProps = type.GetProperties( BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public );

				for ( int i = 0; i < realProps.Length; ++i )
				{
					PropertyInfo thisProp = null;

					string propName = props[i, 0];

					for ( int j = 0; thisProp == null && j < allProps.Length; ++j )
					{
						if ( Insensitive.Equals( propName, allProps[j].Name ) )
							thisProp = allProps[j];
					}

					if ( thisProp != null )
					{
						CPA attr = Properties.GetCPA( thisProp );

						if ( attr != null && AccessLevel.GameMaster >= attr.WriteLevel && thisProp.CanWrite && !attr.ReadOnly )
							realProps[i] = thisProp;
					}
				}
			}

			ConstructorInfo[] ctors = type.GetConstructors();

			for ( int i = 0; i < ctors.Length; ++i )
			{
				ConstructorInfo ctor = ctors[i];

				if ( !Add.IsConstructable( ctor, AccessLevel.GameMaster ) )
					continue;

				ParameterInfo[] paramList = ctor.GetParameters();

				if ( args.Length == paramList.Length )
				{
					object[] paramValues = Add.ParseValues( paramList, args );

					if ( paramValues == null )
						continue;

					object built = ctor.Invoke( paramValues );

					if ( built != null && realProps != null )
					{
						for ( int j = 0; j < realProps.Length; ++j )
						{
							if ( realProps[j] == null )
								continue;

							string result = Properties.InternalSetValue( built, realProps[j], props[j, 1] );
						}
					}

					return (ISpawnable)built;
				}
			}

			return null;
		}

		public Point3D HomeLocation { get { return this.Location; } }

		public virtual bool CheckSpawnerFull()
		{
			return ( m_Spawned.Count >= m_Count );
		}

		public void Spawn( int index )
		{
			Map map = Map;

			if ( map == null || map == Map.Internal || SpawnNamesCount == 0 || index >= SpawnNamesCount || Parent != null )
				return;

			Defrag();

			if ( CheckSpawnerFull() )
				return;

			ISpawnable spawned = CreateSpawnedObject( index );

			if ( spawned == null )
				return;

			spawned.Spawner = this;
			m_Spawned.Add( spawned );

			Point3D loc = ( spawned is BaseVendor ? this.Location : GetSpawnPosition( spawned ) );

			spawned.OnBeforeSpawn( loc, map );

			InvalidateProperties();

			spawned.MoveToWorld( loc, map );

			if ( spawned is BaseCreature )
			{
				BaseCreature bc = (BaseCreature)spawned;

				if( m_WalkingRange >= 0 )
					bc.RangeHome = m_WalkingRange;
				else
					bc.RangeHome = m_HomeRange;

				bc.CurrentWayPoint = m_WayPoint;

				if ( m_Team > 0 )
					bc.Team = m_Team;

				bc.Home = this.HomeLocation;
			}
		}

		public Point3D GetSpawnPosition()
		{
			return GetSpawnPosition( null );
		}

		public Point3D GetSpawnPosition( ISpawnable spawned )
		{
			Map map = Map;

			if ( map == null )
				return Location;

			bool waterMob, waterOnlyMob;

			if ( spawned is Mobile )
			{
				Mobile mob = (Mobile)spawned;

				waterMob = mob.CanSwim;
				waterOnlyMob = ( mob.CanSwim && mob.CantWalk );
			}
			else
			{
				waterMob = false;
				waterOnlyMob = false;
			}

			// Try 10 times to find a spawnable location.
			for ( int i = 0; i < 10; ++i )
			{
				int x, y;

				if ( m_HomeRange > 0 )
				{
					x = Location.X + ( Utility.Random( ( m_HomeRange * 2 ) + 1 ) - m_HomeRange );
					y = Location.Y + ( Utility.Random( ( m_HomeRange * 2 ) + 1 ) - m_HomeRange );
				}
				else
				{
					x = Location.X;
					y = Location.Y;
				}

				int mapZ = map.GetAverageZ( x, y );

				if ( waterMob )
				{
					if ( IsValidWater( map, x, y, this.Z ) )
						return new Point3D( x, y, this.Z );
					else if ( IsValidWater( map, x, y, mapZ ) )
						return new Point3D( x, y, mapZ );
				}

				if ( !waterOnlyMob )
				{
					if ( map.CanSpawnMobile( x, y, this.Z ) )
						return new Point3D( x, y, this.Z );
					else if ( map.CanSpawnMobile( x, y, mapZ ) )
						return new Point3D( x, y, mapZ );
				}
			}

			return this.Location;
		}

		public static bool IsValidWater( Map map, int x, int y, int z )
		{
			if ( !Region.Find( new Point3D( x, y, z ), map ).AllowSpawn() || !map.CanFit( x, y, z, 16, false, true, false ) )
				return false;

			LandTile landTile = map.Tiles.GetLandTile( x, y );

			if ( landTile.Z == z && ( TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Wet ) != 0 )
				return true;

			StaticTile[] staticTiles = map.Tiles.GetStaticTiles( x, y, true );

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

				if ( staticTile.Z == z && ( TileData.ItemTable[staticTile.ID & TileData.MaxItemValue].Flags & TileFlag.Wet ) != 0 )
					return true;
			}

			return false;
		}

		public void DoTimer()
		{
			if ( !m_Running )
				return;

			int minSeconds = (int)m_MinDelay.TotalSeconds;
			int maxSeconds = (int)m_MaxDelay.TotalSeconds;

			TimeSpan delay = TimeSpan.FromSeconds( Utility.RandomMinMax( minSeconds, maxSeconds ) );
			DoTimer( delay );
		}

		public virtual void DoTimer( TimeSpan delay )
		{
			if ( !m_Running )
				return;

			m_End = DateTime.Now + delay;

			if ( m_Timer != null )
				m_Timer.Stop();

			m_Timer = new InternalTimer( this, delay );
			m_Timer.Start();
		}

		private class InternalTimer : Timer
		{
			private Spawner m_Spawner;

			public InternalTimer( Spawner spawner, TimeSpan delay ) : base( delay )
			{
				if ( spawner.IsFull )
					Priority = TimerPriority.FiveSeconds;
				else
					Priority = TimerPriority.OneSecond;

				m_Spawner = spawner;
			}

			protected override void OnTick()
			{
				if ( m_Spawner != null )
					if ( !m_Spawner.Deleted )
						m_Spawner.OnTick();
			}
		}

		public string SpawnedStats()
		{
			Defrag();

			Dictionary<string, int> counts = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase );

			foreach ( string entry in m_SpawnNames )
			{
				string name = ParseType( entry );
				Type type = ScriptCompiler.FindTypeByName( name );

				if ( type == null )
					counts[name] = 0;
				else
					counts[type.Name] = 0;
			}

			foreach ( ISpawnable spawned in m_Spawned )
			{
				string name = spawned.GetType().Name;

				if ( counts.ContainsKey( name ) )
					++counts[name];
				else
					counts[name] = 1;
			}

			List<string> names = new List<string>( counts.Keys );
			names.Sort();

			StringBuilder result = new StringBuilder();

			for ( int i = 0; i < names.Count; ++i )
				result.AppendFormat( "{0}{1}: {2}", ( i == 0 ) ? "" : "<BR>", names[i], counts[names[i]] );

			return result.ToString();
		}

		public int CountCreatures( string creatureName )
		{
			Defrag();

			int count = 0;

			for ( int i = 0; i < m_Spawned.Count; ++i )
				if ( Insensitive.Equals( creatureName, m_Spawned[i].GetType().Name ) )
					++count;

			return count;
		}

		public void RemoveSpawned( string creatureName )
		{
			Defrag();

			for ( int i = m_Spawned.Count - 1; i >= 0; --i )
			{
				IEntity e = m_Spawned[i];

				if ( Insensitive.Equals( creatureName, e.GetType().Name ) )
					e.Delete();
			}

			InvalidateProperties();
		}

		public void RemoveSpawned()
		{
			Defrag();

			for ( int i = m_Spawned.Count - 1; i >= 0; --i )
				m_Spawned[i].Delete();

			InvalidateProperties();
		}

		public void BringToHome()
		{
			Defrag();

			for ( int i = 0; i < m_Spawned.Count; ++i )
			{
				ISpawnable e = m_Spawned[i];

				e.MoveToWorld( this.Location, this.Map );
			}
		}

		public override void OnDelete()
		{
			base.OnDelete();

			RemoveSpawned();

			if ( m_Timer != null )
				m_Timer.Stop();
		}

		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );

			writer.Write( (int) 4 ); // version
			writer.Write( m_WalkingRange );

			writer.Write( m_WayPoint );

			writer.Write( m_Group );

			writer.Write( m_MinDelay );
			writer.Write( m_MaxDelay );
			writer.Write( m_Count );
			writer.Write( m_Team );
			writer.Write( m_HomeRange );
			writer.Write( m_Running );

			if ( m_Running )
				writer.WriteDeltaTime( m_End );

			writer.Write( m_SpawnNames.Count );

			for ( int i = 0; i < m_SpawnNames.Count; ++i )
				writer.Write( m_SpawnNames[i] );

			writer.Write( m_Spawned.Count );

			for ( int i = 0; i < m_Spawned.Count; ++i )
			{
				IEntity e = m_Spawned[i];

				if ( e is Item )
					writer.Write( (Item)e );
				else if ( e is Mobile )
					writer.Write( (Mobile)e );
				else
					writer.Write( Serial.MinusOne );
			}
		}

		private static WarnTimer m_WarnTimer;

		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );

			int version = reader.ReadInt();

			switch ( version )
			{
				case 4:
				{
					m_WalkingRange = reader.ReadInt();

					goto case 3;
				}
				case 3:
				case 2:
				{
					m_WayPoint = reader.ReadItem() as WayPoint;

					goto case 1;
				}

				case 1:
				{
					m_Group = reader.ReadBool();

					goto case 0;
				}

				case 0:
				{
					m_MinDelay = reader.ReadTimeSpan();
					m_MaxDelay = reader.ReadTimeSpan();
					m_Count = reader.ReadInt();
					m_Team = reader.ReadInt();
					m_HomeRange = reader.ReadInt();
					m_Running = reader.ReadBool();

					TimeSpan ts = TimeSpan.Zero;

					if ( m_Running )
						ts = reader.ReadDeltaTime() - DateTime.Now;

					int size = reader.ReadInt();

					m_SpawnNames = new List<string>( size );

					for ( int i = 0; i < size; ++i )
					{
						string creatureString = reader.ReadString();

						m_SpawnNames.Add( creatureString );
						string typeName = ParseType( creatureString );

						if ( ScriptCompiler.FindTypeByName( typeName ) == null )
						{
							if ( m_WarnTimer == null )
								m_WarnTimer = new WarnTimer();

							m_WarnTimer.Add( Location, Map, typeName );
						}
					}

					int count = reader.ReadInt();

					m_Spawned = new List<ISpawnable>(count);

					for ( int i = 0; i < count; ++i )
					{
						ISpawnable e = World.FindEntity(reader.ReadInt()) as ISpawnable;

						if (e != null)
						{
							e.Spawner = this;
							m_Spawned.Add(e);
						}
					}

					if ( m_Running )
						DoTimer( ts );

					break;
				}
			}

			if ( version < 3 && Weight == 0 )
				Weight = -1;
		}

		private class WarnTimer : Timer
		{
			private List<WarnEntry> m_List;

			private class WarnEntry
			{
				public Point3D m_Point;
				public Map m_Map;
				public string m_Name;

				public WarnEntry( Point3D p, Map map, string name )
				{
					m_Point = p;
					m_Map = map;
					m_Name = name;
				}
			}

			public WarnTimer() : base( TimeSpan.FromSeconds( 1.0 ) )
			{
				m_List = new List<WarnEntry>();
				Start();
			}

			public void Add( Point3D p, Map map, string name )
			{
				m_List.Add( new WarnEntry( p, map, name ) );
			}

			protected override void OnTick()
			{
				try
				{
					Console.WriteLine( "Warning: {0} bad spawns detected, logged: 'badspawn.log'", m_List.Count );

					using ( StreamWriter op = new StreamWriter( "badspawn.log", true ) )
					{
						op.WriteLine( "# Bad spawns : {0}", DateTime.Now );
						op.WriteLine( "# Format: X Y Z F Name" );
						op.WriteLine();

						foreach ( WarnEntry e in m_List )
							op.WriteLine( "{0}\t{1}\t{2}\t{3}\t{4}", e.m_Point.X, e.m_Point.Y, e.m_Point.Z, e.m_Map, e.m_Name );

						op.WriteLine();
						op.WriteLine();
					}
				}
				catch
				{
				}
			}
		}
	}
}