context: using System;
  using System.Reflection;
  using System.Collections;
  using Server;
  using Server.Targeting;
  using Server.Items;
  using Server.Gumps;
  using CPA = Server.CommandPropertyAttribute;
  using Server.Commands;
  using Server.Commands.Generic;
  
  namespace Server.Commands
  {
  	public enum PropertyAccess
  	{
  		Read		= 0x01,
  		Write		= 0x02,
  		ReadWrite	= Read | Write
  	}
  
  	public class Properties
  	{
  		public static void Initialize()
  		{
  			CommandSystem.Register( "Props", AccessLevel.Counselor, new CommandEventHandler( Props_OnCommand ) );
  		}
  
  		private class PropsTarget : Target
  		{
  			public PropsTarget() : base( -1, true, TargetFlags.None )
  			{
  			}
  
  			protected override void OnTarget( Mobile from, object o )
  			{
  				if ( !BaseCommand.IsAccessible( from, o ) )
  					from.SendMessage( "That is not accessible." );
  				else
  					from.SendGump( new PropertiesGump( from, o ) );
  			}
  		}
  
  		[Usage( "Props [serial]" )]
  		[Description( "Opens a menu where you can view and edit all properties of a targeted (or specified) object." )]
  		private static void Props_OnCommand( CommandEventArgs e )
  		{
  			if ( e.Length == 1 )
  			{
  				IEntity ent = World.FindEntity( e.GetInt32( 0 ) );
  
  				if ( ent == null )
  					e.Mobile.SendMessage( "No object with that serial was found." );
  				else if ( !BaseCommand.IsAccessible( e.Mobile, ent ) )
  					e.Mobile.SendMessage( "That is not accessible." );
  				else
  					e.Mobile.SendGump( new PropertiesGump( e.Mobile, ent ) );
  			}
  			else
  			{
  				e.Mobile.Target = new PropsTarget();
  			}
  		}
  
  		private static bool CIEqual( string l, string r )
  		{
  			return Insensitive.Equals( l, r );
  		}
  
  		private static Type typeofCPA = typeof( CPA );
  
  		public static CPA GetCPA( PropertyInfo p )
  		{
  			object[] attrs = p.GetCustomAttributes( typeofCPA, false );
  
  			if ( attrs.Length == 0 )
  				return null;
  
  			return attrs[0] as CPA;
  		}
  
  		public static PropertyInfo[] GetPropertyInfoChain( Mobile from, Type type, string propertyString, PropertyAccess endAccess, ref string failReason )
  		{
  			string[] split = propertyString.Split( '.' );
  
  			if ( split.Length == 0 )
  				return null;
  
  			PropertyInfo[] info = new PropertyInfo[split.Length];
  
  			for ( int i = 0; i < info.Length; ++i )
  			{
  				string propertyName = split[i];
  
  				if ( CIEqual( propertyName, "current" ) )
  					continue;
  
  				PropertyInfo[] props = type.GetProperties( BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public );
  
  				bool isFinal = ( i == (info.Length - 1) );
  
  				PropertyAccess access = endAccess;
  
  				if ( !isFinal )
  					access |= PropertyAccess.Read;
  
  				for ( int j = 0; j < props.Length; ++j )
  				{
  					PropertyInfo p = props[j];
  
  					if ( CIEqual( p.Name, propertyName ) )
  					{
  						CPA attr = GetCPA( p );
  
  						if ( attr == null )
  						{
  							failReason = String.Format( "Property '{0}' not found.", propertyName );
  							return null;
  						}
  						else if ( (access & PropertyAccess.Read) != 0 && from.AccessLevel < attr.ReadLevel )
  						{
  							failReason = String.Format( "You must be at least {0} to get the property '{1}'.",
  								Mobile.GetAccessLevelName( attr.ReadLevel ), propertyName );
  
  							return null;
  						}
  						else if ( (access & PropertyAccess.Write) != 0 && from.AccessLevel < attr.WriteLevel )
  						{
  							failReason = String.Format( "You must be at least {0} to set the property '{1}'.",
  								Mobile.GetAccessLevelName( attr.WriteLevel ), propertyName );
  
  							return null;
  						}
  						else if ( (access & PropertyAccess.Read) != 0 && !p.CanRead )
  						{
  							failReason = String.Format( "Property '{0}' is write only.", propertyName );
  							return null;
  						}
  						else if ( (access & PropertyAccess.Write) != 0 && (!p.CanWrite || attr.ReadOnly) && isFinal )
  						{
  							failReason = String.Format( "Property '{0}' is read only.", propertyName );
  							return null;
  						}
  
  						info[i] = p;
  						type = p.PropertyType;
  						break;
  					}
  				}
  
  				if ( info[i] == null )
  				{
  					failReason = String.Format( "Property '{0}' not found.", propertyName );
  					return null;
  				}
  			}
  
  			return info;
  		}
  
  		public static PropertyInfo GetPropertyInfo( Mobile from, ref object obj, string propertyName, PropertyAccess access, ref string failReason )
  		{
  			PropertyInfo[] chain = GetPropertyInfoChain( from, obj.GetType(), propertyName, access, ref failReason );
  
  			if ( chain == null )
  				return null;
  
  			return GetPropertyInfo( ref obj, chain, ref failReason );
  		}
  
  		public static PropertyInfo GetPropertyInfo( ref object obj, PropertyInfo[] chain, ref string failReason )
  		{
  			if ( chain == null || chain.Length == 0 )
  			{
  				failReason = "Property chain is empty.";
  				return null;
  			}
  
  			for ( int i = 0; i < chain.Length - 1; ++i )
  			{
  				if ( chain[i] == null )
  					continue;
  
  				obj = chain[i].GetValue( obj, null );
  
  				if ( obj == null )
  				{
  					failReason = String.Format( "Property '{0}' is null.", chain[i] );
  					return null;
  				}
  			}
  
  			return chain[chain.Length-1];
  		}
  
  		public static string GetValue( Mobile from, object o, string name )
  		{
  			string failReason = "";
  
  			PropertyInfo[] chain = GetPropertyInfoChain( from, o.GetType(), name, PropertyAccess.Read, ref failReason );
  
  			if ( chain == null || chain.Length == 0 )
  				return failReason;
  
  			PropertyInfo p = GetPropertyInfo( ref o, chain, ref failReason );
  
  			if ( p == null )
  				return failReason;
  
  			return InternalGetValue( o, p, chain );
  		}
  
  		public static string IncreaseValue( Mobile from, object o, string[] args )
  		{
  			Type type = o.GetType();
  
  			object[] realObjs = new object[args.Length/2];
  			PropertyInfo[] realProps = new PropertyInfo[args.Length/2];
  			int[] realValues = new int[args.Length/2];
  
  			bool positive = false, negative = false;
  
  			for ( int i = 0; i < realProps.Length; ++i )
  			{
  				string name = args[i*2];
  
  				try
  				{
  					string valueString = args[1 + (i*2)];
  
  					if ( valueString.StartsWith( "0x" ) )
  					{
  						realValues[i] = Convert.ToInt32( valueString.Substring( 2 ), 16 );
  					}
  					else
  					{
  						realValues[i] = Convert.ToInt32( valueString );
  					}
  				}
  				catch
  				{
  					return "Offset value could not be parsed.";
  				}
  
  				if ( realValues[i] > 0 )
  					positive = true;
  				else if ( realValues[i] < 0 )
  					negative = true;
  				else
  					return "Zero is not a valid value to offset.";
  
  				string failReason = null;
  				realObjs[i] = o;
  				realProps[i] = GetPropertyInfo( from, ref realObjs[i], name, PropertyAccess.ReadWrite, ref failReason );
  
  				if ( failReason != null )
  					return failReason;
  
  				if ( realProps[i] == null )
  					return "Property not found.";
  			}
  
  			for ( int i = 0; i < realProps.Length; ++i )
  			{
  				object obj = realProps[i].GetValue( realObjs[i], null );
  
  				if( !( obj is IConvertible ) )
  					return "Property is not IConvertable.";
  
  				try
  				{
  
  					long v = (long)Convert.ChangeType( obj, TypeCode.Int64 );
  					v += realValues[i];
  
  					realProps[i].SetValue( realObjs[i], Convert.ChangeType( v, realProps[i].PropertyType ), null );
  				}
  				catch
  				{
  					return "Value could not be converted";
  				}
  			}
  
  			if ( realProps.Length == 1 )
  			{
  				if ( positive )
  					return "The property has been increased.";
  
  				return "The property has been decreased.";
  			}
  
  			if ( positive && negative )
  				return "The properties have been changed.";
  
  			if ( positive )
  				return "The properties have been increased.";
  
  			return "The properties have been decreased.";
  		}
  
  		private static string InternalGetValue( object o, PropertyInfo p )
  		{
  			return InternalGetValue( o, p, null );
  		}
  
  		private static string InternalGetValue( object o, PropertyInfo p, PropertyInfo[] chain )
  		{
  			Type type = p.PropertyType;
  
  			object value = p.GetValue( o, null );
  			string toString;
  
  			if ( value == null )
  				toString = "null";
  			else if ( IsNumeric( type ) )
  				toString = String.Format( "{0} (0x{0:X})", value );
  			else if ( IsChar( type ) )
  				toString = String.Format( "'{0}' ({1} [0x{1:X}])", value, (int) value );
  			else if ( IsString( type ) )
  				toString = ( (string) value == "null" ? @"@""null""" : String.Format( "\"{0}\"", value ) );
  			else if ( IsText( type ) )
  				toString = ( (TextDefinition) value ).Format( false );
  			else
  				toString = value.ToString();
  
  			if ( chain == null )
  				return String.Format( "{0} = {1}", p.Name, toString );
  
  			string[] concat = new string[chain.Length*2+1];
  
  			for ( int i = 0; i < chain.Length; ++i )
  			{
  				concat[(i*2)+0] = chain[i].Name;
  				concat[(i*2)+1] = ( i < (chain.Length - 1) ) ? "." : " = ";
  			}
  
  			concat[concat.Length-1] = toString;
  
  			return String.Concat( concat );
  		}
  
  		public static string SetValue( Mobile from, object o, string name, string value )
  		{
  			object logObject = o;
  
  			string failReason = "";
  			PropertyInfo p = GetPropertyInfo( from, ref o, name, PropertyAccess.Write, ref failReason );
  
  			if ( p == null )
  				return failReason;
  
  			return InternalSetValue( from, logObject, o, p, name, value, true );
  		}
  
  		private static Type typeofSerial = typeof( Serial );
  
  		private static bool IsSerial( Type t )
  		{
  			return ( t == typeofSerial );
  		}
  
  		private static Type typeofType = typeof( Type );
  
  		private static bool IsType( Type t )
  		{
  			return ( t == typeofType );
  		}
  
  		private static Type typeofChar = typeof( Char );
  
  		private static bool IsChar( Type t )
  		{
  			return ( t == typeofChar );
  		}
  
  		private static Type typeofString = typeof( String );
  
  		private static bool IsString( Type t )
  		{
  			return ( t == typeofString );
  		}
  
  		private static Type typeofText = typeof( TextDefinition );
  
  		private static bool IsText( Type t )
  		{
  			return ( t == typeofText );
  		}
  
  		private static bool IsEnum( Type t )
  		{
  			return t.IsEnum;
  		}
  
  		private static Type typeofTimeSpan = typeof( TimeSpan );
  		private static Type typeofParsable = typeof( ParsableAttribute );
  
  		private static bool IsParsable( Type t )
  		{
  			return ( t == typeofTimeSpan || t.IsDefined( typeofParsable, false ) );
  		}
  
  		private static Type[] m_ParseTypes = new Type[]{ typeof( string ) };
  		private static object[] m_ParseParams = new object[1];
  
  		private static object Parse( object o, Type t, string value )
  		{
  			MethodInfo method = t.GetMethod( "Parse", m_ParseTypes );
  
  			m_ParseParams[0] = value;
  
  			return method.Invoke( o, m_ParseParams );
  		}
  
  		private static Type[] m_NumericTypes = new Type[]
  			{
  				typeof( Byte ), typeof( SByte ),
  				typeof( Int16 ), typeof( UInt16 ),
  				typeof( Int32 ), typeof( UInt32 ),
  				typeof( Int64 ), typeof( UInt64 )
  			};
  
  		private static bool IsNumeric( Type t )
  		{
  			return ( Array.IndexOf( m_NumericTypes, t ) >= 0 );
  		}
  
  		public static string ConstructFromString( Type type, object obj, string value, ref object constructed )
  		{
  			object toSet;
  			bool isSerial = IsSerial( type );
  
  			if ( isSerial ) // mutate into int32
  				type = m_NumericTypes[4];
  
  			if ( value == "(-null-)" && !type.IsValueType )
  				value = null;
  
  			if ( IsEnum( type ) )
  			{
  				try
  				{
  					toSet = Enum.Parse( type, value, true );
  				}
  				catch
  				{
  					return "That is not a valid enumeration member.";
  				}
  			}
  			else if ( IsType( type ) )
  			{
  				try
  				{
  					toSet = ScriptCompiler.FindTypeByName( value );
  
  					if ( toSet == null )
  						return "No type with that name was found.";
  				}
  				catch
  				{
  					return "No type with that name was found.";
  				}
  			}
  			else if ( IsParsable( type ) )
  			{
  				try
  				{
  					toSet = Parse( obj, type, value );
  				}
  				catch
  				{
  					return "That is not properly formatted.";
  				}
  			}
  			else if ( value == null )
  			{
  				toSet = null;
  			}
  			else if ( value.StartsWith( "0x" ) && IsNumeric( type ) )
  			{
  				try
  				{
  					toSet = Convert.ChangeType( Convert.ToUInt64( value.Substring( 2 ), 16 ), type );
  				}
  				catch
  				{
  					return "That is not properly formatted.";
  				}
  			}
  			else
  			{
  				try
  				{
  					toSet = Convert.ChangeType( value, type );
  				}
  				catch
  				{
  					return "That is not properly formatted.";
  				}
  			}
  
  			if ( isSerial ) // mutate back
  				toSet = (Serial)((Int32)toSet);
  
  			constructed = toSet;
  			return null;
  		}
  
  		public static string SetDirect( Mobile from, object logObject, object obj, PropertyInfo prop, string givenName, object toSet, bool shouldLog )
  		{
  			try
  			{
  				if ( toSet is AccessLevel )
  				{
  					AccessLevel newLevel = (AccessLevel) toSet;
  					AccessLevel reqLevel = AccessLevel.Administrator;
  
  					if ( newLevel == AccessLevel.Administrator )
  						reqLevel = AccessLevel.Developer;
  					else if ( newLevel >= AccessLevel.Developer )
  						reqLevel = AccessLevel.Owner;
  
  					if ( from.AccessLevel < reqLevel )
  						return "You do not have access to that level.";
  				}
  
  				if ( shouldLog )
  					CommandLogging.LogChangeProperty( from, logObject, givenName, toSet == null ? "(-null-)" : toSet.ToString() );
  
  				prop.SetValue( obj, toSet, null );
  				return "Property has been set.";
  			}
  			catch
  			{
  				return "An exception was caught, the property may not be set.";
  			}
  		}
  
  		public static string SetDirect( object obj, PropertyInfo prop, object toSet )
  		{
  			try
  			{
  				if ( toSet is AccessLevel )
  				{
  					return "You do not have access to that level.";
  				}
  
  				prop.SetValue( obj, toSet, null );
  				return "Property has been set.";
  			}
  			catch
  			{
  				return "An exception was caught, the property may not be set.";
  			}
  		}
  
  		public static string InternalSetValue( Mobile from, object logobj, object o, PropertyInfo p, string pname, string value, bool shouldLog )
  		{
  			object toSet = null;
  			string result = ConstructFromString( p.PropertyType, o, value, ref toSet );
  
  			if ( result != null )
  				return result;
  
  			return SetDirect( from, logobj, o, p, pname, toSet, shouldLog );
  		}
  
  		public static string InternalSetValue( object o, PropertyInfo p, string value )
  		{
  			object toSet = null;
  			string result = ConstructFromString( p.PropertyType, o, value, ref toSet );
  
  			if ( result != null )
  				return result;
  
  			return SetDirect( o, p, toSet );
  		}
  	}
  }
  
  namespace Server
  {
  	public abstract class PropertyException : ApplicationException
  	{
  		protected Property m_Property;
  
  		public Property Property
  		{
  			get { return m_Property; }
  		}
  
  		public PropertyException( Property property, string message )
  			: base( message )
  		{
  			m_Property = property;
  		}
  	}
  
  	public abstract class BindingException : PropertyException
  	{
  		public BindingException( Property property, string message )
  			: base( property, message )
  		{
  		}
  	}
  
  	public sealed class NotYetBoundException : BindingException
  	{
  		public NotYetBoundException( Property property )
  			: base( property, String.Format( "Property has not yet been bound." ) )
  		{
  		}
  	}
  
  	public sealed class AlreadyBoundException : BindingException
  	{
  		public AlreadyBoundException( Property property )
  			: base( property, String.Format( "Property has already been bound." ) )
  		{
  		}
  	}
  
  	public sealed class UnknownPropertyException : BindingException
  	{
  		public UnknownPropertyException( Property property, string current )
  			: base( property, String.Format( "Property '{0}' not found.", current ) )
  		{
  		}
  	}
  
  	public sealed class ReadOnlyException : BindingException
  	{
  		public ReadOnlyException( Property property )
  			: base( property, "Property is read-only." )
  		{
  		}
  	}
  
  	public sealed class WriteOnlyException : BindingException
  	{
  		public WriteOnlyException( Property property )
  			: base( property, "Property is write-only." )
  		{
  		}
  	}
  
  	public abstract class AccessException : PropertyException
  	{
  		public AccessException( Property property, string message )
  			: base( property, message )
  		{
  		}
  	}
  
  	public sealed class InternalAccessException : AccessException
  	{
  		public InternalAccessException( Property property )
  			: base( property, "Property is internal." )
  		{
  		}
  	}
  
  	public abstract class ClearanceException : AccessException
  	{
  		protected AccessLevel m_PlayerAccess;
  		protected AccessLevel m_NeededAccess;
  
  		public AccessLevel PlayerAccess
  		{
  			get { return m_PlayerAccess; }
  		}
  
  		public AccessLevel NeededAccess
  		{
  			get { return m_NeededAccess; }
  		}
  
  		public ClearanceException( Property property, AccessLevel playerAccess, AccessLevel neededAccess, string accessType )
  			: base( property, string.Format(
  				"You must be at least {0} to {1} this property.",
  				Mobile.GetAccessLevelName( neededAccess ),
  				accessType
  			) )
  		{
  		}
  	}
  
  	public sealed class ReadAccessException : ClearanceException
  	{
  		public ReadAccessException( Property property, AccessLevel playerAccess, AccessLevel neededAccess )
  			: base( property, playerAccess, neededAccess, "read" )
  		{
  		}
  	}
  
  	public sealed class WriteAccessException : ClearanceException
  	{
  		public WriteAccessException( Property property, AccessLevel playerAccess, AccessLevel neededAccess )
  			: base( property, playerAccess, neededAccess, "write" )
  		{
  		}
  	}
  
  	public sealed class Property
  	{
  		private string m_Binding;
  
  		private PropertyInfo[] m_Chain;
  		private PropertyAccess m_Access;
  
  		public string Binding
  		{
  			get { return m_Binding; }
  		}
  
  		public bool IsBound
  		{
  			get { return ( m_Chain != null ); }
  		}
  
  		public PropertyAccess Access
  		{
  			get { return m_Access; }
  		}
  
  		public PropertyInfo[] Chain
  		{
  			get
  			{
  				if ( !IsBound )
  					throw new NotYetBoundException( this );
  
  				return m_Chain;
  			}
  		}
  
  		public Type Type
  		{
  			get
  			{
  				if ( !IsBound )
  					throw new NotYetBoundException( this );
  
  				return m_Chain[m_Chain.Length - 1].PropertyType;
  			}
  		}
  
  		public bool CheckAccess( Mobile from )
  		{
  			if ( !IsBound )
  				throw new NotYetBoundException( this );
  
  			for ( int i = 0; i < m_Chain.Length; ++i )
  			{
  				PropertyInfo prop = m_Chain[i];
  
  				bool isFinal = ( i == ( m_Chain.Length - 1 ) );
  
  				PropertyAccess access = m_Access;
  
  				if ( !isFinal )
  					access |= PropertyAccess.Read;
  
  				CPA security = Properties.GetCPA( prop );
  
  				if ( security == null )
  
gt: 					throw new InternalAccessException( this );