//#define Westwind.Web.Controls
/*
 **************************************************************
 * wwMethodCallback Class
 **************************************************************
 *  Author: Rick Strahl 
 *          (c) West Wind Technologies
 *          http://www.west-wind.com/
 * 
 * Created: 9/20/2006 
 * 
 * This ASP.NET Control class provides page and control level
 * callback to the current ASPX page. This makes it super simple
 * to hook up AJAX callbacks with literally a single method
 * call. This control handles routing the request to the server
 * and to the appropriate server side method. The control also
 * manages parameter marshalling via JSON to the server and 
 * JSON results including support for many complex types like
 * entity object, nested objects, collections, datatables and
 * datarows.    
 * 
 * The class works through using the [CallbackMethod] on
 * any Page or custom server or user control methods.
 * 
 * The class is accompanied by a client library contained in
 * wwScriptLibrary.js which matches the wwHoverPanel class on
 * the client. The client and server pieces communicate with
 * each other. The library also contains many utility functions.
 * 
 * Source Dependencies:
 * JSONSerializer.cs
 * wwUtils.cs
 *  
 * Acknowledgements:
 * This code borrows concepts from Jason Diamonds Anthem.NET
 * http://sourceforge.net/mailarchive/forum.php?forum=anthem-dot-net-devel
 * 
 * and the ATLAS framework.
 * 
 * The original JavaScript JSON deserialization code file is based on:
 *     copyright: '(c)2005 JSON.org',
 *       license: 'http://www.crockford.com/JSON/license.html',
 * 
 * License:
 * Free without any restrictions whatsoever
 * 
 * I only ask that if you make changes that you think are useful
 * you let us know and post a message at:
 * 
 * http://www.west-wind.com/wwThreads/default.asp?forum=West+Wind+Web+Tools+for+.NET
 * 
 * Version History:
 * Version 1.5 
 * -----------
 * * Release
 **************************************************************  
*/
using System;
using System.Text;

using System.ComponentModel;
using System.Web.UI;
using System.Collections;

using System.Web;
using System.Reflection;
using System.IO;
using System.Web.UI.Design;
using Westwind.Tools;


namespace Westwind.Web.Controls
{
    /// <summary>
    /// The wwMethodCallback control provides an easy mechanism for making page or 
    /// control level method callbacks from an HTML page. Working with both client 
    /// side and server side logic you can make remote method calls from the client
    ///  using two-way JSON serialization to access server side content from client
    ///  pages.
    /// Methods are mapped from server to the client with an optional client proxy 
    /// class that allows making direct method calls to the server. Methods on the 
    /// server are marked up with a [CallbackMethod] to indicate that the methods 
    /// are accessible for callbacks. These methods can be implemented on the page,
    ///  any user control, or custom server control. The latter also allows control
    ///  developers to dynamically route callbacks to their own controls.
    /// 
    /// Methods called use JSON to pass data and simple types, hierarchical 
    /// objects, arrays and IList based classes are supported for two-way 
    /// transfers. DataSets/DataTables/DataRows are support for downloading only at
    ///  this time.
    /// 
    /// This control makes accessing server side content as easy as calling a 
    /// single proxy method and implementing a single client side handler to 
    /// receive the result value as a strongly typed object.
    /// </summary>
    [DefaultProperty("Url"), Designer(typeof(wwMethodCallbackDesigner)),
    ToolboxData("<{0}:wwMethodCallback runat='server' />")]
    public class wwMethodCallback : Control
    {
        /// <summary>
        /// Wrapper ClientScript used to allow use ScriptManager methods
        /// </summary>
        private ClientScriptProxy ClientScriptProxy;

        /// <summary>
        /// The Url to hit on the server for the callback to return the result. Note: Not used when doing a MethodCallback
        /// </summary>
        [Description("The Url to hit on the server for the callback to return the result. Empty posts back to the current page."),
        DefaultValue(""), Category("Callback")]
        public string ServerUrl
        {
            get
            {
                return _ServerUrl;
            }
            set
            {
                _ServerUrl = value;
            }
        }
        private string _ServerUrl = "";


