/**********************************************************
RunUO AoV C# script file
Official Age of Valor Script :: www.uovalor.com
Last modified by Red Squirrel on Jan-01-2012 10:08:57pm
Filepath: scripts\Accounting\Account.cs
Lines of code: 977

Description: 

***********************************************************/


using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using Server;
using Server.Misc;
using Server.Mobiles;
using Server.Network;
using Server.DataStorage;

namespace Server.Accounting
{
	public class Account : IAccount, IComparable, IComparable<Account>
	{
		public static readonly TimeSpan YoungDuration = TimeSpan.FromHours( 40.0 );

		private int m_Id;
		private string m_Username, m_Email , m_NewCryptPassword;
		private AccessLevel m_AccessLevel;
		private int m_Flags;
		private DateTime m_Created, m_LastLogin;
		private TimeSpan m_TotalGameTime;
		private List<AccountComment> m_Comments;
		private List<AccountTag> m_Tags;
		private Mobile[] m_Mobiles;
		private string[] m_IPRestrictions;
		private IPAddress[] m_LoginIPs;
		private HardwareInfo m_HardwareInfo;
		private int m_DonationPoints=0;
		
		
		
		public int Id
		{
			get { return m_Id; }
		}
		
		
		public string SubType
		{
			get { return ""; }
		}	
		
		public int CurHouses;
		private int m_MaxHouses;
		public int MaxHouses
		{
			get {return m_MaxHouses;}
			set{
			Modify();
			m_MaxHouses=value;
			}		
		}		
		
		
		private bool m_Deleted=false;		
		public bool Deleted{get{return m_Deleted;}}
		

		/// <summary>
		/// Deletes the account, all characters of the account, and all houses of those characters
		/// </summary>
		public void Delete()
		{
				//only marks for deletation, will be deleted durring save
				//SaveState=SQLsaveState.Deleted;
		}
		

		public void DoDelete()
		{
			for ( int i = 0; i < this.Length; ++i )
			{
				Mobile m = this[i];

				if ( m == null )
					continue;

				ArrayList list = Multis.BaseHouse.GetHouses( m );

				for ( int j = 0; j < list.Count; ++j )
					( (Item) list[j] ).Delete();

				m.Delete();

				m.Account = null;
				m_Mobiles[i] = null;
			}
			
			Accounts.Remove( m_Username );
			Accounts.ReleaseSerial(m_Id);
		}
		

		/// <summary>
		/// Object detailing information about the hardware of the last person to log into this account
		/// </summary>
		public HardwareInfo HardwareInfo
		{
			get { return m_HardwareInfo; }
			set { m_HardwareInfo = value; Modify();}
		}
		
		
		public int DonationPoints
		{
			get { return m_DonationPoints;}
			set { 
				if(value<0)value=0;			
				m_DonationPoints=value; Modify();
				}
		}
		

		/// <summary>
		/// List of IP addresses for restricted access. '*' wildcard supported. If the array contains zero entries, all IP addresses are allowed.
		/// </summary>
		public string[] IPRestrictions
		{
			get { return m_IPRestrictions; }
			set { m_IPRestrictions = value; Modify();}
		}

		/// <summary>
		/// List of IP addresses which have successfully logged into this account.
		/// </summary>
		public IPAddress[] LoginIPs
		{
			get { return m_LoginIPs; }
			set { m_LoginIPs = value; Modify();}
		}

		/// <summary>
		/// List of account comments. Type of contained objects is AccountComment.
		/// </summary>
		public List<AccountComment> Comments
		{
			get { if ( m_Comments == null ) m_Comments = new List<AccountComment>(); return m_Comments; }
		}

		/// <summary>
		/// List of account tags. Type of contained objects is AccountTag.
		/// </summary>
		public List<AccountTag> Tags
		{
			get { if ( m_Tags == null ) m_Tags = new List<AccountTag>(); return m_Tags; }
		}

		/// <summary>
		/// Account username. Case insensitive validation.
		/// </summary>
		public string Username
		{
			get { return m_Username; }
			set { m_Username = value; Modify();}
		}
		
		public string Email
		{
			get { return m_Email; }
			set { m_Email = value; Modify();}
		}		


		/// <summary>
		/// Account username and password hashed with MD5. May be null.
		/// </summary>
		public string NewCryptPassword
		{
			get { return m_NewCryptPassword; }
			set { m_NewCryptPassword = value; Modify();}
		}

		/// <summary>
		/// Initial AccessLevel for new characters created on this account.
		/// </summary>
		public AccessLevel AccessLevel
		{
			get { return m_AccessLevel; }
			set { m_AccessLevel = value; Modify();}
		}

		/// <summary>
		/// Internal bitfield of account flags. Consider using direct access properties (Banned, Young), or GetFlag/SetFlag methods
		/// </summary>
		public int Flags
		{
			get { return m_Flags; }
			set { m_Flags = value; Modify();}
		}

