///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxPan
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

function gxEnablePan(ele, panStart, panEnd, pan) {
  ele = $(ele);
  ele.gxPanStart = panStart;
  ele.gxPanEnd = panEnd;
  ele.gxPan = pan;
  if (ele.useMap) {
    if (ele.useMap.substr(0,1) == '#') {
      imageMapElem = $(ele.useMap.substring(1,ele.useMap.length));
      if (imageMapElem == null)
        alert('gxEnablePan: image map specified, but element could not be found');
      else {
        areas = imageMapElem.areas;
        dragFunc = function(ele2, dx, dy) {   
          xMoveTo(ele, xLeft(ele) + dx, xTop(ele) + dy);
          xClip(ele, -xTop(ele), xWidth(ele)-xLeft(ele), xHeight(ele)-xTop(ele), -xLeft(ele));
        };
        count = 0;
        for (var i = areas.length-1; i >= 0; i--)
          xEnableDrag(areas[i], panStart, dragFunc, panEnd);
      }
    }
    else {
      alert('gxEnablePan: non-local image maps are not supported');
    }
  }
  else {
    xEnableDrag(ele, gxPanStart, gxPan, gxPanEnd);
  }
}

function gxPan(ele, dx, dy) {
  xMoveTo(ele, xLeft(ele) + dx, xTop(ele) + dy);
  xClip(ele, -xTop(ele), xWidth(ele)-xLeft(ele), xHeight(ele)-xTop(ele), -xLeft(ele));
  if (ele.gxPan)
    ele.gxPan(dx, dy);
}

function gxPanStart(ele, pageX, pageY) {
  ele.gxPanStartX = pageX;
  ele.gxPanStartY = pageY;
  if (ele.gxPanStart)
    ele.gxPanStart(pageX, pageY);
}

function gxPanEnd(ele, pageX, pageY) {
  if (ele.gxPanEnd) 
    ele.gxPanEnd(pageX - ele.gxPanStartX, pageY - ele.gxPanStartY); 
}

function gxDisablePan(ele) {
  xDisableDrag(ele);
}

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxDoubleClick
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
function gxDoubleClick(ele, onClick, onDblClick) {
  ele = $(ele);
  if (!ele)
    alert('gxDoubleClick: element not found');
  else {
	this.userOnClick = onClick;
	this.userOnDblClick = onDblClick;
	this.clicked = false;
	this.tmr = null;
    this.clickEvent = null;
	ele.gxDoubleClickObj = this;
	xAddEventListener(ele, 'click', this.click);
  }  
}

gxDoubleClick.prototype.click = function(evt) {
  var xe = new xEvent(evt);
  var _this = xe.target.gxDoubleClickObj;
  if (_this.clicked) {
    _this.tmr.stop();
    _this.userOnDblClick(xe);
    _this.clicked = false;
  }
  else {
    _this.clicked = true;
    _this.clickEvent = xe;
    if (_this.tmr == null) {
      _this.tmr = xTimer.set('timeout', _this, 'timeout', 200);
    }
    else {
      _this.tmr.reset();
    }
  }
};

gxDoubleClick.prototype.timeout = function() {
  this.clicked = false;
  this.userOnClick(this.clickEvent);
};

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxTooltip
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

function gxTooltip(uTimeout, parent, x, y, sStyle, sId, sHTML, afterHide)
{
  if (document.getElementById && document.createElement &&
      document.body && document.body.appendChild)
  { 
    parent = $(parent);
    // create popup element
    var e = document.createElement('DIV');
    this.ele = e;
    e.id = sId;
    e.style.position = 'absolute';
    e.className = sStyle;
    e.innerHTML = sHTML;
    parent.appendChild(e);
    xMoveTo(e, x, y);
    xShow(e);
    this.open = true;
    this.tmr = xTimer.set('timeout', this, 'timeout', uTimeout);
    this.afterHide = afterHide;
  } 
} 

// methods
gxTooltip.prototype.hide = function()
{
  var e = this.ele;
  xParent(e, true).removeChild(e);
  this.open = false;
  this.tmr.stop();
};

gxTooltip.prototype.timeout = function() 
{
  this.hide();
  if (this.afterHide)
    eval(this.afterHide);
};

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxMapTip
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

function gxMapTip(ele, tips, maxDist, timeout, styleClass, offsetX, offsetY, hasLink, beforeShow, afterHide) { 
  ele = $(ele);
  if (ele) {
	  this.tips = tips;
	  this.hasLink = hasLink;
	  this.maxDist = maxDist;
	  this.timeout = timeout;
	  this.styleClass = styleClass;
	  this.offsetX = offsetX;
	  this.offsetY = offsetY;
	  this.beforeShow = beforeShow;
	  this.afterHide = afterHide;
	  this.current = null;
	  this.enabled = true;
	  ele.mapTipObj = this;
	  xAddEventListener(ele, 'mousemove', this.mouseMove);
	  xAddEventListener(ele, 'mouseout', this.mouseOut);
	  if (hasLink)
	    xAddEventListener(ele, 'click', this.click);
  }
}

gxMapTip.prototype.click = function(evt) {
  xe = new xEvent(evt);
  mapTip = xe.target.mapTipObj;
  if (mapTip.enabled) {
    nearestTipIndex = mapTip.getNearest(xe.offsetX, xe.offsetY, mapTip);  
    if (nearestTipIndex != null  &&  mapTip.tips[nearestTipIndex+3]) {
      eval(mapTip.tips[nearestTipIndex+3]);
    }
  }
}