        /// <summary>
        /// Determines whether the current request is a callback from the 
        /// wwMethodCallback or wwHoverPanel control.
        /// 
        /// This property is used internally to trap for method processing, but you can
        ///  also use this in your page or control level code to determine whether you 
        /// need to do any special processing based on the callback situation.
        /// <seealso>Class wwMethodCallback</seealso>
        /// </summary>
        [Browsable(false)]
        public bool IsCallback
        {
            get
            {
                if (_IsCallback == 0)
                {
                    string Id = this.Context.Request.Params["__WWEVENTCALLBACK"];
                    if (Id != null && Id == this.ID)
                    {
                        _IsCallback = 2;
                        return true;
                    }
                    _IsCallback = 1;
                    return false;
                }
                else
                {
                    if (_IsCallback == 2)
                        return true;
                    return false;
                }
            }
        }
        private int _IsCallback = 0;

        /// <summary>
        /// If true causes the page to post back all form variables.
        /// </summary>
        [Description("If true causes the page to post back all form variables."), DefaultValue(PostBackModes.PostMethodParametersOnly), Category("Callback")]
        public PostBackModes PostBackMode
        {
            get
            {
                return _PostBackFormData;
            }
            set
            {
                _PostBackFormData = value;
            }
        }
        private PostBackModes _PostBackFormData = PostBackModes.PostMethodParametersOnly;

        /// <summary>
        /// The name of the form from which values are posted back to the server. Note only a single form's 
        /// values can be posted back!
        /// </summary>
        [Description("If PostBackData is set, this variable determines which form is posted back."), DefaultValue(""), Category("Callback")]
        public string PostBackFormName
        {
            get
            {
                return _PostBackFormName;
            }
            set
            {
                _PostBackFormName = value;
            }
        }
        private string _PostBackFormName = "";


        /// <summary>
        /// Determines how the JavasScript code gets embedded into the page.
        /// </summary>
        [Description("Determines how the JavaScript code gets emedded into the page."),
        DefaultValue(JavaScriptCodeLocationTypes.WebResource), Category("Callback")]
        public JavaScriptCodeLocationTypes ScriptLocationType
        {
            get { return _ScriptLocationType; }
            set { _ScriptLocationType = value; }
        }
        private JavaScriptCodeLocationTypes _ScriptLocationType = JavaScriptCodeLocationTypes.WebResource;


        /// <summary>
        /// Determines the location of thte JavaScript support file if ScriptLocationType is set to ExternalFile
        /// </summary>
        [Description("Determines the location of the JavaScript support file but only *IF* ScriptLocationType is set to ExternalFile"),
        DefaultValue("~/wwScriptLibrary.js"), Category("Callback")]
        public string ScriptLocation
        {
            get { return _ScriptLocation; }
            set { _ScriptLocation = value; }
        }
        private string _ScriptLocation = "~/wwScriptLibrary.js";

        /// <summary>
        /// Determines which control holds the methods that are to be called.
        /// By default this is the Page object, but you can use this in a custom
        /// control and route method calls to the control instead. 
        /// </summary>
        [Description("The control ID that is responsible for handling this request. By default the Page is used (Page). Use this only if you need to handle callbacks on a specific custom server control rather than on the page"),
         DefaultValue("Page"), Category("Callback")]
        public string TargetControlId
        {
            get { return _TargetControlId; }
            set { _TargetControlId = value; }
        }
        private string _TargetControlId = "Page";

        /// <summary>
        /// The type of the object that is going to be the target of
        /// the callback. Typically this will be this.Page, but if 
        /// you route calls to another control or a handler you
        /// need to set this type to allow the control to create
        /// the proxy.
        /// </summary>
        [Browsable(false)]
        public Type TargetCallbackType
        {
            get { return _TargetCallbackType; }
            set { _TargetCallbackType = value; }
        }
        private Type _TargetCallbackType = null;

