using System;
using System.Web;
using System.Collections;
using System.Reflection;
using System.IO;


namespace Westwind.Web.Controls
{

    /// <summary>
    /// Inteface that defines the Callback operations for handling method callbacks
    /// on the server.
    /// 
    /// This interface serves as an abstraction for potentially different implementations
    /// that use XML instead of JSON.
    /// </summary>
    public interface ICallbackMethodProcessor
    {
        /// <summary>
        /// Generic method that handles processing a Callback request by routing to
        /// a method in a provided target object.
        /// 
        /// </summary>
        /// <param name="target">The target object that is to be called. If null this is used</param>
        void ProcessCallbackMethodCall(object target);

        /// <summary>
        /// Returns an error response to the client from a callback. Code
        /// should exit after this call.
        /// </summary>
        /// <param name="ErrorMessage"></param>
        void WriteErrorResponse(string ErrorMessage);

    }

    /// <summary>
    /// This is the core implementation of the JSON callback method handler that 
    /// picks up POST data from the request and uses it to call the actual callback
    ///  method on the specified object and return the results back as JSON.
    /// 
    /// This processor is generic and can be used easily from anywhere that needs 
    /// to feed back JSON data from a method callback, simply calling the 
    /// ProcessCallbackMethod() with an object that contains methods that are 
    /// marked up with the [CallbackMethod] attribute.
    /// 
    /// For example, wwCallbackHandler simply forwards all processing like this:
    /// 
    /// &lt;&lt;code lang="C#"&gt;&gt;public void ProcessRequest(HttpContext 
    /// context)
    /// {
    ///     // *** Pass off to the worker Callback Processor
    ///     ICallbackMethodProcessor processor = new JsonCallbackMethodProcessor();
    /// 
    ///     // *** Process the inbound request and execute it on this
    ///     // *** Http Handler's methods
    ///     processor.ProcessCallbackMethodCall(this);
    /// }&lt;&lt;/code&gt;&gt;
    /// 
    /// This processor is expected to execute in an environment where 
    /// HttpContext.Current is available and where POST data is available to 
    /// describe the incoming parameter data and method to call.
    /// </summary>
    public class JsonCallbackMethodProcessor : ICallbackMethodProcessor
    {
        /// <summary>
        /// Generic method that handles processing a Callback request by routing to
        /// a method in a provided target object.
        /// 
        /// </summary>
        /// <param name="target">The target object that is to be called. If null this is used</param>
        public void ProcessCallbackMethodCall(object target, string methodToCall)
        {
            if (target == null)
                target = this;

            HttpRequest Request = HttpContext.Current.Request;
            HttpResponse Response = HttpContext.Current.Response;
            Response.Charset = null;

            CallbackMethodProcessorHelper helper = new CallbackMethodProcessorHelper(this);

            // *** this is what we want to end up with:
            ArrayList ParameterList = new ArrayList();
            //string MethodToCall = null;

            string contentType = Request.ContentType.ToLower();

            if (contentType.StartsWith(ControlResources.STR_JavaScriptContentType) ||
                contentType.StartsWith(ControlResources.STR_JsonContentType))
            {
                if (string.IsNullOrEmpty(methodToCall))
                    methodToCall = Request.Params["Method"];

                if (string.IsNullOrEmpty(methodToCall))
                {
                    this.WriteErrorResponse("No method to call specified.");
                    return;
                }

                if (Request.ContentLength > 0L)
                {
                    // *** Pick up single unencoded JSON parameter
                    StreamReader sr = new StreamReader(Request.InputStream);
                    string singleParm = sr.ReadToEnd();
                    sr.Close();

                    if (!string.IsNullOrEmpty(singleParm))
                        ParameterList.Add(singleParm);
                }
            }
            // *** Post wwMethodCallback style interface
            else if (contentType.StartsWith(ControlResources.STR_UrlEncodedContentType) && Request.Params["CallbackMethod"] != null)
                // *** Pick up the POST/Query parameters
                helper.ProcessPostParameters(ParameterList, out methodToCall);

            // *** assume method with no parameters with any kind of content type
            else
            {
                if (string.IsNullOrEmpty(methodToCall)) 
                    methodToCall = Request.QueryString["Method"];

                if (string.IsNullOrEmpty(methodToCall))
                {
                    this.WriteErrorResponse("No method to call specified.");
                    return;
                }
            }

            // *** No parameters IS valid - other errors should be handled parsing
            if (ParameterList.Count == 0)
                ParameterList = null;

            object Result = null;
            string StringResult = null;

            try
            {
                // *** Finally: Ready to execute the method
                Result = helper.ExecuteMethod(methodToCall, ParameterList, target);
            }
            catch (Exception ex)
            {
                Exception ActiveException = null;
                if (ex.InnerException != null)
                    ActiveException = ex.InnerException;
                else
                    ActiveException = ex;

                this.WriteErrorResponse(ActiveException.Message);
                return;
            }

            if (StringResult == null)
            {
                try
                {
                    JSONSerializer Serializer = new JSONSerializer();
                    StringResult = Serializer.Serialize(Result);
                }
                catch (Exception ex)
                {
                    this.WriteErrorResponse(ex.Message);
                    return;
                }
            }

            // *** Explicitly set the content type here
            Response.ContentType = ControlResources.STR_JsonContentType;

            Response.Write(StringResult);
            Response.End();
        }