		/// <summary>
		/// Gets or sets a flag indiciating if this account is banned.
		/// </summary>
		public bool Banned
		{
			get
			{
				bool isBanned = GetFlag( 0 );

				if ( !isBanned )
					return false;

				DateTime banTime;
				TimeSpan banDuration;

				if ( GetBanTags( out banTime, out banDuration ) )
				{
					if ( banDuration != TimeSpan.MaxValue && DateTime.Now >= ( banTime + banDuration ) )
					{
						SetUnspecifiedBan( null ); // clear
						Banned = false;
						return false;
					}
				}

				return true;
			}
			set { SetFlag( 0, value ); Modify();}
		}
		
		//whether account is active or not.  While this is saved in flags, it actually also has it's own SQL value which takes proceedence
		public bool Active
		{
			get { return GetFlag( 2 );}
			set { SetFlag( 2, value ); Modify(); }
		}

		/// <summary>
		/// Gets or sets a flag indicating if the characters created on this account will have the young status.
		/// </summary>
		public bool Young
		{
			get { return !GetFlag( 1 ); }
			set
			{
				Modify();
				SetFlag( 1, !value );

				if ( m_YoungTimer != null )
				{
					m_YoungTimer.Stop();
					m_YoungTimer = null;
				}
			}
		}

		/// <summary>
		/// The date and time of when this account was created.
		/// </summary>
		public DateTime Created
		{
			get { return m_Created; }
		}

		/// <summary>
		/// Gets or sets the date and time when this account was last accessed.
		/// </summary>
		public DateTime LastLogin
		{
			get { return m_LastLogin; }
			set { m_LastLogin = value; Modify();}
		}

		/// <summary>
		/// Gets the total game time of this account, also considering the game time of characters
		/// that have been deleted.
		/// </summary>
		public TimeSpan TotalGameTime
		{
			get
			{
				for ( int i = 0; i < m_Mobiles.Length; i++ )
				{
					PlayerMobile m = m_Mobiles[i] as PlayerMobile;

					if ( m != null && m.NetState != null )
						return m_TotalGameTime + ( DateTime.Now - m.SessionStart );
				}

				return m_TotalGameTime;
			}
		}

		/// <summary>
		/// Gets the value of a specific flag in the Flags bitfield.
		/// </summary>
		/// <param name="index">The zero-based flag index.</param>
		public bool GetFlag( int index )
		{
			return ( m_Flags & ( 1 << index ) ) != 0;
		}

		/// <summary>
		/// Sets the value of a specific flag in the Flags bitfield.
		/// </summary>
		/// <param name="index">The zero-based flag index.</param>
		/// <param name="value">The value to set.</param>
		public void SetFlag( int index, bool value )
		{
			if ( value )
				m_Flags |= ( 1 << index );
			else
				m_Flags &= ~( 1 << index );
		}

		/// <summary>
		/// Adds a new tag to this account. This method does not check for duplicate names.
		/// </summary>
		/// <param name="name">New tag name.</param>
		/// <param name="value">New tag value.</param>
		public void AddTag( string name, string value )
		{
			Modify();
			Tags.Add( new AccountTag( name, value ) );
		}

		/// <summary>
		/// Removes all tags with the specified name from this account.
		/// </summary>
		/// <param name="name">Tag name to remove.</param>
		public void RemoveTag( string name )
		{
			Modify();
			for ( int i = Tags.Count - 1; i >= 0; --i )
			{
				if ( i >= Tags.Count )
					continue;

				AccountTag tag = Tags[i];

				if ( tag.Name == name )
					Tags.RemoveAt( i );
			}
		}

		/// <summary>
		/// Modifies an existing tag or adds a new tag if no tag exists.
		/// </summary>
		/// <param name="name">Tag name.</param>
		/// <param name="value">Tag value.</param>
		public void SetTag( string name, string value )
		{
			Modify();
			for ( int i = 0; i < Tags.Count; ++i )
			{
				AccountTag tag = Tags[i];

				if ( tag.Name == name )
				{
					tag.Value = value;
					return;
				}
			}

			AddTag( name, value );
		}

		/// <summary>
		/// Gets the value of a tag -or- null if there are no tags with the specified name.
		/// </summary>
		/// <param name="name">Name of the desired tag value.</param>
		public string GetTag( string name )
		{
			for ( int i = 0; i < Tags.Count; ++i )
			{
				AccountTag tag = Tags[i];

				if ( tag.Name == name )
					return tag.Value;
			}

			return null;
		}

		public void SetUnspecifiedBan( Mobile from )
		{
			SetBanTags( from, DateTime.MinValue, TimeSpan.Zero );
		}

