//
// System.Data.Common.DbDataPermission.cs
//
// Authors:
//	Rodrigo Moya (rodrigo@ximian.com)
//	Tim Coleman (tim@timcoleman.com)
//	Sebastien Pouliot  <sebastien@ximian.com>
//
// (C) Ximian, Inc
// Copyright (C) Tim Coleman, 2002-2003
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System.Collections;
using System.Security;
using System.Security.Permissions;

namespace System.Data.Common {

	[Serializable]
	public abstract class DBDataPermission : CodeAccessPermission, IUnrestrictedPermission {

		#region Fields

		private const int version = 1;

		private bool allowBlankPassword;
		private PermissionState state;
		private Hashtable _connections;

		#endregion // Fields

		#region Constructors

#if NET_2_0
		[Obsolete ("use DBDataPermission (PermissionState.None)", true)]
#endif
		protected DBDataPermission () 
			: this (PermissionState.None)
		{
		}

		protected DBDataPermission (DBDataPermission permission)
		{
			if (permission == null)
				throw new ArgumentNullException ("permission");

			state = permission.state;
			if (state != PermissionState.Unrestricted) {
				allowBlankPassword = permission.allowBlankPassword;
				_connections = (Hashtable) permission._connections.Clone ();
			}
		}

		protected DBDataPermission (DBDataPermissionAttribute permissionAttribute)
		{
			if (permissionAttribute == null)
				throw new ArgumentNullException ("permissionAttribute");

			_connections = new Hashtable ();
			if (permissionAttribute.Unrestricted) {
				state = PermissionState.Unrestricted;
			}
			else {
				state = PermissionState.None;
				allowBlankPassword = permissionAttribute.AllowBlankPassword;
				if (permissionAttribute.ConnectionString.Length > 0) {
					Add (permissionAttribute.ConnectionString, permissionAttribute.KeyRestrictions, 
						permissionAttribute.KeyRestrictionBehavior);
				}
			}
		}

		protected DBDataPermission (PermissionState state) 
		{
			this.state = PermissionHelper.CheckPermissionState (state, true);
			_connections = new Hashtable ();
		}

#if NET_2_0
		[Obsolete ("use DBDataPermission (PermissionState.None)", true)]
		protected 
#else
		public
#endif
		DBDataPermission (PermissionState state, bool allowBlankPassword)
			: this (state)
		{
			this.allowBlankPassword = allowBlankPassword;
		}

		#endregion // Constructors

		#region Properties

		public bool AllowBlankPassword {
			get { return allowBlankPassword; }
			set { allowBlankPassword = value; }
		}
		
		#endregion // Properties

		#region Methods

		public virtual void Add (string connectionString, string restrictions, KeyRestrictionBehavior behavior)
		{
			state = PermissionState.None;
			_connections [connectionString] = new object [2] { restrictions, behavior };
		}

		protected void Clear ()
		{
			_connections.Clear ();
		}

		public override IPermission Copy () 
		{
			DBDataPermission dbdp = CreateInstance ();
			dbdp.allowBlankPassword = this.allowBlankPassword;
			dbdp._connections = (Hashtable) this._connections.Clone ();
			return dbdp;
		}

		protected virtual DBDataPermission CreateInstance ()
		{
			return (DBDataPermission) Activator.CreateInstance (this.GetType (), new object [1] { PermissionState.None });
		}

		public override void FromXml (SecurityElement securityElement) 
		{
			PermissionHelper.CheckSecurityElement (securityElement, "securityElement", version, version);
			// Note: we do not (yet) care about the return value 
			// as we only accept version 1 (min/max values)

			state = (PermissionHelper.IsUnrestricted (securityElement) ? 
				PermissionState.Unrestricted : PermissionState.None);

			allowBlankPassword = false;
			string blank = securityElement.Attribute ("AllowBlankPassword");
			if (blank != null) {
#if NET_2_0
				// avoid possible exceptions with Fx 2.0
				if (!Boolean.TryParse (blank, out allowBlankPassword))
					allowBlankPassword = false;
#else
				try {
					allowBlankPassword = Boolean.Parse (blank);
				}
				catch {
					allowBlankPassword = false;
				}
#endif
			}

			if (securityElement.Children != null) {
				foreach (SecurityElement child in securityElement.Children) {
					string connect = child.Attribute ("ConnectionString");
					string restricts = child.Attribute ("KeyRestrictions");
					KeyRestrictionBehavior behavior = (KeyRestrictionBehavior) Enum.Parse (
						typeof (KeyRestrictionBehavior), child.Attribute ("KeyRestrictionBehavior"));

					if ((connect != null) && (connect.Length > 0))
						Add (connect, restricts, behavior);
				}
			}
		}

