//Version0.92
// Berücksichtigung des URL-Anchors (location.hash)

// PROTOTYPE FIXING end EXTENSION

$A([Array, Hash]).each(function(c){Object.extend( 
  c.prototype,
  {
    sizeof:   function(){ return this.inject(0, function(count){ return count+1; })},
    first:    function(){ return (this.detect(function(){ return true; }) || null);},
    findAll:  function(iterator){
      var results = this._new();
      this.each(function(value, index) {
        if (iterator(value, index))
          results._add(value, index);
      });
      return results;
    },
    reject:   function(iterator) {
      var results = this._new();
      this.each(function(value, index) {
        if (!iterator(value, index))
          results._add(value, index);
      });
      return results;
    }
  }
)});

Object.extend(
  Array.prototype, {
    _new: function()      { return $A() },
    _add: function(value) { this.push(value) }
  } 
);

Object.extend(
  Hash.prototype, {
    _new:   function()      { return $H() }, 
    _add:   function(value) { this[value[0]] = value[1] },
    _each:  function(iterator) {
      for (key in this) {
        var value = this[key];
        if (typeof value == 'function' && !(value.enumerable)) continue;

        var pair = [key, value];
        pair.key = key;
        pair.value = value;
        iterator(pair);
      }
    }
  } 
);

Function.prototype.e = function(){
  this.enumerable = true;
  return this;
}

String.prototype.removeWhiteSpaces = function() {return this.replace(/\s+/g,"") };
String.prototype.leftTrim   = function() {return  this.replace(/^\s+/,""); };
String.prototype.rightTrim  = function() {return  this.replace(/\s+$/,""); };
String.prototype.trim       = function() {return  this.leftTrim().rightTrim(); };

Function.prototype.inherits = function(parent){
  this.prototype              = new parent();
  this.prototype.constructor  = this;
  this.prototype.uber = function(name){
    var func = this.constructor.prototype[name];
    if (func == this[name]) {
      func = parent.prototype[name];
    }
    return func.apply(this, Array.prototype.slice.apply(arguments, [1]));
  }
  return this;
};

function co(){
  return $A(arguments).detect(function(val){return (val)});
}

Function.prototype.andThen = function(g){
  var f=this; return function(){f(); g();}
}

// type checking 
function isAlien(a) {
 return isObject(a) && typeof a.constructor != 'function';
}

function isArray(a) {
  return isObject(a) && a.constructor == Array;
}

function isBoolean(a) {
  return typeof a == 'boolean';
}

function isFunction(a) {
  return typeof a == 'function';
}

function isNull(a) {
  return typeof a == 'object' && !a;
}

function isNumber(a) {
  return typeof a == 'number' && isFinite(a);
}

function isObject(a) {
  return (typeof a == 'object' && !!a) || isFunction(a);
}

function isString(a) {
  return typeof a == 'string';
}

function isUndefined(a) {
  return typeof a == 'undefined';
} 

// CLASS BASE

