//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
//     Copyright (c) Telligent Systems Corporation.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using System;
using System.Collections;
using System.ComponentModel;
using System.Web.Caching;
using System.Xml;
using CommunityServer.Configuration;

namespace CommunityServer.Components
{

	#region Delegates
	//Do we want one single delegate or a custom one for each type
	//public delegate void CSEventHandler(object sender, CSEventArgs e);
	public delegate void CSUserEventHandler(User user, CSEventArgs e);
	public delegate void CSPostEventHandler(IContent content, CSPostEventArgs e);
	public delegate void CSPostAttachmentEventHandler(IContent content, PostAttachment attachment, CSPostEventArgs e);
	public delegate void CSSectionEventHandler(Section section, CSEventArgs e);
	public delegate void CSGroupEventHandler(Group group, CSEventArgs e);
	public delegate void CSRateEventHandler(Rating rating, CSEventArgs e);
	public delegate void CSFavoriteEventHandler(Favorite favorite, CSEventArgs e);
	public delegate void CSCategoryEventHandler(PostCategory category, CSEventArgs e);
	public delegate void CSExceptionHandler(CSException csEx, CSEventArgs e);
	public delegate void CSUserInvitationHandler(UserInvitation invitation, CSEventArgs e);
	public delegate void CSAcceptUserInvitationHandler(UserInvitation invitation, CSAcceptUserInvitationEventArgs e);
	#endregion

	/// <summary>
	/// Summary description for CSApplication.
	/// </summary>
	public class CSApplication
	{
		#region private members
		private EventHandlerList Events = new EventHandlerList();
		private static readonly object sync = new object();
		private Hashtable modules = new Hashtable();
		#endregion

		#region Event Keys (static)
		private static object EventAuthorizePost = new object();
		private static object EventPrePostUpdate = new object();
		private static object EventPreProcessPost = new object();
		private static object EventPostPostUpdate = new object();
		private static object EventPrePostAttachmentUpdate = new object();
		private static object EventPostPostAttachmentUpdate = new object();
		private static object EventPreRenderPost = new object();

		private static object EventPreUserUpdate = new object();
		private static object EventPostUserUpdate = new object();
		private static object EventUserRemove = new object();
		private static object EventUserKnown = new object();
		private static object EventUserValidated = new object();

		private static object EventPreSectionUpdate = new object();
		private static object EventPostSectionUpdate = new object();

		private static object EventPreSectionGroupUpdate = new object();
		private static object EventPostSectionGroupUpdate = new object();

		private static object EventRate = new object();

		private static object EventFavorite = new object();

		private static object EventCategory = new object();

		private static object EventUnhandledException = new object();

        private static object PreEventSearch = new object();
        private static object PostEventSearch = new object();

		private static object EventPreUserInvitationUpdate = new object();
		private static object EventPostUserInvitationUpdate = new object();
		private static object EventAcceptUserInvitation = new object();

        private static object ConfigurationInitialized = new object();
		#endregion

		#region cnstr
		private CSApplication()
		{
		}

		internal static CSApplication Instance()
		{
			const string key = "CSApplication";
			CSApplication app = CSCache.Get(key) as CSApplication;
			if(app == null)
			{
				lock(sync)
				{
					app = CSCache.Get(key) as CSApplication;
					if(app == null)
					{
						CSConfiguration config = CSContext.Current.Config;

						XmlNode node = config.GetConfigSection("CommunityServer/CSModules");
						app = new CSApplication();
						if(node != null)
						{
							foreach(XmlNode n in node.ChildNodes)
							{
								if(n.NodeType != XmlNodeType.Comment)
								{
									switch(n.Name)
									{
										case "clear":
											app.modules.Clear();
											break;
										case "remove":
                                            XmlAttribute removeNameAtt = n.Attributes["name"];
                                            string removeName = removeNameAtt == null ? null : removeNameAtt.Value;

                                            if(!Globals.IsNullorEmpty(removeName) && app.modules.ContainsKey(removeName))
                                            {
                                                app.modules.Remove(removeName);    
                                            }

											break;
										case "add":

                                            XmlAttribute en = n.Attributes["enabled"];
                                            if(en != null && en.Value == "false")
                                                continue;
										
                                            XmlAttribute nameAtt = n.Attributes["name"];
                                            XmlAttribute typeAtt = n.Attributes["type"];
											string name = nameAtt == null ? null : nameAtt.Value;
											string itype = typeAtt == null ? null : typeAtt.Value;
                                            
                                            if(Globals.IsNullorEmpty(name))
                                            {
                                                EventLogs.Warn(string.Format("A CSModule could not be loaded. The name was not defined. Type {0}", itype), "CSModules", 654, CSContext.Current.SettingsID);
                                                continue;
                                            }

                                            if(Globals.IsNullorEmpty(itype))
                                            {
                                                EventLogs.Warn(string.Format("A CSModule ({0}) could not be loaded. No type was defined", name), "CSModules", 655, CSContext.Current.SettingsID);
                                                continue;
                                            }

											Type type = Type.GetType(itype);

                                            if(type == null)
                                            {
                                                EventLogs.Warn(string.Format("A CSModule ({0}) could not be loaded. The type {1} does not exist", name,itype), "CSModules", 656, CSContext.Current.SettingsID);
                                                continue;
                                            }

											ICSModule mod = Activator.CreateInstance(type) as ICSModule;

											if(mod == null)
											{
                                                EventLogs.Warn(string.Format("A CSModule ({0}) could not be loaded. The type {1} could not be instantiated", name,itype), "CSModules", 657, CSContext.Current.SettingsID);
                                                continue;
                                            }

											mod.Init(app, n);
											app.modules.Add(name,mod);


											break;

									}
								}
							}
						}
						CacheDependency dep = new CacheDependency(null, new string[]{CSConfiguration.CacheKey});
						CSCache.Max(key, app,dep);
					}

					
				}
			}
			return app;
		}
		#endregion

