// $Id: ServerMethods.js,v 1.12 2007-09-19 10:06:47 tamnguyet Exp $
/// <reference path="~/Script/jQuery/jquery.js" />
/// <reference path="~/Script/jQuery/UI/jquery_ui_all.js" />


var TYPE_VOID = 1;
var TYPE_BOOL = 2;
var TYPE_CHAR = 3;
var TYPE_BYTE = 4
var TYPE_INT = 5;
var TYPE_LONG = 6;
var TYPE_FLOAT = 7;
var TYPE_DOUBLE = 8;
var TYPE_STRING = 9;
var TYPE_DATETIME = 10;
var TYPE_ARRAY = 11;
var TYPE_XMLDOCUMENT = 12;
var TYPE_OBJECT = 13;
var TYPE_CGDATETIME = 14;

var SERVER_CALL_TIMEOUT = 1000; // 3 minutes

//
// Small utilities
//
String.prototype.encodeSgml = function()
{
   var str = new String(this);
   str = str.replace(/&/g, "&amp;");
   str = str.replace(/</g, "&lt;");
   str = str.replace(/>/g, "&gt;");
   str = str.replace(/"/g, "&quot;");
   return str;
}

String.prototype.decodeSgml = function()
{
   var str = new String(this);
   str = str.replace(/&quot;/g, "\"");
   str = str.replace(/&gt;/g, ">");
   str = str.replace(/&lt;/g, "<");
   str = str.replace(/&amp;/g, "&");
   return str;
}

//
// Represents a server method exception that could
// be returned from a server method call
//
function ServerMethodException(xml)
{
   if (xml != null)
   {
      var messageElement = xml.firstChild;
      var stackTraceElement = messageElement.nextSibling;

      // Information is placed inside CDATA elements hence the extra getFirstChild
      this.message = "";
      for (var i = 0; i < messageElement.childNodes.length; i++)
       this.message += messageElement.childNodes[i].nodeValue;
      this.stackTrace = "";
      for (var i = 0; i < stackTraceElement.childNodes.length; i++)
       this.stackTrace += stackTraceElement.childNodes[i].nodeValue;
     }
}

// Returns the message for this exception
ServerMethodException.prototype.getMessage = function()
{
   return this.message;
}

// Returns the stack traces for this exception
ServerMethodException.prototype.getStackTrace = function()
{
   return this.stackTrace;
}

// Always true because objects of this type are exceptions
ServerMethodException.prototype.isException = true;

//
// Represents a server method request time out exception
//
function ServerMethodRequestTimeOutException(message)
{
   this.message = message;
}

// Returns the message for this exception
ServerMethodRequestTimeOutException.getMessage = function()
{
   return this.message;
}

// Returns the stack traces for this exception
ServerMethodRequestTimeOutException.getStackTrace = function()
{
   return "";
}

// Always true because objects of this type are exceptions
ServerMethodRequestTimeOutException.prototype.isException = true;

//
// Represents a server method message
//
function ServerMethodMessage(message, width, height, resizable)
{
   this.message = message;

   this.properties = new Array();
   this.properties.width = (width == null) ? screen.width : width;
   this.properties.height = (height == null) ? screen.height : height;
   this.properties.resizable = (resizable == null) ? true : resizable;
}

ServerMethodMessage.prototype.show = function()
{
   var windowsFeatures = "";
   for (var propertyName in this.properties)
   {
      var propertyValue = this.properties[propertyName];
      switch (typeof (propertyValue))
      {
         case "boolean":
            windowsFeatures += propertyName + "=" + ((propertyValue) ? "yes" : "no") + ",";
            break;
         case "number":
            windowsFeatures += propertyName + "=" + propertyValue + ",";
            break;
         default:
            break;
      }
   }

   var winHnd = window.open("about:blank", "ServerMethodMessage", windowsFeatures);
   winHnd.document.open();
   winHnd.document.writeln(this.message);
   winHnd.document.close();
   winHnd.focus();
}

//
// Represents a server object returned from a server method call
//
function ServerObject()
{
}

function loginCallback(result)
{
   window.open(result.Status);
}

function SessionOut()
{
   if (window.confirm("You have been logged out of the system. Do you want to log in again and keep the current page?"))
   {
      //getCenterWindow().openDialog(virtualAppHost, virtualAppHost + "Login.aspx", false);
      window.open(virtualAppHost + "Login.aspx?reconnect=1", "Reconnect", "width=600,height=400");
   }
}

// Parses the return value from a server method call
ServerObject.__parse = function(xmlString) {
 if (String.isNullOrEmpty(xmlString))
  return null;
 var xml = null;
 function onerror() {
  try {
   var _dom = $(xmlString);
   if (_dom.length > 0 && _dom.find("#normalLoginDiv").length > 0) {
    SessionOut();
    return;
   }
  }
  catch (ex) { }
  var smm = new ServerMethodMessage("ERROR: " + e.message + "<hr />" + xmlString);
  smm.show();
 };
 try {
  if (window.ActiveXObject) {
   xml = new ActiveXObject("Microsoft.XMLDOM");
   xml.async = "false";
   xml.loadXML(xmlString);
  }
  else {
   xml = document.implementation.createDocument("", "", null);
   xml.async = "false";
   var parser = new DOMParser();
   xml = parser.parseFromString(xmlString, "text/xml");
  }
 }
 catch (e) {
  onerror();
  return null;
 }
 if (xml.documentElement.nodeName == 'parsererror') {
  onerror();
  return null;
 }
 var result = ServerObject.__parseSimple(xml.documentElement);
 return result;
}

// Parses the return value as one of the simple types
ServerObject.__parseSimple = function(xml)
{
   var result = null;

   switch (xml.nodeName)
   {
      case "null":
         break;
      case "bool":
       result = (xml.firstChild.nodeValue == "true");
         break;
      case "float":
      case "double":
       result = parseFloat(xml.firstChild.nodeValue);
         break;
      case "byte":
      case "int":
      case "long":
       result = parseInt(xml.firstChild.nodeValue);
         break;
      case "guid":
         alert("Cannot handle returned guid objects yet");
         break;
      case "datetime":
      case "cgdatetime":
       result = ServerObject.__parseDateTime(xml.firstChild.nodeValue);
         break;
      case "char":
      case "string":
         result = "";
         for (var i = 0; i < xml.childNodes.length; i++)
          result += xml.childNodes[i].nodeValue;
         break;
      case "array":
         result = ServerObject.__parseArray(xml);
         break;
        case "xmldocument":
         result = "";
         for (var i = 0; i < xml.childNodes.length; i++)
          result += xml.childNodes[i].nodeValue;
         result = ServerObject.__parseXmlDocument(result);
         break;
      case "object":
         var smm = new ServerMethodMessage("ERROR: Cannot handle abitrary objects yet<hr />" + xml.toString(), 200, 200);
         smm.show();
         break;
      case "exception":
         throw new ServerMethodException(xml);
         break;
      default:
         var xmlToString = xml.toString();
         var smm = new ServerMethodMessage("ERROR: Unrecognized xml<hr />" + xmlToString + "<hr />" + xmlToString.encodeSgml(), 200, 200);
         smm.show();
         break;
   }

   return result;
}

// Parses the return value as a date
ServerObject.__parseDateTime = function(dateString)
{
   var dateRegEx = /[0-9]+/ig;
   var dateMatches = dateString.match(dateRegEx);

   var year = parseInt(dateMatches[0], 10);
   var month = parseInt(dateMatches[1], 10) - 1;
   var date = parseInt(dateMatches[2], 10);
   var hours = parseInt(dateMatches[3], 10);
   var minutes = parseInt(dateMatches[4], 10);
   var seconds = parseInt(dateMatches[5], 10);
   var milliseconds = parseInt(dateMatches[6], 10);

   var date = new Date(year, month, date, hours, minutes, seconds, milliseconds);

   return date;
}

// Parses the return value as an array
ServerObject.__parseArray = function(xml)
{
   var array = new Array();
   var element = null;

   var xmlNode = xml.firstChild;
   while (xmlNode != null)
   {
      element = ServerObject.__parseSimple(xmlNode);
      array.push(element);

      xmlNode = xmlNode.nextSibling;
   }

   return array;
}

// Parses the return value as a xml document
ServerObject.__parseXmlDocument = function(xml)
{
   var xmlDocument;
   try
   {
      var xmlParser = new XMLParser();
      xmlParser.parse(xml);
      xmlDocument = xmlParser.doc;
      xmlParser = null;
   }
   catch (e)
   {
      alert("ERROR: " + e.message + "\n" + xmlString);
      return null;
   }

   return xmlDocument;
}

ServerObject.__parseObject = function(xml)
{
}

//
// Represents a property for a server object
//
function ServerObjectProperty()
{
}

//
// Represents a parameter for a method
//
function Parameter(type, name)
{
   this.type = type;
   this.name = name;

   switch (this.type)
   {
      case TYPE_VOID:
         this.typeName = "null";
         break;
      case TYPE_BOOL:
         this.typeName = "boolean";
         break;
      case TYPE_CHAR:
         this.typeName = "char";
         break;
      case TYPE_BYTE:
         this.typeName = "byte";
         break;
      case TYPE_INT:
         this.typeName = "integer";
         break;
      case TYPE_LONG:
         this.typeName = "long";
         break;
      case TYPE_FLOAT:
         this.typeName = "float";
         break;
      case TYPE_DOUBLE:
         this.typeName = "double";
         break;
      case TYPE_STRING:
         this.typeName = "string";
         break;
      case TYPE_DATETIME:
         this.typeName = "datetime";
         break;
      case TYPE_CGDATETIME:
         this.typeName = "cgdatetime";
         break;
      case TYPE_ARRAY:
         this.typeName = "array";
         break;
      case TYPE_XMLDOCUMENT:
         this.typeName = "xmldocument";
         break;
      case TYPE_OBJECT:
         this.typeName = "object";
         break;
   }
}

// Gets this parameters type
Parameter.prototype.getType = function()
{
   return this.type;
}

// Gets this parameters type name
Parameter.prototype.getTypeName = function()
{
   return this.typeName;
}

// Gets this parameters name
Parameter.prototype.getName = function()
{
   return this.name;
}

//
// Represents a method on the server
//
function Method(type, name, asynch, cback, relativeuc)
{
   this.type = type;
   this.name = name;
   this.parameters = new Array();
   this.relativeuc = relativeuc;

   this.isAsynchrone = asynch;

   if (cback == null)
   {
      this.callback = function() { };
   }
   else
   {
      this.callback = cback;
   }

   if (0 < (arguments.length - 2))
   {
      for (var i = 5; i < arguments.length; i++)
      {
         this.parameters.push(arguments[i]);
      }
   }
}

// Gets this methods name
Method.prototype.getName = function()
{
   return this.name;
}

// Gets this methods parameters
Method.prototype.getParameters = function()
{
   return this.parameters;
}

// Gets this methods relative user control url
Method.prototype.getRelativeUC = function()
{
   return this.relativeuc;
}

// Gets this methods body
Method.prototype.getBody = function()
{
   var me = this;

   var body =
      function()
      {
         var result = ServerMethods.__callServer(me, arguments);
         return result;
      }

   body.method = me;

   return body;
}


/*
var xhReq = createXMLHttpRequest();
xhReq.open("get", "infiniteLoop.phtml", true); // Server stuck in a loop.
*** var requestTimer = setTimeout(function() {
xhReq.abort();
// Handle timeout situation, e.g. Retry or inform user.
}, MAXIMUM_WAITING_TIME); ***
xhReq.onreadystatechange = function() {
if (xhReq.readyState != 4)  { return; }
** clearTimeout(requestTimer); **
if (xhReq.status != 200)  {
// Handle error, e.g. Display error message on page
return;
}
var serverResponse = xhReq.responseText;
...
};   
*/

//
// Represents the collection of server methods
//
var ServerMethods = new Array();

// Validates the type of a parameter against the value
// passed for that parameter
ServerMethods.__validateType = function(parameterType, callerArgument)
{
   switch (parameterType)
   {
      case TYPE_BOOL:
         return ((typeof callerArgument) == "boolean");
      case TYPE_CHAR:
         return (((typeof callerArgument) == "string") && callerArgument.length == 1);
      case TYPE_BYTE:
         return ((typeof callerArgument) == "number") && callerArgument <= 255;
      case TYPE_INT:
      case TYPE_LONG:
      case TYPE_FLOAT:
      case TYPE_DOUBLE:
         return ((typeof callerArgument) == "number");
      case TYPE_STRING:
         return ((typeof callerArgument) == "string");
      case TYPE_DATETIME:
         return ((typeof callerArgument) == "object");
      case TYPE_ARRAY:
         return ((typeof callerArgument) == "object");
      case TYPE_XMLDOCUMENT:
         return ((typeof callerArgument) == "object");
      case TYPE_CGDATETIME:
         return ((typeof callerArgument) == "object");
      case TYPE_OBJECT:
         // Anything is an object, true always returned.
         return true;
   }

   // No match is a validation error
   return false;
}

// Validates a server method and the parameters passed
// to this server method
ServerMethods.__validate = function(serverMethod, callerArguments)
{
   // Get parameters for the server method
   var parameters = serverMethod.getParameters();

   // Validate number of parameters
   if (parameters.length != callerArguments.length)
   {
      // "Server method "+serverMethod.getName()+" received an invalid number of arguments"
      return false;
   }

   // Validate types
   for (var i = 0; i < parameters.length; i++)
   {
      if (!ServerMethods.__validateType(parameters[i].getType(), callerArguments[i]))
      {
         // Some kind of error showing there is a type mismatch
         return false;
      }
   }

   // Everything is valid
   return true;
}

// Prepares the parameter values for the server method
ServerMethods.__prepareArguments = function(serverMethod, callerArguments)
{
   var argument = "";
   var preparedArguments = "";

   var parameters = serverMethod.getParameters();

   // Define that this is a server method call, and give the
   // name for the method we want to call on the server
   preparedArguments +=
      "--ServerMethods\r\n" +
      "Content-Disposition: form-data; name=\"serverMethodCall\"\r\n\r\n" +
      "true\r\n" +

      "--ServerMethods\r\n" +
      "Content-Disposition: form-data; name=\"methodName\"\r\n\r\n" +
      serverMethod.getName() + "\r\n";

   if (serverMethod.getRelativeUC() != '')
   {
      preparedArguments +=
      "--ServerMethods\r\n" +
      "Content-Disposition: form-data; name=\"relativeuc\"\r\n\r\n" +
      serverMethod.getRelativeUC() + "\r\n";
   }

   // Prepare method arguments
   for (var i = 0; i < parameters.length; i++)
   {
      switch (parameters[i].getType())
      {
         case TYPE_DATETIME:
         case TYPE_CGDATETIME:
            var date = callerArguments[i];

            argument = "";
            argument += date.getFullYear() + "-";
            argument += (date.getMonth() + 1) + "-";
            argument += date.getDate() + " ";
            argument += date.getHours() + ":";
            argument += date.getMinutes() + ":";
            argument += date.getSeconds() + ".";
            argument += date.getMilliseconds();

            break;
         case TYPE_XMLDOCUMENT:
            argument = callerArguments[i].toString();
            break;
         default:
            argument = callerArguments[i];
            break;
      }

      preparedArguments +=
         "--ServerMethods\r\n" +
         "Content-Disposition: form-data; name=\"" + parameters[i].getName() + "\"\r\n\r\n" +
         argument + "\r\n";
   }
   preparedArguments += "--ServerMethods--";

   return preparedArguments;
}

// Calls the server method on the server
ServerMethods.__callServer = function(serverMethod, callerArguments)
{
   if (ServerMethods.__validate(serverMethod, callerArguments))
   {
      //      var result;
      //      Concurrent.Thread.create(function()
      //      {
      //         var wRequest = new Sys.Net.WebRequest();
      //         wRequest.set_url(document.location.href);
      //         wRequest.get_headers()["Cache-Control"] = "must-revalidate";
      //         wRequest.get_headers()["Content-Type"] = "multipart/form-data; boundary=ServerMethods";
      //         wRequest.set_body(ServerMethods.__prepareArguments(serverMethod, callerArguments));
      //         wRequest.set_timeout(3000);
      //         wRequest.add_completed(
      //            function(executor, eventArgs)
      //            {
      //               if (executor.get_responseAvailable())
      //               {
      //                  var result = ServerObject.__parse(executor.get_responseData());
      //                  serverMethod.callback(result);
      //               }
      //               else if (executor.get_timedOut())
      //               {
      //                  alert("timeout");
      //               }
      //               else if (executor.get_aborted())
      //               {
      //                  alert("aborted");
      //               }
      //            });
      //         var executor = new Sys.Net.XMLHttpExecutor();
      //         wRequest.set_executor(executor);
      //         executor.executeRequest();

      //         while (!executor.get_responseAvailable() && !executor.get_timedOut() && !executor.get_aborted())
      //         {
      //            Concurrent.Thread.sleep(1000);
      //         }

      //         if (executor.get_responseAvailable())
      //         {
      //            result = ServerObject.__parse(executor.get_responseData());
      //            //serverMethod.callback(result);
      //         }
      //         else if (executor.get_timedOut())
      //         {
      //            alert("timeout");
      //         }
      //         else if (executor.get_aborted())
      //         {
      //            alert("aborted");
      //         }
      //      });
      //      alert("finished");
      //      return result;
      //      if (executor.get_responseAvailable())
      //      {
      //         return ServerObject.__parse(executor.get_responseData());
      //      }
      //      else if (executor.get_timedOut())
      //      {
      //         alert("timeout");
      //      }
      //      else if (executor.get_aborted())
      //      {
      //         alert("aborted");
      //      }

      //      executor.executeRequest();
      //      return null;

      //      var xmlHttp = new XMLHttpRequest();
      if (window.XMLHttpRequest)
      {
         xmlHttp = new XMLHttpRequest();
      }
      else if (window.ActiveXObject)
      {
         xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
      }

      if (serverMethod.isAsynchrone) // In case of async call, register 
         xmlHttp.onreadystatechange = function()
         {
            if (xmlHttp.readyState != 4) return;

            var result;

            try
            {
               result = ServerObject.__parse(xmlHttp.responseText);
            }
            catch (e)
            {
               if (ServerMethods.onCallServerError == null)
               {
                  result = e;
               }
               else
               {
                  ServerMethods.onCallServerError(e, serverMethod, callerArguments)
                  return null;
               }
            }
            serverMethod.callback(result);
         };

      xmlHttp.open("POST", document.location.href, serverMethod.isAsynchrone);
      // set the require headers for working with server method
      xmlHttp.setRequestHeader("Cache-Control", "must-revalidate");
      xmlHttp.setRequestHeader("Content-Type", "multipart/form-data; boundary=ServerMethods");

      // For IE.8 only, set the timeout
      //      if (xmlHttp.timeout != undefined)
      //      {
      //         xmlHttp.timeout = SERVER_CALL_TIMEOUT;
      //         xmlHttp.ontimeout = function()
      //         {
      //            throw new Error("Timeout occurs during call to server method ");
      //         };
      //      }

      xmlHttp.send(ServerMethods.__prepareArguments(serverMethod, callerArguments));
      if (serverMethod.isAsynchrone)
         return null;

      return ServerObject.__parse(xmlHttp.responseText);
   }
   else
   {
      ServerMethods.onValidationError(serverMethod, callerArguments);
   }
}

// Registers a server method
ServerMethods.registerMethod = function(serverMethod)
{
   ServerMethods[serverMethod.getName()] = serverMethod.getBody();
}

// On validation error event handler
ServerMethods.onValidationError = function(serverMethod, callerArguments)
{
   // Get parameters for the server method
   var parameters = serverMethod.getParameters();

   // Validate number of parameters
   if (parameters.length != callerArguments.length)
   {
      alert("Server method " + serverMethod.getName() + " received an invalid number of arguments");
      return;
   }

   // Validate types
   var messages = "";
   for (var i = 0; i < parameters.length; i++)
   {
      if (!ServerMethods.__validateType(parameters[i].getType(), callerArguments[i]))
      {
         // Some kind of error showing there is a type mismatch
         messages += " - Parameter '" + parameters[i].getName() + "' expected type '" + parameters[i].getTypeName() + "' not type '" + (typeof callerArguments[i]) + "'";
      }
   }

   if (0 < messages.length)
   {
      messages = "Type mismatch(es) in server method " + serverMethod.getName() + ":\n" + messages;
      alert(messages);
      return;
   }
}

// On error event handler
ServerMethods.onCallServerError = function(exception, serverMethod, callerArguments)
{
   alert(exception.getMessage());
}