BaseClass = function(){
  var myself = this;

  var includedScripts     = [];
  var includedStylesheets = [];
  var initFunctions       = [];
  var superInitFunctions  = [];
  var exitFunctions       = [];
  var initComplete        = false;

  var logWin;

  var mousewheel = {
    currentTarget:  null,
    targetFuncs:    $H(),
    targetPos:      $H()
  };

  // Mozilla detection
  var geckoDate = navigator.userAgent.match(/Gecko\/(\d{8})/);
  var msie      = navigator.userAgent.match(/MSIE/);

  this.Browser = {
    isGecko : (geckoDate && (1*geckoDate[1] >= 20020311)),
    isIE    : (msie)
  };

  this.dump = function(obj, level){
    var result = [];
    level = (level || 0);

    var isObj = isObject(obj);
    var isArr = isArray(obj);

    if (isObj) {
      var enu   = (isArr)?$A(obj):$H(obj);

      enu.each(function(el, idx){
        var key = (isArr) ? '' : el[0]+': '; 
        var val = (isArr) ? el : el[1];
        var spacer = "  ";
        for (var i=0; i<level; i++) spacer += "  ";
        result.push(spacer + key + Base.dump(val, level+1))
      });
      if (isArr){
        result = "[\n"+result.join(",\n")+"\n]";  
      } else {
        result = "{\n"+result.join(",\n")+"\n}";  
      }
    } else {
      result = (isString(obj))?'"'+obj+'"':obj.toString();
    }
    return result;
  };

  // dynamic JS-Loader

  this.require = function(src){
    if (includedScripts[src]) {return;}
    var func = function(){
      var script    = document.createElement("script");
      script.type   = "text/javascript";
      script.src    = src;

      document.getElementsByTagName("head")[0].appendChild(script);
    };
    if (this.initComplete) func(); else this.addSuperInitFunction(func);
    includedScripts[src] = true;
  }

  this.requireCSS = function(src){
    if (includedStylesheets[src]) {return;}
    var func = function(){
      var sheet    = document.createElement("link");
      sheet.rel    = "stylesheet";
      sheet.type   = "text/css";
      sheet.href   = src;
      
      document.getElementsByTagName("head")[0].appendChild(sheet);
    };
    if (this.initComplete) func(); else this.addSuperInitFunction(func);
    includedStylesheets[src] = true;
  
  }

  // Init handling
  this.addInitFunction = function(func){
    if (this.initComplete) func();
    else initFunctions.push(func);
  }

  this.addSuperInitFunction = function(func){
    if (this.initComplete) func();
    else superInitFunctions.push(func);
  }

  this.initAll = function(){
    var f;
    while (superInitFunctions.length || initFunctions.length){
      while (f = superInitFunctions.shift()) {f()}
      while (f = initFunctions.shift()) {f()}
    }
    this.initComplete = true; 
  }


  // Exit handling
  this.addExitFunction = function (func){
    exitFunctions.push(func);
  }

  this.exitAll = function(){
    exitFunctions.each (function(func){ func(); });
  }

  this.popup = function(url, name, params, focus){
    var myWin;
    name   = (name || 'popupwindow');
    params = Object.extend({
        width:600, height:400, resizable:'yes', status:'no', directories:'no', hotkeys:'yes', location:'no', menubar:'no', scrollbars:'yes', toolbar:'no'
      }, (params || {})
    );

    paramString = $H(params).map(function(pair){return pair.join('=')}).join(',');
    
    if ((myWin = window.open(url,name,paramString))){
      if(focus) myWin.focus();
      return myWin;  
    }
    return null;
  }

  this.log = function(txt){
    if (isObject(txt)) txt = Base.dump(txt);
    try { console.log(txt); } catch (e) { 
      // alert(e);
    }
  }

  // Status messages
  this.setStatus = function(text){
    window.status = text;
    setTimeout("window.status=''",500);
  }

  this. setCookie = function( name, value, days) {
    var expires_date = new Date( (new Date).getTime() + days*24*60*60*1000 );
    if (days) {
      var date = new Date();
      date.setTime(date.getTime()+(days*24*60*60*1000));
      var expires = "; expires="+date.toGMTString();
    }
    else var expires = "";
    document.cookie = name+"="+value+expires+"; path=/";
  }

  this.getCookie = function(name){
    var pair =  $A(document.cookie.split(';')).
                map(function(val)   {return val.split('=');}).
                find(function(val)  {return (val[0].trim() == name)});
    return (pair)?pair[1]:null;
  }

  this.initMousewheel = function(arr){
    if (window.addEventListener) {
      window.addEventListener(
        "DOMMouseScroll",
        function(e){
          if (mousewheel.currentTarget) {
            var delta = e.detail;
            var pos = mousewheel.targetPos[mousewheel.currentTarget.id] += delta;
            mousewheel.targetFuncs[mousewheel.currentTarget.id](pos, mousewheel.currentTarget, delta); 
            Event.stop(e)
          }
        },
        true
      );
    }

    document.onmousewheel=function(e){
      if (mousewheel.currentTarget) {
        if (!e) e = window.event;
        var delta =  - e.wheelDelta / 40;
        var pos = mousewheel.targetPos[mousewheel.currentTarget.id] += delta;
        mousewheel.targetFuncs[mousewheel.currentTarget.id](pos, mousewheel.currentTarget, delta);
        Event.stop(e);
      }
    }

    $H(arr).each( function(pair){
      el = $(pair[0]);
      mousewheel.targetPos[el.id]   = 0;
      mousewheel.targetFuncs[el.id] = pair[1];
      el.onmouseover  = function(){mousewheel.currentTarget = this};
      el.onmouseout   = function(){mousewheel.currentTarget = null};
    });
  }

}
var Base = new BaseClass();
window.onload   = Base.initAll;
window.onunload = Base.exitAll;


