/**
 * Base classes for using FritZ3 in javascript.
 *
 * Include this file once at the top of the HTML page.
 * A reference to the fApplication object is retrieved
 * using:
 *
 *    var app = fApplication.singleton();
 *
 */

if (typeof fritz3 == "undefined") var fritz3 = {};

// A module pattern combined with a singleton pattern.
// http://yuiblog.com/blog/2007/06/12/module-pattern/
var fApplication = (function() {
   var instance = null;

   function __construct()
   {
      // Initialize stuff
      var handlers = [];
      var error = false;
      var server;
      var options;
      var currentHash = "";
      var pageTracker;

      // Member functions
      this.initialize = function(properties) {
         options = properties || {};

         var ro = options["rpc"] || {};
         ro["asynchronous"] = ro["asynchronous"] || true;
         ro["sanitize"] = ro["sanitize"] || true;
         ro["methods"] = ro["methods"] || ['fApplication.invoke', 'fApplication.keepAlive', 'fApplication.getComponent', 'fLog.log'];
         ro["protocol"] = ro["protocol"] || 'JSON-RPC';

         var loc = window.location.href;
         if(window.location.hash != '')
            loc = loc.substr(0, loc.length - window.location.hash.length);
         server = new rpc.ServiceProxy(loc, ro);

         if(options['googleTrackerID'])
            this.googleTrack();
      }

      // Keep the session alive
      this.keepAlive = function() {
         if(server == undefined)
            this.initialize();

         server.fApplication.keepAlive();
      }

      this.addHistory = function(params) {
         var qs = "";

         if(typeof params == 'string')
            qs = params;
         else
            for(var i in params)
            {
               if(qs != "") qs += "&";

               qs += i + "=";
               if(typeof params[i] == "object" || params[i][0] == "[" || params[i][0] == "{" || params[i][0] == "\"")
                  qs += escape($.toJSON(params[i]));
               else
                  qs += escape(params[i]);
            }
         window.dhtmlHistory.add(qs);
         currentHash = qs;
      }

      this.processHistory = function(hash)
      {
         if(hash == currentHash) return;

         var params = {};
         var p = hash.split("&");
         for(var i in p)
         {
            var nv = p[i].split("=");
            nv[1] = unescape(nv[1]);

            if(nv[1][0] == "[" || nv[1][0] == "{" || nv[1][0] == "\"")
               params[nv[0]] = $.evalJSON(nv[1]);
            else
               params[nv[0]] = nv[1];
         }
         currentHash = hash;
         this.triggerEvent(fEvent.STATE_CHANGE, params);
      }

      this.triggerEvent = function(event, target) {
         if(!(event instanceof fEvent))
            event = new fEvent(event, target);

         var r = "";

         for(var i = 0; i < handlers.length; i++)
         {
            var h = handlers[i];
            if(h == undefined) continue;

            var key;
            if(h.key instanceof fEvent)
            {
               key = h.key.type;
               var f = h.key.target;
               if(f != "")
               {
                  var r = false;
                  if(f.substr(0,1) == "#" && $(event.target).attr("id"))
                     r = $(event.target).attr("id") == f.substr(1);
                  else if(f.substr(0,1) == "." && $(event.target).attr("class"))
                     r = $(event.target).hasClass(f.substr(1));
                  else
                     r = $(event.target).is(f);

                  if(!r) continue;
               }
            }
            else
               key = h.key;

            if(h != undefined && (key == "*" || key == event.type || key[event.type] != undefined))
            {
               try
               {
                  var pe = h.handler.call(h, event);
                  if(typeof(pe) == 'string' && typeof(r) == 'string')
                     r += pe;
                  else
                     r = pe;
               }
               catch(e)
               {
                  fLog.error("fApplication.triggerEvent: " + e.message);
               }
            }
         }

         return r;
      }

      this.addListener = function(type, handler, prio) {
         type = type || "*";
         prio = prio || 0;

         var add = -1;
         for(var i = 0; i < handlers.length; i++)
         {
            if(handlers[i] == undefined && add == -1)
            {
               add = i;
               break;
            }
            else if(type == handlers[i].key && handlers[i].handler.toString() == handler.toString())
               return;
         }
         if(add == -1) add = handlers.length;

         handlers[add] = { key: type, handler: handler, prio: prio, id: add };
         handlers.sort(function(a, b) {
            return a.prio > b.prio ? -1 : 1;
         });

         return add;
      }

      this.removeListener = function(eventid) {
         for(var i = 0; i < handlers.length; i++)
            if(handlers[i] != undefined && handlers[i].id == eventid)
               handlers[i] = undefined;
      }

      this.lastError = function() {
         return error;
      }

      /**
       * Invoke a method. Method can be either a reference to a
       * javascript function, or php function. Especially useful for
       * event handlers defined server side, where the client can remain
       * unaware where it is executed.
       * @param string method      Either javascript:<function> or php:<function>
       *                           or php:<class>::<method>
       * @param object args        Arguments passed to function called
       * @param function onsuccess Function to call if method succeeded
       * @param function onexception Function to call if methow threw error
       */
      this.invoke = function(method, args, onsuccess, onexception) {
         if(method.substr(0, 11) == "javascript:")
         {
            method = method.substr(11);
            try
            {
               var r = window[method](args);
               if(onsuccess)
                  onsuccess(r);
            }
            catch(e)
            {
               if(!(e instanceof fError))
                  e = new fError(e, 0);
               error = e;
               if(onexception)
                  onexception(e);
            }
         }
         else
         {
            if(server == undefined)
               this.initialize();

            if($("#busy").length == 0)
               $("body").append("<div id='busy'/>");

            $("#busy").show();

            server.fApplication.invoke({
               params: { method: method, args: args },
               onSuccess:function(message) {
                  if(onsuccess)
                     onsuccess(message);
                  fApplication.singleton().triggerEvent(fEvent.INVOKE_COMPLETE, method);
                  $("#busy").hide();
               },
               onException:function(e) {
                  error = e;
                  if(onexception)
                     onexception(e);
                  else
                     fLog.error("fApplication.invoke: " + e.message);
                  $("#busy").hide();
                  return true;
               }
            });
         }
      }

      /**
       * Track an URL with Google. This will only work if the htmlDocument has been given
       * the tracker ID as googleTracker property.
       * @param string url (Pseudo)-URL to track
       */
      this.googleTrack = function(url)
      {
         if(options["googleTrackerID"] != undefined)
         {
            if(pageTracker == undefined)
            {
               pageTracker = _gat._getTracker(options["googleTrackerID"]);
               if(pageTracker._initData != undefined)
                  pageTracker._initData();
            }

            if(pageTracker != undefined && pageTracker._trackPageview != undefined)
               pageTracker._trackPageview(url);
         }
      }

      /**
       * Retrieve a component from the server and insert it somewhere
       * @param string name       Name of fComponent class to retrieve
       * @param object properties Object containing initialization properties
       * @param node where        Node whose content to replace
       */
      this.getComponent = function(name, properties, where, onsuccess, onexception, replace) {
         properties = properties || [];
         where = where || document;
         replace = replace || false;

         if(server == undefined)
            this.initialize();

         if($("#busy").length == 0)
            $("body").append("<div id='busy'/>");

         $("#busy").show();

         server.fApplication.getComponent({
               params: { component: name, properties: properties },
            onSuccess: function(r) {
                          // Find which required items need to be loaded first
                          for(var i in r.required)
                          {
                             var src = r.required[i].replace("{FRITZ3}", fApplication.prefix);
                             if(src.match('.js$'))
                             {
                                if($('script[src=' + src + ']').length == 0)
                                   $('head').append('<script src="' + src + '"></script>');
                             }
                             else if(src.match('.css$'))
                             {
                                if($('link[href=' + src + ']').length == 0)
                                   $('head').append('<link type="text/css" rel="stylesheet" src="' + src + '"/>');
                             }
                          }

                          // Process any initialization blocks
                          for(var src in r.init)
                          {
                             // If the code is not yet defined, add as 'class method'
                             if(!window[src])
                             {
                                window[src] = {
                                   init: new Function(r.init[src])
                                };
                             }
                             // Else replace method, as initialization may depend on login status etc
                             else
                                window[src].init = new Function(r.init[src]);
                          }

                          // Replace actual content and run initializers
                          if(replace)
                             $(where).replaceWith(r.content);
                          else
                             $(where).html(r.content);
                          for(var src in r.init)
                             window[src].init();

                          if(onsuccess)
                             onsuccess(r.content);
                          fApplication.singleton().triggerEvent(fEvent.COMPONENT_LOADED, where);

                          $("#busy").hide();
                       },
          onException: function(e) {
                          if(onexception)
                             onexception(e);
                          else
                          {
                             if(e.message) e = e.message;
                             fLog.error("fApplication.getComponent: " + e);
                          }

                          $("#busy").hide();
                          return true;
                       }
         });
      }
   }

   return new function() {
      this.version = "0.2";
      this.timers = {};
      this.googleTracker = undefined;

      this.getScript = function(file, callback) {
         var doc = document.getElementsByTagName('head')[0];
         var js = document.createElement("script");
         js.type = "text/javascript";
         js.src = file;
         doc.appendChild(js);

         if(callback != undefined)
         {
            // test for onreadystatechange to trigger callback
            script.onreadystatechange = function () {
               if (script.readyState == 'loaded' || script.readyState == 'complete') {
                  callback();
               }
            }
            // test for onload to trigger callback
            script.onload = function () {
               callback();
               return;
            }
            // safari doesn't support either onload or readystate, create a timer
            // only way to do this in safari
            if(($.browser.safari && !$.browserLanguage.version.match(/Version\/3/)) || $.browser.opera)
            {
               fApplication.timers[file] = setInterval(function() {
                  if (/loaded|complete/.test(document.readyState)) {
                     clearInterval(fApplication.timers[url]);
                     callback(); // call the callback handler
                  }
               }, 10);
            }
         }
      }

      this.singleton = function() {
         if(instance === null)
         {
            instance = new __construct();
            delete instance.__construct;
            window.dhtmlHistory.create({
                 toJSON: function(o) { return $.toJSON(o); },
               fromJSON: function(s) { return $.evalJSON(s); },
               blankURL: fApplication.prefix + "external/rsh/blank.html?"
            });

            // Do this once everybody has loaded, so the STATE_CHANGE listener is in place
            $(window).load(function() {
               window.dhtmlHistory.initialize();
               window.dhtmlHistory.addListener(function(newLocation, historyData) {
                  fApplication.singleton().processHistory(newLocation);
               });
            });

            // Every 15 minutes, refresh server session
            setInterval("fApplication.singleton().keepAlive()", 15 * 60 * 1000);
         }
         return instance;
      }
   }
})();