		public void SetBanTags( Mobile from, DateTime banTime, TimeSpan banDuration )
		{
			Modify();
			if ( from == null )
				RemoveTag( "BanDealer" );
			else
				SetTag( "BanDealer", from.ToString() );

			if ( banTime == DateTime.MinValue )
				RemoveTag( "BanTime" );
			else
				SetTag( "BanTime", XmlConvert.ToString( banTime, XmlDateTimeSerializationMode.Local ) );

			if ( banDuration == TimeSpan.Zero )
				RemoveTag( "BanDuration" );
			else
				SetTag( "BanDuration", banDuration.ToString() );
		}

		public bool GetBanTags( out DateTime banTime, out TimeSpan banDuration )
		{
			string tagTime = GetTag( "BanTime" );
			string tagDuration = GetTag( "BanDuration" );

			if ( tagTime != null )
				banTime = Utility.GetDateTime( tagTime, DateTime.MinValue );
			else
				banTime = DateTime.MinValue;

			if ( tagDuration == "Infinite" )
			{
				banDuration = TimeSpan.MaxValue;
			}
			else if ( tagDuration != null )
			{
				banDuration = Utility.ToTimeSpan( tagDuration );
			}
			else
			{
				banDuration = TimeSpan.Zero;
			}

			return ( banTime != DateTime.MinValue && banDuration != TimeSpan.Zero );
		}

		private static MD5CryptoServiceProvider m_MD5HashProvider;
		private static SHA1CryptoServiceProvider m_SHA1HashProvider;
		private static byte[] m_HashBuffer;

		public static string HashMD5( string phrase )
		{
			if ( m_MD5HashProvider == null )
				m_MD5HashProvider = new MD5CryptoServiceProvider();

			if ( m_HashBuffer == null )
				m_HashBuffer = new byte[256];

			int length = Encoding.ASCII.GetBytes( phrase, 0, phrase.Length > 256 ? 256 : phrase.Length, m_HashBuffer, 0 );
			byte[] hashed = m_MD5HashProvider.ComputeHash( m_HashBuffer, 0, length );

			return BitConverter.ToString( hashed );
		}

		public static string HashSHA1( string phrase )
		{
			if ( m_SHA1HashProvider == null )
				m_SHA1HashProvider = new SHA1CryptoServiceProvider();

			if ( m_HashBuffer == null )
				m_HashBuffer = new byte[256];

			int length = Encoding.ASCII.GetBytes( phrase, 0, phrase.Length > 256 ? 256 : phrase.Length, m_HashBuffer, 0 );
			byte[] hashed = m_SHA1HashProvider.ComputeHash( m_HashBuffer, 0, length );

			return BitConverter.ToString( hashed );
		}

		public void SetPassword( string plainPassword )
		{
			Modify();

			m_NewCryptPassword = HashSHA1( m_Username + plainPassword );
		}

		public bool CheckPassword( string plainPassword )
		{
			bool ok= ( m_NewCryptPassword == HashSHA1( m_Username + plainPassword ) );

	

			return ok;
		}

		private Timer m_YoungTimer;

		public static void Initialize()
		{
			EventSink.Connected += new ConnectedEventHandler( EventSink_Connected );
			EventSink.Disconnected += new DisconnectedEventHandler( EventSink_Disconnected );
			EventSink.Login += new LoginEventHandler( EventSing_Login );
		}

		private static void EventSink_Connected( ConnectedEventArgs e )
		{
			Account acc = e.Mobile.Account as Account;

			if ( acc == null )
				return;

			if ( acc.Young && acc.m_YoungTimer == null )
			{
				acc.m_YoungTimer = new YoungTimer( acc );
				acc.m_YoungTimer.Start();
			}
		}

		private static void EventSink_Disconnected( DisconnectedEventArgs e )
		{
			Account acc = e.Mobile.Account as Account;

			if ( acc == null )
				return;

			if ( acc.m_YoungTimer != null )
			{
				acc.m_YoungTimer.Stop();
				acc.m_YoungTimer = null;
			}

			PlayerMobile m = e.Mobile as PlayerMobile;
			if ( m == null )
				return;

			acc.m_TotalGameTime += DateTime.Now - m.SessionStart;
		}

		private static void EventSing_Login( LoginEventArgs e )
		{
			PlayerMobile m = e.Mobile as PlayerMobile;

			if ( m == null )
				return;

			Account acc = m.Account as Account;

			if ( acc == null )
				return;

			if ( m.Young && acc.Young )
			{
				TimeSpan ts = YoungDuration - acc.TotalGameTime;
				int hours = Math.Max( (int) ts.TotalHours, 0 );

				m.SendAsciiMessage( "You will enjoy the benefits and relatively safe status of a young player for {0} more hour{1}.", hours, hours != 1 ? "s" : "" );
			}
		}