// CLASS QUERY

function QueryClass(){
  var myself      = this;
  var sessionName = 'smgsession'; // Hier null setzen, wenn nicht gebraucht

  var currentServer, currentPath, currentSearch, currentFragment, filteredSearch;

  // Bestimmt den Search-Anteil (Query-String) eines hrefs als Hash
  this.searchToHash = function (search){
    return $H(search.toQueryParams());

    // FIXME
    // Rest überflüssig oder gut für strukturierte Hashes???

    if (!search.length) return $H();
    var i, pairs, pair, value, varname, oldvalue;
  
    // Führendes ? wegätzen
    if (search.charAt(0) == '?') search = search.substring(1);
    
    // replace all plusses with spaces because unescape() doesnt do it
    search = search.replace(/\+/g, ' ');
    
    // for each pair, separate, unescape and place into the associate array
    pairs = $A(search.split('&'));

    // The following implements PHP-Style []-Recognition, so that Hashes and Arrays are recognised by JS too
    return $H(pairs.inject({}, function(params, pairString) {
      var pair = pairString.split('=');
      var key;

      varname = unescape(pair[0]);
      value   = unescape(pair[1]);

      if (!params) params = $A();
      if (varname.match(/(.+)\[([a-zA-Z0-9_-]*)\]$/)){
        params  = $H(params);
        varname = RegExp.$1;
        if (!params[varname]) params[varname] = $A();
        if (key = RegExp.$2){
          params[varname]       = $H(params[varname]);
          params[varname][key]  = value;   
        } else {
          params[varname].push(value);
        }
      } else {
        params[varname] = value;
      }
      return params;
    }));
  }

  
  currentServer   = location.protocol + '//' + location.hostname;
  currentPath     = location.pathname;
  currentSearch   = myself.searchToHash(location.search);
  currentFragment = (location.hash)?location.hash.substring(1):'';
  filteredSearch  = currentSearch.reject(function(pair){ return (pair[0].charAt(0)== "_") });  //  vars rausfiltern werden (_...)

  this.makeUrl = function(path, params, fragment, server){
    search = $H();
    if (params && params.keep)  search = filteredSearch.reject(function(pair){ return (!params.keep.include(pair[0])) });
    if (params && params.set)   search = search.merge($H(params.set));

    // Base.log(search.inspect());

    server    = server    || currentServer;
    path      = path      || currentPath;
    fragment  = fragment  || currentFragment;

    if (sessionName && currentSearch[sessionName]) search[sessionName] = currentSearch[sessionName];

    var search = search.toQueryString();

    return ((path[0] == '/')?server:'') + path + ((search)?'?'+search:'') + ((fragment)?("#"+fragment):'');
  }

  this.getParams = function(){
    return currentSearch;  
  }

  this.getFragment = function(){
    return currentFragment;  
  }

  this.fire = function(path, params, fragment){
    // Base.log(this.makeUrl(path, params, fragment));
    window.location.href = this.makeUrl(path, params, fragment);
  }
}

Base.addSuperInitFunction( function(){ Query = new QueryClass(); });

// SELECT

