﻿// © 2007-2008, Applied Geographics, Inc.  All rights reserved.

/// <reference name="AppGeo.Web.Extensions.js" assembly="AppGeo.Web" />

// Ag.Net.ProxyCall (class)
// Ag.Net.WebRequestQueue (component)

// Define our namespaces
Type.registerNamespace("Ag");
Type.registerNamespace("Ag.Net");

Ag.Net.ProxyCall = function(service, verb, method, params, succeededCallback, failedCallback) {
  /// <summary>A class to store information about a proxy call</summary>
  /// <param name="service" type="Object">The web service</param>
  /// <param name="method" type="String">The method name to call</param>
  /// <param name="params" type="Object">A dictionary of parameters</param>
  /// <param name="succeededCallback" type="Function">A method called if the request succeeds</param>
  /// <param name="failedCallback" type="Function">A method called if the request fails</param>

  this._service = typeof (service) !== "undefined" ? service : null;
  this._verb = typeof (verb) !== "undefined" ? verb : "GET";
  this._method = typeof (method) !== "undefined" ? method : "";
  this._params = typeof (params) !== "undefined" ? params : null;
  this._target = "";

  this._isFormPost = false;
  this._isDataMethod = false;

  this._webRequest = null;
  this._succeededCallback = null;
  this._failedCallback = null;

  if (String.isInstanceOfType(service)) {
    this._isFormPost = true;
    this._target = succeededCallback;
  } else {
    this._isDataMethod = Sys.Data.DataService.isInstanceOfType(this._service);
    if (this._isDataMethod && this._method.length > 0 && this._method.charAt(0) != "/")
      this._method = "/" + this._method;

    if (!this._isDataMethod && verb !== "GET")
      throw Error.argument(verb, "Only DataServices support insert, update and delete operations");

    var re = /\.asmx|\.svc|[\.\\\/]/g;
    var serviceName = this.get_name().replace(re, "");
    var methodName = this._method.replace(re, "");

    if (typeof (succeededCallback) === "undefined" || succeededCallback == null) {
      if (service.get_defaultSucceededCallback() != null) {
        succeededCallback = service.get_defaultSucceededCallback();
      } else if (String.isInstanceOfType(method)) {
        var defaultSucceededCallbackSucceeded = serviceName + "_" + methodName + "_succeeded";
        try {
          succeededCallback = $common.resolveFunction(defaultSucceededCallbackSucceeded);
        } catch (ex) {
          succeededCallback = null;
        }
      }
    }

    if (typeof (failedCallback) === "undefined" || failedCallback == null) {
      if (service.get_defaultFailedCallback() != null) {
        failedCallback = service.get_defaultFailedCallback();
      } else if (String.isInstanceOfType(method)) {
        var defaultFailedCallbackName = serviceName + "_" + methodName + "_failed";
        try {
          failedCallback = $common.resolveFunction(defaultFailedCallbackName);
        } catch (ex) {
          failedCallback = null;
        }
      }
    }

    this._succeededCallback = succeededCallback;
    this._failedCallback = failedCallback;
  }
}

Ag.Net.ProxyCall.prototype = {
  // Public Methods

  equals: function(other) {
    /// <param name="other" type="Ag.Net.ProxyCall">A proxy call to test equality</param>
    return (other.get_path() == this.get_path() && other.get_method() == this._method);
  },

  // Public Properties

  get_service: function() {
    /// <value type="Object">The script method's service</value>
    return this._service;
  },

  set_service: function(value) {
    this._service = value;
  },

  get_verb: function() {
    /// <value type="String">The HTTP verb</value>
    return this._verb;
  },

  set_verb: function(value) {
    this._verb = value;
  },

  get_path: function() {
    /// <value type="String">The service's path</value>
    if (this._service != null) {
      if (this._isFormPost)
        return this._service;
      else if (this._isDataMethod)
        return this._service.get_serviceUri();
      else
        return this._service.get_path();
    } else {
      return "";
    }
  },

  get_name: function() {
    /// <value type="String">The script service's name</value>
    if (this._service != null) {
      if (this._isDataMethod) {
        var parts = this._service.get_serviceUri().split("/");
        return parts[parts.length - 1];
      } else {
        var parts = this._service.get_path().split("/");
        return parts[parts.length - 1];
      }
    } else {
      return "";
    }
  },

  get_isDataMethod: function() {
    /// <value type="Boolean">Returns whether this proxy is for a data method</value>
    return this._isDataMethod;
  },

  get_method: function() {
    /// <value type="String">The script method's method</value>
    return this._method;
  },

  get_isFormPost: function() {
    /// <value type="Boolean">Returns whether this proxy is for posting form data</value>
    return this._isFormPost;
  },

  get_method: function() {
    /// <value type="String">The script method's method</value>
    return this._method;
  },

  set_method: function(value) {
    this._method = value;
  },

  get_params: function() {
    /// <value type="Array">The script method's params</value>
    return this._params;
  },

  set_params: function(params) {
    this._params = params;
  },

  get_succeededCallback: function() {
    /// <value type="Function">The script method's succeeded callback</value>
    return this._succeededCallback;
  },

  set_succeededCallback: function(succeededCallback) {
    this._succeededCallback = succeededCallback;
  },

  get_failedCallback: function() {
    /// <value type="Function">The script method's failed callback</value>
    return this._failedCallback;
  },

  set_failedCallback: function(failedCallback) {
    this._failedCallback = failedCallback;
  },

  get_target: function() {
    /// <value type="String">The window or frame at which to target returning content</value>
    return this._target;
  },

  set_target: function(value) {
    this._target = value;
  },

  // Private Properties (friend WebRequestQueue)

  get__webRequest: function() {
    return this._webRequest;
  },

  set__webRequest: function(value) {
    this._webRequest = value;
  }
}

