// Javascript Library of general helper functions / objects
// $Id: mx_library.js,v 1.2 2009-10-01 11:27:37 cvs-daniel Exp $
// Author: Daniel Kabs
// Copyright 2006 MOBOTIX AG, Kaiserslautern

/*
  FireBug available on Firefox only
  
  FireBug lite can be activated on IE.
  see http://www.getfirebug.com/lite.html
*/

//window.MxDebugIE = true;

if ("MxDebugIE" in window && document.all) {
  // load Firebug lite
  document.documentElement.debug = "true";
  document.write('<script language="javascript" type="text/javascript" src="/control/firebug/firebug.js"><\/script>');
} else if (!("console" in window)) {
    console = new Object();
    console.log = console.debug = console.info = console.warn =
    console.error =  function() {  };
    console.error = alert;
}


/* Javascript does not have block statement scope
   instead variables introduced are scoped to the containing function.
   So create namespace by calling an anonymous function that returns
   an object which contains public functions.
*/
var MxJsLib = (function() {
  var URLCOUNTER = Math.floor(Math.random() * 10000000);
  var self = {
    URLNoRecordingsImage           : '/decor/m1m-error.jpg',
    URLNoRecordingsImage_PDA       : '/decor/m1m-error_PDA.jpg',
    URLNoImagesImage               : '/decor/err_no_images.jpg',
    URLNoImagesImage_PDA           : '/decor/err_no_images_PDA.jpg',
    URLDummyCameraImage     : '/decor/m1m-foto-l.jpg',
    URLDummyCameraImage_PDA : '/decor/m1m-foto-l_PDA.jpg',

    registerBodyOnloadHandler : function(onloadHandlerFunction) {
      if (typeof onloadHandlerFunction != 'undefined') {
        var oldOnloadHandlerFunction = window.onload;
        if (typeof(oldOnloadHandlerFunction) != 'function') {
          window.onload = onloadHandlerFunction;
        } else {
          window.onload = function() {
            oldOnloadHandlerFunction();
            onloadHandlerFunction();
          }
        }
      }
    },
    registerBodyOnunloadHandler : function(FuncRef) {
      if (typeof FuncRef != 'undefined') {
        var oldHandlerFunction = window.onunload;
        if (typeof(oldHandlerFunction) != 'function') {
          window.onunload = FuncRef;
        } else {
          window.onunload = function() {
            oldHandlerFunction();
            FuncRef();
          }
        }
      }
    },
    clearNode : function(node) {
      if (!node) return;
      while (node.hasChildNodes()) {
        self.discardElement(node.firstChild);
      }
    },
    /* first remove the element from the DOM,
       then avoid non-permanent IE leak, code shamelessly copied from
       http://outofhanwell.com/ieleak/index.php?title=Fixing_Leaks
    */
    discardElement : function(element) {
      if (element.parentNode) {
        element.parentNode.removeChild(element);
      }
      if (!document.all) {
        return;
      }
      var garbageBin = document.getElementById('IELeakGarbageBin');
      if (!garbageBin) {
        garbageBin = document.createElement('DIV');
        garbageBin.id = 'IELeakGarbageBin';
        garbageBin.style.display = 'none';
        document.body.appendChild(garbageBin);
      }
      // move the element to the garbage bin
      garbageBin.appendChild(element);
      garbageBin.innerHTML = '';
    },
    makeClosureToCallObjectMethod : function(obj,fun) {
      return function() { obj[fun]() };
    },
    Exception : function(msg) {
      this.message = msg;
    },
    makeUncachedURL : function (path) {
      if (path.substr(-1,1) == "&") {
        ;
      } else if (path.indexOf("?") < 0) {
        path += "?";
      } else {
        path += "&";
      }
      return path + (URLCOUNTER++);
    },
    convertAlarmAddress : function(Address) {
      /* input  : address (<sequence number> . <offset>)*/
      /* output : array ([0]=sequence, [1]=alarm image offset) */
      if (typeof Address != "string") {
        Address = Address.toString();
      }
      if (Address == '') {
        throw new  MxJsLib.Exception("Address must not be empty.");
      }
      if (Address.indexOf(".") == -1) {
        Address += ".0";
      }
      var AddressArray = Address.split(".",2);
      if (AddressArray[0] < 0 || AddressArray[1] < 0) {
        throw new  MxJsLib.Exception("Address '"+Address+"' must not be negative.");
      }
      if (isNaN(AddressArray[0]) || isNaN(AddressArray[1])) {
        throw new  MxJsLib.Exception("Address '"+Address+"' must be numerical.");
      }
      return AddressArray;
    },
    openCenteredPopup : function (URL,Name,Options) {
      /* for Options see http://developer.mozilla.org/en/docs/DOM:window.open */
      if (!Options) {
        Options = { width:200, height:200 };
      }
      var Position = self.calcPopupPositionOnWindow(window, null,
                                   Options.width, Options.height);
      if (Position) {
        Options['left'] = Position[0];
        Options['top'] = Position[1];
      }
      var OptionsString = "";
      for (var i in Options) {
        if (OptionsString.length > 0) {
          OptionsString += ",";
        }
        OptionsString += i + "=" + Options[i];
      }
      return window.open(URL,Name,OptionsString);
    },
    calcPopupPositionOnWindow : function (WindowReference, PopupReference,
                      /* optional -> */    PopupWidth, PopupHeight) {
      /*
        Either PopupReference or
        both PopupWidth and PopupHeight have to be given.

        Returns array of left and top offset (in user's desktop viewport).
        These coordinates can be fed to e.g. window.moveTo();
      */
      var wp_x = 0, wp_y = 0;
      if (WindowReference.screenLeft) { // IE
        if (PopupReference) {
          PopupWidth = PopupReference.document.body.offsetWidth;
          PopupHeight = PopupReference.document.body.offsetHeight
        }
        wp_x = parseInt(WindowReference.screenLeft
                      + WindowReference.document.body.offsetWidth / 2
                      - PopupWidth / 2);
        wp_y = parseInt(WindowReference.screenTop
                      + WindowReference.document.body.offsetHeight / 2
                      - PopupHeight / 2);
        return [ wp_x, wp_y ];
      } else if (WindowReference.screenX) { // Moz/FF
        if (PopupReference) {
          PopupWidth = PopupReference.outerWidth;
          PopupHeight = PopupReference.outerWidth;
        }
        wp_x = parseInt(WindowReference.screenX
                      + WindowReference.outerWidth / 2
                      - PopupWidth / 2);
        wp_y = parseInt(WindowReference.screenY
                      + WindowReference.outerHeight / 2
                      - PopupHeight / 2);
        return [ wp_x, wp_y ];
      }
      return null;
    },
    optimizeWindowSize : function (w,h) {
      /*
        Resize window so given dimensions and then optimize height
        so that content fits the window.
      */
      window.resizeTo(w,h);
      if (window.innerHeight) { // W3D
        var wh = window.innerHeight;
        var dh = document.documentElement.offsetHeight;
        window.resizeBy(0, dh - wh);
      } else if (document.compatMode
              && document.compatMode == "BackCompat"
              && document.body
              && document.body.scrollHeight > 0
              && document.body.clientHeight > 0) { // IE quirks mode
        window.resizeBy(0, document.body.scrollHeight - document.body.clientHeight);
      } else if (document.documentElement
              && document.documentElement.scrollHeight > 0
              && document.documentElement.clientHeight > 0) {
        window.resizeBy(0, document.documentElement.scrollHeight - document.documentElement.clientHeight);
      }
    },
    getScrollPositionY : function () {
      /* return vertical scrollbar position */
     return window.pageYOffset
         || document.documentElement.scrollTop
         || document.body.scrollTop
         || 0;
    }
  };
  self.Exception.prototype.toString = function() {
    if (console.trace) {
      console.trace();
    }
    return this.message;
  }
  return self;
})();

