//
// System.Web.SessionState.SessionSQLServerHandler
//
// Author(s):
//  Jackson Harper (jackson@ximian.com)
//
// (C) 2003 Novell, Inc. (http://www.novell.com), All rights reserved
//

//
// 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;
using System.IO;
using System.Data;
using System.Reflection;
using System.Configuration;
using System.Collections.Specialized;

namespace System.Web.SessionState {

	internal class SessionSQLServerHandler : ISessionHandler
	{
		const int DefTimeout = 600;

		private Type cncType = null;
		private IDbConnection cnc = null;
		private SessionConfig config;

		public void Dispose ()
		{
			if (cnc != null) {
				cnc.Close ();
				cnc = null;
			}
		}

		public void Init (SessionStateModule module, HttpApplication context, SessionConfig config)
		{
			string connectionTypeName;
			string providerAssemblyName;
			string cncString;

			this.config = config;

			GetConnectionData (out providerAssemblyName, out connectionTypeName, out cncString);
			if (cncType == null) {
				Assembly dbAssembly = Assembly.Load (providerAssemblyName);
				cncType = dbAssembly.GetType (connectionTypeName, true);
				if (!typeof (IDbConnection).IsAssignableFrom (cncType))
					throw new ApplicationException ("The type '" + cncType +
							"' does not implement IDB Connection.\n" +
							"Check 'DbConnectionType' in server.exe.config.");
			}

			cnc = (IDbConnection) Activator.CreateInstance (cncType);
			cnc.ConnectionString = cncString;
			try {
				cnc.Open ();
			} catch (Exception exc) {
				cnc = null;
				throw exc;
			}
		}

		public void UpdateHandler (HttpContext context, SessionStateModule module)
		{
			if (context.Session == null || context.Session.IsReadOnly)
				return;

			string id = context.Session.SessionID;
			SessionDictionary dict = context.Session.SessionDictionary;

			UpdateSession (id, dict);
		}

		public HttpSessionState UpdateContext (HttpContext context, SessionStateModule module,
							bool required, bool read_only, ref bool isNew)
		{
			if (!required)
				return null;

			HttpSessionState session = null;
			string id = SessionId.Lookup (context.Request, config.CookieLess);

			if (id != null) {
				session = SelectSession (id, read_only);
				if (session != null)
					return session;
			}

			id = SessionId.Create (module.Rng);
			session = new HttpSessionState (id, new SessionDictionary (),
					HttpApplicationFactory.ApplicationState.SessionObjects, config.Timeout,
					true, config.CookieLess, SessionStateMode.SQLServer, read_only);

			InsertSession (session, config.Timeout);
			isNew = true;
			return session;
		}

		private void GetConnectionData (out string providerAssembly,
				out string cncTypeName, out string cncString)
		{
			providerAssembly = null;
			cncTypeName = null;
			cncString = null;

			NameValueCollection config = ConfigurationSettings.AppSettings as NameValueCollection;
			if (config != null) {
				foreach (string s in config.Keys) {
					if (0 == String.Compare ("StateDBProviderAssembly", s, true)) {
						providerAssembly = config [s];
					} else if (0 == String.Compare ("StateDBConnectionType", s, true)) {
						cncTypeName = config [s];
					}
				}
			}

			cncString = this.config.SqlConnectionString;

			if (providerAssembly == null || providerAssembly == String.Empty)
				providerAssembly = "Npgsql.dll";

			if (cncTypeName == null || cncTypeName == String.Empty)
				cncTypeName = "Npgsql.NpgsqlConnection";

			if (cncString == null || cncString == String.Empty)
				cncString = "SERVER=127.0.0.1;USER ID=monostate;PASSWORD=monostate;dbname=monostate";
		}

		private HttpSessionState SelectSession (string id, bool read_only)
		{
			HttpSessionState session = null;
			IDbCommand command = cnc.CreateCommand();
			IDataReader reader;

			string select = "SELECT * from aspstatetempsessions WHERE SessionID = :SessionID";

			command.CommandText = select;

			command.Parameters.Add (CreateParam (command, DbType.String, ":SessionID", id));

			try {
				reader = command.ExecuteReader ();

				if (!reader.Read ())
					return null;

				SessionDictionary dict; 
				HttpStaticObjectsCollection sobjs;
				
				dict = SessionDictionary.FromByteArray (ReadBytes (reader, reader.FieldCount-1));
				sobjs = HttpStaticObjectsCollection.FromByteArray (ReadBytes (reader, reader.FieldCount-2));
				
				session = new HttpSessionState (id, dict, sobjs, 100, false, config.CookieLess,
						SessionStateMode.SQLServer, read_only);
				return session;
			} catch {
				throw;
			}
		}

		private void InsertSession (HttpSessionState session, int timeout)
		{
			IDbCommand command = cnc.CreateCommand ();
			IDataParameterCollection param;

			string insert = "INSERT INTO ASPStateTempSessions VALUES " +
			"(:SessionID, :Created, :Expires, :Timeout, :StaticObjectsData, :SessionData)";

			command.CommandText = insert;

			param = command.Parameters;
			param.Add (CreateParam (command, DbType.String, ":SessionID", session.SessionID));
			param.Add (CreateParam (command, DbType.DateTime, ":Created", DateTime.Now));
			param.Add (CreateParam (command, DbType.DateTime, ":Expires", Tommorow ()));
			param.Add (CreateParam (command, DbType.Int32, ":Timeout", timeout));
			param.Add (CreateParam (command, DbType.Binary, ":StaticObjectsData",
						   session.StaticObjects.ToByteArray ()));
			param.Add (CreateParam (command, DbType.Binary, ":SessionData",
						   session.SessionDictionary.ToByteArray ()));

			command.ExecuteNonQuery ();
		}

		private void UpdateSession (string id, SessionDictionary dict)
		{
			IDbCommand command = cnc.CreateCommand ();
			IDataParameterCollection param;

			string update = "UPDATE ASPStateTempSessions SET " +
			"SessionData = :SessionData WHERE SessionId = :SessionID";

			command.CommandText = update;

			param = command.Parameters;
			param.Add (CreateParam (command, DbType.String, ":SessionID", id));
			param.Add (CreateParam (command, DbType.Binary, ":SessionData",
								dict.ToByteArray ()));

			command.ExecuteNonQuery ();
		}

		private IDataParameter CreateParam (IDbCommand command, DbType type,
				string name, object value)
		{
			IDataParameter result = command.CreateParameter ();
			result.DbType = type;
			result.ParameterName = name;
			result.Value = value;
			return result;
		}

		private DateTime Tommorow ()
		{
			return DateTime.Now.AddDays (1);
		}

		private byte [] ReadBytes (IDataReader reader, int index)
		{
			int len = (int) reader.GetBytes (reader.FieldCount-1, 0, null, 0, 0);
			byte [] data = new byte [len];
			reader.GetBytes (index, 0, data, 0, len);
			return data;
		}
	}
}