        /// <summary>
        /// Generic method that handles processing a Callback request by routing to
        /// a method in a provided target object.
        /// 
        /// This version doesn't pass in the method name but retrieves it from the
        /// POST data or query string.
        /// </summary>
        /// <param name="target"></param>
        public void ProcessCallbackMethodCall(object target)
        {
            ProcessCallbackMethodCall(target, null);
        }

        /// <summary>
        /// Returns an error response to the client from a callback. Code
        /// should exit after this call.
        /// </summary>
        /// <param name="ErrorMessage"></param>
        public void WriteErrorResponse(string ErrorMessage)
        {
            CallbackException Error = new CallbackException();
            Error.message = ErrorMessage;
            Error.isCallbackError = true;

            JSONSerializer Serializer = new JSONSerializer();
            string Result = Serializer.Serialize(Error);


            HttpResponse Response = HttpContext.Current.Response;
            Response.ContentType = ControlResources.STR_JsonContentType;

            Response.StatusCode = 500;
            Response.Write(Result);
            Response.End();
        }

    }


    /// <summary>
    /// This class provides helper services to the CallbackProcessor classes with 
    /// the generic services that deal with method execution and parsing POST 
    /// parameters which should be independent of the specific implementation 
    /// (JSON, XML etc.).
    /// 
    /// Extracted here so other Callback Processors can be created more easily 
    /// later on, using other request formats (Xml etc.)
    /// </summary>
    public class CallbackMethodProcessorHelper
    {
        private ICallbackMethodProcessor Processor = null;

        public CallbackMethodProcessorHelper(ICallbackMethodProcessor processor)
        {
            this.Processor = processor;
        }

        /// <summary>
        /// Handle parsing of the POST/Query string data Parameters are passed as follows:
        /// 
        /// CallbackMethod        -  the method to call
        /// CallbackParmCount     -  the number of parameters that are passed
        /// Parm1..n              -  the actual parameters passed
        /// 
        /// The two parameters passed are Output parameters and are filled by this method
        /// </summary>
        /// <param name="parameterList"></param>
        /// <param name="methodToCall"></param>
        public void ProcessPostParameters(ArrayList parameterList, out string methodToCall)
        {
            methodToCall = null;

            HttpRequest Request = HttpContext.Current.Request;

            // *** First get the method name 
            methodToCall = Request.Params["CallbackMethod"];
            if (methodToCall == null)
            {
                this.Processor.WriteErrorResponse("No CallbackMethod specified");
                return;
            }

            // *** We'll need the parm count so we can loop through POST/Query data
            string parmCount = Request.Params["CallbackParmCount"];
            if (parmCount == null)
            {
                this.Processor.WriteErrorResponse("CallbackParmCount is missing.");
                return;
            }

            int parameters = -1;
            int.TryParse(parmCount, out parameters);

            if (parameters < 0)
            {
                this.Processor.WriteErrorResponse("Invalid parameter count.");
                return;
            }

            // *** Get the encoded ParmX parameters from POST or Query            
            for (int i = 1; i <= parameters; i++)
            {
                object Parm = Request.Params["Parm" + i.ToString()];
                if (Parm == null)
                    break;
                parameterList.Add(Parm);
            }
        }

        /// <summary>
        /// Executes the requested method. converts the String parameters
        /// to the proper types for execution.
        /// </summary>
        /// <param name="Method"></param>
        /// <param name="ParameterList"></param>
        /// <returns></returns>
        public object ExecuteMethod(string Method, ArrayList ParameterList, object target)
        {
            object Result = null;

            object[] AdjustedParms = null;

            if (ParameterList != null && ParameterList.Count != 0)
            {
                object[] Parms = null;
                Parms = ParameterList.ToArray(typeof(object)) as object[];

                Type PageType = target.GetType();
                MethodInfo MI = PageType.GetMethod(Method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                if (MI == null)
                    throw new ApplicationException("Invalid Server Method.");

                object[] Methods = MI.GetCustomAttributes(typeof(CallbackMethodAttribute), false);
                if (Methods.Length < 1)
                    throw new ApplicationException("Server method is not accessible due to missing CallbackMethod Attribute");

                ParameterInfo[] Parameters = MI.GetParameters();

                JSONSerializer Serializer = new JSONSerializer();

                int Count = 0;
                AdjustedParms = new object[Parms.Length];
                foreach (ParameterInfo Parameter in Parameters)
                {
                    AdjustedParms[Count] = Serializer.Deserialize(Parms[Count] as string, Parameter.ParameterType);
                    Count++;
                }
            }
            Result = target.GetType().GetMethod(Method,BindingFlags.Instance |
                                                       BindingFlags.Public | 
                                                       BindingFlags.NonPublic).Invoke(target, AdjustedParms);

            return Result;
        }
    }
}