gxMapTip.prototype.getNearest = function(x, y, mapTip) {
    tips = mapTip.tips;
    inXRange = false;
    found = false;
    var minDistSq;
    var foundIndex = null;
    increment = 3;
    if (mapTip.hasLink)
      increment = 4;
    for (var i = tips.length-increment; i >= 0; i-=increment) {
      if (Math.abs(x - tips[i]) < mapTip.maxDist) {
        inXRange = true;
        if (Math.abs(y - tips[i+1]) < mapTip.maxDist) {
          distSq = Math.abs(x - tips[i])*Math.abs(x - tips[i]) + Math.abs(y - tips[i+1])*Math.abs(y - tips[i+1]); 
          if (!found  ||  distSq < minDistSq) {
            minDistSq = distSq;
            foundIndex = i;
            found = true;
          }
        }
      } 
      else if (inXRange)
        break;
    }
    
    if (found)
      return foundIndex;
    else 
      return null;
}

gxMapTip.prototype.mouseMove = function(evt) {
  xe = new xEvent(evt);
  if (xe.target.mapTipObj) { // For IE only. Is not defined if event of transparent layer above received.
      mapTip = xe.target.mapTipObj;
	  if (mapTip.enabled) {
	    nearestTipIndex = mapTip.getNearest(xe.offsetX, xe.offsetY, mapTip);
	    current = mapTip.current;
	
	    if ((nearestTipIndex != null) &&  (!current  ||  !current.open  ||  current.id != nearestTipIndex)) {
	      if (current  &&  current.open)
	        current.hide();
	      else if (mapTip.beforeShow)
            eval(mapTip.beforeShow);
            
	      mapTip.current = new gxTooltip(
	         mapTip.timeout,  
	         xParent(xe.target),
	         xe.offsetX + mapTip.offsetX,
	         xe.offsetY + mapTip.offsetY,
	         mapTip.styleClass,  
	         nearestTipIndex,           
	         tips[nearestTipIndex+2],
             mapTip.afterHide);  
	    }
	
	    if ((nearestTipIndex == null)  &&  current  &&  current.open) {
	      current.hide();
          if (mapTip.afterHide)
            eval(mapTip.afterHide);
        }
	  }
  }
};

/*
gxMapTip.prototype.mouseMove = function(evt) {
  xe = new xEvent(evt);
  mapTip = xe.target.mapTipObj;
  if (mapTip.enabled) {
    tips = mapTip.tips;
    current = mapTip.current;
    inXRange = false;
    found = false;
    var minDistSq;
    var foundIndex;
    for (var i = tips.length-3; i >= 0; i-=3) {
      if (Math.abs(xe.offsetX - tips[i]) < mapTip.maxDist) {
        inXRange = true;
        if (Math.abs(xe.offsetY - tips[i+1]) < mapTip.maxDist) {
          distSq = Math.abs(xe.offsetX - tips[i])*Math.abs(xe.offsetX - tips[i]) + Math.abs(xe.offsetY - tips[i+1])*Math.abs(xe.offsetY - tips[i+1]); 
          if (!found  ||  distSq < minDistSq) {
            minDistSq = distSq;
            foundIndex = i;
            found = true;
          }
        }
      } 
      else if (inXRange)
        break;
    }

    if (found  &&  (!current  ||  !current.open  ||  current.id != foundIndex)) {
	    if (current  &&  current.open)
	      current.hide();
	    
	    mapTip.current = new gxTooltip(
	       mapTip.timeout,  
	       xParent(xe.target),
	       xe.offsetX + mapTip.offsetX,
	       xe.offsetY + mapTip.offsetY,
	       mapTip.styleClass,  
	       foundIndex,           // id
	       tips[foundIndex+2]);  // tooltip content (HTML)
    }

    if (!found  &&  current  &&  current.open)
      current.hide();
  }
};*/

gxMapTip.prototype.mouseOut = function(evt) {
  xe = new xEvent(evt);
  if (xe.target  &&  xe.target.mapTipObj) {
	  mapTip = xe.target.mapTipObj;
	  current = mapTip.current;
	  if (current  &&  current.open) {
	    current.hide();
        if (mapTip.afterHide)
          eval(mapTip.afterHide);
      }
  }
};

gxMapTip.prototype.setEnabled = function(enabled) {
  if (this.enabled != enabled) {
    this.enabled = enabled;
    if (!enabled  &&  this.current  &&  this.current.open) {
      this.current.hide();
      if (this.afterHide)
        eval(this.afterHide);
    }
  }
}


///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxMapTip2
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

function gxMapTip2(ele, xcoords, ycoords, tips, onClicks, maxDist, timeout, styleClass, offsetX, offsetY, hasLink, beforeShow, afterHide) { 
  ele = $(ele);
  if (ele) {
	  this.xcoords = xcoords;
	  this.ycoords = ycoords;
	  this.tips = tips;
	  this.onclicks = onClicks;
	  this.hasLink = hasLink;
	  this.maxDist = maxDist;
	  this.timeout = timeout;
	  this.styleClass = styleClass;
	  this.offsetX = offsetX;
	  this.offsetY = offsetY;
	  this.beforeShow = beforeShow;
	  this.afterHide = afterHide;
	  this.current = null;
	  this.enabled = true;
	  ele.mapTipObj = this;
	  xAddEventListener(ele, 'mousemove', this.mouseMove);
	  xAddEventListener(ele, 'mouseout', this.mouseOut);
	  if (hasLink)
	    xAddEventListener(ele, 'click', this.click);
  }
}