// Try and find our prefix, so we can preload jQuery and RPC. Let's see
// if this works on all browsers :-)
fApplication.prefix = "";
var _s = document.getElementsByTagName("SCRIPT");
for(var _i = 0; _i < _s.length; _i++)
   if((fApplication.prefix = _s[_i].getAttribute("src").indexOf("javascript/fritz.js")) > -1)
      fApplication.prefix = _s[_i].getAttribute("src").substr(0, fApplication.prefix);

if(!jQuery)
   fApplication.getScript(fApplication.prefix + "external/jquery/jquery-1.2.6.min.js");
if(!rpc)
{
   fApplication.getScript(fApplication.prefix + "external/json-xml-rpc/client/rpc.js");
   fApplication.getScript(fApplication.prefix + "external/jquery/jquery.json-1.2.min.js");
}

// This a locally modified version, using $("body").append() instead of document.write(), and a fix for initial load
if(!window.dhtmlHistory)
   fApplication.getScript(fApplication.prefix + "external/rsh/rsh4jquery.compressed.js");

if(!fLog)
   fApplication.getScript(fApplication.prefix + "javascript/fLog.js");
if(!fEvent)
   fApplication.getScript(fApplication.prefix + "javascript/fEvent.js");
if(!fError)
   fApplication.getScript(fApplication.prefix + "javascript/fError.js");