		public void RemoveYoungStatus( int message )
		{
			Modify();
			this.Young = false;

			for ( int i = 0; i < m_Mobiles.Length; i++ )
			{
				PlayerMobile m = m_Mobiles[i] as PlayerMobile;

				if ( m != null && m.Young )
				{
					m.Young = false;

					if ( m.NetState != null )
					{
						if ( message > 0 )
							m.SendLocalizedMessage( message );

						m.SendLocalizedMessage( 1019039 ); // You are no longer considered a young player of Ultima Online, and are no longer subject to the limitations and benefits of being in that caste.
					}
				}
			}
		}

		public void CheckYoung()
		{
			if ( TotalGameTime >= YoungDuration )
				RemoveYoungStatus( 1019038 ); // You are old enough to be considered an adult, and have outgrown your status as a young player!
		}

		private class YoungTimer : Timer
		{
			private Account m_Account;

			public YoungTimer( Account account )
				: base( TimeSpan.FromMinutes( 1.0 ), TimeSpan.FromMinutes( 1.0 ) )
			{
				m_Account = account;

				Priority = TimerPriority.FiveSeconds;
			}

			protected override void OnTick()
			{
				m_Account.CheckYoung();
			}
		}
		
		//SQL load constructor
		public Account(int id)
		{	
			m_Id=id;
			m_SaveInfo = new SqlSaveInfo(new SqlDataEntry((int)SqlCont.Account,m_Id),this,true);								
		}	
		

		public Account( string username, string password )
		{
			//Notice: Order is very important for some of these declarations!
			
			m_Username = username;
			
			SetPassword( password );

			m_AccessLevel = AccessLevel.Player;

			Active=true;
			
			m_Created = m_LastLogin = DateTime.Now;
			m_TotalGameTime = TimeSpan.Zero;

			m_Mobiles = new Mobile[7];

			m_IPRestrictions = new string[0];
			m_LoginIPs = new IPAddress[0];
			
			m_MaxHouses=1;			

			m_Id=Accounts.GetNextSerial();
			m_SaveInfo = new SqlSaveInfo(new SqlDataEntry((int)SqlCont.Account,m_Id),this,false);						

			Accounts.Add( this );
		}

		public Account( XmlElement node, int id )
		{			
			m_Id=id;
			
			m_SaveInfo = new SqlSaveInfo(new SqlDataEntry((int)SqlCont.Account,m_Id),this,false);
			
			m_Username = Utility.GetText( node["username"], "empty" );

			//string plainPassword = Utility.GetText( node["password"], null );
			//string cryptPassword = Utility.GetText( node["cryptPassword"], null );
			string newCryptPassword = Utility.GetText( node["newCryptPassword"], null );

			m_NewCryptPassword = newCryptPassword;
			

			m_AccessLevel = (AccessLevel)Enum.Parse( typeof( AccessLevel ), Utility.GetText( node["accessLevel"], "Player" ), true );
			m_Flags = Utility.GetInt32( Utility.GetText( node["flags"], "0" ), 0 );
			m_Created = Utility.GetDateTime( Utility.GetText( node["created"], null ), DateTime.Now );
			m_LastLogin = Utility.GetDateTime( Utility.GetText( node["lastLogin"], null ), DateTime.Now );

			m_Mobiles = LoadMobiles( node );
			m_Comments = LoadComments( node );
			m_Tags = LoadTags( node );
			m_LoginIPs = LoadAddressList( node );
			m_IPRestrictions = LoadAccessCheck( node );

			for ( int i = 0; i < m_Mobiles.Length; ++i )
			{
				if ( m_Mobiles[i] != null )
				{
					m_Mobiles[i].Account = this;
					
				//# of houses was previously loaded into each individual mobile - now we merge the number into the account info
				if(m_Mobiles[i] is PlayerMobile)CurHouses+=((PlayerMobile)m_Mobiles[i]).CharHousesCur;					
				}
			}
			
			string strmaxhouses=GetTag("maxhouses");
			if(strmaxhouses=="" || strmaxhouses==null)MaxHouses=1;
			else MaxHouses= Convert.ToInt32(strmaxhouses);


			TimeSpan totalGameTime = Utility.GetTimeSpan( Utility.GetText( node["totalGameTime"], null ), TimeSpan.Zero );
			if ( totalGameTime == TimeSpan.Zero )
			{
				for ( int i = 0; i < m_Mobiles.Length; i++ )
				{
					PlayerMobile m = m_Mobiles[i] as PlayerMobile;

					if ( m != null )
						totalGameTime += m.GameTime;
				}
			}
			m_TotalGameTime = totalGameTime;

			if ( this.Young )
				CheckYoung();

				
			Accounts.Add( this );
		}
		

		