gxMapTip2.prototype.click = function(evt) {
  xe = new xEvent(evt);
  mapTip = xe.target.mapTipObj;
  if (mapTip.enabled) {
    nearestTipIndex = mapTip.getNearest(xe.offsetX, xe.offsetY, mapTip);  
    if (nearestTipIndex != null  &&  mapTip.onClicks[nearestTipIndex]) {
      eval(mapTip.onClicks[nearestTipIndex]);
    }
  }
}

gxMapTip2.prototype.getNearest = function(x, y, mapTip) {
    inXRange = false;
    found = false;
    var minDistSq;
    var foundIndex = null;
    for (var i = mapTip.tips.length-1; i >= 0; i--) {
      if (Math.abs(x - mapTip.xcoords[i]) < mapTip.maxDist) {
        inXRange = true;
        if (Math.abs(y - mapTip.ycoords[i]) < mapTip.maxDist) {
          distSq = Math.abs(x - mapTip.xcoords[i])*Math.abs(x - mapTip.xcoords[i]) + Math.abs(y - mapTip.ycoords[i])*Math.abs(y - mapTip.ycoords[i]); 
          if (!found  ||  distSq < minDistSq) {
            minDistSq = distSq;
            foundIndex = i;
            found = true;
          }
        }
      } 
      else if (inXRange)
        break;
    }
    
    if (found)
      return foundIndex;
    else 
      return null;
}

gxMapTip2.prototype.mouseMove = function(evt) {
  xe = new xEvent(evt);
  if (xe.target.mapTipObj) { // For IE only. Is not defined if event of transparent layer above received.
      mapTip = xe.target.mapTipObj;
	  if (mapTip.enabled) {
	    nearestTipIndex = mapTip.getNearest(xe.offsetX, xe.offsetY, mapTip);
	    current = mapTip.current;
	
	    if ((nearestTipIndex != null) &&  (!current  ||  !current.open  ||  current.id != nearestTipIndex)) {
	      if (current  &&  current.open)
	        current.hide();
	      else if (mapTip.beforeShow)
            eval(mapTip.beforeShow);
            
	      mapTip.current = new gxTooltip(
	         mapTip.timeout,  
	         xParent(xe.target),
	         xe.offsetX + mapTip.offsetX,
	         xe.offsetY + mapTip.offsetY,
	         mapTip.styleClass,  
	         nearestTipIndex,           
	         mapTip.tips[nearestTipIndex],
            mapTip.afterHide);  
	    }
	
	    if ((nearestTipIndex == null)  &&  current  &&  current.open) {
	      current.hide();
          if (mapTip.afterHide)
            eval(mapTip.afterHide);
        }
	  }
  }
};

gxMapTip2.prototype.mouseOut = function(evt) {
  xe = new xEvent(evt);
  if (xe.target  &&  xe.target.mapTipObj) {
	  mapTip = xe.target.mapTipObj;
	  current = mapTip.current;
	  if (current  &&  current.open) {
	    current.hide();
        if (mapTip.afterHide)
          eval(mapTip.afterHide);
      }
  }
};

gxMapTip2.prototype.setEnabled = function(enabled) {
  if (this.enabled != enabled) {
    this.enabled = enabled;
    if (!enabled  &&  this.current  &&  this.current.open) {
      this.current.hide();
      if (this.afterHide)
        eval(this.afterHide);
    }
  }
}


///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxDragBox
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

//declaring the class
var gxDragBox = Class.create();
gxDragBox.prototype = {

initialize: function(ele, styleClass, boxStart, boxEnd) {
  ele = $(ele);
  this.styleClass = styleClass;
  this.boxStart = boxStart;
  this.boxEnd = boxEnd;
  this.originX = null;
  this.originY = null;
  this.target = ele;
  this.box = document.createElement('DIV');
  xHide(this.box);
  this.box.style.position = 'absolute';
  this.box.style.lineHeight = '1px';
  this.box.style.fontSize = '1px';
  this.box.className = this.styleClass;
  xParent(ele).appendChild(this.box);
  this.boxActive = false;

  xAddEventListener(ele, 'mouseup', this.mouseUp.bindAsEventListener(this));
  xAddEventListener(ele, 'mousedown', this.mouseDown.bindAsEventListener(this));
},
     
mouseDown: function(evt) {
  var xe = new xEvent(evt);
  if (!this.boxActive) {
    xPreventDefault(evt);
    if (this.boxStart)
      this.boxStart(xe.offsetX, xe.offsetY);

    this.originX = xe.offsetX;
    this.originY = xe.offsetY;
    gxMoveAndResize(this.box, this.originX, this.originY, this.originX+1, this.originY+1);
    xShow(this.box);
    xAddEventListener(this.box, 'mousemove', this.mouseMoveBox.bindAsEventListener(this));
    xAddEventListener(this.box, 'mouseup', this.mouseUpBox.bindAsEventListener(this));
    xAddEventListener(this.target, 'mousemove', this.mouseMove.bindAsEventListener(this));
    this.boxActive = true;
  }
},

mouseMove: function(evt) {
  var xe = new xEvent(evt);
  var x;
  var y;
  xPreventDefault(evt);
  if (xe.target == this.target) { 
    x = xe.offsetX;
    y = xe.offsetY;
    gxMoveAndResize(this.box, this.originX, this.originY, x, y);
  }
  else if (xe.target == this.box) {  // IE only. Event of transparent top layer received.
    x = Math.max(0, xe.offsetX + xLeft(this.box));
    y = Math.max(0, xe.offsetY + xTop(this.box));
    gxMoveAndResize(this.box, this.originX, this.originY, x, y);
  }
},

mouseUp: function(evt) {
  var xe = new xEvent(evt);
  xPreventDefault(evt);
  var box = this.box;
  if (this.boxEnd) {
//    alert('mouseUp: x1: ' + this.originX + ', y1: ' + this.originY + ', x2: ' + xe.offsetX + ', y2: ' + xe.offsetY);
    var x = xe.offsetX < 0 ? xLeft(box) + xe.offsetX : xe.offsetX;  // IE sometimes returns -1 (e.g. on single click (no drag)) 
    var y = xe.offsetY < 0 ? xTop(box) + xe.offsetY : xe.offsetY;  // IE sometimes returns -1 (e.g. on single click (no drag)) 
    this.boxEnd(this.originX, this.originY, x, y);
  }
  xHide(box);
  xParent(box).removeChild(box);
  this.box = null;
},

mouseMoveBox: function(evt) {
  var xe = new xEvent(evt);
  var box = this.box;
  if (this.boxActive) {
    xPreventDefault(evt);
    var x = xLeft(box) + xe.offsetX;
    var y = xTop(box) + xe.offsetY;
    if (x >= 0  &&  y >= 0  &&  x < xWidth(this.target)  &&  y < xHeight(this.target))  
      gxMoveAndResize(box, this.originX, this.originY, x, y);
  }
},

mouseUpBox: function(evt) {
  xPreventDefault(evt);
  xe = new xEvent(evt);
  box = this.box;
  if (this.boxEnd) {
//    alert('mouseUpBox: x1: ' + this.originX + ', y1: ' + this.originY + ', x2: ' + xe.offsetX + ', y2: ' + xe.offsetY);
    this.boxEnd(this.originX, this.originY, xLeft(box) + xe.offsetX, xTop(box) + xe.offsetY);
  }
  xHide(box);
  this.boxActive = false;
}
};

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxControlSelect
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