		#region Post Events

		#region Execute Events

		internal void ExecuteAuthorizePost()
		{
			ExecuteUserEvent(EventAuthorizePost,CSContext.Current.User);
		}

		internal void ExecutePrePostEvents(IContent content, ObjectState state, ApplicationType appType)
		{
			ExecutePostEvent(EventPreProcessPost,content,state,appType);
		}

		internal void ExecutePrePostUpdateEvents(IContent content, ObjectState state, ApplicationType appType)
		{
			ExecutePostEvent(EventPrePostUpdate,content,state,appType);
		}

		internal void ExecutePostPostUpdateEvents(IContent content, ObjectState state, ApplicationType appType)
		{
			ExecutePostEvent(EventPostPostUpdate,content,state,appType);
		}

		internal void ExecutePrePostAttachmentUpdateEvents(IContent content, PostAttachment attachment, ObjectState state, ApplicationType appType)
		{
			ExecutePostAttachmentEvent(EventPrePostAttachmentUpdate,content,attachment,state,appType);
		}

		internal void ExecutePostPostAttachmentUpdateEvents(IContent content, PostAttachment attachment, ObjectState state, ApplicationType appType)
		{
			ExecutePostAttachmentEvent(EventPostPostAttachmentUpdate,content,attachment,state,appType);
		}

		internal void ExecutePrePostRender(IContent content, ApplicationType appType, PostTarget target)
		{
			ExecutePostEvent(EventPreRenderPost,content,ObjectState.Runtime,appType,target);
		}

        protected void ExecutePostEvent(object EventKey, IContent content,ObjectState state, ApplicationType appType)
        {
            ExecutePostEvent(EventKey,content,state,appType,PostTarget.Unknown);
        }

		protected void ExecutePostEvent(object EventKey, IContent content,ObjectState state, ApplicationType appType, PostTarget target)
		{
			CSPostEventHandler handler = Events[EventKey] as CSPostEventHandler;
			if (handler != null)
			{
				handler(content, new CSPostEventArgs(state,appType, content.FormattedBody, target));
			}
		}

		protected void ExecutePostAttachmentEvent(object EventKey, IContent content, PostAttachment attachment,ObjectState state, ApplicationType appType)
		{
			CSPostAttachmentEventHandler handler = Events[EventKey] as CSPostAttachmentEventHandler;
			if (handler != null)
			{
				handler(content, attachment, new CSPostEventArgs(state,appType, content.FormattedBody));
			}
		}

		#endregion

		#region Events
		/// <summary>
		/// Event raised before a user accesses a page which can be used to create content
		/// </summary>
		public event CSUserEventHandler AuthorizePost
		{
			add{Events.AddHandler(EventAuthorizePost, value);}
			remove{Events.RemoveHandler(EventAuthorizePost, value);}
		}

		/// <summary>
		/// Event raised before any post processing takes place
		/// </summary>
		public event CSPostEventHandler PreProcessPost
		{
			add{Events.AddHandler(EventPreProcessPost, value);}
			remove{Events.RemoveHandler(EventPreProcessPost, value);}
		}

		/// <summary>
		/// Fires after PreProcessPost but before the post change is commited to the datastore
		/// </summary>
		public event CSPostEventHandler PrePostUpdate
		{
			add{Events.AddHandler(EventPrePostUpdate, value);}
			remove{Events.RemoveHandler(EventPrePostUpdate, value);}
		}