var SelectClass = {

  sweep: function(keepIndex){ 
    while(node = this.firstChild) this.removeChild(node)
    if (!(keepIndex) && this.index) this.index();
  },

  addOption: function(name,id,keepIndex){
    this.appendChild(option = document.createElement("option"));
    option.innerHTML  = name;
    option.value      = id;
    if (!(keepIndex) && this.index) this.index();
  },

  addHash: function (hash, keepIndex, defaulttext, oglabel){
    var parent = this;
    if (defaulttext) parent.addOption(defaulttext,'',true);
    if (oglabel) {
      parent = $S(document.createElement('optgroup'));
      this.appendChild(parent);
      parent.label = oglabel;
    }
    $H(hash).each(function(val){ parent.addOption(val[1],val[0],true);});
    if (!(keepIndex) && this.index) this.index();
  },

  fromHash: function (hash, keepIndex, defaulttext, oglabel){
    this.sweep(true);
    this.addHash($H(hash), true, defaulttext, oglabel);
    if (!(keepIndex) && this.index) this.index();
  },

  toHash: function(){
    return $A(this.options).inject($H(), function(hash, o){hash._add([o.value, o.text]); return hash;});
  },

  selectValue: function(value,def){
    var option;
    if (option = $A(this.options).find(function(v){ return(v.value == value) })){ option.selected = true; } 
    else if (def) this.options[0].selected = true;
  },

  selectedValue: function(){
    try {
      var index = this.selectedIndex;
      var value = (this.options[index])?this.options[index].value:null;
    } catch (e) {}
    return value;
  },

  selectedText: function(){
    var index = this.selectedIndex;
    return (this.options[index])?this.options[index].text:null;
  },

  selectedValues: function(){
    return $A(this.options).findAll(function(o){return o.selected;}).map(function(o){return o.value });
  },

  getValues: function(){
    return $A(this.options).map(function(o){return o.value });
  },

  selectedHash: function(){
    return $A(this.options).inject($H(), function(hash, o){if (o.selected) {hash._add([o.value, o.text]);} return hash;});
  },

  contains: function(needle){
    return $A(this.options).find(function(el){return (el.value == needle)});
  },

  quickref: function(input, useRegexp, currentId, create, onselect, onunselect){
    // input      - Text-Input-Element
    // useRegexp  - sollen Regular Expressions verwendet werden? (Kritisch bei Sonderzeichen)
    // currentId  - aktuelle Auswahl
    var checkFrequency  = 100;
    var keyPressLatency = 400;
    var myself          = this;

    this._onchange = this.onchange;

    onselectDo = function(val) {
      if (onselect) onselect(myself.selectedValue());
      myself._onchange();
      if (!created) input.value = myself.selectedText();
    };

    this.onchange  = onselectDo;
    
    var timeout         = false;
    var cache           = {};
    var lastTime        = false;
    var created         = false;  

    this.index = function(){ /* Base.log('indexing'); */ items = myself.toHash() }
    this.index();

    var showResult = function(hash ,string){
      myself.sweep(true);

      var preselected = -1; // voreingestellter Wert
      var matched     = -1; // genauer Match, selektiert auch bei mehreren Ergebnissen

      hash.each(function(value, index){
        myself.addOption(value[1], value[0], true);
        if ((matched < 0) && string.toLowerCase().trim() == value[1].toLowerCase().trim()) {
           matched = index;
        }
        if (currentId == value[0]) {
          preselected = index;
        }
      });

      var selectPos = -1;
      if (myself.length == 1){
        selectPos = 0; 
      } else if (matched >= 0){               // genauer Match
        selectPos = matched;
      } else if (preselected >= 0){           // voreingestellt
        selectPos = preselected;
      }

      if (selectPos >= 0){
        try {myself.options[selectPos].selected = true} catch(e) {};
        onselectDo();
      } 

      // Wert neu anlegen?
      if ((myself.length == 0) && create){   
        var option = new Option(string+' (neu)',string);
        myself.options[myself.length] = option;
        myself.options[0].selected    = true;
        created = true;
      } 
    }

    this.doSearch = function(){
      var string = input.value;
     
      if (typeof(cache[string]) != 'undefined') {
        result  = cache[string];
      } else {
        if (useRegexp){
          var regexp;
          try {
            regexp = new RegExp(string,'i');
          } catch (e) {
            regexp = new RegExp('','i');
          }
        }
        
        result = items.findAll(function(el, index){
          val = el[1];
          if (useRegexp){
            if (val.match(regexp)) return true;
          } else {
            return !($A(string.split(' ')).find(function(curval){
              return (val.toLowerCase().indexOf(curval.toLowerCase()) == -1);
            }));
          }   
          return false;
        });
        cache[string] = result;
      }

      showResult(result,string);
    }

    var triggerSearch = function(keyPressed){
      var time = new Date().getTime(); 

      if (lastTime == false || keyPressed) { 
        lastTime = time; 
        if (timeout != false) { clearTimeout(timeout); }
        timeout = setTimeout(function(){triggerSearch(false)},checkFrequency); 
        return; 
      }

      var millis = time-lastTime; 
      
      if (millis > keyPressLatency){
        // Bereit
        lastTime = false;
        myself.doSearch();
      } else {
        // Nachgeguckt, noch nicht bereit
        if (timeout != false) { clearTimeout(timeout); }
        timeout = setTimeout(function(){triggerSearch(false)},checkFrequency); 
      }
    }

    // Bei Tastendruck:
    input.onkeyup = function(evt){
      evt = (evt) ? evt : event;
      var charCode = (evt.charCode) ? evt.charCode : ((evt.keyCode) ? evt.keyCode : ((evt.which) ? evt.which : 0));

      if ( charCode<37 || charCode > 40 ){
        // Keine Pfeiltasten
        triggerSearch(true)
      }
      else {
        // Mit Pfeil-Unten in Selectfeld springen
        if (myself.length && charCode == 40) {
          myself.options[0].selected = true;
          try{ myself.focus() } catch(e) {} // Hier schmeisst Firefox einen XUL error, der geht durch das catch nich weg :(
        }
      }
    };


    // Backspace im Selectfeld:
    this.onkeydown = function(evt){
      evt = (evt) ? evt : event;
      var charCode = (evt.charCode) ? evt.charCode : ((evt.keyCode) ? evt.keyCode : ((evt.which) ? evt.which : 0));

      if (charCode == 8) {
        input.focus();
        input.value=input.value;
        return;

        // Eventuelles Browser-Back abfangen:
        /* scheinbar doch nicht nötig...
          evt.cancelBubble = true;
          if (evt.stopPropagation) evt.stopPropagation();
          evt.returnValue = false;
          return false;
        */
      }

    };


  } 
}

