//
// RestHandler.cs
//
// Author:
//   Konstantin Triger <kostat@mainsoft.com>
//
// (C) 2007 Mainsoft, Inc.  http://www.mainsoft.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;
using System.Collections.Generic;
using System.Text;
using System.Web.Script.Serialization;
using System.Collections.Specialized;
using System.IO;
using System.Web.SessionState;
using System.Reflection;

namespace System.Web.Script.Services
{
	sealed class RestHandler : IHttpHandler
	{
		#region SessionWrappers

		class SessionWrapperHandler : IHttpHandler, IRequiresSessionState
		{
			readonly IHttpHandler _handler;

			public SessionWrapperHandler (IHttpHandler handler) {
				_handler = handler;
			}

			public bool IsReusable {
				get { return _handler.IsReusable; }
			}

			public void ProcessRequest (HttpContext context) {
				_handler.ProcessRequest (context);
			}
		}

		sealed class ReadOnlySessionWrapperHandler : SessionWrapperHandler, IReadOnlySessionState
		{
			public ReadOnlySessionWrapperHandler (IHttpHandler handler) : base (handler) { }
		}

		#endregion

		#region ExceptionSerializer

		sealed class ExceptionSerializer
		{
			readonly Exception _e;
			public ExceptionSerializer (Exception e) {
				_e = e;
			}

			public string Message {
				get { return _e.Message; }
			}

			public string StackTrace {
				get { return _e.StackTrace; }
			}

			public string ExceptionType {
				get { return _e.GetType ().FullName; }
			}			
		}

		#endregion

		readonly LogicalTypeInfo.LogicalMethodInfo _logicalMethodInfo;

		private RestHandler (HttpContext context, Type type, string filePath) {
			LogicalTypeInfo logicalTypeInfo = LogicalTypeInfo.GetLogicalTypeInfo (type, filePath);
			HttpRequest request = context.Request;
			string methodName = request.PathInfo.Substring (1);
			if (logicalTypeInfo == null || String.IsNullOrEmpty(methodName))
				ThrowInvalidOperationException (methodName);

			_logicalMethodInfo = logicalTypeInfo [methodName];
			if (_logicalMethodInfo == null)
				ThrowInvalidOperationException (methodName);
		}

		static void ThrowInvalidOperationException (string pathInfo) {
			throw new InvalidOperationException (
					string.Format ("Request format is unrecognized unexpectedly ending in '{0}'.", pathInfo));
		}

		static readonly Type IRequiresSessionStateType = typeof (IRequiresSessionState);
		static readonly Type IReadOnlySessionStateType = typeof (IReadOnlySessionState);
		public static IHttpHandler GetHandler (HttpContext context, Type type, string filePath) {
			RestHandler handler = new RestHandler (context, type, filePath);
			LogicalTypeInfo.LogicalMethodInfo mi = handler._logicalMethodInfo;
			if (mi.MethodInfo.IsStatic) {
				if (IRequiresSessionStateType.IsAssignableFrom (type))
					return IReadOnlySessionStateType.IsAssignableFrom (type) ?
						new ReadOnlySessionWrapperHandler (handler) : new SessionWrapperHandler (handler);
			}
			else
				if (mi.EnableSession)
					return new SessionWrapperHandler (handler);

			return handler;

		}
		#region IHttpHandler Members

		public bool IsReusable {
			get { return false; }
		}
		
		public void ProcessRequest (HttpContext context) {
			HttpRequest request = context.Request;
			HttpResponse response = context.Response;
			response.ContentType =
				_logicalMethodInfo.ResponseFormat == ResponseFormat.Json ?
				"application/json" : "text/xml";
			response.Cache.SetCacheability (HttpCacheability.Private);
			response.Cache.SetMaxAge (TimeSpan.Zero);

			try {
				_logicalMethodInfo.Invoke (request, response);
			}
			catch (TargetInvocationException e) {
				response.AddHeader ("jsonerror", "true");
				response.ContentType = "application/json";
				response.StatusCode = 500;
				JavaScriptSerializer.DefaultSerializer.Serialize (new ExceptionSerializer (e.GetBaseException ()), response.Output);
				response.End ();
			}
		}
		
		#endregion
	}
}