		public override IPermission Intersect (IPermission target) 
		{
			// FIXME: restrictions not completely implemented - nor documented
			DBDataPermission dbdp = Cast (target);
			if (dbdp == null)
				return null;
			if (IsUnrestricted ()) {
				if (dbdp.IsUnrestricted ()) {
					DBDataPermission u = CreateInstance ();
					u.state = PermissionState.Unrestricted;
					return u;
				}
				return dbdp.Copy ();
			}
			if (dbdp.IsUnrestricted ())
				return Copy ();
			if (IsEmpty () || dbdp.IsEmpty ())
				return null;

			DBDataPermission p = CreateInstance ();
			p.allowBlankPassword = (allowBlankPassword && dbdp.allowBlankPassword);
			foreach (DictionaryEntry de in _connections) {
				object o = dbdp._connections [de.Key];
				if (o != null)
					p._connections.Add (de.Key, de.Value);
			}
			return (p._connections.Count > 0) ? p : null;
		}

		public override bool IsSubsetOf (IPermission target) 
		{
			// FIXME: restrictions not completely implemented - nor documented
			DBDataPermission dbdp = Cast (target);
			if (dbdp == null)
				return IsEmpty ();
			if (dbdp.IsUnrestricted ())
				return true;
			if (IsUnrestricted ())
				return dbdp.IsUnrestricted ();

			if (allowBlankPassword && !dbdp.allowBlankPassword)
				return false;
			if (_connections.Count > dbdp._connections.Count)
				return false;

			foreach (DictionaryEntry de in _connections) {
				object o = dbdp._connections [de.Key];
				if (o == null)
					return false;
				// FIXME: this is a subset of what is required
				// it seems that we must process both the connect string
				// and the restrictions - but this has other effects :-/
			}
			return true;
		}

		public bool IsUnrestricted () 
		{
			return (state == PermissionState.Unrestricted);
		}

		public override SecurityElement ToXml ()
		{
			SecurityElement se = PermissionHelper.Element (this.GetType (), version);
			if (IsUnrestricted ()) {
				se.AddAttribute ("Unrestricted", "true");
			}
			else {
				// attribute is present for both True and False
				se.AddAttribute ("AllowBlankPassword", allowBlankPassword.ToString ());
				foreach (DictionaryEntry de in _connections) {
					SecurityElement child = new SecurityElement ("add");
					child.AddAttribute ("ConnectionString", (string) de.Key);
					object[] restrictionsInfo = (object[]) de.Value;
					child.AddAttribute ("KeyRestrictions", (string) restrictionsInfo [0]);
					KeyRestrictionBehavior krb = (KeyRestrictionBehavior) restrictionsInfo [1];
					child.AddAttribute ("KeyRestrictionBehavior", krb.ToString ());
					se.AddChild (child);
				}
			}
			return se;
		}

		public override IPermission Union (IPermission target) 
		{
			// FIXME: restrictions not completely implemented - nor documented
			DBDataPermission dbdp = Cast (target);
			if (dbdp == null)
				return Copy ();
			if (IsEmpty () && dbdp.IsEmpty ())
				return Copy ();

			DBDataPermission p = CreateInstance ();
			if (IsUnrestricted () || dbdp.IsUnrestricted ()) {
				p.state = PermissionState.Unrestricted;
			}
			else {
				p.allowBlankPassword = (allowBlankPassword || dbdp.allowBlankPassword);
				p._connections = new Hashtable (_connections.Count + dbdp._connections.Count);
				foreach (DictionaryEntry de in _connections)
					p._connections.Add (de.Key, de.Value);
				// don't duplicate
				foreach (DictionaryEntry de in dbdp._connections)
					p._connections [de.Key] = de.Value;
			}
			return p;
		}

		// helpers

		private bool IsEmpty ()
		{
			return ((state != PermissionState.Unrestricted) && (_connections.Count == 0));
		}

		private DBDataPermission Cast (IPermission target)
		{
			if (target == null)
				return null;

			DBDataPermission dbdp = (target as DBDataPermission);
			if (dbdp == null) {
				PermissionHelper.ThrowInvalidPermission (target, this.GetType ());
			}

			return dbdp;
		}

		#endregion // Methods
	}
}