/*
    Adding some important features that Javascript lacks.
    grabbed from http://javascript.crockford.com/remedial.html
*/

function isFunction(a) {
  return typeof a == 'function';
}
function isObject(a) {
  return (a && typeof a == 'object') || isFunction(a);
}
function isArray(a) {
  return isObject(a) && a.constructor == Array;
}

/****************************************************************************
    MX UI Library
 ****************************************************************************/

var MxUILib = new Object();
MxUILib.ElementCounter = 0;
MxUILib.getUniqueElementID = function() {return 'MxUIElement' + (++MxUILib.ElementCounter);}


/*
  UIToggleController
  Implements a controller for a view (UI element) that can handle
  two states (true or false).

  Note:  Toggling the view calls registered event listeners with
         new state but does NOT update the view.
         View has to be updated through one event listener or by
         adding setState as event listener:
           MyInstance.addToggleEventListener(MyInstance.setState);


  Parameters to constructor function:
    button_view: view instance of button
    initial_state: initial button state, either true or false.

  A view for the button has to support the following methods:
    create(listener);
      creates the HTML representation of the view. View calls 'listener'
      in case of a toggle / click event.
    setState(state);
      set the state of the view, 'state' can be either true or false;

  methods:
    addToggleEventListener(callback)
      add callback function which is called every time the users toggles
      the button state by clicking on it. Callback function is called with
      new toggle state as parameter.
    getState
      returns button state.
    setState(state)
      set button state and update view.
      NOTE: Calling this function will NOT call the registered event listeners.
*/
MxUILib.UIToggleController = function(button_view, initial_state) {
  // private variables
  if ( !button_view
    || !isFunction(button_view.create)
    || !isFunction(button_view.setState)) {
    throw new MxJsLib.Exception("UIToggleController: view missing.");
  }
  if (typeof initial_state == 'undefined') initial_state = false;
  var ButtonState = !!initial_state;
  var CallBacks = new Array();
  var toggleButtonState = function() {
    var NewState = !ButtonState;
    for (var i = 0; i < CallBacks.length; i++) {
      CallBacks[i](NewState);
    }
  };
  // public methods
  var self = {
    getState:function() {
      return ButtonState;
    },
    setState:function(on_off) {
      if (arguments.length < 1)
        return;
      on_off = !!on_off;
      if (ButtonState != on_off) {
        ButtonState = on_off;
        button_view.setState(ButtonState);
      }
    },
    addToggleEventListener: function (cb_function) {
      if (! isFunction(cb_function)) return;
      CallBacks.push(cb_function);
    }
  };
  button_view.create(toggleButtonState);
  button_view.setState(ButtonState);
  return self;
};