		/// <summary>
		/// Deserializes a list of string values from an xml element. Null values are not added to the list.
		/// </summary>
		/// <param name="node">The XmlElement from which to deserialize.</param>
		/// <returns>String list. Value will never be null.</returns>
		public static string[] LoadAccessCheck( XmlElement node )
		{
			string[] stringList;
			XmlElement accessCheck = node["accessCheck"];

			if ( accessCheck != null )
			{
				List<string> list = new List<string>();

				foreach ( XmlElement ip in accessCheck.GetElementsByTagName( "ip" ) )
				{
					string text = Utility.GetText( ip, null );

					if ( text != null )
						list.Add( text );
				}

				stringList = list.ToArray();
			}
			else
			{
				stringList = new string[0];
			}

			return stringList;
		}

		/// <summary>
		/// Deserializes a list of IPAddress values from an xml element.
		/// </summary>
		/// <param name="node">The XmlElement from which to deserialize.</param>
		/// <returns>Address list. Value will never be null.</returns>
		public static IPAddress[] LoadAddressList( XmlElement node )
		{
			IPAddress[] list;
			XmlElement addressList = node["addressList"];

			if ( addressList != null )
			{
				int count = Utility.GetInt32( Utility.GetAttribute( addressList, "count", "0" ), 0 );

				list = new IPAddress[count];

				count = 0;

				foreach ( XmlElement ip in addressList.GetElementsByTagName( "ip" ) )
				{
					if ( count < list.Length )
					{
						IPAddress address;

						if( IPAddress.TryParse( Utility.GetText( ip, null ), out address ) )
						{
							list[count] = address;
							count++;
						}
					}
				}

				if ( count != list.Length )
				{
					IPAddress[] old = list;
					list = new IPAddress[count];

					for ( int i = 0; i < count && i < old.Length; ++i )
						list[i] = old[i];
				}
			}
			else
			{
				list = new IPAddress[0];
			}

			return list;
		}

		/// <summary>
		/// Deserializes a list of Mobile instances from an xml element.
		/// </summary>
		/// <param name="node">The XmlElement instance from which to deserialize.</param>
		/// <returns>Mobile list. Value will never be null.</returns>
		public static Mobile[] LoadMobiles( XmlElement node )
		{
			Mobile[] list = new Mobile[6];
			XmlElement chars = node["chars"];

			//int length = Accounts.GetInt32( Accounts.GetAttribute( chars, "length", "6" ), 6 );
			//list = new Mobile[length];
			//Above is legacy, no longer used

			if ( chars != null )
			{
				foreach ( XmlElement ele in chars.GetElementsByTagName( "char" ) )
				{
					try
					{
						int index = Utility.GetInt32( Utility.GetAttribute( ele, "index", "0" ), 0 );
						int serial = Utility.GetInt32( Utility.GetText( ele, "0" ), 0 );

						if ( index >= 0 && index < list.Length )
							list[index] = World.FindMobile( serial );
					}
					catch
					{
					}
				}
			}
		

			return list;
		}

		/// <summary>
		/// Deserializes a list of AccountComment instances from an xml element.
		/// </summary>
		/// <param name="node">The XmlElement from which to deserialize.</param>
		/// <returns>Comment list. Value will never be null.</returns>
		public static List<AccountComment> LoadComments( XmlElement node )
		{
			List<AccountComment> list = null;
			XmlElement comments = node["comments"];

			if ( comments != null )
			{
				list = new List<AccountComment>();

				foreach ( XmlElement comment in comments.GetElementsByTagName( "comment" ) )
				{
					try { list.Add( new AccountComment( comment ) ); }
					catch { }
				}
			}

			return list;
		}

		/// <summary>
		/// Deserializes a list of AccountTag instances from an xml element.
		/// </summary>
		/// <param name="node">The XmlElement from which to deserialize.</param>
		/// <returns>Tag list. Value will never be null.</returns>
		public static List<AccountTag> LoadTags( XmlElement node )
		{
			List<AccountTag> list = null;
			XmlElement tags = node["tags"];

			if ( tags != null )
			{
				list = new List<AccountTag>();

				foreach ( XmlElement tag in tags.GetElementsByTagName( "tag" ) )
				{
					try { list.Add( new AccountTag( tag ) ); }
					catch { }
				}
			}

			return list;
		}