		/// <summary>
		/// Fires after a post change is commited to the datastore
		/// </summary>
		public event CSPostEventHandler PostPostUpdate
		{
			add{Events.AddHandler(EventPostPostUpdate, value);}
			remove{Events.RemoveHandler(EventPostPostUpdate, value);}
		}

		/// <summary>
		/// Fires after the post change is commited but before the attachement is commited
		/// </summary>
		public event CSPostAttachmentEventHandler PrePostAttachmentUpdate
		{
			add{Events.AddHandler(EventPrePostAttachmentUpdate, value);}
			remove{Events.RemoveHandler(EventPrePostAttachmentUpdate, value);}
		}

		/// <summary>
		/// Fires after a post change and attachment are commited to the datastore
		/// </summary>
		public event CSPostAttachmentEventHandler PostPostAttachmentUpdate
		{
			add{Events.AddHandler(EventPostPostAttachmentUpdate, value);}
			remove{Events.RemoveHandler(EventPostPostAttachmentUpdate, value);}
		}

		/// <summary>
		/// Event raised before an individual post is rendered
		/// </summary>
		public event CSPostEventHandler PreRenderPost
		{
			add{Events.AddHandler(EventPreRenderPost, value);}
			remove{Events.RemoveHandler(EventPreRenderPost, value);}
		}

		#endregion

		#endregion

		#region PostCategory Events

		internal void ExecuteCategoryUpdated(PostCategory category)
		{
			ExecuteCategoryEvent(EventCategory, category, ObjectState.Update, ApplicationType.Unknown);
		}

		internal void ExecuteCategoryRemoved(PostCategory category)
		{
			ExecuteCategoryEvent(EventCategory, category, ObjectState.Delete, ApplicationType.Unknown);
		}

		protected void ExecuteCategoryEvent(object EventKey, PostCategory category, ObjectState state, ApplicationType appType)
		{
			CSCategoryEventHandler handler = Events[EventKey] as CSCategoryEventHandler;
			if (handler != null)
			{
				handler(category, new CSEventArgs(state, appType));
			}
		}

		/// <summary>
		/// Fires after a PostCategory is updated or deleted (but not created)
		/// </summary>
		public event CSCategoryEventHandler Category
		{
			add{Events.AddHandler(EventCategory, value);}
			remove{Events.RemoveHandler(EventCategory, value);}
		}

		#endregion

		#region Rating Events
		internal void ExecuteRateEvents(Rating rating, ApplicationType appType)
		{
			ExecuteRateEvent(EventRate, rating, ObjectState.None, appType);
		}

		protected void ExecuteRateEvent(object EventKey, Rating rating, ObjectState state, ApplicationType appType)
		{
			CSRateEventHandler handler = Events[EventKey] as CSRateEventHandler;
			if (handler != null)
			{
				handler(rating, new CSEventArgs(state, appType));
			}
		}

		/// <summary>
		/// Fires after an item is rated
		/// </summary>
		public event CSRateEventHandler Rate
		{
			add{Events.AddHandler(EventRate, value);}
			remove{Events.RemoveHandler(EventRate, value);}
		}
		#endregion

		#region Favorite Events
		internal void ExecuteFavoriteAdded(Favorite favorite, ApplicationType appType)
		{
			ExecuteFavoriteEvent(EventFavorite, favorite, ObjectState.Create, appType);
		}

		internal void ExecuteFavoriteRemoved(Favorite favorite, ApplicationType appType)
		{
			ExecuteFavoriteEvent(EventFavorite, favorite, ObjectState.Delete, appType);
		}

		protected void ExecuteFavoriteEvent(object EventKey, Favorite favorite, ObjectState state, ApplicationType appType)
		{
			CSFavoriteEventHandler handler = Events[EventKey] as CSFavoriteEventHandler;
			if (handler != null)
			{
				handler(favorite, new CSEventArgs(state, appType));
			}
		}

		/// <summary>
		/// Fires after an item is added to favorites
		/// </summary>
		public event CSFavoriteEventHandler Favorite
		{
			add{Events.AddHandler(EventFavorite, value);}
			remove{Events.RemoveHandler(EventFavorite, value);}
		}
		#endregion

		#region UserInvitation Events

		internal void ExecutePreUserInvitationUpdate(UserInvitation invitation, ObjectState state)
		{
			CSUserInvitationHandler handler = Events[EventPreUserInvitationUpdate] as CSUserInvitationHandler;
			if (handler != null)
			{
				handler(invitation, new CSEventArgs(state, ApplicationType.Unknown));
			}
		}