/*
  UIButtonToggleView
  Implements a toggle button view providing create() and setState()
  using HTML element <button>.

  Parameters to constructor function:
    node_id:  ID of container HTML element where button view is to be created.
              Note: Button is APPENDED to the container!
    button_html_false, button_html_true: HTML for button text.

  Methods used before applying Controller:
    setCSSClass(class_name);
      sets 'class_name' as CSS class for the <button> element.
    setBubbleHelpText(text_false,text_true);
      sets bubble help text.
  Methods used by Controller:
    create(event_listener)
      creates the ui element and registers click event listener function "event_listener".
    setState(state)
      sets state of ui element to "state"
      NOTE: Calling this method will NOT call the registered click event listener function.

*/

MxUILib.UIButtonToggleView = function(node_id,
                                      button_html_false,
                                      button_html_true) {
  // parameters
  if (!node_id || !document.getElementById(node_id)) {
    throw new MxJsLib.Exception("UIButtonToggleView: node does not exist.");
  }
  // private variables
  var ViewID = "myview" + MxUILib.getUniqueElementID(); // access button by ID to
                                                        // avoid circular references
                                                        // BT -> Controller -> View -> BT
  var CSSClassName = '';
  var HelpTextFalse = '';
  var HelpTextTrue = '';
  // public methods providing view interface.
  this.create = function(ToggleEventListener) {
     var ButtonNode = document.createElement("button");
     ButtonNode.id = ViewID;
     ButtonNode.innerHTML = "";
     ButtonNode.setAttribute('type', 'button'); // type defaults to "submit" on FF
     if (CSSClassName) ButtonNode.className = CSSClassName;
     document.getElementById(node_id).appendChild(ButtonNode);
    // add event handler
    ButtonNode.onclick = ToggleEventListener;
    // Catch double click as well, otherwise IE annoys fast clicking users...
    if (typeof ButtonNode.ondblclick == "object") {
      ButtonNode.ondblclick = ToggleEventListener;
    }
  };
  this.setState = function(state) {
    var ButtonNode = document.getElementById(ViewID);
    if (state) {
      ButtonNode.innerHTML = button_html_true;
      ButtonNode.title = HelpTextTrue;
    } else {
      ButtonNode.innerHTML = button_html_false;
      ButtonNode.title = HelpTextFalse;
    }
  };
  // public methods changing appearance of view.
  this.setBubbleHelpText = function(text_false,text_true) {
    if (typeof text_false == 'undefined') text_false = '';
    if (typeof text_true == 'undefined') text_true = '';
    HelpTextFalse = text_false;
    HelpTextTrue = text_true;
  };
  this.setCSSClass = function(class_name) {
    if (typeof class_name == 'undefined') return;
    CSSClassName = class_name;
    var ButtonNode = document.getElementById(ViewID);
    if (ButtonNode) ButtonNode.className = CSSClassName;
  };
};