function gxControlSelect(inputEle, selectEle, onChange) {
  inputEle = $(inputEle);
  if (!inputEle)
    alert('gxControlSelect: input element not found');
  selectEle = $(selectEle);
  if (!selectEle)
    alert('gxControlSelect: select element not found');
  this.inputEle = inputEle;
  this.selectEle = selectEle;
  this.onChangeFunc = onChange;
  inputEle.controlSelectObj = this;
  xAddEventListener(inputEle, 'keyup', this.keyUp);
}

gxControlSelect.prototype.keyUp = function(evt) {
  xe = new xEvent(evt);
  controlSelect = xe.target.controlSelectObj;
  sel = controlSelect.selectEle;
  len = sel.options.length;
  tipped = controlSelect.inputEle.value;
  if (tipped.length > 0) {
    for (i = 0; i < len; i++) {
      if (sel.options[i].text.substr(0, tipped.length).toLowerCase() == tipped.toLowerCase()) {
        sel.options[i].selected = true;
        if (controlSelect.onChangeFunc)
          controlSelect.onChangeFunc(sel);
        break;    
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxMasterSlaveSelect
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

function gxMasterSlaveSelect(masterEle, slaveEle, dataEle) {
  masterEle = $(masterEle);
  if (!masterEle)
    alert('gxMasterSlaveSelect: master element not found');
  slaveEle = $(slaveEle);
  if (!slaveEle)
    alert('gxMasterSlaveSelect: slave element not found');
  dataEle = $(dataEle);
  if (!dataEle)
    alert('gxMasterSlaveSelect: data element not found');
  this.masterEle = masterEle;
  this.slaveEle = slaveEle;
  this.dataEle = dataEle;

  this.data = new Array();
  var io = 0;
  var og = dataEle.firstChild;
  var ig = 0;

  while (og) {
    if (og.nodeName.toLowerCase() == 'optgroup') {
      io = 0;
      this.data[ig] = new Array();
      op = og.firstChild;
      while (op) {
        if (op.nodeName.toLowerCase() == 'option') {
          this.data[ig][io] = op.innerHTML;
          io++;
        }
        op = op.nextSibling;
      }
      ig++;
    }
    og = og.nextSibling;
  }

  masterEle.masterSlaveSelectObj = this;
  xAddEventListener(masterEle, 'change', this.change);
  this.updateSlave(masterEle);
}

gxMasterSlaveSelect.prototype.updateSlave = function(masterEle) {
  _this = masterEle.masterSlaveSelectObj;
  selIndex = _this.masterEle.selectedIndex;
  
  // clear existing
  for (i = _this.slaveEle.options.length-1; i >= 0; i--) {
    _this.slaveEle.options[i].selected = false;
    _this.slaveEle.options[i] = null;
  }
  
  if (selIndex != -1) {
	for (io=0; io<_this.data[selIndex].length; io++) {
	  op = new Option(_this.data[selIndex][io]);
	  _this.slaveEle.options[io] = op;
	}
  }
}

gxMasterSlaveSelect.prototype.change = function(evt) {
  xe = new xEvent(evt);
  _this = xe.target.masterSlaveSelectObj;
  _this.updateSlave(_this.masterEle);
}

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxMasterSlaveSelectArray
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

function gxMasterSlaveSelectArray(masterEle, slaveEle, data) {
  masterEle = $(masterEle);
  if (!masterEle)
    alert('gxMasterSlaveSelectArray: master element not found');
  slaveEle = $(slaveEle);
  if (!slaveEle)
    alert('gxMasterSlaveSelectArray: slave element not found');
  if (!data)
    alert('gxMasterSlaveSelectArray: data array not found');
  this.masterEle = masterEle;
  this.slaveEle = slaveEle;
  this.data = data;
  masterEle.masterSlaveSelectArrayObj = this;
  xAddEventListener(masterEle, 'change', this.change);
  this.updateSlave(masterEle);
}

gxMasterSlaveSelectArray.prototype.updateSlave = function(masterEle) {
  var _this = masterEle.masterSlaveSelectArrayObj;
  var selIndex = _this.masterEle.selectedIndex;
  
  // clear existing
  for (var i = _this.slaveEle.options.length-1; i >= 0; i--) {
    _this.slaveEle.options[i].selected = false;
    _this.slaveEle.options[i] = null;
  }
  
  if (selIndex != -1) {
	for (var i = 0; i < _this.data[selIndex].length; i++) {
	  var op = new Option(_this.data[selIndex][i]);
//new Option() kennt vier Parameter von denen die drei letzten Parameter optional sind.
//1. text = angezeigter Text in der Liste
//2. value = zu uebertragender Wert der Liste (optional)
//3. defaultSelected = true uebergeben, wenn der Eintrag der defaultm??ig vorselektierte Eintrag sein soll, sonst false (optional)
//4. selected = true uebergeben, wenn der Eintrag selektiert werden soll (optional)    
	  _this.slaveEle.options[i] = op;
    }
  }
}

gxMasterSlaveSelectArray.prototype.change = function(evt) {
  xe = new xEvent(evt);
  _this = xe.target.masterSlaveSelectArrayObj;
  _this.updateSlave(_this.masterEle);
}

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// various gx tool functions
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

function gxEscapeJS(s) {
  var retVal = s;
  retVal = retVal.replace(/\\/g, '\\\\');
  retVal = retVal.replace(/'/g, "\\'");
  retVal = retVal.replace(/"/g, '\\"');
  retVal = retVal.replace(/\\n/g, '\\n');
  return retVal;
}

function gxPrependToLoadEvent(func) {
    var loadFunc = window.onload || function(){/* */};
    window.onload = function(){ func(); loadFunc(); };
}

function gxAppendToLoadEvent(func) {
    var loadFunc = window.onload || function(){/* */};
    window.onload = function(){ loadFunc(); func(); };
}

function gxSetGlobalCursor(cursor) {
    var visit = function(el) {
      if (el.style)
        el.style.cursor = cursor;
    };
    
    xWalkTree(document, visit);
}

function gxPreventSubmitOnEnter(ele) {

  function keyPress(evt)
  {
    var xe = new xEvent(evt);
	if (xe.keyCode == 13) 
	  xPreventDefault(evt);
  }
  
  xAddEventListener(ele, 'keypress', keyPress);
}

function gxClickInsteadSubmit(eleToWatch, eleToClick) {

  function keyPress(evt)
  {
    var xe = new xEvent(evt);
    if (xe.keyCode == 13) {
      xPreventDefault(evt);
      $(eleToClick).click();
    }
  }
  
  xAddEventListener(eleToWatch, 'keypress', keyPress);
}


function gxMoveAndResize(ele, x1, y1, x2, y2) {
  if (x2 < x1) {
    t = x2;
    x2 = x1;
    x1 = t;
  }
  if (y2 < y1) {
    t = y2;
    y2 = y1;
    y1 = t;
  }
  //window.status = 'moveAndResize: ' + x1 + ',' + y1 + ',' + (x2-x1) + ',' + (y2-y1);
  xMoveTo(ele, x1, y1);
  xResizeTo(ele, x2-x1, y2-y1);  
}


function gxGetURLArgNames()
{
  var idx = location.href.indexOf('?');
  var params = new Array();
  if (idx != -1) {
    var pairs = location.href.substring(idx+1, location.href.length).split('&');
    for (var i=0; i<pairs.length; i++) {
      nameVal = pairs[i].split('=');
      params[i] = nameVal[0];
    }
  }
  return params;
}

function gxGetURLParams() {
  
  var idx = location.href.indexOf('?');
  var params = new Object();
  if (idx != -1) {
    var pairs = location.href.substring(idx+1, location.href.length).split('&');
    for (var i=0; i<pairs.length; i++) {
      nameVal = pairs[i].split('=');
      params[nameVal[0]] = nameVal[1];
    }
  }
  return params;
}

function gxBuildParamString(params) {
  retVal = '';
  separator = '';
  for (var p in params) {
    retVal = retVal + separator + p + '=' + params[p];
    separator = '&';
  }
  if (retVal != '')
    retVal = '?' + retVal;
    
  return retVal;
}

function gxGetURLWithoutParams(loc) {
  var retVal;
  
  if (!loc)
    loc = location;
    
  var idx = loc.href.indexOf('?');
  if (idx != -1) 
    retVal = loc.href.substring(0, idx);
  else
    retVal = loc.href;
    
  return retVal;
}

function gxHideScrollBars() {  
  document.body.style.overflow='hidden';
}

function gxUnhideScrollBars() {
  document.body.style.overflow='';
}


// IE and FF only
function gxClientWidth() {
  var retVal = -1;
  if (xIE4Up) {
    retVal = document.documentElement.clientWidth != 0 ?
             document.documentElement.clientWidth : 
             document.body.clientWidth; 
  }
  else {
    retVal = document.body.clientWidth; 
  }
  return retVal;
}

// IE and FF only
function gxClientHeight() {
  var retVal = -1;
  if (xIE4Up) {
    retVal = document.documentElement.clientHeight != 0 ?
             document.documentElement.clientHeight : 
             document.body.clientHeight; 
  }
  else {
    retVal = document.documentElement.clientHeight; // note: does only work with DTD set !
  }
  return retVal;
}





function gxToString(o) {
  var retVal = '';
  if (xDef(o)) {
    if (xDef(o.length)) {
      retVal = '[';
      var comma = '';
      for (var i = 0; i < o.length; i++) {
        retVal += comma + o[i];
        comma = ',';
      }
      retVal += ']';
    }
  }
  return retVal;
}

function gxStringToIntArray(str, delimiter) {
  var retVal = new Array();
  if (str && delimiter) {
	  var sa = str.split(delimiter);
	  
	  for (var i=0; i < sa.length; i++) {
	    retVal.push(parseInt(sa[i]));
	  }
  }
    
  return retVal;
}

function gxDistPointPoint(p1x, p1y, p2x, p2y) {
  return Math.sqrt((p1x-p2x)*(p1x-p2x)+(p1y-p2y)*(p1y-p2y));
}

function gxDistPointSeg(px, py, v1x, v1y, v2x, v2y) {
  var vx = v2x - v1x;
  var vy = v2y - v1y;

  var wx = px - v1x;  
  var wy = py - v1y;  
  
  var c1 = wx*vx + wy*vy;  
  var c2 = vx*vx + vy*vy;
  
  if (c1 <= 0)
    retVal = gxDistPointPoint(px, py, v1x, v1y);
  else if (c2 <= c1)
    retVal = gxDistPointPoint(px, py, v2x, v2y);
  else {
    var b = c1 / c2;
    var pbx = v1x + b * vx;
    var pby = v1y + b * vy;
    retVal = gxDistPointPoint(px, py, pbx, pby);
  }  
  
  return retVal;
}

function gxGetSnapPoint(x, y, snapPoints, increment, maxDist) {
  var inXRange = false;
  var minDistSq;
  var foundIndex = -1;
  for (var i = snapPoints.length-increment; i >= 0; i-=increment) {
    var sx = snapPoints[i];
    var dx = Math.abs(x - sx);
    if (dx < maxDist) {
      inXRange = true;
      var sy = snapPoints[i+1];
      var dy = Math.abs(y - sy);
      if (dy < maxDist) {
        distSq = dx*dx + dy*dy; 
        if (foundIndex == -1  ||  distSq < minDistSq) {
          minDistSq = distSq;
          foundIndex = i;
        }
      }
    } 
    else if (inXRange)
      break;
  }
  
  return foundIndex;
}

function gxHideSelects() {
  var selEles = xGetElementsByTagName('select');
  for (var i = selEles.length-1; i >= 0; i--)
    xHide(selEles[i]);
}

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// gxEditPolygon  
//                
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

//declaring the class
var gxEditPolygon = Class.create();
gxEditPolygon.prototype = {

initialize: function(drawEle, selEle, eventEle, xs, ys, disabled, borderWidth, polyLine) {
  eventEle = $(eventEle);
  this.eventEle = eventEle;
  drawEle = $(drawEle);
  selEle = $(selEle);
  if (!borderWidth)
    borderWidth = 0;

  this.prefix = xName(drawEle);
  this.lineColor = '#000000';
  this.vertexColor = '#0078CD';
  this.vertexSize = 6;
  this.selVertexColor = '#000000';
  this.selVertexSize = 10;
  this.moveCursor = null;
  this.defaultCursor = eventEle.style.cursor;

  if (!xs) 
    xs = new Array();
  if (!ys)
    ys = new Array();  
  this.xs = xs;
  this.ys = ys;
  
  this.jg = new jsGraphics(drawEle, null, true, borderWidth);
  this.disabled = disabled;
  this.polyLine = polyLine;

  this.changeListeners = new Array();

  this.dragPart = -1;
  this.dragIndex = -1;
  this.selPart = -1;
  this.selIndex = -1;

  if (eventEle  &&  !disabled) {
    this.jgSel = new jsGraphics(selEle, null, true, borderWidth);

    this.edits = new Array(this.xs.length);
    
    for (var p = this.xs.length-1; p >= 0; p--) {
      this.edits[p] = new Array(this.xs[p].length);
      for (var i = 0; i < this.edits[p].length; i++)
        this.edits[p][i] = i;
    }
    
    this.mode = 'select';
    this.drawAll();
      
    xAddEventListener(eventEle, 'click', this.click.bindAsEventListener(this));
    xAddEventListener(eventEle, 'dblclick', this.doubleClick.bindAsEventListener(this));
    
    xAddEventListener(document, 'keydown', this.keyDown.bindAsEventListener(this));
    xAddEventListener(eventEle, 'mousemove', this.mouseMove.bindAsEventListener(this));
    xAddEventListener(eventEle, 'mousedown', this.mouseDown.bindAsEventListener(this));
    xAddEventListener(eventEle, 'mouseup', this.mouseUp.bindAsEventListener(this));
    
    if (this.xs.length == 0)
      this.newPart();
  }
  else if (drawEle  &&  disabled) {
    this.drawAll();
  }
  this.setMoveCursor('move');
},

newPart : function() {
  if (this.mode != 'draw') {
    this.mode = 'draw';
    this.dragPart = -1;
  }
},
     
keyDown : function(evt) {  
  var xe = new xEvent(evt);
  // 46 = del
  if (xe.keyCode == 46  &&  this.selPart != -1  &&  this.mode == 'select') {
    if (xe.shiftKey) { // delete whole part
      this.xs.splice(this.selPart, 1);
      this.ys.splice(this.selPart, 1);
      this.edits.splice(this.selPart, 1);
    }
    else {
      this.xs[this.selPart].splice(this.selIndex, 1);
      this.ys[this.selPart].splice(this.selIndex, 1);
      this.edits[this.selPart].splice(this.selIndex, 1);
      if (this.xs[this.selPart].length == 0)  { // delete part
        this.xs.splice(this.selPart, 1);
        this.ys.splice(this.selPart, 1);
        this.edits.splice(this.selPart, 1);
      }
    }
    this.drawAll();
    this.setSelectedVertex(-1, -1);
    this.onChange();
  }
},      

setSnapPoints : function(points, maxDist) {
  this.snapPoints = points;
  this.maxSnapDist = maxDist;
},

setMoveCursor : function(cursor) {
  this.moveCursor = cursor;
},

onChange : function() {
  for (var i = 0; i < this.changeListeners.length; i++) {
    this.changeListeners[i](this.xs, this.ys, this.edits);
  }
},

addChangeListener : function(listener) {
  this.changeListeners.push(listener);
  listener(this.xs, this.ys, this.edits); // transmitting initialized edits array to onChange listener
},

removeChangeListener : function(listener) {
  for (var i = this.changeListeners.length-1; i >= 0; i--)
    if (this.changeListeners[i] == listener)
      this.changeListeners.splice(i, 1);
},

drawAllVertices : function() {
  var retVal = '';
  this.jg.setColor(this.vertexColor);
  for (var p = this.xs.length-1; p >= 0; p--) {
    for (var i = this.xs[p].length-1; i >= 0; i--)
      retVal += this.jg.fillRect(this.vertexId(p,i), this.xs[p][i]-(this.vertexSize/2), this.ys[p][i]-(this.vertexSize/2), this.vertexSize, this.vertexSize); 
  }
  return retVal;
},

lineId : function(p, i) {
  return this.prefix+'line' + p + '_' + i;
},
      
vertexId : function(p, i) {
  return this.prefix+'vertex' + p + '_' + i;
},

drawAll : function() {
  var s = '';
  this.jg.clear();
  s += this.drawAllVertices();
  
  this.jg.setColor(this.lineColor);
  for (var p = this.xs.length-1; p >= 0; p--) {
      if (this.xs[p].length > 1) {
        for (var i = this.xs[p].length-(this.polyLine?2:1); i >= 0; i--) {
          var j = (i+1) % this.xs[p].length;
          s += this.jg.drawLine(this.lineId(p,i), this.xs[p][j], this.ys[p][j], this.xs[p][i], this.ys[p][i]);
        }
      }
  }
  this.jg.paint(s);
},
      
setVertex : function(p, i, x, y) {
  this.xs[p][i] = x;
  this.ys[p][i] = y;
  if (i >= this.edits[p].length)
    this.edits[p][i] = -1;
  var j = (i-1 + this.xs[p].length) % this.xs[p].length;
  this.jg.remove(this.vertexId(p,i));
  this.jg.remove(this.lineId(p,j));
  this.jg.remove(this.lineId(p,i));
  var s = '';
  if (i != j) {
    this.jg.setColor(this.lineColor);
    if (!this.polyLine  ||  i != 0)
      s += this.jg.drawLine(this.lineId(p,j), this.xs[p][j], this.ys[p][j], this.xs[p][i], this.ys[p][i]);
    j = (i+1) % this.xs[p].length;
    if (!this.polyLine  ||  j != 0)
      s += this.jg.drawLine(this.lineId(p,i), this.xs[p][i], this.ys[p][i], this.xs[p][j], this.ys[p][j]);
  }
  this.jg.setColor(this.vertexColor);
  s += this.jg.fillRect(this.vertexId(p,i), this.xs[p][i]-(this.vertexSize/2), this.ys[p][i]-(this.vertexSize/2), this.vertexSize, this.vertexSize); 
  this.jg.paint(s);
  this.onChange();
},
      
setSelectedVertex : function(selPart, selIndex) {
  if (this.selPart != selPart  ||  this.selIndex != selIndex) {
    if (this.selPart != -1) {
      this.jgSel.remove(this.prefix+'selVertex');
      if (this.moveCursor != null)
        this.eventEle.style.cursor = this.defaultCursor;
    }
    this.selPart = selPart;
    this.selIndex = selIndex;
    if (this.selPart != -1) {
      this.jgSel.setColor(this.selVertexColor);
      this.jgSel.paint(this.jgSel.fillRect(this.prefix+'selVertex', this.xs[this.selPart][this.selIndex]-(this.selVertexSize/2), this.ys[this.selPart][this.selIndex]-(this.selVertexSize/2), this.selVertexSize, this.selVertexSize));
      if (this.moveCursor != null)
        this.eventEle.style.cursor = this.moveCursor;
    }
  }
},

getSnappedPoint : function(x, y) {
  var retVal = new Object();
  retVal.x = x;
  retVal.y = y;
  if (this.snapPoints) {
    var snapIndex = gxGetSnapPoint(x, y, this.snapPoints, 2, this.maxSnapDist);
    if (snapIndex != -1) {
      retVal.x = this.snapPoints[snapIndex];
      retVal.y = this.snapPoints[snapIndex+1];
    }
  }
  return retVal;
},
      
click : function(evt) {
  var xe = new xEvent(evt);
  if (this.mode == 'draw') {
    if (this.dragPart == -1) {
      this.jg.remove('drawStartPoint');
      this.dragPart = this.xs.length;
      this.dragIndex = 0;
      this.xs[this.dragPart] = new Array();
      this.ys[this.dragPart] = new Array();
      this.edits[this.dragPart] = new Array();
    }
    var pt = this.getSnappedPoint(xe.offsetX, xe.offsetY);
    this.setVertex(this.dragPart,this.dragIndex, pt.x, pt.y);
    this.dragIndex++;
    this.setVertex(this.dragPart,this.dragIndex, xe.offsetX, xe.offsetY);
  }
  else if (this.mode == 'select'  &&  this.selPart == -1) {
    var index = this.getSelectedLine(xe.offsetX, xe.offsetY);
    if (index != null) {
      // note: code is (also) valid for index == -1 
      this.xs[index[0]].splice(index[1]+1, 0, xe.offsetX); 
      this.ys[index[0]].splice(index[1]+1, 0, xe.offsetY); 
      this.edits[index[0]].splice(index[1]+1, 0, -1);
      this.drawAll();
      this.setSelectedVertex(index[0],index[1]+1);
      this.onChange();
    }
  }

},
  
doubleClick : function(evt) {
  var xe = new xEvent(evt);
  if (this.mode == 'draw') {
    if (!xIE4Up) {
      this.xs[this.dragPart].splice(this.dragIndex-1, 2);
      this.ys[this.dragPart].splice(this.dragIndex-1, 2);        
      this.edits[this.dragPart].splice(this.dragIndex-1, 2);        
    }
    else {
      this.xs[this.dragPart].splice(this.dragIndex, 1);
      this.ys[this.dragPart].splice(this.dragIndex, 1);
      this.edits[this.dragPart].splice(this.dragIndex, 1);        
    }
    this.mode = 'select';
    this.drawAll();
    this.setSelectedVertex(this.dragPart, this.xs[this.dragPart].length-1);
    this.onChange();
  }
},
      
mouseMove : function(evt) {
  var xe = new xEvent(evt);
  xPreventDefault(evt);
  if (xe.target == this.eventEle) { // in IE this may happen if mouse leaves element
    if (this.mode == 'draw'  &&  this.dragPart == -1) {
        var xe = new xEvent(evt);
        var pt = this.getSnappedPoint(xe.offsetX, xe.offsetY);
        this.jg.remove('drawStartPoint');
        this.jg.setColor(this.vertexColor);
        var s = this.jg.fillRect('drawStartPoint', pt.x-(this.vertexSize/2), pt.y-(this.vertexSize/2), this.vertexSize, this.vertexSize); 
        this.jg.paint(s);
    }
    if ((this.mode == 'draw'  ||  this.mode == 'drag')  &&  this.dragPart != -1  &&  this.xs[this.dragPart].length > 0) {   
      var xe = new xEvent(evt);
      var pt = this.getSnappedPoint(xe.offsetX, xe.offsetY);
      this.setVertex(this.dragPart, this.dragIndex, pt.x, pt.y);
    }
    else if (this.mode == 'select') {
      var selVertex = this.getSelectedVertex(xe.offsetX, xe.offsetY);
      this.setSelectedVertex(selVertex != null ? selVertex[0] : -1, selVertex != null ? selVertex[1] : -1);
    }
  }
  //window.status = this.toString(this.xs) + ' - ' + this.toString(this.ys) + ' - ' + this.toString(this.edits);
},
      
mouseDown : function(evt) {
  var xe = new xEvent(evt);
  if (this.mode == 'select') {
    if (this.selPart != -1) {
      xPreventDefault(evt);
      this.mode = 'drag';
      this.dragPart = this.selPart;
      this.dragIndex = this.selIndex;
      this.setSelectedVertex(-1, -1);
    }
  }
},
      
mouseUp : function(evt) {
  var xe = new xEvent(evt);
  if (this.mode == 'drag') {
    this.mode = 'select';
    this.setSelectedVertex(this.dragPart, this.dragIndex);
  }
},


getSelectedVertex : function(x, y) {
  var retVal = null;
  var minDist;
  
  for (var p = this.xs.length-1; p >= 0; p--) {
    for (var i = this.xs[p].length-1; i >= 0; i--) {
      if (Math.abs(this.xs[p][i]-x) <= 7  &&  Math.abs(this.ys[p][i]-y) <= 7) {
        var dist = gxDistPointPoint(this.xs[p][i], this.ys[p][i], x, y);
        if (retVal == null  ||  dist < minDist) {
          if (retVal == null)
            retVal = new Array(2);
          retVal[0] = p;
          retVal[1] = i;
          minDist = dist;
        }
      }
    }
  }
  return retVal;
},
      
getSelectedLine : function(x, y) {
  var retVal = null;
  var i = -1;
  var minDist = -1;
  for (var p = this.xs.length-1; p >= 0; p--) {
    for (i = this.xs[p].length-1; i >= 0; i--) {
      var j = (i+1) % this.xs[p].length;
      var d = gxDistPointSeg(x, y, this.xs[p][i], this.ys[p][i], this.xs[p][j], this.ys[p][j]);
      if (minDist == -1  ||  d < minDist) {
        if (retVal == null)
          retVal = new Array(2);
        retVal[0] = p;
        retVal[1] = i;
        minDist = d;
      }
    }
  }
  return retVal;
},

toString : function(arr) {
  var s = '';
  if (arr != null) {
    var commaPart = '';
    for (var p = 0; p < arr.length; p++) {
      s += commaPart + '(';
      var commaPoint = '';
      for (i = 0; i < arr[p].length; i++) {
        s += commaPoint + arr[p][i];
        commaPoint = ',';
      }
      s += ')';
      commaPart = ',';    
    }
  } 
  return s;
}
};