		internal void ExecutePostUserInvitationUpdate(UserInvitation invitation, ObjectState state)
		{
			CSUserInvitationHandler handler = Events[EventPostUserInvitationUpdate] as CSUserInvitationHandler;
			if (handler != null)
			{
				handler(invitation, new CSEventArgs(state, ApplicationType.Unknown));
			}
		}

		internal void ExecuteUserInvitationAccepted(UserInvitation invitation, User userAccepted)
		{
			CSAcceptUserInvitationHandler handler = Events[EventAcceptUserInvitation] as CSAcceptUserInvitationHandler;
			if (handler != null)
			{
				handler(invitation, new CSAcceptUserInvitationEventArgs(ObjectState.None, ApplicationType.Unknown, userAccepted));
			}
		}

		/// <summary>
		/// Event raised before a UserInvitation change is committed to the datastore (create/update)
		/// </summary>
		public event CSUserInvitationHandler PreUserInvitationUpdate
		{
			add{Events.AddHandler(EventPreUserInvitationUpdate, value);}
			remove{Events.RemoveHandler(EventPreUserInvitationUpdate, value);}
		}

		/// <summary>
		/// Event raised after a UserInvitation chage is committed to the data store
		/// </summary>
		public event CSUserInvitationHandler PostUserInvitationUpdate
		{
			add{Events.AddHandler(EventPostUserInvitationUpdate, value);}
			remove{Events.RemoveHandler(EventPostUserInvitationUpdate, value);}
		}

		/// <summary>
		/// Event raised after a UserInvitation has been accepted
		/// </summary>
		public event CSAcceptUserInvitationHandler UserInvitationAccepted
		{
			add{Events.AddHandler(EventAcceptUserInvitation, value);}
			remove{Events.RemoveHandler(EventAcceptUserInvitation, value);}
		}

		#endregion

		#region User Events

		#region Execute Events
		
		internal void ExecuteUserValidated(User user)
		{
			ExecuteUserEvent(EventUserValidated,user);
		}

		internal void ExecuteUserKnown(User user)
		{
			ExecuteUserEvent(EventUserKnown,user);
		}

		internal void ExecutePreUserUpdate(User user, ObjectState state)
		{
			ExecuteUserEvent(EventPreUserUpdate,user,state,ApplicationType.Unknown);
		}

		internal void ExecutePostUserUpdate(User user, ObjectState state)
		{
			ExecuteUserEvent(EventPostUserUpdate,user,state,ApplicationType.Unknown);
		}

		internal void ExecuteUserRemove(User user)
		{
			ExecuteUserEvent(EventUserRemove,user,ObjectState.Delete,ApplicationType.Unknown);
		}

		protected void ExecuteUserEvent(object EventKey, User user)
		{
			ExecuteUserEvent(EventKey,user,ObjectState.None,ApplicationType.Unknown);
		}
		protected void ExecuteUserEvent(object EventKey, User user,ObjectState state, ApplicationType appType)
		{
			CSUserEventHandler handler = Events[EventKey] as CSUserEventHandler;
			if (handler != null)
			{
				handler(user, new CSEventArgs(state,appType));
			}
		}

		#endregion

		#region Events

		/// <summary>
		/// Fires after a user's credentials have been validated.
		/// </summary>
		public event CSUserEventHandler UserValidated
		{
			add{Events.AddHandler(EventUserValidated, value);}
			remove{Events.RemoveHandler(EventUserValidated, value);}
		}

		/// <summary>
		/// Fires once the current user has been identified. This user may still be anonymous.
		/// </summary>
		public event CSUserEventHandler UserKnown
		{
			add{Events.AddHandler(EventUserKnown, value);}
			remove{Events.RemoveHandler(EventUserKnown, value);}
		}

		/// <summary>
		/// Fires before a User is saved/updated to the datastore
		/// </summary>
		public event CSUserEventHandler PreUserUpdate
		{
			add{Events.AddHandler(EventPreUserUpdate, value);}
			remove{Events.RemoveHandler(EventPreUserUpdate, value);}
		}

		/// <summary>
		/// Fires after a User is saved/updated to the datastore
		/// </summary>
		public event CSUserEventHandler PostUserUpdate
		{
			add{Events.AddHandler(EventPostUserUpdate, value);}
			remove{Events.RemoveHandler(EventPostUserUpdate, value);}
		}

		/// <summary>
		/// Fires before a User is removed from the datastore.
		/// </summary>
		public event CSUserEventHandler UserRemove
		{
			add{Events.AddHandler(EventUserRemove, value);}
			remove{Events.RemoveHandler(EventUserRemove, value);}
		}

