/***************************************************************************
 *                                 Region.cs
 *                            -------------------
 *   begin                : May 1, 2002
 *   copyright            : (C) The RunUO Software Team
 *   email                : info@runuo.com
 *
 *   $Id$
 *
 ***************************************************************************/

/***************************************************************************
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 ***************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using Server.Network;
using Server.Targeting;

namespace Server
{
	public enum MusicName
	{
		Invalid = -1,
		OldUlt01 = 0,
		Create1,
		DragFlit,
		OldUlt02,
		OldUlt03,
		OldUlt04,
		OldUlt05,
		OldUlt06,
		Stones2,
		Britain1,
		Britain2,
		Bucsden,
		Jhelom,
		LBCastle,
		Linelle,
		Magincia,
		Minoc,
		Ocllo,
		Samlethe,
		Serpents,
		Skarabra,
		Trinsic,
		Vesper,
		Wind,
		Yew,
		Cave01,
		Dungeon9,
		Forest_a,
		InTown01,
		Jungle_a,
		Mountn_a,
		Plains_a,
		Sailing,
		Swamp_a,
		Tavern01,
		Tavern02,
		Tavern03,
		Tavern04,
		Combat1,
		Combat2,
		Combat3,
		Approach,
		Death,
		Victory,
		BTCastle,
		Nujelm,
		Dungeon2,
		Cove,
		Moonglow,
		Dungeon3,
		Tavern05,
		Travel
	}

	public class Region : IComparable
	{
		private static List<Region> m_Regions = new List<Region>();

		public static List<Region> Regions{ get{ return m_Regions; } }

		public static Region Find( Point3D p, Map map )
		{
			if ( map == null )
				return Map.Internal.DefaultRegion;

			Sector sector = map.GetSector( p );
			List<RegionRect> list = sector.RegionRects;

			for ( int i = 0; i < list.Count; ++i )
			{
				RegionRect regRect = list[i];

				if ( regRect.Contains( p ) )
					return regRect.Region;
			}

			return map.DefaultRegion;
		}

		private static Type m_DefaultRegionType = typeof( Region );
		public static Type DefaultRegionType{ get{ return m_DefaultRegionType; } set{ m_DefaultRegionType = value; } }

		private static TimeSpan m_StaffLogoutDelay = TimeSpan.FromSeconds( 10.0 );
		private static TimeSpan m_DefaultLogoutDelay = TimeSpan.FromMinutes( 5.0 );

		public static TimeSpan StaffLogoutDelay{ get{ return m_StaffLogoutDelay; } set{ m_StaffLogoutDelay = value; } }
		public static TimeSpan DefaultLogoutDelay{ get{ return m_DefaultLogoutDelay; } set{ m_DefaultLogoutDelay = value; } }

		public static readonly int DefaultPriority = 50;

		public static readonly int MinZ = sbyte.MinValue;
		public static readonly int MaxZ = sbyte.MaxValue + 1;

		public static Rectangle3D ConvertTo3D( Rectangle2D rect )
		{
			return new Rectangle3D( new Point3D( rect.Start, MinZ ), new Point3D( rect.End, MaxZ ) );
		}

		public static Rectangle3D[] ConvertTo3D( Rectangle2D[] rects )
		{
			Rectangle3D[] ret = new Rectangle3D[rects.Length];

			for ( int i = 0; i < ret.Length; i++ )
			{
				ret[i] = ConvertTo3D( rects[i] );
			}

			return ret;
		}


		private string m_Name;
		private Map m_Map;
		private Region m_Parent;
		private List<Region> m_Children = new List<Region>();
		private Rectangle3D[] m_Area;
		private Sector[] m_Sectors;
		private bool m_Dynamic;
		private int m_Priority;
		private int m_ChildLevel;
		private bool m_Registered;

		private Point3D m_GoLocation;
		private MusicName m_Music;

		public string Name{ get{ return m_Name; } }
		public Map Map{ get{ return m_Map; } }
		public Region Parent{ get{ return m_Parent; } }
		public List<Region> Children{ get{ return m_Children; } }
		public Rectangle3D[] Area{ get{ return m_Area; } }
		public Sector[] Sectors{ get{ return m_Sectors; } }
		public bool Dynamic{ get{ return m_Dynamic; } }
		public int Priority{ get{ return m_Priority; } }
		public int ChildLevel{ get{ return m_ChildLevel; } }
		public bool Registered{ get{ return m_Registered; } }

		public Point3D GoLocation{ get{ return m_GoLocation; } set { m_GoLocation = value; } }
		public MusicName Music{ get{ return m_Music; } set{ m_Music = value; } }

		public bool IsDefault{ get{ return m_Map.DefaultRegion == this; } }
		public virtual MusicName DefaultMusic{ get{ return m_Parent != null ? m_Parent.Music : MusicName.Invalid; } }

		public Region( string name, Map map, int priority, params Rectangle2D[] area ) : this( name, map, priority, ConvertTo3D( area ) )
		{
		}

		public Region( string name, Map map, int priority, params Rectangle3D[] area ) : this( name, map, null, area )
		{
			m_Priority = priority;
		}

		public Region( string name, Map map, Region parent, params Rectangle2D[] area ) : this( name, map, parent, ConvertTo3D( area ) )
		{
		}

		public Region( string name, Map map, Region parent, params Rectangle3D[] area )
		{
			m_Name = name;
			m_Map = map;
			m_Parent = parent;
			m_Area = area;
			m_Dynamic = true;
			m_Music = this.DefaultMusic;

			if ( m_Parent == null )
			{
				m_ChildLevel = 0;
				m_Priority = DefaultPriority;
			}
			else
			{
				m_ChildLevel = m_Parent.ChildLevel + 1;
				m_Priority = m_Parent.Priority;
			}
		}

		public void Register()
		{
			if ( m_Registered )
				return;

			OnRegister();

			m_Registered = true;

			if ( m_Parent != null )
			{
				m_Parent.m_Children.Add( this );
				m_Parent.OnChildAdded( this );
			}

			m_Regions.Add( this );

			m_Map.RegisterRegion( this );

			List<Sector> sectors = new List<Sector>();

			for ( int i = 0; i < m_Area.Length; i++ )
			{
				Rectangle3D rect = m_Area[i];

				Point2D start = m_Map.Bound( new Point2D( rect.Start ) );
				Point2D end = m_Map.Bound( new Point2D( rect.End ) );

				Sector startSector = m_Map.GetSector( start );
				Sector endSector = m_Map.GetSector( end );

				for ( int x = startSector.X; x <= endSector.X; x++ )
				{
					for ( int y = startSector.Y; y <= endSector.Y; y++ )
					{
						Sector sector = m_Map.GetRealSector( x, y );

						sector.OnEnter( this, rect );

						if ( !sectors.Contains( sector ) )
							sectors.Add( sector );
					}
				}
			}

			m_Sectors = sectors.ToArray();
		}

		public void Unregister()
		{
			if ( !m_Registered )
				return;

			OnUnregister();

			m_Registered = false;

			if ( m_Children.Count > 0 )
				Console.WriteLine( "Warning: Unregistering region '{0}' with children", this );

			if ( m_Parent != null )
			{
				m_Parent.m_Children.Remove( this );
				m_Parent.OnChildRemoved( this );
			}

			m_Regions.Remove( this );

			m_Map.UnregisterRegion( this );

			if ( m_Sectors != null )
			{
				for ( int i = 0; i < m_Sectors.Length; i++ )
					m_Sectors[i].OnLeave( this );
			}

			m_Sectors = null;
		}

		public bool Contains( Point3D p )
		{
			for ( int i = 0; i < m_Area.Length; i++ )
			{
				Rectangle3D rect = m_Area[i];

				if ( rect.Contains( p ) )
					return true;
			}

			return false;
		}

		public bool IsChildOf( Region region )
		{
			if ( region == null )
				return false;

			Region p = m_Parent;

			while ( p != null )
			{
				if ( p == region )
					return true;

				p = p.m_Parent;
			}

			return false;
		}

		public Region GetRegion( Type regionType )
		{
			if ( regionType == null )
				return null;

			Region r = this;

			do
			{
				if ( regionType.IsAssignableFrom( r.GetType() ) )
					return r;

				r = r.m_Parent;
			}
			while ( r != null );

			return null;
		}

		public Region GetRegion( string regionName )
		{
			if ( regionName == null )
				return null;

			Region r = this;

			do
			{
				if ( r.m_Name == regionName )
					return r;

				r = r.m_Parent;
			}
			while ( r != null );

			return null;
		}

		public bool IsPartOf( Region region )
		{
			if ( this == region )
				return true;

			return IsChildOf( region );
		}

		public bool IsPartOf( Type regionType )
		{
			return ( GetRegion( regionType ) != null );
		}

		public bool IsPartOf( string regionName )
		{
			return ( GetRegion( regionName ) != null );
		}

		public virtual bool AcceptsSpawnsFrom( Region region )
		{
			if ( !AllowSpawn() )
				return false;

			if ( region == this )
				return true;

			if ( m_Parent != null )
				return m_Parent.AcceptsSpawnsFrom( region );

			return false;
		}

		public List<Mobile> GetPlayers()
		{
			List<Mobile> list = new List<Mobile>();

			if ( m_Sectors != null )
			{
				for ( int i = 0; i < m_Sectors.Length; i++ )
				{
					Sector sector = m_Sectors[i];

					foreach ( Mobile player in sector.Players )
					{
						if ( player.Region.IsPartOf( this ) )
							list.Add( player );
					}
				}
			}

			return list;
		}

		public int GetPlayerCount()
		{
			int count = 0;

			if ( m_Sectors != null )
			{
				for ( int i = 0; i < m_Sectors.Length; i++ )
				{
					Sector sector = m_Sectors[i];

					foreach ( Mobile player in sector.Players )
					{
						if ( player.Region.IsPartOf( this ) )
							count++;
					}
				}
			}

			return count;
		}

		public List<Mobile> GetMobiles()
		{
			List<Mobile> list = new List<Mobile>();

			if ( m_Sectors != null )
			{
				for ( int i = 0; i < m_Sectors.Length; i++ )
				{
					Sector sector = m_Sectors[i];

					foreach ( Mobile mobile in sector.Mobiles )
					{
						if ( mobile.Region.IsPartOf( this ) )
							list.Add( mobile );
					}
				}
			}

			return list;
		}

		public int GetMobileCount()
		{
			int count = 0;

			if ( m_Sectors != null )
			{
				for ( int i = 0; i < m_Sectors.Length; i++ )
				{
					Sector sector = m_Sectors[i];

					foreach ( Mobile mobile in sector.Mobiles )
					{
						if ( mobile.Region.IsPartOf( this ) )
							count++;
					}
				}
			}

			return count;
		}

		int IComparable.CompareTo( object obj )
		{
			if ( obj == null )
				return 1;

			Region reg = obj as Region;

			if ( reg == null )
				throw new ArgumentException( "obj is not a Region", "obj" );

			// Dynamic regions go first
			if ( this.Dynamic )
			{
				if ( !reg.Dynamic )
					return -1;
			}
			else if ( reg.Dynamic )
			{
				return 1;
			}

			int thisPriority = this.Priority;
			int regPriority = reg.Priority;

			if ( thisPriority != regPriority )
				return ( regPriority - thisPriority );

			return ( reg.ChildLevel - this.ChildLevel );
		}

		public override string ToString()
		{
			if ( m_Name != null )
				return m_Name;
			else
				return GetType().Name;
		}


		public virtual void OnRegister()
		{
		}

		public virtual void OnUnregister()
		{
		}

		public virtual void OnChildAdded( Region child )
		{
		}

		public virtual void OnChildRemoved( Region child )
		{
		}

		public virtual bool OnMoveInto( Mobile m, Direction d, Point3D newLocation, Point3D oldLocation )
		{
			return ( m.WalkRegion == null || AcceptsSpawnsFrom( m.WalkRegion ) );
		}

		public virtual void OnEnter( Mobile m )
		{
		}

		public virtual void OnExit( Mobile m )
		{
		}

		public virtual void MakeGuard( Mobile focus )
		{
			if ( m_Parent != null )
				m_Parent.MakeGuard( focus );
		}

		public virtual Type GetResource( Type type )
		{
			if ( m_Parent != null )
				return m_Parent.GetResource( type );

			return type;
		}

		public virtual bool CanUseStuckMenu( Mobile m )
		{
			if ( m_Parent != null )
				return m_Parent.CanUseStuckMenu( m );

			return true;
		}

		public virtual void OnAggressed( Mobile aggressor, Mobile aggressed, bool criminal )
		{
			if ( m_Parent != null )
				m_Parent.OnAggressed( aggressor, aggressed, criminal );
		}

		public virtual void OnDidHarmful( Mobile harmer, Mobile harmed )
		{
			if ( m_Parent != null )
				m_Parent.OnDidHarmful( harmer, harmed );
		}

		public virtual void OnGotHarmful( Mobile harmer, Mobile harmed )
		{
			if ( m_Parent != null )
				m_Parent.OnGotHarmful( harmer, harmed );
		}

		public virtual void OnLocationChanged( Mobile m, Point3D oldLocation )
		{
			if ( m_Parent != null )
				m_Parent.OnLocationChanged( m, oldLocation );
		}

		public virtual bool OnTarget( Mobile m, Target t, object o )
		{
			if ( m_Parent != null )
				return m_Parent.OnTarget( m, t, o );

			return true;
		}

		public virtual bool OnCombatantChange( Mobile m, Mobile Old, Mobile New )
		{
			if ( m_Parent != null )
				return m_Parent.OnCombatantChange( m, Old, New );

			return true;
		}

		public virtual bool AllowHousing( Mobile from, Point3D p )
		{
			if ( m_Parent != null )
				return m_Parent.AllowHousing( from, p );

			return true;
		}

		public virtual bool NoMounts( Mobile from, Point3D p )
		{
			if ( m_Parent != null )
				return m_Parent.NoMounts( from, p );

			return false;
		}

		public virtual bool SendInaccessibleMessage( Item item, Mobile from )
		{
			if ( m_Parent != null )
				return m_Parent.SendInaccessibleMessage( item, from );

			return false;
		}

		public virtual bool CheckAccessibility( Item item, Mobile from )
		{
			if ( m_Parent != null )
				return m_Parent.CheckAccessibility( item, from );

			return true;
		}

		public virtual bool OnDecay( Item item )
		{
			if ( m_Parent != null )
				return m_Parent.OnDecay( item );

			return true;
		}

		public virtual bool AllowHarmful( Mobile from, Mobile target )
		{
			if ( m_Parent != null )
				return m_Parent.AllowHarmful( from, target );

			if ( Mobile.AllowHarmfulHandler != null )
				return Mobile.AllowHarmfulHandler( from, target );

			return true;
		}

		public virtual void OnCriminalAction( Mobile m, bool message )
		{
			if ( m_Parent != null )
				m_Parent.OnCriminalAction( m, message );
			else if ( message )
				m.SendLocalizedMessage( 1005040 ); // You've committed a criminal act!!
		}

		public virtual bool AllowBeneficial( Mobile from, Mobile target )
		{
			if ( m_Parent != null )
				return m_Parent.AllowBeneficial( from, target );

			if ( Mobile.AllowBeneficialHandler != null )
				return Mobile.AllowBeneficialHandler( from, target );

			return true;
		}

		public virtual void OnBeneficialAction( Mobile helper, Mobile target )
		{
			if ( m_Parent != null )
				m_Parent.OnBeneficialAction( helper, target );
		}

		public virtual void OnGotBeneficialAction( Mobile helper, Mobile target )
		{
			if ( m_Parent != null )
				m_Parent.OnGotBeneficialAction( helper, target );
		}

		public virtual void SpellDamageScalar( Mobile caster, Mobile target, ref double damage )
		{
			if ( m_Parent != null )
				m_Parent.SpellDamageScalar( caster, target, ref damage );
		}

		public virtual void OnSpeech( SpeechEventArgs args )
		{
			if ( m_Parent != null )
				m_Parent.OnSpeech( args );
		}

		public virtual bool OnSkillUse( Mobile m, int Skill )
		{
			if ( m_Parent != null )
				return m_Parent.OnSkillUse( m, Skill );

			return true;
		}

		public virtual bool OnBeginSpellCast( Mobile m, ISpell s )
		{
			if ( m_Parent != null )
				return m_Parent.OnBeginSpellCast( m, s );

			return true;
		}

		public virtual void OnSpellCast( Mobile m, ISpell s )
		{
			if ( m_Parent != null )
				m_Parent.OnSpellCast( m, s );
		}

		public virtual bool OnResurrect( Mobile m )
		{
			if ( m_Parent != null )
				return m_Parent.OnResurrect( m );

			return true;
		}

		public virtual bool OnBeforeDeath( Mobile m )
		{
			if ( m_Parent != null )
				return m_Parent.OnBeforeDeath( m );

			return true;
		}

		public virtual void OnDeath( Mobile m )
		{
			if ( m_Parent != null )
				m_Parent.OnDeath( m );
		}

		public virtual bool OnDamage( Mobile m, ref int Damage )
		{
			if ( m_Parent != null )
				return m_Parent.OnDamage( m, ref Damage );

			return true;
		}

		public virtual bool OnHeal( Mobile m, ref int Heal )
		{
			if ( m_Parent != null )
				return m_Parent.OnHeal( m, ref Heal );

			return true;
		}

		public virtual bool OnDoubleClick( Mobile m, object o )
		{
			if ( m_Parent != null )
				return m_Parent.OnDoubleClick( m, o );

			return true;
		}

		public virtual bool OnSingleClick( Mobile m, object o )
		{
			if ( m_Parent != null )
				return m_Parent.OnSingleClick( m, o );

			return true;
		}

		public virtual bool AllowSpawn()
		{
			if ( m_Parent != null )
				return m_Parent.AllowSpawn();

			return true;
		}

		public virtual void AlterLightLevel( Mobile m, ref int global, ref int personal )
		{
			if ( m_Parent != null )
				m_Parent.AlterLightLevel( m, ref global, ref personal );
		}

		public virtual TimeSpan GetLogoutDelay( Mobile m )
		{
			if ( m_Parent != null )
				return m_Parent.GetLogoutDelay( m );
			else if ( m.AccessLevel > AccessLevel.Player )
				return m_StaffLogoutDelay;
			else
				return m_DefaultLogoutDelay;
		}


		internal static bool CanMove( Mobile m, Direction d, Point3D newLocation, Point3D oldLocation, Map map )
		{
			Region oldRegion = m.Region;
			Region newRegion = Find( newLocation, map );

			while ( oldRegion != newRegion )
			{
				if ( !newRegion.OnMoveInto( m, d, newLocation, oldLocation ) )
					return false;

				if ( newRegion.m_Parent == null )
					return true;

				newRegion = newRegion.m_Parent;
			}

			return true;
		}

		internal static void OnRegionChange( Mobile m, Region oldRegion, Region newRegion )
		{
			if ( newRegion != null && m.NetState != null )
			{
				m.CheckLightLevels( false );

				if ( oldRegion == null || oldRegion.Music != newRegion.Music )
				{
					m.Send( PlayMusic.GetInstance( newRegion.Music ) );
				}
			}

			Region oldR = oldRegion;
			Region newR = newRegion;

			while ( oldR != newR )
			{
				int oldRChild = ( oldR != null ? oldR.ChildLevel : -1 );
				int newRChild = ( newR != null ? newR.ChildLevel : -1 );

				if ( oldRChild >= newRChild )
				{
					oldR.OnExit( m );
					oldR = oldR.Parent;
				}

				if ( newRChild >= oldRChild )
				{
					newR.OnEnter( m );
					newR = newR.Parent;
				}
			}
		}

		internal static void Load()
		{
			if ( !System.IO.File.Exists( "Data/Config/Regions.xml" ) )
			{
				Console.WriteLine( "Error: Data/Config/Regions.xml does not exist" );
				return;
			}

			Console.Write( "Regions: Loading..." );

			XmlDocument doc = new XmlDocument();
			doc.Load( System.IO.Path.Combine( Core.BaseDirectory, "Data/Config/Regions.xml" ) );

			XmlElement root = doc["ServerRegions"];

			if ( root == null )
			{
				Console.WriteLine( "Could not find root element 'ServerRegions' in Regions.xml" );
			}
			else
			{
				foreach ( XmlElement facet in root.SelectNodes( "Facet" ) )
				{
					Map map = null;
					if ( ReadMap( facet, "name", ref map ) )
					{
						if ( map == Map.Internal )
							Console.WriteLine( "Invalid internal map in a facet element" );
						else
							LoadRegions( facet, map, null );
					}
				}
			}

			Console.WriteLine( "done" );
		}

		private static void LoadRegions( XmlElement xml, Map map, Region parent )
		{
			foreach ( XmlElement xmlReg in xml.SelectNodes( "region" ) )
			{
				Type type = DefaultRegionType;

				ReadType( xmlReg, "type", ref type, false );

				if ( !typeof( Region ).IsAssignableFrom( type ) )
				{
					Console.WriteLine( "Invalid region type '{0}' in regions.xml", type.FullName );
					continue;
				}

				Region region = null;
				try
				{
					region = (Region) Activator.CreateInstance( type, new object[] { xmlReg, map, parent } );
				}
				catch ( Exception ex )
				{
					Console.WriteLine( "Error during the creation of region type '{0}': {1}", type.FullName, ex );
					continue;
				}

				region.Register();

				LoadRegions( xmlReg, map, region );
			}
		}

		public Region( XmlElement xml, Map map, Region parent )
		{
			m_Map = map;
			m_Parent = parent;
			m_Dynamic = false;

			if ( m_Parent == null )
			{
				m_ChildLevel = 0;
				m_Priority = DefaultPriority;
			}
			else
			{
				m_ChildLevel = m_Parent.ChildLevel + 1;
				m_Priority = m_Parent.Priority;
			}

			ReadString( xml, "name", ref m_Name, false );

			if ( parent == null )
				ReadInt32( xml, "priority", ref m_Priority, false );


			int minZ = MinZ;
			int maxZ = MaxZ;

			XmlElement zrange = xml["zrange"];
			ReadInt32( zrange, "min", ref minZ, false );
			ReadInt32( zrange, "max", ref maxZ, false );


			List<Rectangle3D> area = new List<Rectangle3D>();
			foreach ( XmlElement xmlRect in xml.SelectNodes( "rect" ) )
			{
				Rectangle3D rect = new Rectangle3D();
				if ( ReadRectangle3D( xmlRect, minZ, maxZ, ref rect ) )
					area.Add( rect );
			}

			m_Area = area.ToArray();

			if ( m_Area.Length == 0 )
				Console.WriteLine( "Empty area for region '{0}'", this );


			if ( !ReadPoint3D( xml["go"], map, ref m_GoLocation, false ) && m_Area.Length > 0 )
			{
				Point3D start = m_Area[0].Start;
				Point3D end = m_Area[0].End;

				int x = start.X + (end.X - start.X) / 2;
				int y = start.Y + (end.Y - start.Y) / 2;

				m_GoLocation = new Point3D( x, y, m_Map.GetAverageZ( x, y ) );
			}


			MusicName music = this.DefaultMusic;

			ReadEnum( xml["music"], "name", ref music, false );

			m_Music = music;
		}

		protected static string GetAttribute( XmlElement xml, string attribute, bool mandatory )
		{
			if ( xml == null )
			{
				if ( mandatory )
					Console.WriteLine( "Missing element for attribute '{0}'", attribute );

				return null;
			}
			else if ( xml.HasAttribute( attribute ) )
			{
				return xml.GetAttribute( attribute );
			}
			else
			{
				if ( mandatory )
					Console.WriteLine( "Missing attribute '{0}' in element '{1}'", attribute, xml.Name );

				return null;
			}
		}

		public static bool ReadString( XmlElement xml, string attribute, ref string value )
		{
			return ReadString( xml, attribute, ref value, true );
		}

		public static bool ReadString( XmlElement xml, string attribute, ref string value, bool mandatory )
		{
			string s = GetAttribute( xml, attribute, mandatory );

			if ( s == null )
				return false;

			value = s;
			return true;
		}

		public static bool ReadInt32( XmlElement xml, string attribute, ref int value )
		{
			return ReadInt32( xml, attribute, ref value, true );
		}

		public static bool ReadInt32( XmlElement xml, string attribute, ref int value, bool mandatory )
		{
			string s = GetAttribute( xml, attribute, mandatory );

			if ( s == null )
				return false;

			try
			{
				value = XmlConvert.ToInt32( s );
			}
			catch
			{
				Console.WriteLine( "Could not parse integer attribute '{0}' in element '{1}'", attribute, xml.Name );
				return false;
			}

			return true;
		}

		public static bool ReadBoolean( XmlElement xml, string attribute, ref bool value )
		{
			return ReadBoolean( xml, attribute, ref value, true );
		}

		public static bool ReadBoolean( XmlElement xml, string attribute, ref bool value, bool mandatory )
		{
			string s = GetAttribute( xml, attribute, mandatory );

			if ( s == null )
				return false;

			try
			{
				value = XmlConvert.ToBoolean( s );
			}
			catch
			{
				Console.WriteLine( "Could not parse boolean attribute '{0}' in element '{1}'", attribute, xml.Name );
				return false;
			}

			return true;
		}

		public static bool ReadDateTime( XmlElement xml, string attribute, ref DateTime value )
		{
			return ReadDateTime( xml, attribute, ref value, true );
		}

		public static bool ReadDateTime( XmlElement xml, string attribute, ref DateTime value, bool mandatory )
		{
			string s = GetAttribute( xml, attribute, mandatory );

			if ( s == null )
				return false;

			try
			{
				value = XmlConvert.ToDateTime( s, XmlDateTimeSerializationMode.Local );
			}
			catch
			{
				Console.WriteLine( "Could not parse DateTime attribute '{0}' in element '{1}'", attribute, xml.Name );
				return false;
			}

			return true;
		}

		public static bool ReadTimeSpan( XmlElement xml, string attribute, ref TimeSpan value )
		{
			return ReadTimeSpan( xml, attribute, ref value, true );
		}

		public static bool ReadTimeSpan( XmlElement xml, string attribute, ref TimeSpan value, bool mandatory )
		{
			string s = GetAttribute( xml, attribute, mandatory );

			if ( s == null )
				return false;

			try
			{
				value = XmlConvert.ToTimeSpan( s );
			}
			catch
			{
				Console.WriteLine( "Could not parse TimeSpan attribute '{0}' in element '{1}'", attribute, xml.Name );
				return false;
			}

			return true;
		}

		public static bool ReadEnum<T>( XmlElement xml, string attribute, ref T value )
		{
			return ReadEnum( xml, attribute, ref value, true );
		}

		public static bool ReadEnum<T>( XmlElement xml, string attribute, ref T value, bool mandatory )
		{
			string s = GetAttribute( xml, attribute, mandatory );

			if ( s == null )
				return false;

			Type type = typeof(T);

			try
			{
				value = (T)Enum.Parse(type, s, true);
				//TODO: On .NET 4.0, use Enum.TryParse
			}
			catch
			{
				Console.WriteLine( "Could not parse {0} enum attribute '{1}' in element '{2}'", type, attribute, xml.Name );
				return false;
			}

			return true;
		}

		public static bool ReadMap( XmlElement xml, string attribute, ref Map value )
		{
			return ReadMap( xml, attribute, ref value, true );
		}

		public static bool ReadMap( XmlElement xml, string attribute, ref Map value, bool mandatory )
		{
			string s = GetAttribute( xml, attribute, mandatory );

			if ( s == null )
				return false;

			try
			{
				value = Map.Parse( s );
			}
			catch
			{
				Console.WriteLine( "Could not parse Map attribute '{0}' in element '{1}'", attribute, xml.Name );
				return false;
			}

			return true;
		}

		public static bool ReadType( XmlElement xml, string attribute, ref Type value )
		{
			return ReadType( xml, attribute, ref value, true );
		}

		public static bool ReadType( XmlElement xml, string attribute, ref Type value, bool mandatory )
		{
			string s = GetAttribute( xml, attribute, mandatory );

			if ( s == null )
				return false;

			Type type;
			try
			{
				type = ScriptCompiler.FindTypeByName( s, false );
			}
			catch
			{
				Console.WriteLine( "Could not parse Type attribute '{0}' in element '{1}'", attribute, xml.Name );
				return false;
			}

			if ( type == null )
			{
				Console.WriteLine( "Could not find Type '{0}'", s );
				return false;
			}

			value = type;
			return true;
		}

		public static bool ReadPoint3D( XmlElement xml, Map map, ref Point3D value )
		{
			return ReadPoint3D( xml, map, ref value, true );
		}

		public static bool ReadPoint3D( XmlElement xml, Map map, ref Point3D value, bool mandatory )
		{
			int x = 0, y = 0, z = 0;

			bool xyOk = ReadInt32( xml, "x", ref x, mandatory ) & ReadInt32( xml, "y", ref y, mandatory );
			bool zOk = ReadInt32( xml, "z", ref z, mandatory && map == null );

			if ( xyOk && ( zOk || map != null ) )
			{
				if ( !zOk )
					z = map.GetAverageZ( x, y );

				value = new Point3D( x, y, z );
				return true;
			}

			return false;
		}

		public static bool ReadRectangle3D( XmlElement xml, int defaultMinZ, int defaultMaxZ, ref Rectangle3D value )
		{
			return ReadRectangle3D( xml, defaultMinZ, defaultMaxZ, ref value, true );
		}

		public static bool ReadRectangle3D( XmlElement xml, int defaultMinZ, int defaultMaxZ, ref Rectangle3D value, bool mandatory )
		{
			int x1 = 0, y1 = 0, x2 = 0, y2 = 0;

			if ( xml.HasAttribute( "x" ) )
			{
				if ( ReadInt32( xml, "x", ref x1, mandatory )
					& ReadInt32( xml, "y", ref y1, mandatory )
					& ReadInt32( xml, "width", ref x2, mandatory )
					& ReadInt32( xml, "height", ref y2, mandatory ) )
				{
					x2 += x1;
					y2 += y1;
				}
				else
				{
					return false;
				}
			}
			else
			{
				if ( !ReadInt32( xml, "x1", ref x1, mandatory )
					| !ReadInt32( xml, "y1", ref y1, mandatory )
					| !ReadInt32( xml, "x2", ref x2, mandatory )
					| !ReadInt32( xml, "y2", ref y2, mandatory ) )
				{
					return false;
				}
			}

			int z1 = defaultMinZ;
			int z2 = defaultMaxZ;

			ReadInt32( xml, "zmin", ref z1, false );
			ReadInt32( xml, "zmax", ref z2, false );

			value = new Rectangle3D( new Point3D( x1, y1, z1 ), new Point3D( x2, y2, z2 ) );

			return true;
		}
	}
}