		/// <summary>
		/// Checks if a specific NetState is allowed access to this account.
		/// </summary>
		/// <param name="ns">NetState instance to check.</param>
		/// <returns>True if allowed, false if not.</returns>
		public bool HasAccess( NetState ns )
		{
			if ( ns == null )
				return false;

			AccessLevel level = Misc.AccountHandler.LockdownLevel;

			if ( level > AccessLevel.Player )
			{
				bool hasAccess = false;

				if ( m_AccessLevel >= level )
				{
					hasAccess = true;
				}
				else
				{
					for ( int i = 0; !hasAccess && i < this.Length; ++i )
					{
						Mobile m = this[i];

						if ( m != null && m.AccessLevel >= level )
							hasAccess = true;
					}
				}

				if ( !hasAccess )
					return false;
			}

			IPAddress ipAddress;

			try { ipAddress = ( (IPEndPoint) ns.Socket.RemoteEndPoint ).Address; }
			catch { return false; }

			bool accessAllowed = ( m_IPRestrictions.Length == 0 || IPLimiter.IsExempt( ipAddress ) );

			for ( int i = 0; !accessAllowed && i < m_IPRestrictions.Length; ++i )
				accessAllowed = Utility.IPMatch( m_IPRestrictions[i], ipAddress );

			return accessAllowed;
		}

		/// <summary>
		/// Records the IP address of 'ns' in its 'LoginIPs' list.
		/// </summary>
		/// <param name="ns">NetState instance to record.</param>
		public void LogAccess( NetState ns )
		{
			if ( ns == null )
				return;

			IPAddress ipAddress = ns.Address;

			if ( IPLimiter.IsExempt( ipAddress ) )
				return;

			if ( m_LoginIPs.Length == 0 ) {
				if ( AccountHandler.IPTable.ContainsKey( ipAddress ) )
					AccountHandler.IPTable[ipAddress]++;
				else
					AccountHandler.IPTable[ipAddress] = 1;
			}

			bool contains = false;

			for ( int i = 0; !contains && i < m_LoginIPs.Length; ++i )
				contains = m_LoginIPs[i].Equals( ipAddress );

			if ( contains )
				return;

			IPAddress[] old = m_LoginIPs;
			m_LoginIPs = new IPAddress[old.Length + 1];

			for ( int i = 0; i < old.Length; ++i )
				m_LoginIPs[i] = old[i];

			m_LoginIPs[old.Length] = ipAddress;
		}

		/// <summary>
		/// Checks if a specific NetState is allowed access to this account. If true, the NetState IPAddress is added to the address list.
		/// </summary>
		/// <param name="ns">NetState instance to check.</param>
		/// <returns>True if allowed, false if not.</returns>
		public bool CheckAccess( NetState ns )
		{
			if ( !HasAccess( ns ) )
				return false;

			LogAccess( ns );
			return true;
		}

		/// <summary>
		/// Serializes this Account instance to an XmlTextWriter.
		/// </summary>
		/// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
		public void Save( XmlTextWriter xml )
		{
			xml.WriteStartElement( "account" );

			xml.WriteStartElement( "username" );
			xml.WriteString( m_Username );
			xml.WriteEndElement();


			if ( m_NewCryptPassword != null )
			{
				xml.WriteStartElement( "newCryptPassword" );
				xml.WriteString( m_NewCryptPassword );
				xml.WriteEndElement();
			}

			if ( m_AccessLevel != AccessLevel.Player )
			{
				xml.WriteStartElement( "accessLevel" );
				xml.WriteString( m_AccessLevel.ToString() );
				xml.WriteEndElement();
			}

			if ( m_Flags != 0 )
			{
				xml.WriteStartElement( "flags" );
				xml.WriteString( XmlConvert.ToString( m_Flags ) );
				xml.WriteEndElement();
			}

			xml.WriteStartElement( "created" );
			xml.WriteString( XmlConvert.ToString( m_Created, XmlDateTimeSerializationMode.Local ) );
			xml.WriteEndElement();

			xml.WriteStartElement( "lastLogin" );
			xml.WriteString( XmlConvert.ToString( m_LastLogin, XmlDateTimeSerializationMode.Local ) );
			xml.WriteEndElement();

			xml.WriteStartElement( "totalGameTime" );
			xml.WriteString( XmlConvert.ToString( TotalGameTime ) );
			xml.WriteEndElement();

			xml.WriteStartElement( "chars" );

			//xml.WriteAttributeString( "length", m_Mobiles.Length.ToString() );	//Legacy, Not used anymore

			for ( int i = 0; i < m_Mobiles.Length; ++i )
			{
				Mobile m = m_Mobiles[i];

				if ( m != null && !m.Deleted )
				{
					xml.WriteStartElement( "char" );
					xml.WriteAttributeString( "index", i.ToString() );
					xml.WriteString( m.Serial.Value.ToString() );
					xml.WriteEndElement();
				}
			}

			xml.WriteEndElement();

			if ( m_Comments != null && m_Comments.Count > 0 )
			{
				xml.WriteStartElement( "comments" );

				for ( int i = 0; i < m_Comments.Count; ++i )
					m_Comments[i].Save( xml );

				xml.WriteEndElement();
			}

			if ( m_Tags != null && m_Tags.Count > 0 )
			{
				xml.WriteStartElement( "tags" );

				for ( int i = 0; i < m_Tags.Count; ++i )
					m_Tags[i].Save( xml );

				xml.WriteEndElement();
			}

			if ( m_LoginIPs.Length > 0 )
			{
				xml.WriteStartElement( "addressList" );

				xml.WriteAttributeString( "count", m_LoginIPs.Length.ToString() );

				for ( int i = 0; i < m_LoginIPs.Length; ++i )
				{
					xml.WriteStartElement( "ip" );
					xml.WriteString( m_LoginIPs[i].ToString() );
					xml.WriteEndElement();
				}

				xml.WriteEndElement();
			}

			if ( m_IPRestrictions.Length > 0 )
			{
				xml.WriteStartElement( "accessCheck" );

				for ( int i = 0; i < m_IPRestrictions.Length; ++i )
				{
					xml.WriteStartElement( "ip" );
					xml.WriteString( m_IPRestrictions[i] );
					xml.WriteEndElement();
				}

				xml.WriteEndElement();
			}

			xml.WriteEndElement();
		}