        /// <summary>
        /// If true generates a proxy class that maps each of the methods exposed with 
        /// [CallbackMethod] on the current page, user control or control and exposes 
        /// it as a client side class. The name of the class will be the same as the ID
        ///  of the wwMethodCallback control.
        /// 
        /// By default the class is generated. If false the class is not generated and 
        /// you can use the client side wwMethodCallback object and use declaritive 
        /// code to create the method callbacks manually.
        /// <seealso>Class wwMethodCallback</seealso>
        /// </summary>
        [Description("If true generates a client proxy class that has the name of this control. Each method of the class matches the server methods exposed plus a callback and error client handler"),
         DefaultValue(true), Category("Callback")]
        public bool GenerateClientProxyClass
        {
            get { return _GenerateClientProxyClass; }
            set { _GenerateClientProxyClass = value; }
        }
        private bool _GenerateClientProxyClass = true;




        /// <summary>
        /// Override to force simple IDs all around
        /// </summary>
        public override string UniqueID
        {
            get
            {
                return this.ID;
            }
        }
        /// <summary>
        /// Override to force simple IDs all around
        /// </summary>
        public override string ClientID
        {
            get
            {
                return this.ID;
            }
        }


        protected override void OnLoad(EventArgs e)
        {
            if (!this.IsCallback)
                return;

            // *** Delegate off handling to the Callback Processor 
            // *** which handles routing and calling the method and returning
            // *** a JSON response
            ICallbackMethodProcessor processor = new JsonCallbackMethodProcessor(); 

            // *** We need to specify the target object that holds Callback Methods to execute
            Control targetControl = this.Page; 

            // *** Read the control to execute on from POST buffer
            if (!string.IsNullOrEmpty(this.TargetControlId) && this.TargetControlId != "Page")
            {               
                string targetControlId = HttpContext.Current.Request.Params["__WWEVENTTARGET"]; 
                targetControl = wwWebUtils.FindControlRecursive(this.Page, targetControlId);
                if (targetControl == null)
                {
                    processor.WriteErrorResponse("Invalid Target Control " + targetControlId);
                    return;
                }
            }           

            // *** Let the Processor do all the work of parsing parms, calling and returning result
            processor.ProcessCallbackMethodCall(targetControl);
        }

        /// <summary>
        /// This method just builds the various JavaScript blocks as strings
        /// and assigns them to the ClientScript object.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            if (!this.IsCallback)
            {                
                this.ClientScriptProxy = ClientScriptProxy.Current;

                // *** Generate the class wrapper and class loader function
                this.GenerateControlSpecificJavaScript();

                if (this.ScriptLocationType == JavaScriptCodeLocationTypes.WebResource)
                {
                    ControlResources.LoadwwScriptLibrary(this);                    
                    //this.ClientScriptProxy.RegisterClientScriptResource(this, typeof(ControlResources), ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
                }
                else if (this.ScriptLocationType == JavaScriptCodeLocationTypes.ExternalFile)
                    this.ClientScriptProxy.RegisterClientScriptInclude(this, this.GetType(), ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE, this.ResolveUrl(this.ScriptLocation));
                else if (this.ScriptLocationType == JavaScriptCodeLocationTypes.EmbeddedInPage)
                {
                    // *** Load the wwHoverPanel.js file from Resources for embedding
                    Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream(ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
                    StreamReader sr = new StreamReader(st);
                    this.ClientScriptProxy.RegisterClientScriptBlock(this, this.GetType(), ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE, sr.ReadToEnd(), true);
                }
            }
        }


        /// <summary>
        /// Generates the ControlSpecific JavaScript. This script is safe to
        /// allow multiple callbacks to run simultaneously.
        /// </summary>
        private void GenerateControlSpecificJavaScript()
        {
            // *** Figure out the initial URL we're going to 
            // *** Either it's the provided URL from the control or 
            // *** we're posting back to the current page
            string Url = null;
            if (this.ServerUrl == null || this.ServerUrl == "")
                Url = this.Context.Request.Path;
            else
                Url = this.ResolveUrl(this.ServerUrl);

            Uri ExistingUrl = this.Context.Request.Url;

            // *** Must fix up URL into fully qualified URL for XmlHttp
            if (!this.ServerUrl.ToLower().StartsWith("http"))
                Url = ExistingUrl.Scheme + "://" + ExistingUrl.Authority + Url;

            this.GenerateClassWrapperForCallbackMethods();
        }


