using System;
using System.Collections.Generic;
using System.Reflection;
using System.ComponentModel;
using System.Runtime.Serialization;
using Csla.Properties;
namespace Csla
{
  /// 
  /// This is a base class from which readonly business classes
  /// can be derived.
  /// 
  /// 
  /// This base class only supports data retrieve, not updating or
  /// deleting. Any business classes derived from this base class
  /// should only implement readonly properties.
  /// 
  /// Type of the business object.
  [Serializable()]
  public abstract class ReadOnlyBase : ICloneable, Core.IReadOnlyObject, Csla.Security.IAuthorizeReadWrite
    where T : ReadOnlyBase
  {
    #region Object ID Value
    /// 
    /// Override this method to return a unique identifying
    /// vlaue for this object.
    /// 
    /// 
    /// If you can not provide a unique identifying value, it
    /// is best if you can generate such a unique value (even
    /// temporarily). If you can not do that, then return 
    ///  and then manually override the
    /// ,  and
    ///  methods in your business object.
    /// 
    protected abstract object GetIdValue();
    #endregion
    #region System.Object Overrides
    /// 
    /// Compares this object for equality with another object, using
    /// the results of  to determine
    /// equality.
    /// 
    /// The object to be compared.
    public override bool Equals(object obj)
    {
      if (obj is T)
      {
        object id = GetIdValue();
        if (id == null)
          throw new ArgumentException(Resources.GetIdValueCantBeNull);
        return id.Equals(((T)obj).GetIdValue());
      }
      else
        return false;
    }
    /// 
    /// Returns a hash code value for this object, based on
    /// the results of .
    /// 
    public override int GetHashCode()
    {
      object id = GetIdValue();
      if (id == null)
        throw new ArgumentException(Resources.GetIdValueCantBeNull);
      return id.GetHashCode();
    }
    /// 
    /// Returns a text representation of this object by
    /// returning the  value
    /// in text form.
    /// 
    public override string ToString()
    {
      object id = GetIdValue();
      if (id == null)
        throw new ArgumentException(Resources.GetIdValueCantBeNull);
      return id.ToString();
    }
    #endregion
    #region Constructors
    /// 
    /// Creates an instance of the object.
    /// 
    protected ReadOnlyBase()
    {
      Initialize();
      AddInstanceAuthorizationRules();
      if (!Security.SharedAuthorizationRules.RulesExistFor(this.GetType()))
      {
        lock (this.GetType())
        {
          if (!Security.SharedAuthorizationRules.RulesExistFor(this.GetType()))
            AddAuthorizationRules();
        }
      }
    }
    #endregion
    #region Initialize
    /// 
    /// Override this method to set up event handlers so user
    /// code in a partial class can respond to events raised by
    /// generated code.
    /// 
    protected virtual void Initialize()
    { /* allows subclass to initialize events before any other activity occurs */ }
    #endregion
    #region Authorization
    [NotUndoable()]
    [NonSerialized()]
    private Dictionary _readResultCache;
    [NotUndoable()]
    [NonSerialized()]
    private System.Security.Principal.IPrincipal _lastPrincipal;
    [NotUndoable()]
    [NonSerialized()]
    private Security.AuthorizationRules _authorizationRules;
    /// 
    /// Override this method to add authorization
    /// rules for your object's properties.
    /// 
    protected virtual void AddInstanceAuthorizationRules()
    {
    }
    /// 
    /// Override this method to add per-type
    /// authorization rules for your type's properties.
    /// 
    /// 
    /// AddSharedAuthorizationRules is automatically called by CSLA .NET
    /// when your object should associate per-type authorization roles
    /// with its properties.
    /// 
    protected virtual void AddAuthorizationRules()
    {
    }
    /// 
    /// Provides access to the AuthorizationRules object for this
    /// object.
    /// 
    /// 
    /// Use this object to add a list of allowed and denied roles for
    /// reading and writing properties of the object. Typically these
    /// values are added once when the business object is instantiated.
    /// 
    protected Security.AuthorizationRules AuthorizationRules
    {
      get
      {
        if (_authorizationRules == null)
          _authorizationRules = new Security.AuthorizationRules(this.GetType());
        return _authorizationRules;
      }
    }
    /// 
    /// Returns  if the user is allowed to read the
    /// calling property.
    /// 
    ///  if read is allowed.
    /// Indicates whether a negative
    /// result should cause an exception.
    [System.Runtime.CompilerServices.MethodImpl(
      System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public bool CanReadProperty(bool throwOnFalse)
    {
      string propertyName =
        new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name.Substring(4);
      bool result = CanReadProperty(propertyName);
      if (throwOnFalse && result == false)
        throw new System.Security.SecurityException(
          string.Format("{0} ({1})",
          Resources.PropertyGetNotAllowed, propertyName));
      return result;
    }
    /// 
    /// Returns  if the user is allowed to read the
    /// calling property.
    /// 
    ///  if read is allowed.
    /// Name of the property to read.
    /// Indicates whether a negative
    /// result should cause an exception.
    public bool CanReadProperty(string propertyName, bool throwOnFalse)
    {
      bool result = CanReadProperty(propertyName);
      if (throwOnFalse && result == false)
        throw new System.Security.SecurityException(
          string.Format("{0} ({1})",
          Resources.PropertyGetNotAllowed, propertyName));
      return result;
    }
    /// 
    /// Returns  if the user is allowed to read the
    /// calling property.
    /// 
    ///  if read is allowed.
    public bool CanReadProperty()
    {
      string propertyName = 
        new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name.Substring(4);
      return CanReadProperty(propertyName);
    }
    /// 
    /// Returns  if the user is allowed to read the
    /// specified property.
    /// 
    /// Name of the property to read.
    ///  if read is allowed.
    /// 
    /// 
    /// If a list of allowed roles is provided then only users in those
    /// roles can read. If no list of allowed roles is provided then
    /// the list of denied roles is checked.
    /// 
    /// If a list of denied roles is provided then users in the denied
    /// roles are denied read access. All other users are allowed.
    /// 
    /// If neither a list of allowed nor denied roles is provided then
    /// all users will have read access.
    /// 
    /// 
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    public virtual bool CanReadProperty(string propertyName)
    {
      bool result = true;
      VerifyAuthorizationCache();
      if (_readResultCache.ContainsKey(propertyName))
      {
        // cache contains value - get cached value
        result = _readResultCache[propertyName];
      }
      else
      {
        if (AuthorizationRules.HasReadAllowedRoles(propertyName))
        {
          // some users are explicitly granted read access
          // in which case all other users are denied.
          if (!AuthorizationRules.IsReadAllowed(propertyName))
            result = false;
        }
        else if (AuthorizationRules.HasReadDeniedRoles(propertyName))
        {
          // some users are explicitly denied read access.
          if (AuthorizationRules.IsReadDenied(propertyName))
            result = false;
        }
        // store value in cache
        _readResultCache[propertyName] = result;
      }
      return result;
    }
    bool Csla.Security.IAuthorizeReadWrite.CanWriteProperty(string propertyName)
    {
      return false;
    }
    private void VerifyAuthorizationCache()
    {
      if (_readResultCache == null)
        _readResultCache = new Dictionary();
      if (!ReferenceEquals(Csla.ApplicationContext.User, _lastPrincipal))
      {
        // the principal has changed - reset the cache
        _readResultCache.Clear();
        _lastPrincipal = Csla.ApplicationContext.User;
      }
    }
    #endregion
    #region IClonable
    object ICloneable.Clone()
    {
      return GetClone();
    }
    /// 
    /// Creates a clone of the object.
    /// 
    /// A new object containing the exact data of the original object.
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    public virtual object GetClone()
    {
      return Core.ObjectCloner.Clone(this);
    }
    /// 
    /// Creates a clone of the object.
    /// 
    /// 
    /// A new object containing the exact data of the original object.
    /// 
    public T Clone()
    {
      return (T)GetClone();
    }
    #endregion
    #region Data Access
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    private void DataPortal_Create(object criteria)
    {
      throw new NotSupportedException(Resources.CreateNotSupportedException);
    }
    /// 
    /// Override this method to allow retrieval of an existing business
    /// object based on data in the database.
    /// 
    /// An object containing criteria values to identify the object.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    protected virtual void DataPortal_Fetch(object criteria)
    {
      throw new NotSupportedException(Resources.FetchNotSupportedException);
    }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    private void DataPortal_Update()
    {
      throw new NotSupportedException(Resources.UpdateNotSupportedException);
    }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    private void DataPortal_Delete(object criteria)
    {
      throw new NotSupportedException(Resources.DeleteNotSupportedException);
    }
    /// 
    /// Called by the server-side DataPortal prior to calling the 
    /// requested DataPortal_xyz method.
    /// 
    /// The DataPortalContext object passed to the DataPortal.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e)
    {
    }
    /// 
    /// Called by the server-side DataPortal after calling the 
    /// requested DataPortal_xyz method.
    /// 
    /// The DataPortalContext object passed to the DataPortal.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void DataPortal_OnDataPortalInvokeComplete(DataPortalEventArgs e)
    {
    }
    /// 
    /// Called by the server-side DataPortal if an exception
    /// occurs during data access.
    /// 
    /// The DataPortalContext object passed to the DataPortal.
    /// The Exception thrown during data access.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void DataPortal_OnDataPortalException(DataPortalEventArgs e, Exception ex)
    {
    }
    #endregion
    #region Serialization Notification
    [OnDeserialized()]
    private void OnDeserializedHandler(StreamingContext context)
    {
      OnDeserialized(context);
      AddInstanceAuthorizationRules();
      if (!Security.SharedAuthorizationRules.RulesExistFor(this.GetType()))
      {
        lock (this.GetType())
        {
          if (!Security.SharedAuthorizationRules.RulesExistFor(this.GetType()))
            AddAuthorizationRules();
        }
      }
    }
    /// 
    /// This method is called on a newly deserialized object
    /// after deserialization is complete.
    /// 
    /// Serialization context object.
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void OnDeserialized(StreamingContext context)
    {
      // do nothing - this is here so a subclass
      // could override if needed
    }
    #endregion
  }
}