		/// <summary>
		/// Gets the current number of characters on this account.
		/// </summary>
		public int Count
		{
			get
			{
				int count = 0;

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

				return count;
			}
		}

		/// <summary>
		/// Gets the maximum amount of characters allowed to be created on this account. Values other than 1, 5, or 6 are not supported by the client.
		/// </summary>
		public int Limit
		{
			get { return (7); }
		}

		/// <summary>
		/// Gets the maxmimum amount of characters that this account can hold.
		/// </summary>
		public int Length
		{
			get { return m_Mobiles.Length; }
		}
		
		public int CharAmt
		{
			get { 
			int tmp=0;
				for(int i=0;i<m_Mobiles.Length;i++)
				{
				if(m_Mobiles[i]==null)continue;
				tmp++;
				}
			return tmp;
			}
		}

		/// <summary>
		/// Gets or sets the character at a specified index for this account. Out of bound index values are handled; null returned for get, ignored for set.
		/// </summary>
		public Mobile this[int index]
		{
			get
			{
				if ( index >= 0 && index < m_Mobiles.Length )
				{
					Mobile m = m_Mobiles[index];

					if ( m != null && m.Deleted )
					{
						m.Account = null;
						m_Mobiles[index] = m = null;
					}

					return m;
				}

				return null;
			}
			set
			{
				if ( index >= 0 && index < m_Mobiles.Length )
				{
					if ( m_Mobiles[index] != null )
						m_Mobiles[index].Account = null;

					m_Mobiles[index] = value;

					if ( m_Mobiles[index] != null )
						m_Mobiles[index].Account = this;
						
					Modify();
				}
			}
		}
		
		public int GetCharSerial(int index)
		{			
			Mobile tmp = this[index];
			if(tmp==null)return 0;
			return tmp.Serial;				
		}
		

		public override string ToString()
		{
			return Username;
		}

		public int CompareTo( Account other )
		{
			if ( other == null )
				return -1;

			return m_Username.CompareTo( other.m_Username );
		}

		public int CompareTo( object obj )
		{
			if ( obj is Account )
				return this.CompareTo( (Account) obj );

			throw new ArgumentException();
		}
		
		
		
		public void SendMessage( string text )
		{
			SendMessage( 0x3B2, text );
		}

		public void SendMessage( string format, params object[] args )
		{
			SendMessage( 0x3B2, String.Format( format, args ) );
		}

		public void SendMessage( int hue, string text )
		{
			for(int i=0;i<Limit;i++)
			{
				if (this[i]!=null && this[i].NetState != null )
				{
					this[i].NetState.Send( new UnicodeMessage( Serial.MinusOne, -1, MessageType.Regular, hue, 3, "ENU", "System", text ) );
				}
			}
		}

		public void SendMessage( int hue, string format, params object[] args )
		{
			SendMessage( hue, String.Format( format, args ) );
		}
		

		
		
		
		
		public int TypeReference { get { return 0; } }   //not used, but we have to add it anyway		
		public int SerialIdentity { get { return m_Id;} }	//serial
		