        /// <summary>
        /// Creates the JavaScript client side object that matches the 
        /// server side method signature. The JScript function maps
        /// to a CallMethod() call on the client.
        /// </summary>
        private void GenerateClassWrapperForCallbackMethods()
        {
            StringBuilder sb = new StringBuilder();

            if (this.GenerateClientProxyClass)
            {
                object MethodHost = null;
                if (this.TargetCallbackType != null)
                    MethodHost = Activator.CreateInstance(this.TargetCallbackType);
                if (MethodHost == null && !string.IsNullOrEmpty(this.TargetControlId))
                    MethodHost = wwWebUtils.FindControlRecursive(this.Page, TargetControlId);
                if (MethodHost == null)
                    MethodHost = this.Page;

                sb.Append("var " + this.ID + " = { ");

                MethodInfo[] Methods = MethodHost.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                foreach (MethodInfo Method in Methods)
                {
                    if (Method.GetCustomAttributes(typeof(CallbackMethodAttribute), false).Length > 0)
                    {
                        sb.Append("\r\n    " + Method.Name + ": function " + "(");

                        string ParameterList = "";
                        foreach (ParameterInfo Parm in Method.GetParameters())
                        {
                            ParameterList += Parm.Name + ",";
                        }
                        sb.Append(ParameterList + "CallbackHandler,ErrorHandler)");

                        sb.Append(
    @"
    {
        var _cb = " + this.ID + @"_GetCallback();
        _cb.callMethod('" + Method.Name + @"',[" +
                 ParameterList.TrimEnd(',') + @"],CallbackHandler,ErrorHandler);
        return _cb;           
    },");
                    }
                }

                if (sb.Length > 0)
                    sb.Length--; // strip trailing ,

                // *** End of class
                sb.Append("\r\n}\r\n");
            }
            string Url = null;
            if (string.IsNullOrEmpty(this.ServerUrl))
                Url = this.Context.Request.Path;
            else
                Url = this.ResolveUrl(this.ServerUrl);

            sb.Append(
"function " + this.ID + @"_GetCallback() {
    var _cb = new wwCallbackMethod('" + this.ID + "','" + this.ServerUrl + @"');
    _cb.serverUrl = '" + Url + @"';
    _cb.postbackMode = '" + this.PostBackMode.ToString() + @"';
    _cb.targetControlId = '" + this.TargetControlId + @"';
    _cb.formName = '" + this.PostBackFormName + @"';
    return _cb;
}
");

            this.ClientScriptProxy.RegisterStartupScript(this, this.GetType(), this.ID + "_ClientProxy", sb.ToString(), true);
        }

        /// <summary>
        /// Returns an Event Callback reference string that can be used in Client
        /// script to initiate a callback request. 
        /// </summary>
        /// <param name="QueryStringExpression">
        /// An expression that is evaluated in script code and embedded as the second parameter.
        /// The value of this second parameter is interpreted as a QueryString to the URL that
        /// is fired in response to the request to the server.
        /// 
        /// This expression can be a static string or any value or expression that is in scope
        /// at the time of calling the event method. The expression must evaluate to a string
        ///  
        /// Example: 
        /// string GetCallbackEventReference("'CustomerId=' + this.forms[0].txtCustomerId.value + "'");
        ///  
        /// A callback event reference result looks like this:
        /// 
        /// ControlID_StartCallback(event,'CustomerId=_12312')
        /// </param>
        /// <returns></returns>
        public string GetCallbackEventReference(string QueryStringExpression)
        {
            if (QueryStringExpression == null)
                return "StartCallback('" + this.ID + "',event)";

            return "StartCallback('" + this.ID + "',event,'" + QueryStringExpression + "')";
        }
    }




    /// <summary>
    /// Control designer used so we get a grey button display instead of the 
    /// default label display for the control.
    /// </summary>
    internal class wwMethodCallbackDesigner : ControlDesigner
    {
        public override string GetDesignTimeHtml()
        {
            return base.CreatePlaceHolderDesignTimeHtml("");
        }
    }
}