MxUILib.createCenteredDiv = function(w,h) {
  var OuterDiv  = document.createElement("DIV");
  OuterDiv.style.position = "absolute";
  OuterDiv.style.top = (150 + MxJsLib.getScrollPositionY()) + "px";
  OuterDiv.style.left = "0px";
  OuterDiv.style.right = "0px";

  var InnerDiv = document.createElement("DIV");
  if (isNaN(w)) w = 200;
  InnerDiv.style.width  = w + "px";
  if (!isNaN(h)) InnerDiv.style.height  = h + "px";
  InnerDiv.style.margin = "0px auto";
  InnerDiv.style.backgroundColor = "#EEEEEE";
  InnerDiv.style.border = "2px solid #000088";
// OuterDiv.style.border = "1px solid red"; // for CSS debugging
  OuterDiv.appendChild(InnerDiv);
  document.body.appendChild(OuterDiv);
  return {
    getNode : function() { return InnerDiv; },
    getNode1 : function() { return OuterDiv; },
    close   : function() {
      if (OuterDiv) {
        MxJsLib.discardElement(OuterDiv);
        InnerDiv = OuterDiv = null;
      }
    }
  };
};


/****************************************************************************
 * MxJsLib.Cookie

   Cookie service

   Parameters to constructor function: name of cookie

   Methods:
     get()
       returns cookie data or empty string if cookie not set
     set(data)
       writes data to cookie. Data is escaped and thus can contain
       characters like "=|;".


 */
MxJsLib.Cookie = function(name) {
  this.cookiename = name;

  this.get = function () {
    // cookie data is stored in a key=value fashion
    var c = document.cookie;
    var keyvaluepair = [ ];
    var list = c.split(/;\s+/);
    for (var i = 0; i < list.length; i++) {
      keyvaluepair = list[i].split('=');
      if (keyvaluepair.length == 2) {
        
        if (keyvaluepair[0] == this.cookiename) {
          return unescape(keyvaluepair[1]);
        }
      }
    }
    return "";
  };
  this.set = function (text) {
    if (typeof text == 'undefined') text = "";
    var c = this.cookiename + "=" + escape(text);
    var ny = new Date();
    ny.setFullYear(ny.getFullYear() + 1);
    c += '; expires=' + ny.toGMTString();
    // you can set/update a single cookie at a time
    document.cookie = c;
  };
}