		public void Serialize(GenericWriter writer)
		{
			writer.Write((int)2); //version
			
			writer.Write(m_Username);
			writer.Write(m_Email);
			writer.Write(m_NewCryptPassword);
			writer.Write((int)m_AccessLevel);
			writer.Write(m_Flags);
			writer.Write(m_Created);
			writer.Write(m_LastLogin);			
			writer.Write(m_TotalGameTime);	
			writer.Write(m_MaxHouses);

	
			//comments:
			if(m_Comments!=null)
			{
				writer.Write((int)m_Comments.Count);
				foreach(AccountComment ac in m_Comments)
				{
					ac.Serialize(writer);
				}
			}
			else writer.Write(0);
	
			//account tags:
			if(m_Tags!=null)	
			{			
				writer.Write((int)m_Tags.Count);
				foreach(AccountTag at in m_Tags)
				{
					at.Serialize(writer);
				}
			}
			else writer.Write(0);

			//mobiles:
			
			
			if(m_Mobiles!=null)
			{
				writer.Write(CharAmt);
				for ( int i = 0; i < m_Mobiles.Length; i++ )
				{					
					if(m_Mobiles[i]==null)continue;
					
					writer.Write(m_Mobiles[i]);
				}
			}
			else writer.Write(0);
			
			
			//ip restrictions:
			if(m_IPRestrictions!=null)
			{
				writer.Write((int)m_IPRestrictions.Length);
				for(int i=0;i<m_IPRestrictions.Length;i++)
				{
					writer.Write(m_IPRestrictions[i]);
				}
			}
			else writer.Write(0);
			
			//login ips:
			if(m_LoginIPs!=null)
			{
				writer.Write((int)m_LoginIPs.Length);
				for(int i=0;i<m_LoginIPs.Length;i++)
				{
					writer.Write(m_LoginIPs[i]);
				}
			}
			else writer.Write(0);
			
			
			
			writer.Write(m_DonationPoints);
			
		
		}
		
		
		public void Deserialize(GenericReader reader)
		{
			int version = reader.ReadInt();
			int count=0; //used for array counters			
			
			m_Username = reader.ReadString();
			m_Email = reader.ReadString();
			m_NewCryptPassword = reader.ReadString();
			m_AccessLevel = (AccessLevel)reader.ReadInt();
			m_Flags = reader.ReadInt();
			m_Created = reader.ReadDateTime();
			m_LastLogin = reader.ReadDateTime();
			m_TotalGameTime = reader.ReadTimeSpan();
			m_MaxHouses = reader.ReadInt();
			
			//comments:
			count = reader.ReadInt();
			m_Comments = new List<AccountComment>();
			for(int i=0;i<count;i++)
			{
				m_Comments.Add(new AccountComment(reader));
			}
			
			//tags:
			count = reader.ReadInt();
			m_Tags = new List<AccountTag>();
			for(int i=0;i<count;i++)
			{
				m_Tags.Add(new AccountTag(reader));
			}

			//Mobiles:
			count = reader.ReadInt();
			m_Mobiles = new Mobile[Limit];

			
			for(int i=0;i<count;i++)
			{
				m_Mobiles[i]=reader.ReadMobile();
				
				if ( m_Mobiles[i] != null )
				{
					m_Mobiles[i].Account = this;
			
					
					//# of houses was previously loaded into each individual mobile - now we merge the number into the account info
					if(m_Mobiles[i] is PlayerMobile)CurHouses+=((PlayerMobile)m_Mobiles[i]).CharHousesCur;					
				}
			}	
			
			//ip restrictions:
			count = reader.ReadInt();
			
			m_IPRestrictions = new string[count];
			for(int i=0;i<count;i++)
			{
				m_IPRestrictions[i]=reader.ReadString();
			}
			
			//login ips
			count = reader.ReadInt();
			m_LoginIPs = new IPAddress[count];
			for(int i=0;i<count;i++)
			{
				m_LoginIPs[i]=reader.ReadIPAddress();
			}
			
			
			
			
			//some other stuff:
			
			if ( m_TotalGameTime == TimeSpan.Zero )
			{
				for ( int i = 0; i < m_Mobiles.Length; i++ )
				{
					PlayerMobile m = m_Mobiles[i] as PlayerMobile;

					if ( m != null )
						m_TotalGameTime += m.GameTime;
				}
			}

			if ( this.Young )
				CheckYoung();		



			if(version>=2)
			{
				m_DonationPoints=reader.ReadInt();
			}
		
		}
		

		
		//--- SQL stuff ---
		private SqlSaveInfo m_SaveInfo=null;
		
		[CommandProperty( AccessLevel.Counselor )]
		public SqlSaveState SaveState
		{
			get { return m_SaveInfo!=null ? m_SaveInfo.GetState() : SqlSaveState.Unset; }
		}
		
		//assigns the SqlSaveInfo object when loading from the DB
		public bool PreLoad(SqlSaveInfo info)
		{
			if(m_SaveInfo!=null)return false;
			
			m_SaveInfo=info;
			return true;
		}		
		
		//call this whenever the object gets modified so we can properly set the sql save state
		public void Modify(bool delete)
		{			
			if(m_SaveInfo==null)return;
			
			m_SaveInfo.Modify(String.Format("{0}",this),delete);
		}
		
		public void Modify()
		{
			Modify(false);
		}
		
		//--- end SQL stuff ---	
		
		
		
	}
}