function $S(object) {
  object = $(object);
  Object.extend(object, SelectClass);
  return object;
}

var Img = {
  setSrc : function(src, andThen, fallbackSrc){
    var myself  = this;
    var clone   = function(counter){
      if (ref.complete){
        if( ref.width>0 && ref.height>0 ){
          myself.src     = ref.src;
          myself.width   = ref.width;
          myself.height  = ref.height;
          if (andThen && (typeof(andThen) == 'function')) andThen();
        } else {
          myself.src = fallbackSrc;
        }
      } else {
        if (typeof(counter) != 'Number'){ counter=1; } else { counter++;  }
        if (counter < 20) { setTimeout(function(){ clone(counter)},200); } 
      }
    }
    var ref = new Image();
    ref.src = src+"?"+Math.random();
    clone(ref); 
  },

  setSize: function(x,y){
    if (x) this.style.width  = x+'px'; 
    if (y) this.style.height = y+'px'; 
  }
}

function $I(object) {
  object = $(object);
  Object.extend(object, Img);
  return object;
}




// DOM Manipulation

function createTextButton(imageSrc, func, text){
  var anchorNode  = document.createElement("a");
  anchorNode.appendChild(createImage(imageSrc));
  anchorNode.appendChild(document.createTextNode(' '+text));
  anchorNode.onclick = func;
  return anchorNode;
}

function createButton(imageSrc, func, width, height, title){
  var anchorNode  = document.createElement("a");
  
  var imageNode   = createImage(imageSrc,width,height,title);
  anchorNode.appendChild(imageNode);
  anchorNode.onclick = func;

  return anchorNode;
}

function createImage(src,width,height,title){
  var imageNode = document.createElement("IMG");
  imageNode.src = src;
  if (width)  imageNode.style.width   = width+"px";
  if (height) imageNode.style.height  = height+"px";
  if (title)  imageNode.title         = title;
  imageNode.style.border = "0px";

  return imageNode;
}

function hashToList(hash){
  list = [];
  for (o in hash){
    if (isFunction(hash[o])) continue;
    list.push([hash[o]["id"], hash[o]["name"]]);
  }
  return list;
}
