using System;
using System.Collections.Generic;
using System.Threading;

namespace HttpServer.Sessions
{
    /// <summary>
    /// Session store using memory for each session.
    /// </summary>
    public class MemorySessionStore : IHttpSessionStore
    {
        private readonly IDictionary<string, IHttpSession> _sessions = new Dictionary<string, IHttpSession>();
        private readonly Queue<IHttpSession> _unusedSessions = new Queue<IHttpSession>();
        private int _expireTime = 20;
    	private Timer _expireTimer;

		/// <summary>
		/// Initializes the class setting the expirationtimer to clean the session every minute
		/// </summary>
		public MemorySessionStore()
		{
			 _expireTimer = new Timer(Cleanup, null, 60000, 60000);
		}

		/// <summary>
		/// Delegate for the cleanup timer
		/// </summary>
		private void Cleanup(object o)
		{
			Cleanup();
		}

        #region IHttpSessionStore Members

        /// <summary>
        /// Load a session from the store
        /// </summary>
        /// <param name="sessionId"></param>
        /// <returns>null if session is not found.</returns>
        public IHttpSession this[string sessionId]
        {
            get
            {
                lock (_sessions)
                {
                    if (!_sessions.ContainsKey(sessionId))
                        return null;

                	_sessions[sessionId].Accessed = DateTime.Now;
                    if (sessionId.Length > 0)
                        ((MemorySession)_sessions[sessionId]).SetId(Guid.NewGuid().ToString());
                    return _sessions[sessionId];
                }
            }
        }

        /// <summary>
        /// Creates a new http session
        /// </summary>
        /// <returns></returns>
        public IHttpSession Create()
        {
            return Create(Guid.NewGuid().ToString());
        }


        /// <summary>
        /// Creates a new http session with a specific id
        /// </summary>
        /// <param name="id">Id used to identify the new cookie..</param>
        /// <returns>A <see cref="IHttpSession"/> object.</returns>
        /// <remarks>
        /// Id should be generated by the store implementation if it's null or <see cref="string.Empty"/>.
        /// </remarks>
        public IHttpSession Create(string id)
        {
            if (string.IsNullOrEmpty(id))
                id = Guid.NewGuid().ToString();

            lock (_unusedSessions)
            {
                if (_unusedSessions.Count > 0)
                {
                    MemorySession session = _unusedSessions.Dequeue() as MemorySession;
                    if (session != null)
                    {
                        session.SetId(id);
                        return session;
                    }
                }
            }

            return new MemorySession(id);
        }

        /// <summary>
        /// Load an existing session.
        /// </summary>
        /// <param name="sessionId"></param>
        /// <returns></returns>
        public IHttpSession Load(string sessionId)
        {
            if (_sessions.ContainsKey(sessionId))
                return _sessions[sessionId];
            
            return null;
        }

        /// <summary>
        /// Save an updated session to the store.
        /// </summary>
        /// <param name="session"></param>
        public void Save(IHttpSession session)
        {
            lock (_sessions)
            {
                if (_sessions.ContainsKey(session.Id))
                    _sessions[session.Id] = session;
                else
                    _sessions.Add(session.Id, session);
            }
        }

        /// <summary>
        /// We use the flyweight pattern which reuses small objects
        /// instead of creating new each time.
        /// </summary>
        /// <param name="session">EmptyLanguageNode (unused) session that should be reused next time Create is called.</param>
        public void AddUnused(IHttpSession session)
        {
            lock (_unusedSessions)
                _unusedSessions.Enqueue(session);
        }

        /// <summary>
        /// Remove expired sessions
        /// </summary>
        public void Cleanup()
        {
            lock (_sessions)
            {
                foreach (KeyValuePair<string, IHttpSession> pair in _sessions)
                {
                    // don't reuse used sessions since they may be in use when we remove them from the list.
                    TimeSpan liveTime = DateTime.Now.Subtract(pair.Value.Accessed);
                    if (liveTime.TotalMinutes > _expireTime)
                    {
						pair.Value.Clear(true);
                        _sessions.Remove(pair.Key);
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Remove a session
        /// </summary>
        /// <param name="sessionId">id of the session.</param>
        public void Remove(string sessionId)
        {
            IHttpSession session;
            lock (_sessions)
            {
                if (!_sessions.ContainsKey(sessionId))
                    return;

                session = _sessions[sessionId];
                _sessions.Remove(sessionId);
            }

            session.Clear();
            lock (_unusedSessions)
                _unusedSessions.Enqueue(session);
        }

        /// <summary>
        /// Number of minutes before a session expires.
        /// Default is 20 minutes.
        /// </summary>
        public int ExpireTime
        {
            get { return _expireTime; }
            set { _expireTime = value; }
        }

        #endregion


    }
}