/****************************************************************************
    MX Remote Scripting
 ****************************************************************************/

/*
  MxJsLib.RemoteScripting

  Remote scripting facility.


  Parameters to constructor function: none

  Methods:
    setRequestURL(url)
      set URL for remote request
    setResponseListener(func)

    execute()
      start request
    
#   startet einen Request und ruft die URL ab.
#   Die Antwortseite muss Javascript-Code enthalten, welcher
#   parent.js_callback(self) aufruft.
*/

MxJsLib.RemoteScripting = function() {
  // public properties
  this.name='RemoteScripting';
  // public methods
  this.setRequestURL = function(url) {
    // referencing private data here is valid as all variables
    // declared in a function are defined throughout the function
    // no matter where they are declared.
    if (!url) throw new MxJsLib.Exception("RemoteScripting: url missing.");
    RequestURL = url;
  };
  this.setResponseListener = function(func) {
    if (!isFunction(func)) throw new MxJsLib.Exception("RemoteScripting: callback missing.");
    ResponseListener = func;
  };
  this.setResponseTypeJavascript = function() {
    ResponseTypeJavascript = true;
  }
  this.isExecuting = function() { return (TimeoutHandler != null); };
  this.execute = function() {
    if ( !RequestURL
      || !ResponseListener
      || !IFrameElement ) {
      throw new MxJsLib.Exception("RemoteScripting: not initialized properly.");
    }
    if (TimeoutHandler != null) return false;
    var url = MxJsLib.makeUncachedURL(RequestURL);
    if (IFrameElement.onload == null) {
      IFrameElement.onload = responseSuccess;
      IFrameElement.onerror = responseError;
    }
    TimeoutHandler = window.setTimeout(responseTimeout, TimeoutTime * 1000);
    if (ResponseTypeJavascript) {
      var html = '<html><header><scrip'+'t SRC=\"' + url + '\"></sc'+'ript></header><body>' + url + '</body></html>';
      IFrameWindow.document.open();
      IFrameWindow.document.write(html);
      IFrameWindow.document.close();
    } else {
      IFrameElement.src = url;
    }
    return true;
  };
  this.cancel = function() {
    if (TimeoutHandler != null) {
      window.clearTimeout(TimeoutHandler);
      TimeoutHandler = null;
      IFrameElement.onload = IFrameElement.onerror = null;
      IFrameElement.src = "";
    }
  };
  
  // private data
  var MyID = MxUILib.getUniqueElementID();
  var RequestURL = "";
  var ResponseListener = null;
  var TimeoutHandler = null;
  var TimeoutTime = 5;
  var ResponseTypeJavascript = false;
  // private methods
  var responseTimeout = function() {
    TimeoutHandler = null;
    IFrameElement.onload = IFrameElement.onerror = null;
    IFrameElement.src = "";
    console.log("result timeout:");
    ResponseListener(null);
  }
  var responseSuccess = function() { responseHandler(true); };
  var responseError = function() {   responseHandler(false); };
  var responseHandler = function(success) {
    if (TimeoutHandler != null) {
      window.clearTimeout(TimeoutHandler);
      TimeoutHandler = null;
    } else {
      throw new MxJsLib.Exception("response while timeout");
    }
    console.log("result:"+success);
    ResponseListener(IFrameWindow);
  };
  // initialization
  this.name += MyID;
  var IFrameElement = document.createElement("iframe");
  IFrameElement.name = this.name;
//  IFrameElement.style.display = 'none';
  document.body.appendChild(IFrameElement);
  var IFrameWindow = window.frames[this.name];
}

/*
var o = new MxJsLib.RemoteScripting();
o.setResponseListener( function(a) {global=a;console.log("listener:"+a)});

o.setRequestURL('/control/test.cgi?html=0&data=1&sleep=1');
o.setResponseTypeJavascript();

o.setRequestURL('/control/test.cgi?html=1&data=1&sleep=1');

o.execute();
*/