Ag.Net.ProxyCall.registerClass("Ag.Net.ProxyCall");

Ag.Net.WebRequestQueue = function() {
  /// <summary>A component to add request queuing to the WebRequestManager</summary>
  /// <remarks>
  /// The WebRequestManager is a singleton object of type Sys.Net._WebRequestManager.
  /// </remarks>
  Ag.Net.WebRequestQueue.initializeBase(this);

  this._invokingHandler = Function.createDelegate(this, this._webRequestManager_invokingRequest);
  this._completedHandler = Function.createDelegate(this, this._webRequestManager_completedRequest);

  this._requestQueue = [];
  this._currentProxy = null;
  this._executing = false;
  this._externalRequestCount = 0;
}

Ag.Net.WebRequestQueue.prototype = {

  // Public Methods

  initialize: function() {
    /// <summary>Setup the queue</summary>
    /// <remarks>
    /// I'm not sure if this is the right way to get a reference to this object
    /// while in the event handlers but it works great!
    /// "this" object actually comes through as this[0] in the handler.
    /// </remarks>
    Ag.Net.WebRequestQueue.callBaseMethod(this, "initialize");

    Sys.Net.WebRequestManager.add_invokingRequest(this._invokingHandler);
    Sys.Net.WebRequestManager.add_completedRequest(this._completedHandler);
  },

  dispose: function() {
    Sys.Net.WebRequestManager.remove_invokingRequest(this._invokingHandler);
    Sys.Net.WebRequestManager.remove_completedRequest(this._completedHandler);

    Ag.Net.WebRequestQueue.callBaseMethod(this, "dispose");
  },

  cancel: function(service, method) {
    /// <summary>Cancels all active and queued web requests with the given signature</summary>
    /// <remarks>
    /// The succeeded or failed callbacks will NOT be called.
    /// Processing will continue with the next queued request.
    /// </remarks>
    var hadRequest = this._executing || this._requestQueue.length > 0;
    var canceledProxy = new Ag.Net.ProxyCall(service, "GET", method);

    if (this._executing) {
      if (this._currentProxy.equals(canceledProxy)) {
        this._currentProxy.get__webRequest().get_executor().abort();
        Sys.Debug.trace(String.format("WebRequestQueue.cancel - {0}.{1} aborted", this._currentProxy.get_name(), method));

        this._executing = false;
        this._currentProxy = null;
      }
    }

    for (var i = 0; i < this._requestQueue.length; i++) {
      if (this._requestQueue[i].equals(canceledProxy)) {
        Array.removeAt(this._requestQueue, i);
        i--;
      }
    }

    if (this._requestQueue.length > 0)
      this.executeQueue();
    else if (hadRequest)
      this._onRequestsCompleted();
  },

  clear: function() {
    /// <summary>Cancels current active request and removes all queued requests</summary>
    /// <remarks>
    /// The succeeded or failed callbacks will NOT be called.
    /// </remarks>
    var hadRequest = this._executing || this._requestQueue.length > 0;

    if (this._executing) {
      this._currentProxy.get__webRequest().get_executor().abort();
      Sys.Debug.trace(String.format("WebRequestQueue.clear - {0}.{1} aborted", this._currentProxy.get_name(), method));

      this._executing = false;
      this._currentProxy = null;
    }

    this._requestQueue = [];

    if (hadRequest)
      this._onRequestsCompleted();
  },

  executeQueue: function() {
    /// <summary>Execute the next queued item</summary>
    while (this._requestQueue.length > 0 && !this._executing) {
      var proxy = Array.dequeue(this._requestQueue);

      if (proxy != null) {
        if (proxy.get_isFormPost()) {
          var form = document.forms.namedItem("Ag_Net_WebRequestQueue_Form");

          if (form != null)
            $common.removeElement(form);

          form = $common.createElementFromTemplate({
            nodeName: "form",
            properties: {
              id: "Ag_Net_WebRequestQueue_Form",
              action: proxy.get_path(),
              method: "POST",
              target: proxy.get_target()
            }
          }, document.body);

          var params = proxy.get_params();
          for (var p in params) {
            var value = null;
            if (String.isInstanceOfType(params[p]) || Number.isInstanceOfType(params[p]))
              value = params[p];
            else
              value = Sys.Serialization.JavaScriptSerializer.serialize(params[p]);

            $common.createElementFromTemplate({
              nodeName: "input",
              properties: {
                type: "hidden",
                name: p,
                value: value
              }
            }, form);
          }
          form.submit();

          if (this._requestQueue.length == 0)
            this._onRequestsCompleted();
        } else {
          this._currentProxy = proxy;
          var webRequest = null;

          if (proxy.get_isDataMethod()) {
            webRequest = new Sys.Net.WebRequest()
            var query = proxy.get_method();

            switch (proxy.get_verb()) {
              case "GET":
                var args = new Sys.StringBuilder();
                var params = proxy.get_params();
                for (var p in params) {
                  if (String.isInstanceOfType(params[p])) {
                    if (p.startsWith("$"))
                      args.append(String.format("{0}={1}", p, params[p]));
                    else
                      args.append(String.format("{0}='{1}'", p, params[p]));
                  } else if (Date.isInstanceOfType(params[p])) {
                    if (!(isNaN(params[p]) || isNaN(params[p].valueOf())))
                      args.append(String.format("{0}=datetime'{1}'", p, params[p].format("s")));
                  } else if (!isNaN(params[p])) {
                    args.append(String.format("{0}={1}", p, params[p]));
                  }
                }

                var argString = args.toString("&");
                if (argString.length > 0)
                  query += "?" + argString;

                // TODO: Support user context
                proxy.get_service().query(
                query,
                proxy.get_succeededCallback(),
                proxy.get_failedCallback(),
                null,
                webRequest);
                break;

              case "POST":
                webRequest = proxy.get_service().insert(
                proxy.get_params(),
                query,
                proxy.get_succeededCallback(),
                proxy.get_failedCallback(),
                null,
                webRequest);
                break;

              case "PUT":
                webRequest = proxy.get_service().update(
                proxy.get_params(),
                query,
                proxy.get_succeededCallback(),
                proxy.get_failedCallback(),
                null,
                webRequest);
                break;

              case "DELETE":
                webRequest = proxy.get_service().remove(
                proxy.get_params(),
                query,
                proxy.get_succeededCallback(),
                proxy.get_failedCallback(),
                null,
                webRequest);
                break;
            }
          } else {
            webRequest = Sys.Net.WebServiceProxy.invoke(
            proxy.get_path(),
            proxy.get_method(),
            false,
            proxy.get_params(),
            proxy.get_succeededCallback(),
            proxy.get_failedCallback());
          }

          proxy.set__webRequest(webRequest);
        }
      }
    }
  },

  newContent: function(url, params, target) {
    /// <summary>Use this method to post any data as Form arguments to a handler</summary>
    /// <param name="url" type="String">The URL to post data to</param>
    /// <param name="params" type="Object">Object containing properties that become Form data keyed to the property name</param>
    /// <param name="target" type="String">The window or frame at which to target returning content</param>
    target = (typeof (target) !== "undefined" ? target : "");
    var proxy = new Ag.Net.ProxyCall(url, "POST", null, params, target);
    this._queueProxy(proxy);
  },

  queue: function(service, method, params, succeededCallback, failedCallback) {
    /// <summary>Create and queue a proxy call to a web service</summary>
    /// <remarks>
    /// If the WebRequestManager is currently not executing,
    /// this request start immediately.
    ///
    /// We only create a new default callback if the service DOES NOT have a default callback set.
    /// Be careful when defining a default succeeded callback on a generated proxy class, if you do
    /// then you must pass a new succeeded callback for any service call that you want to be non-default.
    /// </remarks>
    var proxy = new Ag.Net.ProxyCall(service, "GET", method, params, succeededCallback, failedCallback);
    this._queueProxy(proxy);
  },

  queueInsert: function(service, method, item, succeededCallback, failedCallback) {
    var proxy = new Ag.Net.ProxyCall(service, "POST", method, item, succeededCallback, failedCallback);
    this._queueProxy(proxy);
  },

  queueUpdate: function(service, method, item, succeededCallback, failedCallback) {
    var proxy = new Ag.Net.ProxyCall(service, "PUT", method, item, succeededCallback, failedCallback);
    this._queueProxy(proxy);
  },

  queueDelete: function(service, method, item, succeededCallback, failedCallback) {
    var proxy = new Ag.Net.ProxyCall(service, "DELETE", method, item, succeededCallback, failedCallback);
    this._queueProxy(proxy);
  },

  queueExternal: function(count) {
    /// <summary>Add one or more web requests that will be handled outside this object</summary>
    if (typeof (count) === "undefined") {
      count = 1;
    }

    if (count && count > 0) {
      this._externalRequestCount += count;
      this._onRequestsQueued();
    }
  },

  completedExternal: function(count) {
    /// <summary>Remove one or more web requests that were handled outside this object</summary>
    if (typeof (count) === "undefined") {
      count = 1;
    }

    if (count && count > 0) {
      this._externalRequestCount = (count > this._externalRequestCount ? 0 : this._externalRequestCount - count);
      if (this._requestQueue.length == 0 && this._externalRequestCount == 0) {
        this._onRequestsCompleted();
      }
    }
  },

  // Public Properties

  get_length: function() {
    /// <value type="Number">The current size of the request queue</value>
    return this._requestQueue.length + this._externalRequestCount;
  },

  // Public Events

  add_requestsCompleted: function(handler) {
    /// <summary>Occurs when all current requests have finished and the queue is empty</summary>
    this.get_events().addHandler("requestsCompleted", handler);
  },

  remove_requestsCompleted: function(handler) {
    this.get_events().removeHandler("requestsCompleted", handler);
  },

  add_requestsQueued: function(handler) {
    /// <summary>Occurs when a new request is added to an empty queue</summary>
    this.get_events().addHandler("requestsQueued", handler);
  },

  remove_requestsQueued: function(handler) {
    this.get_events().removeHandler("requestsQueued", handler);
  },

  // Private Methods

  _onRequestsCompleted: function() {
    /// <summary>Raises the requestsCompleted event</summary>
    /// <remarks>
    /// This allows you to provide a custom handler for the event.
    /// </remarks>
    var handler = this.get_events().getHandler("requestsCompleted");

    if (handler !== null) {
      handler(this, Sys.EventArgs.Empty);
    }
  },

  _onRequestsQueued: function() {
    /// <summary>Raises the requestsQueued event</summary>
    /// <remarks>
    /// This allows you to provide a custom handler for the event.
    /// </remarks>
    var handler = this.get_events().getHandler("requestsQueued");

    if (handler !== null) {
      handler(this, Sys.EventArgs.Empty);
    }
  },

  _queueProxy: function(proxyCall) {
    /// <summary>Queue a call to a web service</summary>
    /// <param name="proxyCall" type="Ag.Net.ProxyCall">The request proxy to add to the queue</param>
    /// <remarks>
    /// If the WebRequestManager is currently not executing,
    /// this request start immediately.
    /// </remarks>

    if (this._requestQueue.length == 0) {
      this._onRequestsQueued();
    }

    Array.enqueue(this._requestQueue, proxyCall);

    if (!this._executing) {
      this.executeQueue();
    }
  },

  // Private Event Handlers

  _webRequestManager_invokingRequest: function(sender, networkRequestEventArgs) {
    this._executing = true;
  },

  _webRequestManager_completedRequest: function(sender, eventArgs) {
    this._executing = false;
    this._currentProxy = null;

    if (this._requestQueue.length == 0 && this._externalRequestCount == 0) {
      this._onRequestsCompleted();
    } else if (this._requestQueue.length > 0) {
      this.executeQueue();
    }
  }
}

Ag.Net.WebRequestQueue.registerClass("Ag.Net.WebRequestQueue", Sys.Component);

if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();