		#endregion

		#endregion

		#region Section Events

		internal void ExecutePreSectionUpdate(Section section, ObjectState state, ApplicationType appType)
		{
			CSSectionEventHandler handler = Events[EventPreSectionUpdate] as CSSectionEventHandler;
			if (handler != null)
			{
				handler(section, new CSEventArgs(state,appType));
			}
		}

		internal void ExecutePostSectionUpdate(Section section, ObjectState state, ApplicationType appType)
		{
			CSSectionEventHandler handler = Events[EventPostSectionUpdate] as CSSectionEventHandler;
			if (handler != null)
			{
				handler(section, new CSEventArgs(state,appType));
			}
		}


		/// <summary>
		/// Event raised before a section change is committed to the datastore (create/update)
		/// </summary>
		public event CSSectionEventHandler PreSectionUpdate
		{
			add{Events.AddHandler(EventPreSectionUpdate, value);}
			remove{Events.RemoveHandler(EventPreSectionUpdate, value);}
		}


		/// <summary>
		/// Event raised after a section chage is committed to the data store
		/// </summary>
		public event CSSectionEventHandler PostSectionUpdate
		{
			add{Events.AddHandler(EventPostSectionUpdate, value);}
			remove{Events.RemoveHandler(EventPostSectionUpdate, value);}
		}

		#endregion

		#region Group Events

		internal void ExecutePreSectionGroupUpdate(Group group, ObjectState state, ApplicationType appType)
		{
			CSGroupEventHandler handler = Events[EventPreSectionGroupUpdate] as CSGroupEventHandler;
			if (handler != null)
			{
				handler(group, new CSEventArgs(state,appType));
			}
		}

		internal void ExecutePostSectionGroupUpdate(Group group, ObjectState state, ApplicationType appType)
		{
			CSGroupEventHandler handler = Events[EventPostSectionGroupUpdate] as CSGroupEventHandler;
			if (handler != null)
			{
				handler(group, new CSEventArgs(state,appType));
			}
		}

		/// <summary>
		/// Event raised before a group chage is committed to the datastore (create/update)
		/// </summary>
		public event CSGroupEventHandler PreSectionGroupUpdate
		{
			add{Events.AddHandler(EventPreSectionGroupUpdate, value);}
			remove{Events.RemoveHandler(EventPreSectionGroupUpdate, value);}
		}

		/// <summary>
		/// Event raised after a group chage is committed to the data store
		/// </summary>
		public event CSGroupEventHandler PostSectionGroupUpdate
		{
			add{Events.AddHandler(EventPostSectionGroupUpdate, value);}
			remove{Events.RemoveHandler(EventPostSectionGroupUpdate, value);}
		}

		#endregion

		#region Exceptions
		/// <summary>
		/// Event raised before a group chage is committed to the datastore (create/update)
		/// </summary>
		public event CSExceptionHandler CSException
		{
			add{Events.AddHandler(EventUnhandledException, value);}
			remove{Events.RemoveHandler(EventUnhandledException, value);}
		}

		internal void ExecuteCSExcetion(CSException csEx)
		{
			CSExceptionHandler handler = Events[EventUnhandledException] as CSExceptionHandler;
			if (handler != null)
			{
				handler(csEx,new CSEventArgs());
			}
		}

		#endregion

        #region Search

        public event EventHandler PreSearch
        {
            add{Events.AddHandler(PreEventSearch, value);}
            remove{Events.RemoveHandler(PreEventSearch, value);}
        }

        public event EventHandler PostSearch
        {
            add{Events.AddHandler(PostEventSearch, value);}
            remove{Events.RemoveHandler(PostEventSearch, value);}
        }

        internal void ExecutePreSearch(SearchQuery query)
        {
            EventHandler handler = Events[PreEventSearch] as EventHandler;
            if(handler != null)
            {
                handler(query,EventArgs.Empty);
            }
        }

        internal void ExecutePostSearch(SearchResultSet results)
        {
            EventHandler handler = Events[PostEventSearch] as EventHandler;
            if(handler != null)
            {
                handler(results,EventArgs.Empty);
            }
        }

        #endregion 

        public event EventHandler PostConfigurationInitialized
        {
            add{Events.AddHandler(ConfigurationInitialized, value);}
            remove{Events.RemoveHandler(ConfigurationInitialized, value);}
        }

        internal void ExecuteConfigurationInitilized(object configurationObject)
        {
            if(configurationObject != null)
            {
                EventHandler handler = Events[ConfigurationInitialized] as EventHandler;
                if(handler != null)
                {
                    handler(configurationObject,EventArgs.Empty);
                }
            }
        }

	}
}
