/*  Prototype JavaScript framework, version 1.6.0
 *  (c) 2005-2007 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://www.prototypejs.org/
 *
 *--------------------------------------------------------------------------*/

// Browser: IE7 -> dodane przez nas o2.pl

var Prototype = {
  Version: '1.6.0',

  Browser: {
    IE:     !!(window.attachEvent && !window.opera),
    IE7:    !!(window.attachEvent && !window.opera && window.XMLHttpRequest),
    Opera:  !!window.opera,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
  },

  BrowserFeatures: {
    XPath: !!document.evaluate,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      document.createElement('div').__proto__ &&
      document.createElement('div').__proto__ !==
        document.createElement('form').__proto__
  },

  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,

  emptyFunction: function() { },
  K: function(x) { return x }
};

if (Prototype.Browser.MobileSafari)
  Prototype.BrowserFeatures.SpecificElementExtensions = false;

if (Prototype.Browser.WebKit)
  Prototype.BrowserFeatures.XPath = false;

/* Based on Alex Arnell's inheritance implementation. */
var Class = {
  create: function() {
    var parent = null, properties = $A(arguments);
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

    function klass() {
      this.initialize.apply(this, arguments);
    }

    Object.extend(klass, Class.Methods);
    klass.superclass = parent;
    klass.subclasses = [];

    if (parent) {
      var subclass = function() { };
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }

    for (var i = 0; i < properties.length; i++)
      klass.addMethods(properties[i]);

    if (!klass.prototype.initialize)
      klass.prototype.initialize = Prototype.emptyFunction;

    klass.prototype.constructor = klass;

    return klass;
  }
};

Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value, value = Object.extend((function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method), {
          valueOf:  function() { return method },
          toString: function() { return method.toString() }
        });
      }
      this.prototype[property] = value;
    }

    return this;
  }
};

var Abstract = { };

Object.extend = function(destination, source) {
  for (var property in source)
    destination[property] = source[property];
  return destination;
};

Object.extend(Object, {
  inspect: function(object) {
    try {
      if (object === undefined) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : object.toString();
    } catch (e) {
      if (e instanceof RangeError) return '...';
      throw e;
    }
  },

  toJSON: function(object) {
    var type = typeof object;
    switch (type) {
      case 'undefined':
      case 'function':
      case 'unknown': return;
      case 'boolean': return object.toString();
    }

    if (object === null) return 'null';
    if (object.toJSON) return object.toJSON();
    if (Object.isElement(object)) return;

    var results = [];
    for (var property in object) {
      var value = Object.toJSON(object[property]);
      if (value !== undefined)
        results.push(property.toJSON() + ': ' + value);
    }

    return '{' + results.join(', ') + '}';
  },

  toQueryString: function(object) {
    return $H(object).toQueryString();
  },

  toHTML: function(object) {
    return object && object.toHTML ? object.toHTML() : String.interpret(object);
  },

  keys: function(object) {
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
  },

  values: function(object) {
    var values = [];
    for (var property in object)
      values.push(object[property]);
    return values;
  },

  clone: function(object) {
    return Object.extend({ }, object);
  },

  isElement: function(object) {
    return object && object.nodeType == 1;
  },

  isArray: function(object) {
    return object && object.constructor === Array;
  },

  isHash: function(object) {
    return object instanceof Hash;
  },

  isFunction: function(object) {
    return typeof object == "function";
  },

  isString: function(object) {
    return typeof object == "string";
  },

  isNumber: function(object) {
    return typeof object == "number";
  },

  isUndefined: function(object) {
    return typeof object == "undefined";
  }
});

Object.extend(Function.prototype, {
  argumentNames: function() {
    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
    return names.length == 1 && !names[0] ? [] : names;
  },

  bind: function() {
    if (arguments.length < 2 && arguments[0] === undefined) return this;
    var __method = this, args = $A(arguments), object = args.shift();
    return function() {
      return __method.apply(object, args.concat($A(arguments)));
    }
  },

  bindAsEventListener: function() {
    var __method = this, args = $A(arguments), object = args.shift();
    return function(event) {
      return __method.apply(object, [event || window.event].concat(args));
    }
  },

  curry: function() {
    if (!arguments.length) return this;
    var __method = this, args = $A(arguments);
    return function() {
      return __method.apply(this, args.concat($A(arguments)));
    }
  },

  delay: function() {
    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  },

  wrap: function(wrapper) {
    var __method = this;
    return function() {
      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
    }
  },

  methodize: function() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      return __method.apply(null, [this].concat($A(arguments)));
    };
  }
});

Function.prototype.defer = Function.prototype.delay.curry(0.01);

Date.prototype.toJSON = function() {
  return '"' + this.getUTCFullYear() + '-' +
    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
    this.getUTCDate().toPaddedString(2) + 'T' +
    this.getUTCHours().toPaddedString(2) + ':' +
    this.getUTCMinutes().toPaddedString(2) + ':' +
    this.getUTCSeconds().toPaddedString(2) + 'Z"';
};

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) { }
    }

    return returnValue;
  }
};

RegExp.prototype.match = RegExp.prototype.test;

RegExp.escape = function(str) {
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create({
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  execute: function() {
    this.callback(this);
  },

  stop: function() {
    if (!this.timer) return;
    clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.execute();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
});
Object.extend(String, {
  interpret: function(value) {
    return value == null ? '' : String(value);
  },
  specialChar: {
    '\b': '\\b',
    '\t': '\\t',
    '\n': '\\n',
    '\f': '\\f',
    '\r': '\\r',
    '\\': '\\\\'
  }
});

Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

    while (source.length > 0) {
      if (match = source.match(pattern)) {
        result += source.slice(0, match.index);
        result += String.interpret(replacement(match));
        source  = source.slice(match.index + match[0].length);
      } else {
        result += source, source = '';
      }
    }
    return result;
  },

  sub: function(pattern, replacement, count) {
    replacement = this.gsub.prepareReplacement(replacement);
    count = count === undefined ? 1 : count;

    return this.gsub(pattern, function(match) {
      if (--count < 0) return match[0];
      return replacement(match);
    });
  },

  scan: function(pattern, iterator) {
    this.gsub(pattern, iterator);
    return String(this);
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = truncation === undefined ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : String(this);
  },

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script) });
  },

  escapeHTML: function() {
    var self = arguments.callee;
    self.text.data = this;
    return self.div.innerHTML;
  },

  unescapeHTML: function() {
    var div = new Element('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
      div.childNodes[0].nodeValue) : '';
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return { };

    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var key = decodeURIComponent(pair.shift());
        var value = pair.length > 1 ? pair.join('=') : pair[0];
        if (value != undefined) value = decodeURIComponent(value);

        if (key in hash) {
          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
          hash[key].push(value);
        }
        else hash[key] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  times: function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function() {
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
      var character = String.specialChar[match[0]];
      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
    });
    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  },

  toJSON: function() {
    return this.inspect(true);
  },

  unfilterJSON: function(filter) {
    return this.sub(filter || Prototype.JSONFilter, '#{1}');
  },

  isJSON: function() {
    var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
  },

  evalJSON: function(sanitize) {
    var json = this.unfilterJSON();
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  },

  include: function(pattern) {
    return this.indexOf(pattern) > -1;
  },

  startsWith: function(pattern) {
    return this.indexOf(pattern) === 0;
  },

  endsWith: function(pattern) {
    var d = this.length - pattern.length;
    return d >= 0 && this.lastIndexOf(pattern) === d;
  },

  empty: function() {
    return this == '';
  },

  blank: function() {
    return /^\s*$/.test(this);
  },

  interpolate: function(object, pattern) {
    return new Template(this, pattern).evaluate(object);
  }
});

if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
  escapeHTML: function() {
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  },
  unescapeHTML: function() {
    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (Object.isFunction(replacement)) return replacement;
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match) };
};

String.prototype.parseQuery = String.prototype.toQueryParams;

Object.extend(String.prototype.escapeHTML, {
  div:  document.createElement('div'),
  text: document.createTextNode('')
});

with (String.prototype.escapeHTML) div.appendChild(text);

var Template = Class.create({
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    if (Object.isFunction(object.toTemplateReplacements))
      object = object.toTemplateReplacements();

    return this.template.gsub(this.pattern, function(match) {
      if (object == null) return '';

      var before = match[1] || '';
      if (before == '\\') return match[2];

      var ctx = object, expr = match[3];
      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
      if (match == null) return before;

      while (match != null) {
        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
        ctx = ctx[comp];
        if (null == ctx || '' == match[3]) break;
        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
        match = pattern.exec(expr);
      }

      return before + String.interpret(ctx);
    }.bind(this));
  }
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

var $break = { };

var Enumerable = {
  each: function(iterator, context) {
    var index = 0;
    iterator = iterator.bind(context);
    try {
      this._each(function(value) {
        iterator(value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var index = -number, slices = [], array = this.toArray();
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  },

  all: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator(value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator(value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator(value, index));
    });
    return results;
  },

  detect: function(iterator, context) {
    iterator = iterator.bind(context);
    var result;
    this.each(function(value, index) {
      if (iterator(value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator, context) {
    iterator = iterator.bind(context);
    var results = [];
    this.each(function(value, index) {
      if (iterator(value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(filter, iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator(value, index));
    });
    return results;
  },

  include: function(object) {
    if (Object.isFunction(this.indexOf))
      if (this.indexOf(object) != -1) return true;

    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = fillWith === undefined ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator, context) {
    iterator = iterator.bind(context);
    this.each(function(value, index) {
      memo = iterator(memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator(value, index);
      if (result == undefined || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator(value, index);
      if (result == undefined || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator(value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator, context) {
    iterator = iterator.bind(context);
    var results = [];
    this.each(function(value, index) {
      if (!iterator(value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    iterator = iterator.bind(context);
    return this.map(function(value, index) {
      return {value: value, criteria: iterator(value, index)};
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
};

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  filter:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray,
  every:   Enumerable.all,
  some:    Enumerable.any
});
function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

if (Prototype.Browser.WebKit) {
  function $A(iterable) {
    if (!iterable) return [];
    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
        iterable.toArray) return iterable.toArray();
    var length = iterable.length, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
  }
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0, length = this.length; i < length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(Object.isArray(value) ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  reduce: function() {
    return this.length > 1 ? this : this[0];
  },

  uniq: function(sorted) {
    return this.inject([], function(array, value, index) {
      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
        array.push(value);
      return array;
    });
  },

  intersect: function(array) {
    return this.uniq().findAll(function(item) {
      return array.detect(function(value) { return item === value });
    });
  },

  clone: function() {
    return [].concat(this);
  },

  size: function() {
    return this.length;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  },

  toJSON: function() {
    var results = [];
    this.each(function(object) {
      var value = Object.toJSON(object);
      if (value !== undefined) results.push(value);
    });
    return '[' + results.join(', ') + ']';
  }
});

// use native browser JS 1.6 implementation if available
if (Object.isFunction(Array.prototype.forEach))
  Array.prototype._each = Array.prototype.forEach;

if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
};

if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
  var n = this.slice(0, i).reverse().indexOf(item);
  return (n < 0) ? n : i - n - 1;
};

Array.prototype.toArray = Array.prototype.clone;

function $w(string) {
  if (!Object.isString(string)) return [];
  string = string.strip();
  return string ? string.split(/\s+/) : [];
}

if (Prototype.Browser.Opera){
  Array.prototype.concat = function() {
    var array = [];
    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
    for (var i = 0, length = arguments.length; i < length; i++) {
      if (Object.isArray(arguments[i])) {
        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  };
}
Object.extend(Number.prototype, {
  toColorPart: function() {
    return this.toPaddedString(2, 16);
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator) {
    $R(0, this, true).each(iterator);
    return this;
  },

  toPaddedString: function(length, radix) {
    var string = this.toString(radix || 10);
    return '0'.times(length - string.length) + string;
  },

  toJSON: function() {
    return isFinite(this) ? this.toString() : 'null';
  }
});

$w('abs round ceil floor').each(function(method){
  Number.prototype[method] = Math[method].methodize();
});
function $H(object) {
  return new Hash(object);
};

var Hash = Class.create(Enumerable, (function() {
  if (function() {
    var i = 0, Test = function(value) { this.key = value };
    Test.prototype.key = 'foo';
    for (var property in new Test('bar')) i++;
    return i > 1;
  }()) {
    function each(iterator) {
      var cache = [];
      for (var key in this._object) {
        var value = this._object[key];
        if (cache.include(key)) continue;
        cache.push(key);
        var pair = [key, value];
        pair.key = key;
        pair.value = value;
        iterator(pair);
      }
    }
  } else {
    function each(iterator) {
      for (var key in this._object) {
        var value = this._object[key], pair = [key, value];
        pair.key = key;
        pair.value = value;
        iterator(pair);
      }
    }
  }

  function toQueryPair(key, value) {
    if (Object.isUndefined(value)) return key;
    return key + '=' + encodeURIComponent(String.interpret(value));
  }

  return {
    initialize: function(object) {
      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
    },

    _each: each,

    set: function(key, value) {
      return this._object[key] = value;
    },

    get: function(key) {
      return this._object[key];
    },

    unset: function(key) {
      var value = this._object[key];
      delete this._object[key];
      return value;
    },

    toObject: function() {
      return Object.clone(this._object);
    },

    keys: function() {
      return this.pluck('key');
    },

    values: function() {
      return this.pluck('value');
    },

    index: function(value) {
      var match = this.detect(function(pair) {
        return pair.value === value;
      });
      return match && match.key;
    },

    merge: function(object) {
      return this.clone().update(object);
    },

    update: function(object) {
      return new Hash(object).inject(this, function(result, pair) {
        result.set(pair.key, pair.value);

        return result;
      });
    },

    toQueryString: function() {
      return this.map(function(pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

        if (values && typeof values == 'object') {
          if (Object.isArray(values))
            return values.map(toQueryPair.curry(key)).join('&');
        }
        return toQueryPair(key, values);
      }).join('&');
    },

    inspect: function() {
      return '#<Hash:{' + this.map(function(pair) {
        return pair.map(Object.inspect).join(': ');
      }).join(', ') + '}>';
    },

    toJSON: function() {
      return Object.toJSON(this.toObject());
    },

    clone: function() {
      return new Hash(this);
    }
  }
})());

Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;
var ObjectRange = Class.create(Enumerable, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    while (this.include(value)) {
      iterator(value);
      value = value.succ();
    }
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
};

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new XMLHttpRequest()},
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
    ) || false;
  },

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responder) {
    if (!this.include(responder))
      this.responders.push(responder);
  },

  unregister: function(responder) {
    this.responders = this.responders.without(responder);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (Object.isFunction(responder[callback])) {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) { }
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate:   function() { Ajax.activeRequestCount++ },
  onComplete: function() { Ajax.activeRequestCount-- }
});

Ajax.Base = Class.create({
  initialize: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      encoding:     'UTF-8',
      parameters:   '',
      evalJSON:     true,
      evalJS:       true
    };
    Object.extend(this.options, options || { });

    this.options.method = this.options.method.toLowerCase();
    if (Object.isString(this.options.parameters))
      this.options.parameters = this.options.parameters.toQueryParams();
  }
});

Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,

  initialize: function($super, url, options) {
    $super(options);
    this.transport = Ajax.getTransport();
    this.request(url);
  },

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = Object.clone(this.options.parameters);

    if (!['get', 'post'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';
    }

    this.parameters = params;

    if (params = Object.toQueryString(params)) {
      // when GET, append parameters to URL
      if (this.method == 'get')
        this.url += (this.url.include('?') ? '&' : '?') + params;
      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';
    }

    try {
      var response = new Ajax.Response(this);
      if (this.options.onCreate) this.options.onCreate(response);
      Ajax.Responders.dispatch('onCreate', this, response);

      this.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
      this.transport.send(this.body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

    }
    catch (e) {
      this.dispatchException(e);
    }
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState > 1 && !((readyState == 4) && this._complete))
      this.respondToReadyState(this.transport.readyState);
  },

  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
    };

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (Object.isFunction(extras.push))
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);
  },

  success: function() {
    var status = this.getStatus();
    return !status || (status >= 200 && status < 300);
  },

  getStatus: function() {
    try {
      return this.transport.status || 0;
    } catch (e) { return 0 }
  },

  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);

    if (state == 'Complete') {
      try {
        this._complete = true;
        (this.options['on' + response.status]
         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(response, response.headerJSON);
      } catch (e) {
        this.dispatchException(e);
      }

      var contentType = response.getHeader('Content-type');
      if (this.options.evalJS == 'force'
          || (this.options.evalJS && contentType
          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
        this.evalResponse();
    }

    try {
      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
    } catch (e) {
      this.dispatchException(e);
    }

    if (state == 'Complete') {
      // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name);
    } catch (e) { return null }
  },

  evalResponse: function() {
    try {
      return eval((this.transport.responseText || '').unfilterJSON());
    } catch (e) {
      this.dispatchException(e);
    }
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Response = Class.create({
  initialize: function(request){
    this.request = request;
    var transport  = this.transport  = request.transport,
        readyState = this.readyState = transport.readyState;

    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
      this.status       = this.getStatus();
      this.statusText   = this.getStatusText();
      this.responseText = String.interpret(transport.responseText);
      this.headerJSON   = this._getHeaderJSON();
    }

    if(readyState == 4) {
      var xml = transport.responseXML;
      this.responseXML  = xml === undefined ? null : xml;
      this.responseJSON = this._getResponseJSON();
    }
  },

  status:      0,
  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

  getStatusText: function() {
    try {
      return this.transport.statusText || '';
    } catch (e) { return '' }
  },

  getHeader: Ajax.Request.prototype.getHeader,

  getAllHeaders: function() {
    try {
      return this.getAllResponseHeaders();
    } catch (e) { return null }
  },

  getResponseHeader: function(name) {
    return this.transport.getResponseHeader(name);
  },

  getAllResponseHeaders: function() {
    return this.transport.getAllResponseHeaders();
  },

  _getHeaderJSON: function() {
    var json = this.getHeader('X-JSON');
    if (!json) return null;
    json = decodeURIComponent(escape(json));
    try {
      return json.evalJSON(this.request.options.sanitizeJSON);
    } catch (e) {
      this.request.dispatchException(e);
    }
  },

  _getResponseJSON: function() {
    var options = this.request.options;
    if (!options.evalJSON || (options.evalJSON != 'force' &&
      !(this.getHeader('Content-type') || '').include('application/json')))
        return null;
    try {
      return this.transport.responseText.evalJSON(options.sanitizeJSON);
    } catch (e) {
      this.request.dispatchException(e);
    }
  }
});

Ajax.Updater = Class.create(Ajax.Request, {
  initialize: function($super, container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    options = options || { };
    var onComplete = options.onComplete;
    options.onComplete = (function(response, param) {
      this.updateContent(response.responseText);
      if (Object.isFunction(onComplete)) onComplete(response, param);
    }).bind(this);

    $super(url, options);
  },

  updateContent: function(responseText) {
    var receiver = this.container[this.success() ? 'success' : 'failure'],
        options = this.options;

    if (!options.evalScripts) responseText = responseText.stripScripts();

    if (receiver = $(receiver)) {
      if (options.insertion) {
        if (Object.isString(options.insertion)) {
          var insertion = { }; insertion[options.insertion] = responseText;
          receiver.insert(insertion);
        }
        else options.insertion(receiver, responseText);
      }
      else receiver.update(responseText);
    }

    if (this.success()) {
      if (this.onComplete) this.onComplete.bind(this).defer();
    }
  }
});

Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
  initialize: function($super, container, url, options) {
    $super(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = { };
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(response) {
    if (this.options.decay) {
      this.decay = (response.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = response.responseText;
    }
    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (Object.isString(element))
    element = document.getElementById(element);
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(Element.extend(query.snapshotItem(i)));
    return results;
  };
}

/*--------------------------------------------------------------------------*/

if (!window.Node) var Node = { };

if (!Node.ELEMENT_NODE) {
  // DOM level 2 ECMAScript Language Binding
  Object.extend(Node, {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  });
}

(function() {
  var element = this.Element;
  this.Element = function(tagName, attributes) {
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache;
    if (Prototype.Browser.IE && attributes.name) {
      tagName = '<' + tagName + ' name="' + attributes.name + '">';
      delete attributes.name;
      return Element.writeAttribute(document.createElement(tagName), attributes);
    }
    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
  };
  Object.extend(this.Element, element || { });
}).call(window);

Element.cache = { };

Element.Methods = {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function(element) {
    element = $(element);
    Element[Element.visible(element) ? 'hide' : 'show'](element);
    return element;
  },

  hide: function(element) {
    $(element).style.display = 'none';
    return element;
  },

  show: function(element) {
    $(element).style.display = '';
    return element;
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
    return element;
  },

  update: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);
    content = Object.toHTML(content);
    element.innerHTML = content.stripScripts();
    content.evalScripts.bind(content).defer();
    return element;
  },

  replace: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    else if (!Object.isElement(content)) {
      content = Object.toHTML(content);
      var range = element.ownerDocument.createRange();
      range.selectNode(element);
      content.evalScripts.bind(content).defer();
      content = range.createContextualFragment(content.stripScripts());
    }
    element.parentNode.replaceChild(content, element);
    return element;
  },

  insert: function(element, insertions) {
    element = $(element);

    if (Object.isString(insertions) || Object.isNumber(insertions) ||
        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
          insertions = {bottom:insertions};

    var content, t, range;

    for (position in insertions) {
      content  = insertions[position];
      position = position.toLowerCase();
      t = Element._insertionTranslations[position];

      if (content && content.toElement) content = content.toElement();
      if (Object.isElement(content)) {
        t.insert(element, content);
        continue;
      }

      content = Object.toHTML(content);

      range = element.ownerDocument.createRange();
      t.initializeRange(element, range);
      t.insert(element, range.createContextualFragment(content.stripScripts()));

      content.evalScripts.bind(content).defer();
    }

    return element;
  },

  wrap: function(element, wrapper, attributes) {
    element = $(element);
    if (Object.isElement(wrapper))
      $(wrapper).writeAttribute(attributes || { });
    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
    else wrapper = new Element('div', wrapper);
    if (element.parentNode)
      element.parentNode.replaceChild(wrapper, element);
    wrapper.appendChild(element);
    return wrapper;
  },

  inspect: function(element) {
    element = $(element);
    var result = '<' + element.tagName.toLowerCase();
    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
      var property = pair.first(), attribute = pair.last();
      var value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property) {
    element = $(element);
    var elements = [];
    while (element = element[property])
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
    return elements;
  },

  ancestors: function(element) {
    return $(element).recursivelyCollect('parentNode');
  },

  descendants: function(element) {
    return $A($(element).getElementsByTagName('*')).each(Element.extend);
  },

  firstDescendant: function(element) {
    element = $(element).firstChild;
    while (element && element.nodeType != 1) element = element.nextSibling;
    return $(element);
  },

  immediateDescendants: function(element) {
    if (!(element = $(element).firstChild)) return [];
    while (element && element.nodeType != 1) element = element.nextSibling;
    if (element) return [element].concat($(element).nextSiblings());
    return [];
  },

  previousSiblings: function(element) {
    return $(element).recursivelyCollect('previousSibling');
  },

  nextSiblings: function(element) {
    return $(element).recursivelyCollect('nextSibling');
  },

  siblings: function(element) {
    element = $(element);
    return element.previousSiblings().reverse().concat(element.nextSiblings());
  },

  match: function(element, selector) {
    if (Object.isString(selector))
      selector = new Selector(selector);
    return selector.match($(element));
  },

  up: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(element.parentNode);
    var ancestors = element.ancestors();
    return expression ? Selector.findElement(ancestors, expression, index) :
      ancestors[index || 0];
  },

  down: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return element.firstDescendant();
    var descendants = element.descendants();
    return expression ? Selector.findElement(descendants, expression, index) :
      descendants[index || 0];
  },

  previous: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
    var previousSiblings = element.previousSiblings();
    return expression ? Selector.findElement(previousSiblings, expression, index) :
      previousSiblings[index || 0];
  },

  next: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
    var nextSiblings = element.nextSiblings();
    return expression ? Selector.findElement(nextSiblings, expression, index) :
      nextSiblings[index || 0];
  },

  select: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element, args);
  },

  adjacent: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element.parentNode, args).without(element);
  },

  identify: function(element) {
    element = $(element);
    var id = element.readAttribute('id'), self = arguments.callee;
    if (id) return id;
    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
    element.writeAttribute('id', id);
    return id;
  },

  readAttribute: function(element, name) {
    element = $(element);
    if (Prototype.Browser.IE) {
      var t = Element._attributeTranslations.read;
      if (t.values[name]) return t.values[name](element, name);
      if (t.names[name]) name = t.names[name];
      if (name.include(':')) {
        return (!element.attributes || !element.attributes[name]) ? null :
         element.attributes[name].value;
      }
    }
    return element.getAttribute(name);
  },

  writeAttribute: function(element, name, value) {
    element = $(element);
    var attributes = { }, t = Element._attributeTranslations.write;

    if (typeof name == 'object') attributes = name;
    else attributes[name] = value === undefined ? true : value;

    for (var attr in attributes) {
      var name = t.names[attr] || attr, value = attributes[attr];
      if (t.values[attr]) name = t.values[attr](element, value);
      if (value === false || value === null)
        element.removeAttribute(name);
      else if (value === true)
        element.setAttribute(name, name);
      else element.setAttribute(name, value);
    }
    return element;
  },

  getHeight: function(element) {
    return $(element).getDimensions().height;
  },

  getWidth: function(element) {
    return $(element).getDimensions().width;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    var elementClassName = element.className;
    return (elementClassName.length > 0 && (elementClassName == className ||
      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    if (!element.hasClassName(className))
      element.className += (element.className ? ' ' : '') + className;
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    element.className = element.className.replace(
      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    return element[element.hasClassName(className) ?
      'removeClassName' : 'addClassName'](className);
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    var node = element.firstChild;
    while (node) {
      var nextNode = node.nextSibling;
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        element.removeChild(node);
      node = nextNode;
    }
    return element;
  },

  empty: function(element) {
    return $(element).innerHTML.blank();
  },

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);

    if (element.compareDocumentPosition)
      return (element.compareDocumentPosition(ancestor) & 8) === 8;

    if (element.sourceIndex && !Prototype.Browser.Opera) {
      var e = element.sourceIndex, a = ancestor.sourceIndex,
       nextAncestor = ancestor.nextSibling;
      if (!nextAncestor) {
        do { ancestor = ancestor.parentNode; }
        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
      }
      if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
    }

    while (element = element.parentNode)
      if (element == ancestor) return true;
    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = element.cumulativeOffset();
    window.scrollTo(pos[0], pos[1]);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    style = style == 'float' ? 'cssFloat' : style.camelize();
    var value = element.style[style];
    if (!value) {
      var css = document.defaultView.getComputedStyle(element, null);
      value = css ? css[style] : null;
    }
    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
    return value == 'auto' ? null : value;
  },

  getOpacity: function(element) {
    return $(element).getStyle('opacity');
  },

  setStyle: function(element, styles) {
    element = $(element);
    var elementStyle = element.style, match;
    if (Object.isString(styles)) {
      element.style.cssText += ';' + styles;
      return styles.include('opacity') ?
        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
    }
    for (var property in styles)
      if (property == 'opacity') element.setOpacity(styles[property]);
      else
        elementStyle[(property == 'float' || property == 'cssFloat') ?
          (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
            property] = styles[property];

    return element;
  },

  setOpacity: function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;
    return element;
  },

  getDimensions: function(element) {
    element = $(element);
    var display = $(element).getStyle('display');
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (window.opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
    return element;
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return element;
    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
    if (element._overflow !== 'hidden')
      element.style.overflow = 'hidden';
    return element;
  },

  undoClipping: function(element) {
    element = $(element);
    if (!element._overflow) return element;
    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
    element._overflow = null;
    return element;
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if (element.tagName == 'BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  absolutize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'absolute') return;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    var offsets = element.positionedOffset();
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.width  = width + 'px';
    element.style.height = height + 'px';
    return element;
  },

  relativize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'relative') return;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
    return element;
  },

  cumulativeScrollOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  getOffsetParent: function(element) {
    if (element.offsetParent) return $(element.offsetParent);
    if (element == document.body) return $(element);

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return $(element);

    return $(document.body);
  },

  viewportOffset: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent == document.body &&
        Element.getStyle(element, 'position') == 'absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return Element._returnOffset(valueL, valueT);
  },

  clonePosition: function(element, source) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || { });

    // find page position of source
    source = $(source);
    var p = source.viewportOffset();

    // find coordinate system to use
    element = $(element);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = element.getOffsetParent();
      delta = parent.viewportOffset();
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
    return element;
  }
};

Element.Methods.identify.counter = 1;

Object.extend(Element.Methods, {
  getElementsBySelector: Element.Methods.select,
  childElements: Element.Methods.immediateDescendants
});

Element._attributeTranslations = {
  write: {
    names: {
      className: 'class',
      htmlFor:   'for'
    },
    values: { }
  }
};


if (!document.createRange || Prototype.Browser.Opera) {
  Element.Methods.insert = function(element, insertions) {
    element = $(element);

    if (Object.isString(insertions) || Object.isNumber(insertions) ||
        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
          insertions = { bottom: insertions };

    var t = Element._insertionTranslations, content, position, pos, tagName;

    for (position in insertions) {
      content  = insertions[position];
      position = position.toLowerCase();
      pos      = t[position];

      if (content && content.toElement) content = content.toElement();
      if (Object.isElement(content)) {
        pos.insert(element, content);
        continue;
      }

      content = Object.toHTML(content);
      tagName = ((position == 'before' || position == 'after')
        ? element.parentNode : element).tagName.toUpperCase();

      if (t.tags[tagName]) {
        var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
        if (position == 'top' || position == 'after') fragments.reverse();
        fragments.each(pos.insert.curry(element));
      }
      else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());

      content.evalScripts.bind(content).defer();
    }

    return element;
  };
}

if (Prototype.Browser.Opera) {
  Element.Methods._getStyle = Element.Methods.getStyle;
  Element.Methods.getStyle = function(element, style) {
    switch(style) {
      case 'left':
      case 'top':
      case 'right':
      case 'bottom':
        if (Element._getStyle(element, 'position') == 'static') return null;
      default: return Element._getStyle(element, style);
    }
  };
  Element.Methods._readAttribute = Element.Methods.readAttribute;
  Element.Methods.readAttribute = function(element, attribute) {
    if (attribute == 'title') return element.title;
    return Element._readAttribute(element, attribute);
  };
}

else if (Prototype.Browser.IE) {
  $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
    Element.Methods[method] = Element.Methods[method].wrap(
      function(proceed, element) {
        element = $(element);
        var position = element.getStyle('position');
        if (position != 'static') return proceed(element);
        element.setStyle({ position: 'relative' });
        var value = proceed(element);
        element.setStyle({ position: position });
        return value;
      }
    );
  });

  Element.Methods.getStyle = function(element, style) {
    element = $(element);
    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
    var value = element.style[style];
    if (!value && element.currentStyle) value = element.currentStyle[style];

    if (style == 'opacity') {
      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if (value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }

    if (value == 'auto') {
      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
        return element['offset' + style.capitalize()] + 'px';
      return null;
    }
    return value;
  };

  Element.Methods.setOpacity = function(element, value) {
    function stripAlpha(filter){
      return filter.replace(/alpha\([^\)]*\)/gi,'');
    }
    element = $(element);
    var currentStyle = element.currentStyle;
    if ((currentStyle && !currentStyle.hasLayout) ||
      (!currentStyle && element.style.zoom == 'normal'))
        element.style.zoom = 1;

    var filter = element.getStyle('filter'), style = element.style;
    if (value == 1 || value === '') {
      (filter = stripAlpha(filter)) ?
        style.filter = filter : style.removeAttribute('filter');
      return element;
    } else if (value < 0.00001) value = 0;
    style.filter = stripAlpha(filter) +
      'alpha(opacity=' + (value * 100) + ')';
    return element;
  };

  Element._attributeTranslations = {
    read: {
      names: {
        'class': 'className',
        'for':   'htmlFor'
      },
      values: {
        _getAttr: function(element, attribute) {
          return element.getAttribute(attribute, 2);
        },
        _getAttrNode: function(element, attribute) {
          var node = element.getAttributeNode(attribute);
          return node ? node.value : "";
        },
        _getEv: function(element, attribute) {
          var attribute = element.getAttribute(attribute);
          return attribute ? attribute.toString().slice(23, -2) : null;
        },
        _flag: function(element, attribute) {
          return $(element).hasAttribute(attribute) ? attribute : null;
        },
        style: function(element) {
          return element.style.cssText.toLowerCase();
        },
        title: function(element) {
          return element.title;
        }
      }
    }
  };

  Element._attributeTranslations.write = {
    names: Object.clone(Element._attributeTranslations.read.names),
    values: {
      checked: function(element, value) {
        element.checked = !!value;
      },

      style: function(element, value) {
        element.style.cssText = value ? value : '';
      }
    }
  };

  Element._attributeTranslations.has = {};

  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
      'encType maxLength readOnly longDesc').each(function(attr) {
    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
  });

  (function(v) {
    Object.extend(v, {
      href:        v._getAttr,
      src:         v._getAttr,
      type:        v._getAttr,
      action:      v._getAttrNode,
      disabled:    v._flag,
      checked:     v._flag,
      readonly:    v._flag,
      multiple:    v._flag,
      onload:      v._getEv,
      onunload:    v._getEv,
      onclick:     v._getEv,
      ondblclick:  v._getEv,
      onmousedown: v._getEv,
      onmouseup:   v._getEv,
      onmouseover: v._getEv,
      onmousemove: v._getEv,
      onmouseout:  v._getEv,
      onfocus:     v._getEv,
      onblur:      v._getEv,
      onkeypress:  v._getEv,
      onkeydown:   v._getEv,
      onkeyup:     v._getEv,
      onsubmit:    v._getEv,
      onreset:     v._getEv,
      onselect:    v._getEv,
      onchange:    v._getEv
    });
  })(Element._attributeTranslations.read.values);
}

else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1) ? 0.999999 :
      (value === '') ? '' : (value < 0.00001) ? 0 : value;
    return element;
  };
}

else if (Prototype.Browser.WebKit) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;

    if (value == 1)
      if(element.tagName == 'IMG' && element.width) {
        element.width++; element.width--;
      } else try {
        var n = document.createTextNode(' ');
        element.appendChild(n);
        element.removeChild(n);
      } catch (e) { }

    return element;
  };

  // Safari returns margins on body which is incorrect if the child is absolutely
  // positioned.  For performance reasons, redefine Position.cumulativeOffset for
  // KHTML/WebKit only.
  Element.Methods.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return Element._returnOffset(valueL, valueT);
  };
}

if (Prototype.Browser.IE || Prototype.Browser.Opera) {
  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
  Element.Methods.update = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);

    content = Object.toHTML(content);
    var tagName = element.tagName.toUpperCase();

    if (tagName in Element._insertionTranslations.tags) {
      $A(element.childNodes).each(function(node) { element.removeChild(node) });
      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
        .each(function(node) { element.appendChild(node) });
    }
    else element.innerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

if (document.createElement('div').outerHTML) {
  Element.Methods.replace = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) {
      element.parentNode.replaceChild(content, element);
      return element;
    }

    content = Object.toHTML(content);
    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();

    if (Element._insertionTranslations.tags[tagName]) {
      var nextSibling = element.next();
      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
      parent.removeChild(element);
      if (nextSibling)
        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
      else
        fragments.each(function(node) { parent.appendChild(node) });
    }
    else element.outerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

Element._returnOffset = function(l, t) {
  var result = [l, t];
  result.left = l;
  result.top = t;
  return result;
};

Element._getContentFromAnonymousElement = function(tagName, html) {
  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
  div.innerHTML = t[0] + html + t[1];
  t[2].times(function() { div = div.firstChild });
  return $A(div.childNodes);
};

Element._insertionTranslations = {
  before: {
    adjacency: 'beforeBegin',
    insert: function(element, node) {
      element.parentNode.insertBefore(node, element);
    },
    initializeRange: function(element, range) {
      range.setStartBefore(element);
    }
  },
  top: {
    adjacency: 'afterBegin',
    insert: function(element, node) {
      element.insertBefore(node, element.firstChild);
    },
    initializeRange: function(element, range) {
      range.selectNodeContents(element);
      range.collapse(true);
    }
  },
  bottom: {
    adjacency: 'beforeEnd',
    insert: function(element, node) {
      element.appendChild(node);
    }
  },
  after: {
    adjacency: 'afterEnd',
    insert: function(element, node) {
      element.parentNode.insertBefore(node, element.nextSibling);
    },
    initializeRange: function(element, range) {
      range.setStartAfter(element);
    }
  },
  tags: {
    TABLE:  ['<table>',                '</table>',                   1],
    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
    SELECT: ['<select>',               '</select>',                  1]
  }
};

(function() {
  this.bottom.initializeRange = this.top.initializeRange;
  Object.extend(this.tags, {
    THEAD: this.tags.TBODY,
    TFOOT: this.tags.TBODY,
    TH:    this.tags.TD
  });
}).call(Element._insertionTranslations);

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    attribute = Element._attributeTranslations.has[attribute] || attribute;
    var node = $(element).getAttributeNode(attribute);
    return node && node.specified;
  }
};

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

if (!Prototype.BrowserFeatures.ElementExtensions &&
    document.createElement('div').__proto__) {
  window.HTMLElement = { };
  window.HTMLElement.prototype = document.createElement('div').__proto__;
  Prototype.BrowserFeatures.ElementExtensions = true;
}

Element.extend = (function() {
  if (Prototype.BrowserFeatures.SpecificElementExtensions)
    return Prototype.K;

  var Methods = { }, ByTag = Element.Methods.ByTag;

  var extend = Object.extend(function(element) {
    if (!element || element._extendedByPrototype ||
        element.nodeType != 1 || element == window) return element;

    var methods = Object.clone(Methods),
      tagName = element.tagName, property, value;

    // extend methods for specific tags
    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    for (property in methods) {
      value = methods[property];
      if (Object.isFunction(value) && !(property in element))
        element[property] = value.methodize();
    }

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

  }, {
    refresh: function() {
      // extend methods for all tags (Safari doesn't need this)
      if (!Prototype.BrowserFeatures.ElementExtensions) {
        Object.extend(Methods, Element.Methods);
        Object.extend(Methods, Element.Methods.Simulated);
      }
    }
  });

  extend.refresh();
  return extend;
})();

Element.hasAttribute = function(element, attribute) {
  if (element.hasAttribute) return element.hasAttribute(attribute);
  return Element.Methods.Simulated.hasAttribute(element, attribute);
};

Element.addMethods = function(methods) {
  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

  if (!methods) {
    Object.extend(Form, Form.Methods);
    Object.extend(Form.Element, Form.Element.Methods);
    Object.extend(Element.Methods.ByTag, {
      "FORM":     Object.clone(Form.Methods),
      "INPUT":    Object.clone(Form.Element.Methods),
      "SELECT":   Object.clone(Form.Element.Methods),
      "TEXTAREA": Object.clone(Form.Element.Methods)
    });
  }

  if (arguments.length == 2) {
    var tagName = methods;
    methods = arguments[1];
  }

  if (!tagName) Object.extend(Element.Methods, methods || { });
  else {
    if (Object.isArray(tagName)) tagName.each(extend);
    else extend(tagName);
  }

  function extend(tagName) {
    tagName = tagName.toUpperCase();
    if (!Element.Methods.ByTag[tagName])
      Element.Methods.ByTag[tagName] = { };
    Object.extend(Element.Methods.ByTag[tagName], methods);
  }

  function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    for (var property in methods) {
      var value = methods[property];
      if (!Object.isFunction(value)) continue;
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = value.methodize();
    }
  }

  function findDOMClass(tagName) {
    var klass;
    var trans = {
      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
      "FrameSet", "IFRAME": "IFrame"
    };
    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName.capitalize() + 'Element';
    if (window[klass]) return window[klass];

    window[klass] = { };
    window[klass].prototype = document.createElement(tagName).__proto__;
    return window[klass];
  }

  if (F.ElementExtensions) {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
  }

  if (F.SpecificElementExtensions) {
    for (var tag in Element.Methods.ByTag) {
      var klass = findDOMClass(tag);
      if (Object.isUndefined(klass)) continue;
      copy(T[tag], klass.prototype);
    }
  }

  Object.extend(Element, Element.Methods);
  delete Element.ByTag;

  if (Element.extend.refresh) Element.extend.refresh();
  Element.cache = { };
};

document.viewport = {
  getDimensions: function() {
    var dimensions = { };
    $w('width height').each(function(d) {
      var D = d.capitalize();
      dimensions[d] = self['inner' + D] ||
       (document.documentElement['client' + D] || document.body['client' + D]);
    });
    return dimensions;
  },

  getWidth: function() {
    return this.getDimensions().width;
  },

  getHeight: function() {
    return this.getDimensions().height;
  },

  getScrollOffsets: function() {
    return Element._returnOffset(
      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
  }
};
/* Portions of the Selector class are derived from Jack Slocumâ€™s DomQuery,
 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
 * license.  Please see http://www.yui-ext.com/ for more information. */

var Selector = Class.create({
  initialize: function(expression) {
    this.expression = expression.strip();
    this.compileMatcher();
  },

  compileMatcher: function() {
    // Selectors with namespaced attributes can't use the XPath version
    if (Prototype.BrowserFeatures.XPath && !(/(\[[\w-]*?:|:checked)/).test(this.expression))
      return this.compileXPathMatcher();

    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
        c = Selector.criteria, le, p, m;

    if (Selector._cache[e]) {
      this.matcher = Selector._cache[e];
      return;
    }

    this.matcher = ["this.matcher = function(root) {",
                    "var r = root, h = Selector.handlers, c = false, n;"];

    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
    	      new Template(c[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.matcher.push("return h.unique(n);\n}");
    eval(this.matcher.join('\n'));
    Selector._cache[this.expression] = this.matcher;
  },

  compileXPathMatcher: function() {
    var e = this.expression, ps = Selector.patterns,
        x = Selector.xpath, le, m;

    if (Selector._cache[e]) {
      this.xpath = Selector._cache[e]; return;
    }

    this.matcher = ['.//*'];
    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        if (m = e.match(ps[i])) {
          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
            new Template(x[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.xpath = this.matcher.join('');
    Selector._cache[this.expression] = this.xpath;
  },

  findElements: function(root) {
    root = root || document;
    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
    return this.matcher(root);
  },

  match: function(element) {
    this.tokens = [];

    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
    var le, p, m;

    while (e && le !== e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          // use the Selector.assertions methods unless the selector
          // is too complex.
          if (as[i]) {
            this.tokens.push([i, Object.clone(m)]);
            e = e.replace(m[0], '');
          } else {
            // reluctantly do a document-wide search
            // and look for a match in the array
            return this.findElements(document).include(element);
          }
        }
      }
    }

    var match = true, name, matches;
    for (var i = 0, token; token = this.tokens[i]; i++) {
      name = token[0], matches = token[1];
      if (!Selector.assertions[name](element, matches)) {
        match = false; break;
      }
    }

    return match;
  },

  toString: function() {
    return this.expression;
  },

  inspect: function() {
    return "#<Selector:" + this.expression.inspect() + ">";
  }
});

Object.extend(Selector, {
  _cache: { },

  xpath: {
    descendant:   "//*",
    child:        "/*",
    adjacent:     "/following-sibling::*[1]",
    laterSibling: '/following-sibling::*',
    tagName:      function(m) {
      if (m[1] == '*') return '';
      return "[local-name()='" + m[1].toLowerCase() +
             "' or local-name()='" + m[1].toUpperCase() + "']";
    },
    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
    id:           "[@id='#{1}']",
    attrPresence: "[@#{1}]",
    attr: function(m) {
      m[3] = m[5] || m[6];
      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
    },
    pseudo: function(m) {
      var h = Selector.xpath.pseudos[m[1]];
      if (!h) return '';
      if (Object.isFunction(h)) return h(m);
      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
    },
    operators: {
      '=':  "[@#{1}='#{3}']",
      '!=': "[@#{1}!='#{3}']",
      '^=': "[starts-with(@#{1}, '#{3}')]",
      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
      '*=': "[contains(@#{1}, '#{3}')]",
      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
    },
    pseudos: {
      'first-child': '[not(preceding-sibling::*)]',
      'last-child':  '[not(following-sibling::*)]',
      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
      'checked':     "[@checked]",
      'disabled':    "[@disabled]",
      'enabled':     "[not(@disabled)]",
      'not': function(m) {
        var e = m[6], p = Selector.patterns,
            x = Selector.xpath, le, m, v;

        var exclusion = [];
        while (e && le != e && (/\S/).test(e)) {
          le = e;
          for (var i in p) {
            if (m = e.match(p[i])) {
              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
              e = e.replace(m[0], '');
              break;
            }
          }
        }
        return "[not(" + exclusion.join(" and ") + ")]";
      },
      'nth-child':      function(m) {
        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
      },
      'nth-last-child': function(m) {
        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
      },
      'nth-of-type':    function(m) {
        return Selector.xpath.pseudos.nth("position() ", m);
      },
      'nth-last-of-type': function(m) {
        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
      },
      'first-of-type':  function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
      },
      'last-of-type':   function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
      },
      'only-of-type':   function(m) {
        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
      },
      nth: function(fragment, m) {
        var mm, formula = m[6], predicate;
        if (formula == 'even') formula = '2n+0';
        if (formula == 'odd')  formula = '2n+1';
        if (mm = formula.match(/^(\d+)$/)) // digit only
          return '[' + fragment + "= " + mm[1] + ']';
        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
          if (mm[1] == "-") mm[1] = -1;
          var a = mm[1] ? Number(mm[1]) : 1;
          var b = mm[2] ? Number(mm[2]) : 0;
          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
          "((#{fragment} - #{b}) div #{a} >= 0)]";
          return new Template(predicate).evaluate({
            fragment: fragment, a: a, b: b });
        }
      }
    }
  },

  criteria: {
    tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
    className:    'n = h.className(n, r, "#{1}", c); c = false;',
    id:           'n = h.id(n, r, "#{1}", c);        c = false;',
    attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
    attr: function(m) {
      m[3] = (m[5] || m[6]);
      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
    },
    pseudo: function(m) {
      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
    },
    descendant:   'c = "descendant";',
    child:        'c = "child";',
    adjacent:     'c = "adjacent";',
    laterSibling: 'c = "laterSibling";'
  },

  patterns: {
    // combinators must be listed first
    // (and descendant needs to be last combinator)
    laterSibling: /^\s*~\s*/,
    child:        /^\s*>\s*/,
    adjacent:     /^\s*\+\s*/,
    descendant:   /^\s/,

    // selectors follow
    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
    id:           /^#([\w\-\*]+)(\b|$)/,
    className:    /^\.([\w\-\*]+)(\b|$)/,
    pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
    attrPresence: /^\[([\w]+)\]/,
    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
  },

  // for Selector.match and Element#match
  assertions: {
    tagName: function(element, matches) {
      return matches[1].toUpperCase() == element.tagName.toUpperCase();
    },

    className: function(element, matches) {
      return Element.hasClassName(element, matches[1]);
    },

    id: function(element, matches) {
      return element.id === matches[1];
    },

    attrPresence: function(element, matches) {
      return Element.hasAttribute(element, matches[1]);
    },

    attr: function(element, matches) {
      var nodeValue = Element.readAttribute(element, matches[1]);
      return Selector.operators[matches[2]](nodeValue, matches[3]);
    }
  },

  handlers: {
    // UTILITY FUNCTIONS
    // joins two collections
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        a.push(node);
      return a;
    },

    // marks an array of nodes for counting
    mark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._counted = true;
      return nodes;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._counted = undefined;
      return nodes;
    },

    // mark each child node with its position (for nth calls)
    // "ofType" flag indicates whether we're indexing for nth-of-type
    // rather than nth-child
    index: function(parentNode, reverse, ofType) {
      parentNode._counted = true;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          var node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
      }
    },

    // filters out duplicates and extends all nodes
    unique: function(nodes) {
      if (nodes.length == 0) return nodes;
      var results = [], n;
      for (var i = 0, l = nodes.length; i < l; i++)
        if (!(n = nodes[i])._counted) {
          n._counted = true;
          results.push(Element.extend(n));
        }
      return Selector.handlers.unmark(results);
    },

    // COMBINATOR FUNCTIONS
    descendant: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, node.getElementsByTagName('*'));
      return results;
    },

    child: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
      }
      return results;
    },

    adjacent: function(nodes) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        var next = this.nextElementSibling(node);
        if (next) results.push(next);
      }
      return results;
    },

    laterSibling: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, Element.nextSiblings(node));
      return results;
    },

    nextElementSibling: function(node) {
      while (node = node.nextSibling)
	      if (node.nodeType == 1) return node;
      return null;
    },

    previousElementSibling: function(node) {
      while (node = node.previousSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    // TOKEN FUNCTIONS
    tagName: function(nodes, root, tagName, combinator) {
      tagName = tagName.toUpperCase();
      var results = [], h = Selector.handlers;
      if (nodes) {
        if (combinator) {
          // fastlane for ordinary descendant combinators
          if (combinator == "descendant") {
            for (var i = 0, node; node = nodes[i]; i++)
              h.concat(results, node.getElementsByTagName(tagName));
            return results;
          } else nodes = this[combinator](nodes);
          if (tagName == "*") return nodes;
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.tagName.toUpperCase() == tagName) results.push(node);
        return results;
      } else return root.getElementsByTagName(tagName);
    },

    id: function(nodes, root, id, combinator) {
      var targetNode = $(id), h = Selector.handlers;
      if (!targetNode) return [];
      if (!nodes && root == document) return [targetNode];
      if (nodes) {
        if (combinator) {
          if (combinator == 'child') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (targetNode.parentNode == node) return [targetNode];
          } else if (combinator == 'descendant') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Element.descendantOf(targetNode, node)) return [targetNode];
          } else if (combinator == 'adjacent') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Selector.handlers.previousElementSibling(targetNode) == node)
                return [targetNode];
          } else nodes = h[combinator](nodes);
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node == targetNode) return [targetNode];
        return [];
      }
      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
    },

    className: function(nodes, root, className, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      return Selector.handlers.byClassName(nodes, root, className);
    },

    byClassName: function(nodes, root, className) {
      if (!nodes) nodes = Selector.handlers.descendant([root]);
      var needle = ' ' + className + ' ';
      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
        nodeClassName = node.className;
        if (nodeClassName.length == 0) continue;
        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
          results.push(node);
      }
      return results;
    },

    attrPresence: function(nodes, root, attr) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      var results = [];
      for (var i = 0, node; node = nodes[i]; i++)
        if (Element.hasAttribute(node, attr)) results.push(node);
      return results;
    },

    attr: function(nodes, root, attr, value, operator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      var handler = Selector.operators[operator], results = [];
      for (var i = 0, node; node = nodes[i]; i++) {
        var nodeValue = Element.readAttribute(node, attr);
        if (nodeValue === null) continue;
        if (handler(nodeValue, value)) results.push(node);
      }
      return results;
    },

    pseudo: function(nodes, name, value, root, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      if (!nodes) nodes = root.getElementsByTagName("*");
      return Selector.pseudos[name](nodes, value, root);
    }
  },

  pseudos: {
    'first-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.previousElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'last-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.nextElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'only-child': function(nodes, value, root) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
          results.push(node);
      return results;
    },
    'nth-child':        function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root);
    },
    'nth-last-child':   function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true);
    },
    'nth-of-type':      function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, false, true);
    },
    'nth-last-of-type': function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true, true);
    },
    'first-of-type':    function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, false, true);
    },
    'last-of-type':     function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, true, true);
    },
    'only-of-type':     function(nodes, formula, root) {
      var p = Selector.pseudos;
      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
    },

    // handles the an+b logic
    getIndices: function(a, b, total) {
      if (a == 0) return b > 0 ? [b] : [];
      return $R(1, total).inject([], function(memo, i) {
        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
        return memo;
      });
    },

    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
    nth: function(nodes, formula, root, reverse, ofType) {
      if (nodes.length == 0) return [];
      if (formula == 'even') formula = '2n+0';
      if (formula == 'odd')  formula = '2n+1';
      var h = Selector.handlers, results = [], indexed = [], m;
      h.mark(nodes);
      for (var i = 0, node; node = nodes[i]; i++) {
        if (!node.parentNode._counted) {
          h.index(node.parentNode, reverse, ofType);
          indexed.push(node.parentNode);
        }
      }
      if (formula.match(/^\d+$/)) { // just a number
        formula = Number(formula);
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.nodeIndex == formula) results.push(node);
      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
        if (m[1] == "-") m[1] = -1;
        var a = m[1] ? Number(m[1]) : 1;
        var b = m[2] ? Number(m[2]) : 0;
        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
          for (var j = 0; j < l; j++)
            if (node.nodeIndex == indices[j]) results.push(node);
        }
      }
      h.unmark(nodes);
      h.unmark(indexed);
      return results;
    },

    'empty': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        // IE treats comments as element nodes
        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
        results.push(node);
      }
      return results;
    },

    'not': function(nodes, selector, root) {
      var h = Selector.handlers, selectorType, m;
      var exclusions = new Selector(selector).findElements(root);
      h.mark(exclusions);
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node._counted) results.push(node);
      h.unmark(exclusions);
      return results;
    },

    'enabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node.disabled) results.push(node);
      return results;
    },

    'disabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.disabled) results.push(node);
      return results;
    },

    'checked': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.checked) results.push(node);
      return results;
    }
  },

  operators: {
    '=':  function(nv, v) { return nv == v; },
    '!=': function(nv, v) { return nv != v; },
    '^=': function(nv, v) { return nv.startsWith(v); },
    '$=': function(nv, v) { return nv.endsWith(v); },
    '*=': function(nv, v) { return nv.include(v); },
    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
  },

  matchElements: function(elements, expression) {
    var matches = new Selector(expression).findElements(), h = Selector.handlers;
    h.mark(matches);
    for (var i = 0, results = [], element; element = elements[i]; i++)
      if (element._counted) results.push(element);
    h.unmark(matches);
    return results;
  },

  findElement: function(elements, expression, index) {
    if (Object.isNumber(expression)) {
      index = expression; expression = false;
    }
    return Selector.matchElements(elements, expression || '*')[index || 0];
  },

  findChildElements: function(element, expressions) {
    var exprs = expressions.join(','), expressions = [];
    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    var results = [], h = Selector.handlers;
    for (var i = 0, l = expressions.length, selector; i < l; i++) {
      selector = new Selector(expressions[i].strip());
      h.concat(results, selector.findElements(element));
    }
    return (l > 1) ? h.unique(results) : results;
  }
});

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}
var Form = {
  reset: function(form) {
    $(form).reset();
    return form;
  },

  serializeElements: function(elements, options) {
    if (typeof options != 'object') options = { hash: !!options };
    else if (options.hash === undefined) options.hash = true;
    var key, value, submitted = false, submit = options.submit;

    var data = elements.inject({ }, function(result, element) {
      if (!element.disabled && element.name) {
        key = element.name; value = $(element).getValue();
        if (value != null && (element.type != 'submit' || (!submitted &&
            submit !== false && (!submit || key == submit) && (submitted = true)))) {
          if (key in result) {
            // a key is already present; construct an array of values
            if (!Object.isArray(result[key])) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return options.hash ? data : Object.toQueryString(data);
  }
};

Form.Methods = {
  serialize: function(form, options) {
    return Form.serializeElements(Form.getElements(form), options);
  },

  getElements: function(form) {
    return $A($(form).getElementsByTagName('*')).inject([],
      function(elements, child) {
        if (Form.Element.Serializers[child.tagName.toLowerCase()])
          elements.push(Element.extend(child));
        return elements;
      }
    );
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name) return $A(inputs).map(Element.extend);

    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) || (name && input.name != name))
        continue;
      matchingInputs.push(Element.extend(input));
    }

    return matchingInputs;
  },

  disable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('disable');
    return form;
  },

  enable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('enable');
    return form;
  },

  findFirstElement: function(form) {
    var elements = $(form).getElements().findAll(function(element) {
      return 'hidden' != element.type && !element.disabled;
    });
    var firstByIndex = elements.findAll(function(element) {
      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
    }).sortBy(function(element) { return element.tabIndex }).first();

    return firstByIndex ? firstByIndex : elements.find(function(element) {
      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    form.findFirstElement().activate();
    return form;
  },

  request: function(form, options) {
    form = $(form), options = Object.clone(options || { });

    var params = options.parameters, action = form.readAttribute('action') || '';
    if (action.blank()) action = window.location.href;
    options.parameters = form.serialize(true);

    if (params) {
      if (Object.isString(params)) params = params.toQueryParams();
      Object.extend(options.parameters, params);
    }

    if (form.hasAttribute('method') && !options.method)
      options.method = form.method;

    return new Ajax.Request(action, options);
  }
};

/*--------------------------------------------------------------------------*/

Form.Element = {
  focus: function(element) {
    $(element).focus();
    return element;
  },

  select: function(element) {
    $(element).select();
    return element;
  }
};

Form.Element.Methods = {
  serialize: function(element) {
    element = $(element);
    if (!element.disabled && element.name) {
      var value = element.getValue();
      if (value != undefined) {
        var pair = { };
        pair[element.name] = value;
        return Object.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](element);
  },

  setValue: function(element, value) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    Form.Element.Serializers[method](element, value);
    return element;
  },

  clear: function(element) {
    $(element).value = '';
    return element;
  },

  present: function(element) {
    return $(element).value != '';
  },

  activate: function(element) {
    element = $(element);
    try {
      element.focus();
      if (element.select && (element.tagName.toLowerCase() != 'input' ||
          !['button', 'reset', 'submit'].include(element.type)))
        element.select();
    } catch (e) { }
    return element;
  },

  disable: function(element) {
    element = $(element);
    element.blur();
    element.disabled = true;
    return element;
  },

  enable: function(element) {
    element = $(element);
    element.disabled = false;
    return element;
  }
};

/*--------------------------------------------------------------------------*/

var Field = Form.Element;
var $F = Form.Element.Methods.getValue;

/*--------------------------------------------------------------------------*/

Form.Element.Serializers = {
  input: function(element, value) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element, value);
      default:
        return Form.Element.Serializers.textarea(element, value);
    }
  },

  inputSelector: function(element, value) {
    if (value === undefined) return element.checked ? element.value : null;
    else element.checked = !!value;
  },

  textarea: function(element, value) {
    if (value === undefined) return element.value;
    else element.value = value;
  },

  select: function(element, index) {
    if (index === undefined)
      return this[element.type == 'select-one' ?
        'selectOne' : 'selectMany'](element);
    else {
      var opt, value, single = !Object.isArray(index);
      for (var i = 0, length = element.length; i < length; i++) {
        opt = element.options[i];
        value = this.optionValue(opt);
        if (single) {
          if (value == index) {
            opt.selected = true;
            return;
          }
        }
        else opt.selected = index.include(value);
      }
    }
  },

  selectOne: function(element) {
    var index = element.selectedIndex;
    return index >= 0 ? this.optionValue(element.options[index]) : null;
  },

  selectMany: function(element) {
    var values, length = element.length;
    if (!length) return null;

    for (var i = 0, values = []; i < length; i++) {
      var opt = element.options[i];
      if (opt.selected) values.push(this.optionValue(opt));
    }
    return values;
  },

  optionValue: function(opt) {
    // extend element because hasAttribute may not be native
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
};

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
  initialize: function($super, element, frequency, callback) {
    $super(callback, frequency);
    this.element   = $(element);
    this.lastValue = this.getValue();
  },

  execute: function() {
    var value = this.getValue();
    if (Object.isString(this.lastValue) && Object.isString(value) ?
        this.lastValue != value : String(this.lastValue) != String(value)) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
});

Form.Element.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = Class.create({
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback, this);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        default:
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
});

Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) var Event = { };

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,
  KEY_INSERT:   45,

  cache: { },

  relatedTarget: function(event) {
    var element;
    switch(event.type) {
      case 'mouseover': element = event.fromElement; break;
      case 'mouseout':  element = event.toElement;   break;
      default: return null;
    }
    return Element.extend(element);
  }
});

Event.Methods = (function() {
  var isButton;

  if (Prototype.Browser.IE) {
    var buttonMap = { 0: 1, 1: 4, 2: 2 };
    isButton = function(event, code) {
      return event.button == buttonMap[code];
    };

  } else if (Prototype.Browser.WebKit) {
    isButton = function(event, code) {
      switch (code) {
        case 0: return event.which == 1 && !event.metaKey;
        case 1: return event.which == 1 && event.metaKey;
        default: return false;
      }
    };

  } else {
    isButton = function(event, code) {
      return event.which ? (event.which === code + 1) : (event.button === code);
    };
  }

  return {
    isLeftClick:   function(event) { return isButton(event, 0) },
    isMiddleClick: function(event) { return isButton(event, 1) },
    isRightClick:  function(event) { return isButton(event, 2) },

    element: function(event) {
      var node = Event.extend(event).target;
      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
    },

    findElement: function(event, expression) {
      var element = Event.element(event);
      return element.match(expression) ? element : element.up(expression);
    },

    pointer: function(event) {
      return {
        x: event.pageX || (event.clientX +
          (document.documentElement.scrollLeft || document.body.scrollLeft)),
        y: event.pageY || (event.clientY +
          (document.documentElement.scrollTop || document.body.scrollTop))
      };
    },

    pointerX: function(event) { return Event.pointer(event).x },
    pointerY: function(event) { return Event.pointer(event).y },

    stop: function(event) {
      Event.extend(event);
      event.preventDefault();
      event.stopPropagation();
      event.stopped = true;
    }
  };
})();

Event.extend = (function() {
  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
    m[name] = Event.Methods[name].methodize();
    return m;
  });

  if (Prototype.Browser.IE) {
    Object.extend(methods, {
      stopPropagation: function() { this.cancelBubble = true },
      preventDefault:  function() { this.returnValue = false },
      inspect: function() { return "[object Event]" }
    });

    return function(event) {
      if (!event) return false;
      if (event._extendedByPrototype) return event;

      event._extendedByPrototype = Prototype.emptyFunction;
      var pointer = Event.pointer(event);
      Object.extend(event, {
        target: event.srcElement,
        relatedTarget: Event.relatedTarget(event),
        pageX:  pointer.x,
        pageY:  pointer.y
      });
      return Object.extend(event, methods);
    };

  } else {
    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
    Object.extend(Event.prototype, methods);
    return Prototype.K;
  }
})();

Object.extend(Event, (function() {
  var cache = Event.cache;

  function getEventID(element) {
    if (element._eventID) return element._eventID;
    arguments.callee.id = arguments.callee.id || 1;
    return element._eventID = ++arguments.callee.id;
  }

  function getDOMEventName(eventName) {
    if (eventName && eventName.include(':')) return "dataavailable";
    return eventName;
  }

  function getCacheForID(id) {
    return cache[id] = cache[id] || { };
  }

  function getWrappersForEventName(id, eventName) {
    var c = getCacheForID(id);
    return c[eventName] = c[eventName] || [];
  }

  function createWrapper(element, eventName, handler) {
    var id = getEventID(element);
    var c = getWrappersForEventName(id, eventName);
    if (c.pluck("handler").include(handler)) return false;

    var wrapper = function(event) {
      if (!Event || !Event.extend ||
        (event.eventName && event.eventName != eventName))
          return false;

      Event.extend(event);
      handler.call(element, event)
    };

    wrapper.handler = handler;
    c.push(wrapper);
    return wrapper;
  }

  function findWrapper(id, eventName, handler) {
    var c = getWrappersForEventName(id, eventName);
    return c.find(function(wrapper) { return wrapper.handler == handler });
  }

  function destroyWrapper(id, eventName, handler) {
    var c = getCacheForID(id);
    if (!c[eventName]) return false;
    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
  }

  function destroyCache() {
    for (var id in cache)
      for (var eventName in cache[id])
        cache[id][eventName] = null;
  }

  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  return {
    observe: function(element, eventName, handler) {
      element = $(element);
      var name = getDOMEventName(eventName);

      var wrapper = createWrapper(element, eventName, handler);
      if (!wrapper) return element;

      if (element.addEventListener) {
        element.addEventListener(name, wrapper, false);
      } else {
        element.attachEvent("on" + name, wrapper);
      }

      return element;
    },

    stopObserving: function(element, eventName, handler) {
      element = $(element);
      var id = getEventID(element), name = getDOMEventName(eventName);

      if (!handler && eventName) {
        getWrappersForEventName(id, eventName).each(function(wrapper) {
          element.stopObserving(eventName, wrapper.handler);
        });
        return element;

      } else if (!eventName) {
        Object.keys(getCacheForID(id)).each(function(eventName) {
          element.stopObserving(eventName);
        });
        return element;
      }

      var wrapper = findWrapper(id, eventName, handler);
      if (!wrapper) return element;

      if (element.removeEventListener) {
        element.removeEventListener(name, wrapper, false);
      } else {
        element.detachEvent("on" + name, wrapper);
      }

      destroyWrapper(id, eventName, handler);

      return element;
    },

    fire: function(element, eventName, memo) {
      element = $(element);
      if (element == document && document.createEvent && !element.dispatchEvent)
        element = document.documentElement;

      if (document.createEvent) {
        var event = document.createEvent("HTMLEvents");
        event.initEvent("dataavailable", true, true);
      } else {
        var event = document.createEventObject();
        event.eventType = "ondataavailable";
      }

      event.eventName = eventName;
      event.memo = memo || { };

      if (document.createEvent) {
        element.dispatchEvent(event);
      } else {
        element.fireEvent(event.eventType, event);
      }

      return event;
    }
  };
})());

Object.extend(Event, Event.Methods);

Element.addMethods({
  fire:          Event.fire,
  observe:       Event.observe,
  stopObserving: Event.stopObserving
});

Object.extend(document, {
  fire:          Element.Methods.fire.methodize(),
  observe:       Element.Methods.observe.methodize(),
  stopObserving: Element.Methods.stopObserving.methodize()
});

(function() {
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards and John Resig. */

  var timer, fired = false;

  function fireContentLoadedEvent() {
    if (fired) return;
    if (timer) window.clearInterval(timer);
    document.fire("dom:loaded");
    fired = true;
  }

  if (document.addEventListener) {
    if (Prototype.Browser.WebKit) {
      timer = window.setInterval(function() {
        if (/loaded|complete/.test(document.readyState))
          fireContentLoadedEvent();
      }, 0);

      Event.observe(window, "load", fireContentLoadedEvent);

    } else {
      document.addEventListener("DOMContentLoaded",
        fireContentLoadedEvent, false);
    }

  } else {
    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
    $("__onDOMContentLoaded").onreadystatechange = function() {
      if (this.readyState == "complete") {
        this.onreadystatechange = null;
        fireContentLoadedEvent();
      }
    };
  }
})();
/*------------------------------- DEPRECATED -------------------------------*/

Hash.toQueryString = Object.toQueryString;

var Toggle = { display: Element.toggle };

Element.Methods.childOf = Element.Methods.descendantOf;

var Insertion = {
  Before: function(element, content) {
    return Element.insert(element, {before:content});
  },

  Top: function(element, content) {
    return Element.insert(element, {top:content});
  },

  Bottom: function(element, content) {
    return Element.insert(element, {bottom:content});
  },

  After: function(element, content) {
    return Element.insert(element, {after:content});
  }
};

var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

// This should be moved to script.aculo.us; notice the deprecated methods
// further below, that map to the newer Element methods.
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = Element.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = Element.cumulativeScrollOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = Element.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  // Deprecation layer -- use newer Element methods now (1.5.2).

  cumulativeOffset: Element.Methods.cumulativeOffset,

  positionedOffset: Element.Methods.positionedOffset,

  absolutize: function(element) {
    Position.prepare();
    return Element.absolutize(element);
  },

  relativize: function(element) {
    Position.prepare();
    return Element.relativize(element);
  },

  realOffset: Element.Methods.cumulativeScrollOffset,

  offsetParent: Element.Methods.getOffsetParent,

  page: Element.Methods.viewportOffset,

  clone: function(source, target, options) {
    options = options || { };
    return Element.clonePosition(target, source, options);
  }
};

/*--------------------------------------------------------------------------*/

if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
  function iter(name) {
    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
  }

  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
  function(element, className) {
    className = className.toString().strip();
    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
  } : function(element, className) {
    className = className.toString().strip();
    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
    if (!classNames && !className) return elements;

    var nodes = $(element).getElementsByTagName('*');
    className = ' ' + className + ' ';

    for (var i = 0, child, cn; child = nodes[i]; i++) {
      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
          (classNames && classNames.all(function(name) {
            return !name.toString().blank() && cn.include(' ' + name + ' ');
          }))))
        elements.push(Element.extend(child));
    }
    return elements;
  };

  return function(className, parentElement) {
    return $(parentElement || document.body).getElementsByClassName(className);
  };
}(Element.Methods);

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set($A(this).concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set($A(this).without(classNameToRemove).join(' '));
  },

  toString: function() {
    return $A(this).join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);

/*--------------------------------------------------------------------------*/

Element.addMethods();
// script.aculo.us scriptaculous.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Scriptaculous = {
  Version: '1.8.0',
  require: function(libraryName) {
    // inserting via DOM fails in Safari 2.0, so brute force approach
    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
  },
  REQUIRED_PROTOTYPE: '1.6.0',
  load: function() {
    function convertVersionString(versionString){
      var r = versionString.split('.');
      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
    }

    if((typeof Prototype=='undefined') ||
       (typeof Element == 'undefined') ||
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) <
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);

    $A(document.getElementsByTagName("script")).findAll( function(s) {
      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
    }).each( function(s) {
      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
      var includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
}

Scriptaculous.load();
// script.aculo.us slider.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007

// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

if (!Control) var Control = { };

// options:
//  axis: 'vertical', or 'horizontal' (default)
//
// callbacks:
//  onChange(value)
//  onSlide(value)
Control.Slider = Class.create({
  initialize: function(handle, track, options) {
    var slider = this;

    if (Object.isArray(handle)) {
      this.handles = handle.collect( function(e) { return $(e) });
    } else {
      this.handles = [$(handle)];
    }

    this.track   = $(track);
    this.options = options || { };

    this.axis      = this.options.axis || 'horizontal';
    this.increment = this.options.increment || 1;
    this.step      = parseInt(this.options.step || '1');
    this.range     = this.options.range || $R(0,1);

    this.value     = 0; // assure backwards compat
    this.values    = this.handles.map( function() { return 0 });
    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
    this.options.startSpan = $(this.options.startSpan || null);
    this.options.endSpan   = $(this.options.endSpan || null);

    this.restricted = this.options.restricted || false;

    this.maximum   = this.options.maximum || this.range.end;
    this.minimum   = this.options.minimum || this.range.start;

    // Will be used to align the handle onto the track, if necessary
    this.alignX = parseInt(this.options.alignX || '0');
    this.alignY = parseInt(this.options.alignY || '0');

    this.trackLength = this.maximumOffset() - this.minimumOffset();

    this.handleLength = this.isVertical() ?
      (this.handles[0].offsetHeight != 0 ?
        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
        this.handles[0].style.width.replace(/px$/,""));

    this.active   = false;
    this.dragging = false;
    this.disabled = false;

    if (this.options.disabled) this.setDisabled();

    // Allowed values array
    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
    if (this.allowedValues) {
      this.minimum = this.allowedValues.min();
      this.maximum = this.allowedValues.max();
    }

    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
    this.eventMouseMove = this.update.bindAsEventListener(this);

    // Initialize handles in reverse (make sure first handle is active)
    this.handles.each( function(h,i) {
      i = slider.handles.length-1-i;
      slider.setValue(parseFloat(
        (Object.isArray(slider.options.sliderValue) ?
          slider.options.sliderValue[i] : slider.options.sliderValue) ||
          slider.range.start), i);
      h.makePositioned().observe("mousedown", slider.eventMouseDown);
    });

    this.track.observe("mousedown", this.eventMouseDown);
    document.observe("mouseup", this.eventMouseUp);
    document.observe("mousemove", this.eventMouseMove);

    this.initialized = true;
  },
  dispose: function() {
    var slider = this;
    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
    Event.stopObserving(document, "mouseup", this.eventMouseUp);
    Event.stopObserving(document, "mousemove", this.eventMouseMove);
    this.handles.each( function(h) {
      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
    });
  },
  setDisabled: function(){
    this.disabled = true;
  },
  setEnabled: function(){
    this.disabled = false;
  },
  getNearestValue: function(value){
    if (this.allowedValues){
      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
      if (value <= this.allowedValues.min()) return(this.allowedValues.min());

      var offset = Math.abs(this.allowedValues[0] - value);
      var newValue = this.allowedValues[0];
      this.allowedValues.each( function(v) {
        var currentOffset = Math.abs(v - value);
        if (currentOffset <= offset){
          newValue = v;
          offset = currentOffset;
        }
      });
      return newValue;
    }
    if (value > this.range.end) return this.range.end;
    if (value < this.range.start) return this.range.start;
    return value;
  },
  setValue: function(sliderValue, handleIdx){
    if (!this.active) {
      this.activeHandleIdx = handleIdx || 0;
      this.activeHandle    = this.handles[this.activeHandleIdx];
      this.updateStyles();
    }
    handleIdx = handleIdx || this.activeHandleIdx || 0;
    if (this.initialized && this.restricted) {
      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
        sliderValue = this.values[handleIdx-1];
      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
        sliderValue = this.values[handleIdx+1];
    }
    sliderValue = this.getNearestValue(sliderValue);
    this.values[handleIdx] = sliderValue;
    this.value = this.values[0]; // assure backwards compat

    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
      this.translateToPx(sliderValue);

    this.drawSpans();
    if (!this.dragging || !this.event) this.updateFinished();
  },

  setValueBy: function(delta, handleIdx) {
    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
      handleIdx || this.activeHandleIdx || 0);
  },

  translateToPx: function(value) {
    return Math.round(
      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
      (value - this.range.start)) + "px";
  },

  translateToValue: function(offset) {
    return ((offset/(this.trackLength-this.handleLength) *
      (this.range.end-this.range.start)) + this.range.start);
  },

  getRange: function(range) {
    var v = this.values.sortBy(Prototype.K);
    range = range || 0;
    return $R(v[range],v[range+1]);
  },

  minimumOffset: function(){
    return(this.isVertical() ? this.alignY : this.alignX);
  },

  maximumOffset: function(){
    return(this.isVertical() ?
      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
        this.track.style.height.replace(/px$/,"")) - this.alignY :
      (this.track.offsetWidth != 0 ? this.track.offsetWidth :
        this.track.style.width.replace(/px$/,"")) - this.alignX);
  },

  isVertical:  function(){
    return (this.axis == 'vertical');
  },
  drawSpans: function() {
    var slider = this;
    if (this.spans)
      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
    if (this.options.startSpan)
      this.setSpan(this.options.startSpan,
        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
    if (this.options.endSpan)
      this.setSpan(this.options.endSpan,
        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
  },
  setSpan: function(span, range) {
    if (this.isVertical()) {
      span.style.top = this.translateToPx(range.start);
      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
    } else {
      span.style.left = this.translateToPx(range.start);
      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
    }
  },
  updateStyles: function() {
    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
    Element.addClassName(this.activeHandle, 'selected');
  },
  startDrag: function(event) {
    if (Event.isLeftClick(event)) {
      if (!this.disabled){
        this.active = true;

        var handle = Event.element(event);
        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
        var track = handle;
        if (track==this.track) {
          var offsets  = Position.cumulativeOffset(this.track);
          this.event = event;
          this.setValue(this.translateToValue(
           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
          ));
          var offsets  = Position.cumulativeOffset(this.activeHandle);
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        } else {
          // find the handle (prevents issues with Safari)
          while((this.handles.indexOf(handle) == -1) && handle.parentNode)
            handle = handle.parentNode;

          if (this.handles.indexOf(handle)!=-1) {
            this.activeHandle    = handle;
            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
            this.updateStyles();

            var offsets  = Position.cumulativeOffset(this.activeHandle);
            this.offsetX = (pointer[0] - offsets[0]);
            this.offsetY = (pointer[1] - offsets[1]);
          }
        }
      }
      Event.stop(event);
    }
  },
  update: function(event) {
   if (this.active) {
      if (!this.dragging) this.dragging = true;
      this.draw(event);
      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
      Event.stop(event);
   }
  },
  draw: function(event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = Position.cumulativeOffset(this.track);
    pointer[0] -= this.offsetX + offsets[0];
    pointer[1] -= this.offsetY + offsets[1];
    this.event = event;
    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
    if (this.initialized && this.options.onSlide)
      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
  },
  endDrag: function(event) {
    if (this.active && this.dragging) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },
  finishDrag: function(event, success) {
    this.active = false;
    this.dragging = false;
    this.updateFinished();
  },
  updateFinished: function() {
    if (this.initialized && this.options.onChange)
      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
    this.event = null;
  }
});

Element.isParent = function(child, element) {
  if (!child.parentNode || child == element) return false;
  if (child.parentNode == element) return true;
  return Element.isParent(child.parentNode, element);
}

Element.findChildren = function(element, only, recursive, tagName) {
  if(!element.hasChildNodes()) return null;
  tagName = tagName.toUpperCase();
  if(only) only = [only].flatten();
  var elements = [];
  $A(element.childNodes).each( function(e) {
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
        elements.push(e);
    if(recursive) {
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
      if(grandchildren) elements.push(grandchildren);
    }
  });

  return (elements.length>0 ? elements.flatten() : []);
}

Element.offsetSize = function (element, type) {
  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
}

DragScrollableAbstract = Class.create({
	defaultCursor: "default",

	initialize: function(element, context) {
		this.element=$(element);

		if(context==null)
			this.context=this;
		else
			this.context=context;
	},

	rebuild: function() {
		// sprÃ³buj uaktualniÄ‡ kursory
		try {
			if(this.element==document.body)
				this.context.element.style.cursor=this.defaultCursor;
			else
				this.element.style.cursor=this.defaultCursor;
		} catch(e) { }

		this.context.dshToolBox=new Object();

		// utwÃ³rz referencje do statycznie definiowanych funkcji i podepnij do obsÅ‚ugi zdarzeÅ„.
		if(this.funcMouseDown) {
			this.context.dshToolBox.eventMouseDown=this.funcMouseDown.bindAsEventListener(	this.context,
																					this.transpMouseData.bind(this.map),
																					this.triggerArtEvent.bind(this.map));
			Event.observe(this.element, 'mousedown', this.context.dshToolBox.eventMouseDown);
		}
		if(this.funcMouseMove) {
			this.context.dshToolBox.eventMouseMove=this.funcMouseMove.bindAsEventListener(	this.context,
																					this.transpMouseData.bind(this.map),
																					this.triggerArtEvent.bind(this.map));
			Event.observe(this.element, 'mousemove', this.context.dshToolBox.eventMouseMove);
		}
		if(this.funcMouseUp) {
			this.context.dshToolBox.eventMouseUp=this.funcMouseUp.bindAsEventListener(	this.context,
																				this.transpMouseData.bind(this.map),
																				this.triggerArtEvent.bind(this.map));
			Event.observe(this.element, 'mouseup', this.context.dshToolBox.eventMouseUp);
		}
		if(this.funcMouseOver) {
			this.context.dshToolBox.eventMouseOver=this.funcMouseOver.bindAsEventListener(	this.context,
																					this.transpMouseData.bind(this.map),
																					this.triggerArtEvent.bind(this.map));
			Event.observe(this.element, 'mouseover', this.context.dshToolBox.eventMouseOver);
		}
		if(this.funcMouseOut) {
			this.context.dshToolBox.eventMouseOut=this.funcMouseOut.bindAsEventListener(	this.context,
																					this.transpMouseData.bind(this.map),
																					this.triggerArtEvent.bind(this.map));
			Event.observe(this.element, 'mouseout', this.context.dshToolBox.eventMouseOut);
		}
		if(this.funcClick) {
			this.context.dshToolBox.eventClick=this.funcClick.bindAsEventListener(	this.context,
																			this.transpMouseData.bind(this.map),
																			this.triggerArtEvent.bind(this.map));
			Event.observe(this.element, 'click', this.context.dshToolBox.eventClick);
		}
		if(this.funcDblClick) {
			this.context.dshToolBox.eventDblClick=this.funcDblClick.bindAsEventListener(	this.context,
																					this.transpMouseData.bind(this.map),
																					this.triggerArtEvent.bind(this.map));
			Event.observe(this.element, 'dblclick', this.context.dshToolBox.eventDblClick);
		}
		if(this.funcWheel) {
			this.context.dshToolBox.eventWheel=this.funcWheel.bindAsEventListener(	this.context,
																			this.arbitraryMouseData.bind(this.map),
																			this.triggerArtEvent.bind(this.map));
			// FF ma DOMMouseScroll
			Event.observe(this.element, 'DOMMouseScroll', this.context.dshToolBox.eventWheel);
			// Opera ma mousewheel
			Event.observe(this.element, 'mousewheel', this.context.dshToolBox.eventWheel);
		}
	},

	destroy: function() {
		// odpiÄ™cie niezdefiniowanej funkcji odpina wszystkie handlery danego eventu a nam na tym nie zaleÅ¼y.
		if(this.context.dshToolBox.eventMouseDown)
			Event.stopObserving(this.element, 'mousedown', this.context.dshToolBox.eventMouseDown);
		if(this.context.dshToolBox.eventMouseMove)
			Event.stopObserving(this.element, 'mousemove', this.context.dshToolBox.eventMouseMove);
		if(this.context.dshToolBox.eventMouseUp)
			Event.stopObserving(this.element, 'mouseup', this.context.dshToolBox.eventMouseUp);
		if(this.context.dshToolBox.eventMouseOver)
			Event.stopObserving(this.element, 'mouseover', this.context.dshToolBox.eventMouseOver);
		if(this.context.dshToolBox.eventMouseOut)
			Event.stopObserving(this.element, 'mouseout', this.context.dshToolBox.eventMouseOut);
		if(this.context.dshToolBox.eventClick)
			Event.stopObserving(this.element, 'click', this.context.dshToolBox.eventClick);
		if(this.context.dshToolBox.eventDblClick)
			Event.stopObserving(this.element, 'dblclick', this.context.dshToolBox.eventDblClick);
		if(this.context.dshToolBox.eventWheel) {
			Event.stopObserving(this.element, 'DOMMouseScroll', this.context.dshToolBox.eventWheel);
			Event.stopObserving(this.element, 'mousewheel', this.context.dshToolBox.eventWheel);
		}

		// usunÄ…Ä‡ toolbox
		delete this.context.dshToolBox;
	},

	/**
	przyjmuje event klikniecia i zwraca slownik z pozycja geograficzna i
	pozycja w viewporcie
	*/

	transpMouseData: function(event) {
		if(event['layerX']) {
			var x = event['layerX'];
			var y = event['layerY'];
		} else {
			var x = event['offsetX'];
			var y = event['offsetY'];
		}
		var mapdivPoint = new TPoint(x-this.mapdiv._minx, y-this.mapdiv._miny);
		return {'lnglat': mapdivPoint.getMapDivLL(this.getTechState()),
				'mapdiv_point': mapdivPoint,
//				'viewport_point': point, // nieuÅ¼ywany ze wzglÄ™dÃ³w wydajnoÅ›ciowych
				'b': event.isLeftClick(),
				'element': Event.element(event)};
	},

	arbitraryMouseData: function(event) {
		var mapdivPoint=new TPoint(	Event.pointerX(event)-Element.cumulativeOffset(this.slot)["left"]-this.mapdiv._minx,
										Event.pointerY(event)-Element.cumulativeOffset(this.slot)["top"]-this.mapdiv._miny);
		var viewportPoint=new TPoint(	Event.pointerX(event)-Element.cumulativeOffset(this.slot)["left"],
										Event.pointerY(event)-Element.cumulativeOffset(this.slot)["top"]);
		return {'lnglat': mapdivPoint.getMapDivLL(this.getTechState()),
				'mapdiv_point': mapdivPoint,
				'viewport_point': viewportPoint,
				'b': event.isLeftClick(),
				'element': Event.element(event)};
	},
/* generalnie bÄ™dzie dostÄ™pna jak obfuskacja ruszy bo nie ma na razie metody Element.mapOffset, a bÄ™dzie doÅ‚Ä…czana przy obfuskacji.
	relativeMouseData: function(event) {
		var mapdivPoint=new TPoint(Element.mapOffset(event, this, Event.element(event))["left"]-this.mapdiv._minx,
										Element.mapOffset(event, this, Event.element(event))["top"]-this.mapdiv._miny);
		var viewportPoint=new TPoint(Element.mapOffset(event, this, Event.element(event))["left"],
										Element.mapOffset(event, this, Event.element(event))["top"]);
		return {'lnglat': mapdivPoint.getMapDivLL(this.getTechState()),
				'mapdiv_point': mapdivPoint,
				'viewport_point': viewportPoint,
				'b': event.isLeftClick(),
				'element': Event.element(event)};
	},
*/
	triggerArtEvent: function(event, eventName) {
		event['e']=eventName;
		this.trigger(event);
	}
});


DragScrollableStandard=Class.create(DragScrollableAbstract, {
	defaultCursor: 'url(images/cursors/openhand.cur), pointer',

	rebuild: function($super) {
		$super();

		// referencje do dynamicznych funkcji przesuwania mapy
		this.context.dshToolBox.scrollMouseMove=this.scroll.bindAsEventListener(this.context,
																				this.transpMouseData.bind(this.map),
																				this.triggerArtEvent.bind(this.map));
		this.context.dshToolBox.scrollMouseUp=this.endScroll.bindAsEventListener(this.context,
																				this.transpMouseData.bind(this.map),
																				this.triggerArtEvent.bind(this.map));

		this.context.dshToolBox.noScrollMouseMove=this.startScroll.bindAsEventListener(	this.context,
																				this.transpMouseData.bind(this.map),
																				this.triggerArtEvent.bind(this.map));
		this.context.dshToolBox.noScrollMouseUp=this.noScroll.bindAsEventListener(this.context,
																				this.transpMouseData.bind(this.map),
																				this.triggerArtEvent.bind(this.map));

	},

	funcMouseDown: function(event, mouseData, eventTrigger) {
		// rozpocznij procedurÄ™ scrollowania
		this.dshToolBox.startX = Event.pointerX(event);
		this.dshToolBox.startY = Event.pointerY(event);
		this.dshToolBox.X = Event.pointerX(event);
		this.dshToolBox.Y = Event.pointerY(event);

		// zmieÅ„ kursor na chwytliwÄ… Å‚apkÄ™
		this.element.style.cursor = 'url(images/cursors/closedhand.cur), move';

		// zmieniam tyrb zdarzeÅ„ na przesywanie mapy
		Event.observe(document.body, 'mousemove', this.dshToolBox.noScrollMouseMove);
		Event.observe(document.body, 'mouseup', this.dshToolBox.noScrollMouseUp);

		Event.stop(event);
	},

	startScroll: function(event, mouseData, eventTrigger) {
		// funkcja istnieje tylko dlatego Å¼eby wysÅ‚aÄ‡ movestart do sÅ‚uchaczy mapy
		Event.stopObserving(document.body, 'mousemove', this.dshToolBox.noScrollMouseMove);
		Event.stopObserving(document.body, 'mouseup', this.dshToolBox.noScrollMouseUp);

		Event.observe(document.body, 'mousemove', this.dshToolBox.scrollMouseMove);
		Event.observe(document.body, 'mouseup', this.dshToolBox.scrollMouseUp);

		this.map.move(Event.pointerX(event)-this.dshToolBox.startX, Event.pointerY(event)-this.dshToolBox.startY);
		this.dshToolBox.startX = Event.pointerX(event);
		this.dshToolBox.startY = Event.pointerY(event);

		eventTrigger({}, "movestart");

		Event.stop(event);
	},

	noScroll: function(event, mouseData, eventTrigger) {
		// funkcja istnieje tylko dlatego Å¼e istnieje this.startScroll
		Event.stopObserving(document.body, 'mousemove', this.dshToolBox.noScrollMouseMove);
		Event.stopObserving(document.body, 'mouseup', this.dshToolBox.noScrollMouseUp);

		this.element.style.cursor = 'url(images/cursors/openhand.cur), pointer';

		Event.stop(event);
	},

	scroll: function(event, mouseData, eventTrigger) {
		this.map.move(Event.pointerX(event)-this.dshToolBox.startX, Event.pointerY(event)-this.dshToolBox.startY);
		this.dshToolBox.startX = Event.pointerX(event);
		this.dshToolBox.startY = Event.pointerY(event);

		Event.stop(event);
	},

	endScroll: function(event, mouseData, eventTrigger) {
		Event.stop(event)

		this.element.style.cursor = 'url(images/cursors/openhand.cur), pointer';

		Event.stopObserving(document.body, 'mousemove', this.dshToolBox.scrollMouseMove);
		Event.stopObserving(document.body, 'mouseup', this.dshToolBox.scrollMouseUp);

		eventTrigger({'vector': [this.dshToolBox.startY-this.dshToolBox.Y, this.dshToolBox.startX-this.dshToolBox.X]}, "moveend");
	},

	funcMouseMove: function(event, mouseData, eventTrigger) {
        //Event.stop(event);
		eventTrigger(mouseData(event), "mousemove");
	},

	funcClick: function(event, mouseData, eventTrigger) {
		Event.stop(event);
		eventTrigger(mouseData(event), "click");
	},

	funcDblClick: function(event, mouseData, eventTrigger) {
		Event.stop(event);
		this.map.zoomin((mouseData(event))['lnglat']);
	},

	funcWheel: function(event, mouseData, eventTrigger) {
		var delta = 0;
		if (!event)
			event = window.event;

		if (event.wheelDelta)
			delta = event.wheelDelta/120;
		else if (event.detail)
			delta = -event.detail/3;

		if (delta!=0)
			if (delta < 0)
				this.map.zoomout();
			else
				this.map.zoomin();

		if (event.preventDefault)
			event.preventDefault();

		event.returnValue = false;
	}
});

DragScrollableFreez=Class.create(DragScrollableAbstract, {
	funcDblClick: function(event, mouseData, eventTrigger) {
		Event.stop(event);
		this.map.zoomin((mouseData(event))['lnglat']);
	}
});


DragScrollableDummy=Class.create(DragScrollableAbstract, {
	funcMouseUp: function(event, mouseData, eventTrigger) {
		Event.stop(event);
		eventTrigger(mouseData(event), "mouseup");
	},

	funcMouseMove: function(event, mouseData, eventTrigger) {
		Event.stop(event);
		eventTrigger(mouseData(event), "mousemove");
	},

	funcMouseOver: function(event, mouseData, eventTrigger) {
		Event.stop(event);
		eventTrigger(mouseData(event), "mouseover");
	},

	funcMouseOut: function(event, mouseData, eventTrigger) {
		Event.stop(event);
		eventTrigger(mouseData(event), "mouseout");
	},

	funcClick: function(event, mouseData, eventTrigger) {
		Event.stop(event);
		eventTrigger(mouseData(event), "click");
	},

	funcDblClick: function(event, mouseData, eventTrigger) {
		Event.stop(event);
		eventTrigger(mouseData(event), "dblclick");
	}
});

AlphaImage = {
  create : function(img)
  {
    if (Prototype.Browser['IE'] && !Prototype.Browser['IE7'])
    {
      tmp = 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='+img+',sizingMethod=\'scale\');'
    }
    else
    {
      tmp = 'background: url('+img+') no-repeat';
    }
    return tmp;
  }
}
TEventDispatcher = Class.create({

	bindAsEventListener: function(object,eventname,func)
	{
		if(!this.observers)this.observers=$H({})
		if(this.observers.get(eventname))
		{
			this.observers.get(eventname).push([object,func])
		}else
		{
			this.observers.set(eventname,[[object,func]])
		}
	},

	trigger : function(event,oid)
	{
		if(!this.observers) return
		var observers = this.observers.get(event['e'])
		if(!(observers&&observers.length)) return
		var i = observers.length;
		while(i--)
		{
			 if(oid)
			 {
				 if(observers[i][0].oid==parseInt(oid))
				 {
				    observers[i][1].apply(observers[i][0],[event]);
				    return
				 }
			 }
			 else
			 {
			   observers[i][1].apply(observers[i][0],[event]);
			 }
		}
	},
  unBindEventListener:function(object,eventname)
  {
    if(this.observers.get(eventname))
    {
        var observers = this.observers.get(eventname)
        for(var i=0;i<observers.length;i++)
        {
          if(observers[i][0]==object)
          {
            observers[i]=undefined;
          }
        }
        observers = observers.compact();
        if (observers[0]!=undefined)
        {
          this.observers.set(eventname,observers);
        }
        else
        {
          this.observers.unset(eventname);
        }
    }
  }
});
TEvent = new TEventDispatcher();
JSEvent = Class.create({
	initialize:function(ename,data,oid)
	{
		TEvent.trigger({'e':ename,'responseJSON':data},oid)
	}
})
TLngLat = new Class.create({
  lng: null,
  lat: null,

  initialize: function(lng, lat)
  {
    if(arguments.length==1) {
      var a= lng.split(/[ ,]/);
      this.lng = parseFloat(a[0]);
      this.lat = parseFloat(a[1]);
    } else if(arguments.length==2) {
      this.lng = lng;
      this.lat = lat;
    }
  },

  getLng: function() {
    return this.lng;
  },

  getLat: function() {
    return this.lat;
  },

  setLng: function(lng) {
    this.lng=lng;
  },

  setLat: function(lat) {
    this.lat=lat;
  },

  getMapPixel: function(zoom)
  {
    var tc = new TileCoords(zoom);
    var p = tc.tileToGeo(0,0);
    var r = GP.fromLLtoPixel([p[0][0],p[1][1]],zoom);
    var s = GP.fromLLtoPixel([this.lng, this.lat],zoom);
    return [s[0]-r[0],s[1]-r[1]];
  },

  getMapdivPixel: function(zoom, rootx, rooty)
  {
    if(zoom)
      this.zoom = zoom;
    var tc = new TileCoords(this.zoom);
    var p = tc.tileToGeo(rootx,rooty);
    var r = GP.fromLLtoPixel([p[0][0],p[1][1]],this.zoom);
    var q = GP.fromLLtoPixel([this.lng, this.lat],this.zoom);
    return new TPoint(q[0]-r[0],q[1]-r[1]);
  },

  // ta metoda stworzona jest do API dla webmasterÃ³w, tylko opakowanie, Å¼eby elegancko szÅ‚o przez mapstate a nie jak funkcja niskopoziomowa.
  getMapDivPixel: function(state) {
    return this.getMapdivPixel(state['zoom'], state['rootx'], state['rooty']);
  },
  //TODO: napisac metode getViewPortPixell
  getDistance:function(ll)
  {
      return TLngLat.getDistance(this,ll);
  },

  toStr: function() {
    return this.lng + "," + this.lat;
  }
});

TLngLat.getDistance = function(ll1,ll2) // Haversine formula
{ // przyjmujÄ™ dwa TLngLat i oblicza dystans miÄ™dzy nimi w km
    var R = 6372.795; // promieÅ„ ziemi na rÃ³wniku :-)
    var dLat = (ll2.getLat()-ll1.getLat())*Math.PI/180; // zawsze rÃ³wne zero w tej klasie
    var dLng = (ll2.getLng()-ll1.getLng())*Math.PI/180;
    lat1 = ll1.getLat()*Math.PI/180;
    lat2 = ll2.getLat()*Math.PI/180;

    var a = Math.sin(dLat/2)*Math.sin(dLat/2)+Math.cos(lat1)*Math.cos(lat2)*Math.sin(dLng/2)*Math.sin(dLng/2);
    var c = 2*Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var d = R*c;
    return d;
}

TPoint = Class.create({
	initialize: function(x, y) {
		this.x = parseInt(x);
		this.y = parseInt(y);
	},

	subtract: function(point)
	{
      // w³aœciwa metoda
      this.x -= point.getX();
      this.y -= point.getY();
      return this
	},
	sum: function(point)
	{
	// w³aœciwa metoda
      this.x += point.getX();
      this.y += point.getY();
      return this
	},
	toStr: function() {
		return this.getX() + "," + this.getY();
	},

	getX: function() {
		return this.x;
	},

	getY: function() {
		return this.y;
	},

	setX: function(x) {
		this.x=x;
	},

	setY: function(y) {
		this.y=y;
	},
	getMapDivLL: function(mapstate)
	{
		var zoom=mapstate['zoom']
		var rootx=mapstate['rootx']
		var rooty=mapstate['rooty']
		var tc = new TileCoords(zoom);
		var p = tc.tileToGeo(rootx,rooty);
		var r = GP.fromLLtoPixel([p[0][0], p[1][1]], zoom);
		var k = GP.fromPixelToLL([r[0]+this.getX(), r[1]+this.getY()], zoom);
		return new TLngLat(k[0],k[1]);

	},
	getViewPortLL:function(map)
	{
		return (new TPoint(-map.mapdiv._minx+this.getX(),-map.mapdiv._miny+this.getY())).getMapDivLL(map.getTechState())
	}
});
TPoint.subtract = function(point1,point2)
{
  return new TPoint(point1.getX()-point2.getX(), point1.getY()-point2.getY());
}
TPoint.sum = function(point1,point2)
{
  return new TPoint(point1.getX()+point2.getX(), point1.getY()+point2.getY());
}


TSize = Class.create({
	initialize: function(width, height)
	{
		this.height = height;
		this.width  = width;
	},

	getWidth: function() {
		return this.width;
	},

	getHeight: function() {
		return this.height;
	},

	setWidth: function(width) {
		this.width=width;
	},

	setHeight: function(height) {
		this.height=height;
	},

	toStr: function() {
		return this.getWidth() + "," + this.getHeight();
	}
});
/**
 * Nie znam wzorca, ktory by oddawal te konstrukcje, ale co do funkcji
 * ktora spelnia jest to strategia. Metoda getPosition jest uruchamiana jakby
 * byla podpieta pod obiekt widoku (TMapStaticLayer)
 */

TPositionStrategyExcpetion = new Error("Nie zadeklarowano metody pozyskiwania pozycji")

TPositionStrategy = Class.create({
	initialize:function(){},
	getPosition:function()
	{
		throw TPositionStrategyException
	}
});
TFixedPositionStrategy = Class.create(TPositionStrategy,{
	initialize:function(point)
	{
		this.point=point;
	},
	getPosition:function(object)
    {
      return this.point;
    }
});

TFloatPositionStrategy = Class.create(TPositionStrategy,{
	initialize:function(displacement){
		if(displacement)
		{
			this.displacement = displacement
		}
		else
        {
			this.displacement = new TPoint(0,0)
		}
	},
	getPosition:function(object)
	{
		return new TPoint(0,0).sum(this.displacement);
	}
})

TLeftMiddlePositionStrategy = Class.create(TFloatPositionStrategy,{
	getPosition:function(object)
	{
		var x=0
		var y=object.map.getViewPortHeight()/2 - object.options.get('size').height/2
		return new TPoint(x,y).sum(this.displacement);
	}
});

TCenterMiddlePositionStrategy = Class.create(TFloatPositionStrategy,{
	getPosition:function(object)
	{
		var x=object.map.getViewPortWidth()/2 - object.options.get('size').width/2
		var y=object.map.getViewPortHeight()/2 - object.options.get('size').height/2
		return new TPoint(x,y).sum(this.displacement);
	}
});

TRightMiddlePositionStrategy = Class.create(TFloatPositionStrategy,{
	getPosition:function(object)
	{
		var x=object.map.getViewPortWidth() - object.options.get('size').width
		var y=object.map.getViewPortHeight()/2 - object.options.get('size').height/2
		return new TPoint(x,y).sum(this.displacement);
	}
});

TLeftBottomPositionStrategy = Class.create(TFloatPositionStrategy,{
	getPosition:function(object)
	{
		var x=0
		var y=object.map.getViewPortHeight() - object.options.get('size').height
		return new TPoint(x,y).sum(this.displacement);
	}
});

TRightBottomPositionStrategy = Class.create(TFloatPositionStrategy,{
	getPosition:function(object)
	{
		var x=object.map.getViewPortWidth() - object.options.get('size').width
		var y=object.map.getViewPortHeight() - object.options.get('size').height
		return new TPoint(x,y).sum(this.displacement);
	}
});

TLeftTopPositionStrategy = TFloatPositionStrategy; // to jest to samo

TCenterTopPositionStrategy = Class.create(TFloatPositionStrategy,{
	getPosition:function(object)
	{
		var x=0
		var y=object.map.getViewPortHeight() - object.options.get('size').height
		return new TPoint(x,y).sum(this.displacement);
	}
});

TRightTopPositionStrategy = Class.create(TFloatPositionStrategy,{
	getPosition:function(object)
	{
		var x=object.map.getViewPortWidth() - object.options.get('size').width
		var y=0;
		return new TPoint(x,y).sum(this.displacement);
	}
});

/**
 * Modul hamleta odpowiedzialny za tworzenie elementow DOMowych osadzanych na mapach.
 *
 */
TLayerOptionsError = new Error("Zle opcje")
NoneController = null

TLayer = Class.create(TEventDispatcher,
{
  mandatoryoptions:{},//slownik opcji ktore musza byc przekazane podczas inicjowania obiektu
  defaultoptions:{'size':new TSize(0,0),style:{background:"red"},controller:NoneController,'attachpath':''},//domyslnie zadeklarowane opcje
  /**!!UWAGA!!!
   * style zapodane w opcjach beda dolaczone do elementu, albo do utworzonego w locie diva
   */
  initialize:function(options, element, parentElement)
  {
    this.options = $H(this.defaultoptions).merge($H(options))//mergujemy opcje nadpisujemy domyslne przekazanymi w konstruktorze
    this.checkOptions()//sprawdzenie czy wszystkie opcje sie zaladowaly
    this.setElements(element,parentElement)
  },
  setElements: function(element,parentElement)
  {
    /**
     * mala strategia: zamiast elementu mozemy podac czysty html, zostanie on opakowany w diva i w draw() do
     * parentNodea
     */
    if(element&Object.isElement(element))
    {
      this._element = element
    }
    else if(this.options.get('template')!=null && this.options.get('substitute')!=null)
    {
		this.setHTML(this.options.get('template').evaluate(this.options.get('substitute')))
    }
    else
    {
      this.setHTML(element)
    }
    if(parentElement)this.parente = parentElement
  },

  setElement:function(element)
  {
    Element.setStyle(element,this.getStyle())//zanim colowiek zrobimy z elementem dodamy mu nieco styli
    this.element = element;
  },

  setHTML:function(html)
  {
    var div = document.createElement('div');
    Element.update(div,html);
    this._element=div;
  },
  checkOptions:function()
  {
    $H(this.mandatoryoptions).each(function(pair)
      {// sprawdzenie czy dla kazdej pary, nazwa wartosc
        if( !(typeof(this.options.get(pair.key)) == pair.value || this.options.get(pair.key) instanceof pair.value))
        {
          throw TLayerOptionsError
        }
      }.bind(this)
    );
  },
  draw:function()
  {
    if(this._element && !this.element)
    {
    	this.setElement(this._element) //nadanie stylu elementowi przed wrzuceniem go na mape
    }

    this.parente.attach(this)

    if(!this.controllers)
	{
		this.addControllers()
	}

  },
  hide:function()
  {
  	var el = this.getElement()
  	if(el&&Object.isElement(el))
  	{
	    el.hide();
  	}
  },
  toggle:function()
  {
	var el = this.getElement()
  	if(el&&Object.isElement(el))
  	{
	    el.toggle();
  	}
  },
  show:function()
  {

  	var el = this.getElement()
  	if(el&&Object.isElement(el))
  	{
     	    el.show();
  	}
  	else{
  	  		this.draw()
  	}
  },
  attach:function(element){
    if(element instanceof TLayer)
    {
    	element = element.getElement()
    }
	var attachpath = this.options.get('attachpath')||''
  	this.element.down(attachpath).appendChild(element)
  },
  addControllers:function()
  {
	if(!this.controllers)
	{
		this.controllers=[]
	}
	else
	{
		var i = this.controllers.length
		while(i--)
		{
			this.controllers[i].stopObserving()
			delete this.controllers[i]
		}
		this.controllers=[]
	}

    if(this.options.get('pathcontroller')!=null)
    {
      var i =this.options.get('pathcontroller').length;
      while(i--){

		var a = this.options.get('pathcontroller')[i]

		if(typeof(a['path'])=="undefined")
			var bindElement=this.element;
		else
			var bindElement=this.element.down(a['path']);

		if(bindElement) {
			var c = new a['controller'](this);
			c.setElement(bindElement);
			this.controllers.push(c);
		}
// powinno byÄ‡ takie sprawdzenie, ale obiekty sÄ… lazy i siÄ™ wywalajÄ… jak siÄ™ to wÅ‚Ä…czy
//        } else
//			throw Error("Element do podpiÄ™cia kontrolera nie jest zdefinowany");
      }
    }
    if(this.options.get('o2controllers')!=null)
    {
      var i =this.options.get('o2controllers').length;
      while(i--){

        var a = this.options.get('o2controllers')[i]
        var c = new a['controller'](this)
   	    this.controllers.push(c)

      }
    }
    if(this.options.controller!=null)
    {
      var c = new this.options['controller'](this)
      c.setElement(this.element)
      this.controllers.push(c)
    }
  },
  clear:function(){
    this.parente.detach(this);
		// tu chyba powinno byÄ‡ detachowanie controllerÃ³w tak dla bezpieczeÅ„stwa
  },

  getStyle:function()
  {
    return $H(this.options.get('style'));
  },

  getElement:function()
  {
    return this.element
  }
});


TMapAbstract = Class.create(TLayer,
{
  group:'',
  setElements: function($super,element,map)
  {
    if(map) this.setMap(map)
    $super(element)
  },
  setMap:function(map)
  {
	this.map = map
  }
});
TMapLayerMapError = Error("Brak obiektu mapy")

TMapStaticLayer = Class.create(TMapAbstract,
{
	mandatoryoptions:{'clickable':'boolean','positioner':TPositionStrategy},
	draw: function($super)
	{
	    if(!this.map)
	    {
			throw TMapLayerMapError;
		}
		$super()
	},
	getStyle:function()
	{
		var style=$H(this.options.get('style'))
		var position = this.options.get('positioner').getPosition(this);
		style.set('top', position.y+'px')
		style.set('left', position.x+'px')
		if(this.options.get('size') instanceof TSize) {
			style.set('width',this.options.get('size').getWidth()+'px')
			style.set('height',this.options.get('size').getHeight()+'px')
		}
		style.set('position','absolute')
		return style.toObject()
	},
	setMap:function(map)
	{
		this.map = map
		if(this.options.get('clickable'))
		{
			this.parente = this.map.clickablestaticlay
		}
		else
		{
			this.parente = this.map.nonclickablestaticlay
		}
	},
	refresh:function()
	{
		this.element.setStyle(this.getStyle())
	}
});

TMapMovableLayer = Class.create(TMapAbstract,
{
	mandatoryoptions:{'clickable':'boolean','point':TLngLat},
	defaultoptions:{'displacement':new TPoint(0,0)},
	draw: function($super)
	{
	    if(!this.map)
	    {
			throw TMapLayerMapError;
		}
		$super()

	},
	_getPosition:function()
	{
		var pos = this.options.get('point').getMapDivPixel(this.map.getTechState())
		return pos.subtract(this.options.get('displacement'))
	},
	getStyle:function(){
		var style=$H(this.options.get('style'))
		var position = this._getPosition()
		style.set('top', position.y+'px')
		style.set('left', position.x+'px')
		style.set('width',this.options.get('size').getWidth()+'px')
		style.set('height',this.options.get('size').getHeight()+'px')
		style.set('position','absolute')
		return style.toObject()
	},
	refresh:function()
	{
		var pos = this._getPosition()
//		this.element.setStyle(this.getStyle())
		this.element.style.top = pos.getY()+'px'
		this.element.style.left = pos.getX()+'px'/**/
	},
	setMap:function(map)
	{
		this.map = map
		if(this.options.get('clickable'))
		{
			this.parente = this.map.clickmovable
		}
		else
		{
		    this.parente = this.map.nonclickmovable
		}
	}

});


TPanelTemplate=new Template('<table cellpadding="0px" cellspacing="0px" style="width: 100%; height: 100%; border: solid black 1px"><tr><td style="padding: 5px;" valign="top"><img class="close" src="http://wmapa.pl/img.mapa/dymek_zamykanie.png" alt="zamknij" style="float: right; cursor: pointer" />#{body}</td></tr></table>')



/**
 * Hierarchia
 */
TEventController = Class.create(
{
  /**
   *
   */
  initialize: function(model) {
    this.model = model;
  },

  setElement: function(element) {
    this.element = $(element);
    this.startObserving();
  },

  startObserving: function() {
    this.active=true;
    var events = this.getEvents();
    this.handlers=new Object();

    for(var evname in events)
    {
      this.handlers[evname]= events[evname].bindAsEventListener(this);
      Event.observe(this.element, evname, this.handlers[evname]);
    }
  },

  getEvents: function() {
      return {'click':this.onclick};
  },

  stopObserving: function() {
    this.active=false;
    var events = this.getEvents();
    for(var ename in events)
    {
      Event.stopObserving(this.element, ename, this.handlers[ename]);
    }
  }
});

TElseController = Class.create(
{
	initialize: function(model) {
    	this.model = model;
    	this.startObserving()
  	},
  	getEvents:function()
  	{
		return {}
  	},
  	startObserving: function()
  	{
	    this.observing=true;
	    var events = this.getEvents();
	    this.handlers=new Object();
	    if(this.model.bindAsEventListener)
		{
	    	this.watcher = this.model
		}
		else
		{
			this.watcher = TEvent
		}
	    for(var evname in events)
	    {
	    	  this.handlers[evname]= this.watcher.bindAsEventListener(this,evname,events[evname].bind(this));
   		}
	},
	stopObserving:function()
	{
	    var events = this.getEvents();
	    for(var evname in events)
	    {
	    	  this.handlers[evname]= this.watcher.unBindAsEventListener(this,evname,events[evname]);
   		}
	}
})

TClosingController= Class.create(TEventController, {
	getEvents: function() {
		return {'click': this.remove};
	},

	remove: function() {
		this.model.clear();
	}
});
TClosingController= Class.create(TEventController, {
	getEvents: function() {
		return {'click': this.remove};
	},

	remove: function() {
		this.model.clear();
	}
});

TMapAlertTemplate=new Template(TPanelTemplate.evaluate({'body':'<div style="text-align: center;clear:both">#{msg}<br /><br /><input class="close_alert" type="image" src="http://wikimapa.pl/img.mapa/ok.gif\" class="focus_element" /></div>'}));

TMapAlert = Class.create(TMapStaticLayer,
{
	group:"TMapAlert",
	defaultoptions:{'positioner':new TCenterMiddlePositionStrategy(),
					'template':TMapAlertTemplate,
					'pathcontroller':[{'controller': TClosingController,'path':'input.close_alert'},{'controller': TClosingController,'path':'img.close'}],
					'style':{'border': 'solid white 1px','background':'#fff'},
					'size':new TSize(400,200),
					'clickable':true
					},
	initialize:function($super, msg, map, options)
	{
		options = $H(options)
		options.set('substitute',{'msg':msg})
		if(options.get('pathcontroller'))
		{
			options.set('pathcontroller',options.get('pathcontroller').concat(this.defaultoptions['pathcontroller']))
		}
		$super(options, '',map );
	}

});


TZoomRefreshStrategy = Class.create(
{
  initialize:function(mapdiv)
  {
    this.mapdiv=mapdiv
  },
  refresh:function(event)
  {
    this.setToBeginning()
    var i=this.elements.length;
    while(i--)
    {

      if(this.elements[i]!=null)
      {
        this.elements[i].refresh();
      }
    }
  },
  setToBeginning:function(event)
  {
    this._element.style.left=this.mapdiv._minx + "px";
    this._element.style.top=this.mapdiv._miny + "px";
  }

});
TResizeRefreshStrategy = Class.create(
{
  refresh:function(){
  var i=this.elements.length;
  while(i--){
    if(this.elements[i]!=null&&typeof(this.elements[i].refresh)!="undefined")
    {
      this.elements[i].refresh();
    }
  }

  }
});

TLaysContainer = Class.create({
  /*
   * @left absolute position
   * @top absolute position
   * @zindex layer z-index
   */

  initialize: function(refreshstrategy,zindex)
  {
    this.elements = new Array(),
    // stwÃ³rz klikalnego markerlay'a
    this._element = document.createElement("div");
    Object.extend(this,refreshstrategy)
    Element.setStyle(this._element, {'width': 0, 'height': 0, 'position': "absolute", 'top':0 + "px", 'left':0+ "px"});
    this._element.style.zIndex=zindex
    this.refresh()
  },

  setMap:function(map)
  {
    this.map = map;
  },
  getElement: function()
  {
    return this._element
  },
  attach: function(lay)
  {
    var group = lay.group;
    //console.debug([group]);
    if (lay.options&& lay.options.get('single') && this.getObjectsByGroup([lay.group]).length != 0)
    {
      return 0; // przydaÅ‚oby siÄ™ Å¼eby pozbywaÅ‚ siÄ™ teÅ¼ tego elementu :-)
    }
    lay.markerlayid=this.elements.length;
    this.elements.push(lay);
    this._element.appendChild(lay.element);
  },

  detach: function(lay)
  {
    this.elements[lay.markerlayid]=null;

    try
    {
      this._element.removeChild(lay.element);
    }
    catch(e){}

    // usunÄ…Ä‡ wszystkie dzieci tego ktÃ³ry jest usuwany.
    //for(var i=0; i<this.elements.length; i++)
  //	{
      //if(this.elements[i]!=null && this.elements[i].MasterOid==lay.oid)
      //{
    //			this.detach(this.elements[i]);
      //}
    //}
    delete lay.element
  },

  refresh: function() {
    this.refreshstrategy.apply()
  },

  clearGroup: function(groups) {
    // usuÅ„ objekty z tablicy objektÃ³w o zadanej nazwie jeÅ›li bez arg to wszystko.
    var i=this.elements.length;

    if(groups.length==0) {
      while(i--)
        if(this.elements[i]&&this.elements[i].clear)
          this.elements[i].clear()
      this.elements=new Array();
    } else {
      // poniewaÅ¼ w IE arguments nie ma metody join....
      var RegExpString="^" + groups[0];
      for(var j=1; j<groups.length; j++)
        RegExpString+="$|^" + groups[j];
      RegExpString+="$";

      while(i--) {
        var groupregs=new RegExp(RegExpString);
        if(this.elements[i]){
          if(this.elements[i].group.match(groupregs)) {
            if(this.elements[i].clear)
            {
              this.elements[i].clear()
              this.elements[i]=null;
            }
          }
        }
      }
    }
  },

  getObjectsByGroup: function(GroupArray) {
    // stwÃ³rz tablicÄ™ z obiektami pasujÄ…cymi do ktÃ³rejÅ› z grup w tablicy GroupArray
    var result=new Array();
    if(GroupArray.length!=0)
    {
      // poniewaÅ¼ w IE arguments nie ma metody join....
      var RegExpString="^" + GroupArray[0];
      for(var j=1; j<GroupArray.length; j++)
        RegExpString+="$|^" + GroupArray[j];
      RegExpString+="$";

      var i=this.elements.length;
      for(var j=0; j<i; j++)
      {
        var Groups=new RegExp(RegExpString);
        if(this.elements[j])
        {
          if(this.elements[j].group.match(Groups))
          {
            result.push(this.elements[j]);
          }
        }
      }
    }
    return result;
  },
  getByClass: function(arr)
  {
    var result=new Array();
    if(arr.length!=0)
    {
      for (var i=0; i<arr.length; i++)
      {
        for(var j=0; j<this.elements.length; j++)
        {
          if (this.elements[j] instanceof arr[i])
          {
            result.push(this.elements[j]);
          }
        }
      }
    }
    return result.uniq(); // zapewnia brak powtarzajÄ…cych sie obiektÃ³w
  },
  clearOid: function() {
    // analogicznie do clearGroup usuwa obiekty o zadanych oid.
    // jeszcze siÄ™ nie przydaÅ‚a ale czujÄ™ Å¼e siÄ™ przyda
    // wiÄ™c jak potrzebujesz to napisz
  }

});
//function disableDefaultEvents(target){
//if (typeof target.onselectstart!="undefined") //IE route
//target.onselectstart=function(){return false}
//else if (typeof target.style.MozUserSelect!="undefined") //Firefox route
//target.style.MozUserSelect="none"
//else //All other route (ie: Opera)
//target.onmousedown=function(){return false}
//target.style.cursor = "default"
//target.ondragstart=function(){return false}
//}

TTileGen = new Object();

TTileSrcTmpl = new Template("http://wmapa.pl/tile/#{z},#{y},#{x}")

TTileGen.getStyle = function(x,y)//przesuniecie w lewo i prawo od poczatku mapdiva
{
  return {"position": "absolute", "width": "200px", "height": "200px", "left": (x*TileSize)+"px", "top": (y*TileSize)+"px"};
}
TTileGen.tileRange = $H(
{0: [3, 3], 1: [3, 3], 2: [4, 4], 3: [6, 5], 4: [10, 8], 5: [18, 14], 6: [34, 26], 7: [65, 50], 8: [127, 98], 9: [252, 194], 10: [501, 386], 11: [1000, 770], 12: [1998, 1537], 13: [3993, 3071], 14: [7983, 6139], 15: [15964, 12275]}
)
//{0: [3, 3], 1: [4, 4], 2: [6, 5], 3: [10, 8], 4: [18, 14], 5: [34, 26], 6: [65, 50], 7: [127, 98], 8: [252, 194], 9: [501, 386], 10: [1000, 770], 11: [1998, 1537], 12: [3993, 3071], 13: [7983, 6139], 14: [15964, 12275],15: [15964, 12275], "-1": [3, 3]});
TTileGen.getElement=function(s, x, y, rx, ry)//scale, tilex,tiley,rootx,rooty
{
    var e = Builder.node("img");
    var t = this.tileRange.get((parseInt(s)-3))
//    console.debug(t,x+1,y+1)
	if(x+1<0||y+1<0||x+1>t[0]||y+1>t[1])
	{

			e.src = "http://t"+parseInt(Math.random()*7+1)+".wmapa.pl/1,0,0?"+parseInt(Math.random()*10000);
	}
	else
	{
	    var a = {z:(parseInt(s)-3),x:(x+1),y:(y+1),t:parseInt(Math.random()*7+1)}
        e.src = TTileSrcTmpl.evaluate(a)
		//e.src = "http://t"+parseInt(Math.random()*7+1)+".wikimapa.pl/"+(parseInt(s)-3)+","+(y+1)+","+(x+1)+'?'+parseInt(Math.random()*10000);
//  e.src = "/tile/"+(parseInt(s)-3)+","+(y+1)+","+(x+1)
	}
    //console.debug(e.src)
	Element.setStyle(e, this.getStyle(x-rx,y-ry));
	return e;
}
TTileGen.update = function(e, s ,x ,y ,rx ,ry)//element, scale, tilex,tiley,rootx,rooty
{
	e.src = "img.mapa/lading.gif";
    var t = this.tileRange.get((parseInt(s)-3))
	if(x<0||y<0||x+1>t[0]||y+1>t[1])
	{
			e.src = "http://t"+parseInt(Math.random()*7+1)+".wmapa.pl/1,0,0"+'?'+parseInt(Math.random()*10000);
	}
	else
	{
	    var a = {z:(parseInt(s)-3),x:(x+1),y:(y+1),t:parseInt(Math.random()*7+1)}
        e.src = TTileSrcTmpl.evaluate(a)
		//e.src = "http://t"+parseInt(Math.random()*7+1)+".wikimapa.pl/"+(parseInt(s)-3)+","+(y+1)+","+(x+1)+'?'+parseInt(Math.random()*10000);
//                e.src = "/tile/"+(parseInt(s)-3)+","+(y+1)+","+(x+1)
	}
	Element.setStyle(e, this.getStyle(x-rx,y-ry));
//	console.debug(e.src)
	return e;
}

// The constructor should be called with
// the parent object (optional, defaults to window).

function Timer(){
    this.obj = (arguments.length)?arguments[0]:window;
    return this;
}

// The set functions should be called with:
// - The name of the object method (as a string) (required)
// - The millisecond delay (required)
// - Any number of extra arguments, which will all be
//   passed to the method when it is evaluated.

Timer.prototype.setInterval = function(func, msec){
    var i = Timer.getNew();
    var t = Timer.buildCall(this.obj, i, arguments);
    Timer.set[i].timer = window.setInterval(t,msec);
    return i;
}

Timer.prototype.setTimeout = function(func, msec) {
    var i = Timer.getNew();
    Timer.buildCall(this.obj, i, arguments);
    Timer.set[i].timer = window.setTimeout("Timer.callOnce("+i+");",msec);
    return i;
}

// The clear functions should be called with
// the return value from the equivalent set function.

Timer.prototype.clearInterval = function(i){
    if(!Timer.set[i]) return;
    window.clearInterval(Timer.set[i].timer);
    Timer.set[i] = null;
}

Timer.prototype.clearTimeout = function(i){
    if(!Timer.set[i]) return;
    window.clearTimeout(Timer.set[i].timer);
    Timer.set[i] = null;
}

// Private data

Timer.set = new Array();
Timer.buildCall = function(obj, i, args) {
    var t = "";
    Timer.set[i] = new Array();
    if(obj != window){
        Timer.set[i].obj = obj;
        t = "Timer.set["+i+"].obj.";
    }
    t += args[0]+"(";
    if(args.length > 2){
        Timer.set[i][0] = args[2];
        t += "Timer.set["+i+"][0]";
        for(var j=1; (j+2)<args.length; j++){
            Timer.set[i][j] = args[j+2];
            t += ", Timer.set["+i+"]["+j+"]";
    }}
    t += ");";
    Timer.set[i].call = t;
    return t;
}

Timer.callOnce = function(i) {
    if(!Timer.set[i]) return;
    eval(Timer.set[i].call);
    Timer.set[i] = null;
}

Timer.getNew = function() {
    var i = 0;
    while(Timer.set[i]) i++;
    return i;
}


Object.extend(Event, {
        wheel:function (event) {
                var delta = 0;
                if (!event) event = window.event;
                if (event.wheelDelta) {
                        delta = event.wheelDelta/120;
                        if (window.opera) delta = -delta;
                } else if (event.detail) {
                	delta = -event.detail/3;
                }
               return Math.round(delta); //Safari Round
        }
});

var DEG_TO_RAD = Math.PI/180;
var RAD_TO_DEG = 180/Math.PI;
var TileSize = 256.0;

function minmax (a,b,c){
    var a = Math.max(a,b);
    var a = Math.min(a,c);
    return a;
}

Projection = Class.create({
    initialize: function(levels){
        this.Bc = []
        this.Cc = []
        this.zc = []
        this.Ac = []
        c = TileSize
        for (i=0;i<levels;i++)
        {
            var e = (c/2);
            this.Bc.push((c/360.0));
            this.Cc.push(c/(2 * Math.PI));
            this.zc.push([e,e]);
            this.Ac.push(c);
            c *= 2
        }
    },

    fromLLtoPixel: function(ll, zoom)
    {
         var d = this.zc[zoom];
         var e = Math.round(d[0] + ll[0] * this.Bc[zoom]);
         var f = minmax(Math.sin(DEG_TO_RAD * ll[1]), -0.9999, 0.9999);
         var g = Math.round(d[1] + 0.5*Math.log((1+f)/(1-f))*-this.Cc[zoom]);
         return new Array(e, g);
    },

	// to powinno przyjmowaÄ‡ TPointa i zwracaÄ‡ TLngLat !!
	fromPixelToLL: function(px,zoom)
	{
		var e = this.zc[zoom];
		var f = ((px[0] - e[0])/this.Bc[zoom]);
		var g = ((px[1] - e[1])/-this.Cc[zoom]);
		var h = RAD_TO_DEG * ( 2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI);
		return new Array(f, h);
	}
});

GP = new Projection(19);




TileCoords = Class.create({
	TheBBox: [
	8.36484367936216, 45.7720975335854,30.2847663044987, 56.3083266193716],
	//14.1225547790527, 49.0022010803223, 24.1472015380859, 54.8356437683105],

	initialize: function (zoom)
    {
        this.gp = new Projection(19);
        this.ll0 = [this.TheBBox[0], this.TheBBox[3]];
        this.ll1 = [this.TheBBox[2], this.TheBBox[1]];
        this.zoom = zoom;
        this.TileSize = TileSize;
        this.px0 = this.gp.fromLLtoPixel(this.ll0, zoom);
        this.px1 = this.gp.fromLLtoPixel(this.ll1, zoom);
        this.xTileRangeStart = parseInt(this.px0[0] / this.TileSize);
        this.yTileRangeStart = parseInt(this.px0[1] / this.TileSize);
    },
    /**
    * Zwraca tablice
    * [latlng lewydolnego rogu tilesa,latlng prawego gornego rogu tilesa]
    */
    tileToGeo: function(tx, ty,dx,dy)
    {

       tx += this.xTileRangeStart;
       ty += this.yTileRangeStart;
	   if(!dx)dx=0
	   if(!dy)dy=0
       px0 = [tx * this.TileSize+dx, (ty + 1) * this.TileSize+dy];
       px1 = [(tx + 1) * this.TileSize+dx, ty * this.TileSize+dy];
       g0 = this.gp.fromPixelToLL(px0, this.zoom);

       g1 = this.gp.fromPixelToLL(px1, this.zoom);
       return new Array(g0, g1);
    },
    /**
    * Zwraca tablice
    * [wspolrzedne tilesa, przesuniecie wewnatrz tilesa liczone od lewego gornego rogu]
    */
    geoToTile: function(x, y)
    {
       l= this.gp.fromLLtoPixel([x, y], this.zoom);
       px = l[0];
       py = l[1];
       tx = parseInt(px / this.TileSize);
       ty = parseInt(py / this.TileSize);
       dx = px % this.TileSize;
       dy = py % this.TileSize;
       tx -= this.xTileRangeStart;
       ty -= this.yTileRangeStart;
       return [[tx, ty], [dx, dy]];
    }
});


TTileRange = {0: [3, 3], 1: [3, 3], 2: [4, 4], 3: [6, 5], 4: [10, 8], 5: [18, 14], 6: [34, 26], 7: [65, 50], 8: [127, 98], 9: [252, 194], 10: [501, 386], 11: [1000, 770], 12: [1998, 1537], 13: [3993, 3071], 14: [7983, 6139], 15: [15964, 12275]}
TTile = Class.create(TEventDispatcher,{
  initialize: function(zoom,rootx,rooty,gridcol,gridrow)
  {
    this.retryLimit=0;
    /**
     * stany;
     * 0 - zainicjowany
     * 1 - w trakcie ladowania
     * 2 - zaladowany
     */
    this.state = 0
    /**
     pozycja na mapdivie w px [LEFT,TOP]
    */
    this.position = null
    /*
    indeks w tablicy grid [col,row]
    */
    this.index = [gridcol,gridrow]
    /**
     * indeks tilesa w lewym gornym rogu
     */
    this.root=[rootx,rooty]
    this.zoom = zoom
    this.range = TTileRange[parseInt(zoom)-3]
    this.element = new Image()

    $(this.element).setStyle({"position": "absolute", "width": TileSize+"px", "height": TileSize+"px", 'display':'none','top':0,'left':0});
  },
  initLoad:function()
  {
    this.update(this.root[0] + this.index[0],this.root[1] + this.index[1])
  },

  trigger:function($super,e)
  {
    if(!this.counter){
      this.counter = {}
    }
    if(!this.counter[e['e']])
    {
       this.counter[e['e']]=0
    }
    this.counter[e['e']]++
    $super(e)

  },

  onload:function(){
    this.retryLimit=0;
    try{

      this.element.style['display']='block';
      this.element.style['width']  = TileSize+"px";
      this.element.style['height'] = TileSize+"px";
      this.element.style['z-index'] = 100
      if(this.state!=2)
      {
        this.trigger({'e':'load'})
        this.state = 2
      }

    }
    catch(e){

    }
  },
  update:function(tilex,tiley)
  {
    if(this.state==2||this.state==0)
    {
      this.trigger({'e':'start'})
    }
    this.state = 1;
    this.tilex = tilex
    this.tiley = tiley
    this.position = [(tilex-this.root[0])*TileSize,(tiley-this.root[1])*TileSize]
try { // try catch dodany przez MichaÅ‚a K. Å¼eby dziaÅ‚aÅ‚o pod IE6
    this.element.style['left'] = this.position[0]+'px';
    this.element.style['top'] = this.position[1]+'px';
    this.element.style['display']='none'
} catch(e) { }
    if((tilex>=0 && tilex<this.range[0])&&(tiley>=0 && tiley<this.range[1]))
    {
      var a = {z:(parseInt(this.zoom)-3),x:(tilex+1),y:(tiley+1),t:parseInt(Math.random()*7+1)}
      this.element.src = TTileSrcTmpl.evaluate(a)
      //this.element.src = "http://t"+parseInt((tilex+tiley)%4+1)+".wikimapa.pl/"+(parseInt(this.zoom)-3)+","+(tiley+1)+","+(tilex+1);
//      this.element.src = "http://193.17.41.160/tile/"+(parseInt(this.zoom)-3)+","+(tiley+1)+","+(tilex+1)
    }
    else
    {
      this.element.src = "http://t1"+".wmapa.pl/1,0,0";
    }
    this.element.onload=this.onload.bind(this)
    this.element.onerror=this.retry.bind(this)
    if(this.element.complete)
    {
      this.element.onload()
    }

  },
  retry:function(){
    this.retryLimit+=1;
    if(this.retryLimit<6)
      this.update(this.tilex,this.tiley);
  },
  refresh:function(zoom,rootx,rooty,x,y)
  {
    this.zoom = zoom
    this.root=[rootx,rooty]
    this.range = TTileRange[parseInt(zoom)-3]
    this.update(x+this.root[0],y+this.root[1])
  },
  changeRow:function(dy)
  {
    this.update(this.tilex,this.tiley+dy)
  },
  changeCol:function(dx)
  {
    this.update(this.tilex+dx,this.tiley)
  },
  /**
   * funkcja do resizowania tilesow
   * @param {Int} movex przesuniecie w prawo
   * @param {Int} movey przesuniecie w dol
   * @param {Int} resize ilosc pixeli o ktora trzeba zwiekszyc wymiary tilesa
   */
  resize:function(movex,movey,resize)
  {
    this.element.style.top  = (parseInt(this.element.style.top) - movey)+'px'
    this.element.style.left = (parseInt(this.element.style.left) - movex)+'px'
    this.element.style.width = (parseInt(this.element.style.width)+resize)+'px'
    this.element.style.height = (parseInt(this.element.style.height)+resize)+'px'
    this.element.style['z-index'] = 50
  }
});
Grid = Class.create({
/** inicjuje grida
  cols - lista kolumn
  rows - lista wiersza
  fillarr - tablica o postaci [function, context], uzywana do inicjowania celli
  creation order, tablica z kolejka uzupelniania o postaci [[c1,r1],[c2,r2]]
*/
  initialize:function(cols,rows,fillarr,creationOrder)
  {
  this.rows = $A($R(0,rows,true))
  this.cols = $A($R(0,cols,true))

  if(!creationOrder)
  {
    var creationOrder=this.defaultqueue(cols,rows)
  }
  this.grid=[]
  if(!fillarr) fillarr = [function(x,y){return x+' '+y},null]
  for( var i=0; i<creationOrder.length;i++)
  {
    var x = creationOrder[i][0]
    var y = creationOrder[i][1]
    if(!this.grid[x]) this.grid[x]=[]
    this.grid[x][y] = fillarr[0].apply(fillarr[1],[x,y])

  }

  },
  getCell:function(col,row)
  {
  return this.grid[this.cols[col]][this.rows[row]]
  },
  getSize:function(){return [this.cols.length,this.rows.length]},
  /**
  przesuwa ostatnia (lub pierwsza) kolumne na poczatek (lub koniec)
  dir = 1 pierwsza na koniec
  dir = -1 ostatnia na poczatek
  */
  transferColumn:function(dir,funarr)
  {
  var dir = Math.round(dir/Math.abs(dir))//zeby napewno to bylo 1 lub -1
  if(dir >0)
  {
    var colin = this.cols.shift()
    this.cols.push(colin)
  }
  else
  {
    var colin = this.cols.pop()
    this.cols.unshift(colin)
    }
    if(funarr)
    {
      var col = this.grid[colin].clone()
      col=col.compact()
      funarr[0].apply(funarr[1],[col,this.cols.length*dir])
    }
  },
  /**
  przesuwa ostatni (lub pierwszy) wiersz na poczatek (lub koniec)
  dir = 1 pierwszy na koniec
  dir = -1 ostatni na poczatek
  */
  transferRow:function(dir,funarr)
  {
  var dir = Math.round(dir/Math.abs(dir))//zeby napewno to bylo 1 lub -1

  if(dir >0)
  {
    var rowin = this.rows.shift()
    this.rows.push(rowin)
  }
  else
  {
    var rowin = this.rows.pop()
    this.rows.unshift(rowin)
     }
   if(funarr)
  {

    var row = []
    for(var i=0; i<this.cols.length;i++)
    {
      row.push(this.grid[this.cols[i]][rowin])
    }
    funarr[0].apply(funarr[1],[row,this.rows.length*dir])
  }


  },
  /**
    tworzy kolumne na poczatku
    do funarr przekazywane sa: indeks kolumny i wiersza
  **/
  unshiftColumn:function(funarr)
  {
  var col = this.grid.length
  this.grid[col] = []
  for(var i =0; i<this.rows.length;i++)
  {
    this.grid[col][this.rows[i]]=funarr[0].apply(funarr[1],[0,i])
  }
  this.cols.unshift(col)
  },
  /**
    tworzy wiersz na poczatku
    do funarr przekazywane sa: indeks kolumny i wiersza
  **/
  unshiftRow:function(funarr)
  {
  var row = this.grid[this.cols[0]].length
  for(var i =0; i<this.cols.length;i++)
  {
    this.grid[this.cols[i]][row]=funarr[0].apply(funarr[1],[i,0])
  }
  this.rows.unshift(row)
  },
  /**
    tworzy kolumne na koncu
    do funarr przekazywane sa: indeks kolumny i wiersza
  **/
  pushColumn:function(funarr)
  {
  var col = this.grid.length
  this.grid[col] = []
  for(var i =0; i<this.rows.length;i++)
  {
    try{
    this.grid[col][this.rows[i]]=funarr[0].apply(funarr[1],[this.cols.length,i])
    }
    catch(e){}
  }
  this.cols.push(col)
  },
  /**
    tworzy wiersz na koncu
    do funarr przekazywane sa: indeks kolumny i wiersza
  **/
  pushRow:function(funarr)
  {
  var row = this.grid[this.cols[0]].length

  for(var i =0; i<this.cols.length;i++)
  {
    this.grid[this.cols[i]][row]=funarr[0].apply(funarr[1],[i,this.rows.length])
  }
  this.rows.push(row)
  },

  /**
  usuwa pierwsza kolumne
  do funarr przekazywana jest tablica komorek
  **/
  shiftColumn:function(funarr)
  {
    var col = this.cols.shift()
    if(funarr)
    {
      funarr[0].apply(funarr[1],[this.grid[col].compact()])
    }
    delete this.grid[col]
  },
  /**
  usuwa pierwszy wiersz
  do funarr przekazywana jest tablica komorek
  **/
  shiftRow:function(funarr)
  {
    var row = this.rows.shift()
    var rowcells  = []
    for(var i=0;i<this.cols.length;i++)
    {
    rowcells.push(this.grid[this.cols[i]][row])
    delete this.grid[this.cols[i]][row]
    }

    if(funarr)
    {
      funarr[0].apply(funarr[1],[rowcells])
    }
  },
  /**
  usuwa kolumne na koncu
  do funarr przekazywana jest tablica komorek
  */
  popColumn:function(funarr)
  {
  var col = this.cols.pop()
    if(funarr)
    {
      funarr[0].apply(funarr[1],[this.grid[col].compact()])
    }
    delete this.grid[col]
  },
  popRow:function(funarr)
  {
  var row = this.rows.pop()
  var rows=[]
    for(var i=0;i<this.cols.length;i++)
    {
    rows.push(this.grid[this.cols[i]][row])
    delete this.grid[this.cols[i]][row]
    }
    if(funarr)
    {
      funarr[0].apply(funarr[1],[rows])
    }
  },
  getGrid:function()
  {
  var ret = []
  for(var i=0;i<this.cols.length;i++)
  {
    if(!ret[i]) ret[i]=[]
    for(var j=0;j<this.rows.length;j++)
    {
      ret[i][j]=this.grid[this.cols[i]][this.rows[j]]
    }
  }
  return ret
  },
  defaultqueue:function(cols,rows)
  {
  var creationOrder=[]
  for(var x=0;x<cols;x++)
  {
    for(var y=0;y<rows;y++)
    {
      creationOrder.push([x,y])
    }
  }
  return creationOrder
  },
  updateGrid:function(fillarr,queue)
  {
    try{
      if(!queue) var queue = this.defaultqueue(this.cols.length,this.rows.length)
      for( var i=0; i<queue.length;i++)
      {
        var x = queue[i][0]
        var y = queue[i][1]
        this.grid[this.cols[x]][this.rows[y]] = fillarr[0].apply(fillarr[1],[this.grid[this.cols[x]][this.rows[y]],x,y])
        //console.debug(this.grid[this.cols[x]][this.rows[y]])
      }
    }catch(e)
    {
      //console.debug(e)
    }
  }
});


/**testy



var createf = function(x,y){return {'x':x,'y':y}}

var moverowf0 = function(row,dy){}

var moverowf1 = function(row,dy){
    console.debug(row,dy)
    for(var i=0;i<row.length;i++)
    {
        row[i]['y']+=dy+1
    }
}
var moveColf1 = function(col,dx){
console.debug(col)
    for(var i=0;i<col.length;i++)
    {
        col[i]['x']+=dx
    }
  }
var grid = new Grid(2,3,[createf,])

console.debug('init',grid.getGrid())

grid.pushColumn([createf,])
console.debug('append column end',grid.getGrid())

grid.pushRow([createf,])
console.debug('append row end',grid.getGrid())

grid.popColumn()
console.debug('pop column',grid.getGrid())

grid.popRow()
console.debug('pop row',grid.getGrid())

grid.transferRow(-1,[moverowf0,])
console.debug('transfer row end 0',grid.getGrid())

grid.transferRow(1,[moverowf1,])
console.debug('transfer row end 1',grid.getGrid())


grid.transferCol(1,[moveColf0,])
console.debug('transfer col end 1',grid.getGrid())

grid.transferColumn(1,[moveColf1,])
console.debug('transfer col end 1',grid.getGrid())


grid.shiftColumn()
console.debug('shift column',grid.getGrid())

grid.shiftRow()
console.debug('shift row',grid.getGrid())



grid.unshiftColumn([createf,])
console.debug('append column begining',grid.getGrid())

grid.unshiftRow([createf,])
console.debug('append row begining',grid.getGrid())

*/

GridManager = Class.create(TEventDispatcher,{
  /**
   * kierunki preloadowania
   */
  _NORTH : 1,
  _SOUTH : 2,
  _WEST  : 4,
  _EAST  : 8,
  count: 0,
  active:true,

  initialize: function()
  {
    this.element=document.createElement("div");
    this._tilex=null; // numer x obiektu w lewym gÃ³rnym rogu
    this._tiley=null; // numer y obiektu w lewym gÃ³rnym rogu
    this._minx=0; // pozycja najbardziej wysunietego obrazka w lewo do gory
    this._miny=0;
  },
  setInitData:function(vpsize, lnglat, zoom, margin)
  {
    this.vpsize = vpsize
    this.margin = margin
    this.zoom = zoom

    var tc = new TileCoords(this.zoom);
    var ctile = tc.geoToTile(lnglat.getLng(), lnglat.getLat()); //<- ten tile ma byc w srodku;
    var o = [this.vpsize.getWidth()/2, this.vpsize.getHeight()/2]; // srodek viewportu
    // przesuniecie gornego lewego rogu centralnego tila wzgledem
    // prawego gornego rogu viewportu
    var t1 = [o[0]-ctile[1][0], o[1]-ctile[1][1]];
    // przesuniecie dolnego lewego rogu centralnego tila wzgledem
    // prawego gornego rogu viewportu
    var t2 = [t1[0]+TileSize,t1[1]+TileSize]
    with( Math )
    {
      // ilosc tilesow na lewo od centralnego tile
      var xlc = ceil(max(0,t1[0])/TileSize)+margin
      // ilosc tilesow na prawo od centralnego tile
          var xrc = ceil(max(0,this.vpsize.getWidth()-t2[0])/TileSize)+margin

      // ilosc tilesow w gore od centralnego tile
      var ytc = ceil(max(0,t1[1])/TileSize)+margin
         // ilosc tilesow w dol od centralnego tile
          var ybc = ceil(max(0,this.vpsize.getHeight()-t2[1])/TileSize)+margin
    }

    // odleglosc poczatku mapdiva od viewportu
    this._minx = Math.round(t1[0]-xlc*TileSize);
    this._miny = Math.round(t1[1]-ytc*TileSize);

    this._rootx=ctile[0][0]-xlc; // wyliczenie x pierwszego tile
    this._rooty=ctile[0][1]-ytc; // wyliczenie y pierwszego tile

    this.numoftiles = [1+xlc+xrc,1+ytc+ybc]
  },
  load: function(vpsize, lnglat, zoom, margin)
  {
    this.trigger({'e':'loading'})
    this.tilec = 0
    //this.clear();
    if(!margin) margin = 1
    //wyliczamy dane do inicjalizacji
    this.setInitData(vpsize, lnglat, zoom, margin)
    this.element.style.position = 'absolute'
    this.element.style.left=this._minx+'px'
    this.element.style.top=this._miny+'px'
//    this.element.style.zIndex = '100'

    //ladujemy siatke
    var maxdim =0
    if(this.numoftiles[0]<this.numoftiles[1])
    {
      maxdim = 1
    }
    var mindim = (maxdim+1)%2

    var dimdiff = Math.floor((this.numoftiles[maxdim]-this.numoftiles[mindim])/2)
    var gridsize = this.numoftiles[maxdim]*this.numoftiles[maxdim]

    var pos = [Math.floor(this.numoftiles[maxdim]/2),Math.floor(this.numoftiles[maxdim]/2)]
    var dir = (this.numoftiles[maxdim]+1)%2
    var i = 1
    var tmp
    var copypos = [pos[0],pos[1]]
    copypos[mindim] = copypos[mindim]-dimdiff
    var queue = [copypos]// to zastapic this.loadcel(pos[0]pos[1])
    this._grid = []

    while(i<gridsize+1)
    {
      i++
      tmp = this.getNextSpiralCoord(i, dir, pos)
      dir = tmp[0]
      pos = tmp[1]
      copypos = [pos[0],pos[1]]
      copypos[mindim] = copypos[mindim]-dimdiff
      if(copypos[mindim]>=0&&copypos[mindim]<this.numoftiles[mindim]&&copypos[(mindim+1)%2]<this.numoftiles[(mindim+1)%2])
      {
           queue.push([copypos[0],copypos[1]])
      }
    }
    this.X = Math.floor(this._minx/TileSize);
    this.Y = Math.floor(this._miny/TileSize);
    this.loadgrid(queue)
  },
  loadgrid:function(queue)
  {
    if(!this.grid)
    {

      this.tilec = this.numoftiles[0]*this.numoftiles[1]
      this.grid = new Grid(this.numoftiles[0],this.numoftiles[1],[fun,this],queue)
      this.grid.loaded = true
    }
    else
    {

      var size = this.grid.getSize()
      var dc = this.numoftiles[0]-size[0]
      var dr = this.numoftiles[1]-size[1]
      for(var i=0;i<Math.abs(dc);i++)
      {
        if(dc<0)
        {
          this.grid.popColumn([funremove,this])
        }
        else if(dc>0)
        {
          this.grid.pushColumn([funcol,this])
        }
      }
      for(var i=0;i<Math.abs(dr);i++)
      {
        if(dr<0)
        {
          this.grid.popRow([funremove,this])
        }
        else if(dr<0)
        {
          this.grid.pushRow([funrow,this])
        }
      }
      var numoftiles = this.grid.getSize()
      this.tilec = numoftiles[0]*numoftiles[1]
      //console.debug('updaste!')
      this.grid.updateGrid([funupdate,this],queue)
    }
  },
  tileCounter:function(e)
  {
    if(e['e']=='start')
    {
//       this.tilec++
    }
    else {
      this.tilec--
    }
//    console.debug(this.tilec)
    if(this.tilec==0)
    {
        this.trigger({'e':'load'})
    }
  },
  /*
  to moj ulubiony kawalek kodu w tym pliku :-)
  */
  getNextSpiralCoord: function(i,dir,prevpos)
  {
    var r = Math.floor(Math.sqrt(i-1))
    var n = i - r*r
    if(((n-2)%r)==0){ dir = (dir+1)%4 }
    copypos = [prevpos[0],prevpos[1]]
    copypos[dir%2]+= (dir<2)?1:-1
    return [dir,copypos]
  },
  _preload: function(dir)
  {
    this.trigger({'e':'preloading'})

    if(dir&this._NORTH)
    {
      this.grid.transferRow(-1,[funrowload,])
    }
    if(dir&this._SOUTH)
    {
      this.grid.transferRow(1,[funrowload,])
    }

    if(dir&this._EAST)
    {

      this.grid.transferColumn(1,[funcolload,])

    }

    if(dir&this._WEST)
    {

      this.grid.transferColumn(-1,[funcolload,])
    }

  },
  move: function(dx,dy)
  {
    this.divmove(dx,dy);
    this.checkPreload()
  },
  checkPreload:function()
  {
    var t = false//patrz komentarz nizej
    var x = Math.floor(this._minx/TileSize);
    var y = Math.floor(this._miny/TileSize);
    if(Math.abs(this.X-x)>1||Math.abs(this.Y-y)>1)
      t=true

    if(this.X-x>0)
    {
      this.X--;
      this._preload(this._EAST);
    }
    if(this.X-x<0)
    {
      this.X++;
      this._preload(this._WEST);
    }
    if(this.Y-y>0)
    {
      this.Y--;
      this._preload(this._SOUTH)
    }
    if(this.Y-y<0)
    {
      this.Y++;
      this._preload(this._NORTH)
    }
    if(t){
      // to jest straznik, jesli przesuniecie jest wieksze niz margines, musi preloadowac jeszcze raz
      this.move(0,0)
    }
  },
  divmove: function(dx,dy)
  {

    this._minx+=dx;
    this.element.style.left=this._minx+'px';
    this._miny+=dy;
    this.element.style.top=this._miny+'px';
  },
  getMapDiv:function(){return this.element;},
  getMinTile: function()
  {
    var cell = this.grid.getCell(0,0)
    return [cell.tilex,cell.tiley]
  },
  getMinTilePosition:function()
  {
    try
    {
      var cell = this.grid.getCell(0,0)
      return [parseInt(cell.element.style.left),parseInt(cell.element.style.top)]
    }
    catch(e)
    {

    }
  },
  getMaxTile: function()
  {
    var ind  = this.grid.getSize()
    var cell = this.grid.getCell(ind[0]-1,ind[1]-1)
    return [cell.tilex,cell.tiley]
  },
  /**
  * dostosowuje grida do nowych rozmiarow viewportu, zachowuje centrum
  * algorytm dzialania:
  * 1) przesuwamy mapdiva o polowe roznicy wysokosci i szerokosci, tak by centrum
  *    znajdowalo sie tam gdzie dawniej
  * 2) sprawdzamy czy przesuniecie przez przypadek nie spowodowalo nastepujacych
  *    nastepstw:
  *    - prawy gorny rog mapdiva przesunal sie zbyt blisko prawego gornego rogu viewportu
      (nalezy dodac kolumny z lewej i prawej i zmniejszyc _minx)
    *    - prawy gorny rog mapdiva przesunal sie za daleko od prawego gornego rogu viewportu
      (nalezy usunac kolumny z lewej i prawej i zwiekszyc _minx)
      analogicznie z kolumnami
  */
  resize:function(vpsize)
  {
    var dx = Math.ceil((vpsize.getWidth()-this.vpsize.getWidth())/2)
    var dy = Math.ceil((vpsize.getHeight()-this.vpsize.getHeight())/2)
    this.divmove(dx,dy)

    //return
    var Xch = Math.floor((this._minx)/TileSize)
    var Ych = Math.floor((this._miny)/TileSize)
    this.vpsize = vpsize
    if(Xch>this.X)
    {
      //dodaj kolumny
      var addcountc = Math.abs(Xch-this.X)
      for(var j=0;j<addcountc;j++)
      {
        this.grid.pushColumn([funcol,this])
        this.grid.unshiftColumn([funcol,this])

      }

    }
    if(Xch<this.X)
    {
      //usun kolumny
      var remcountc = Math.abs(Xch-this.X)
      for(var j=0;j<remcountc;j++)
      {//najpiew znajdzmy kolumny, przy okazji usunmy je z listy indeksacyjnej
        this.grid.popColumn([funremove,this])
        this.grid.shiftColumn([funremove,this])
      }
    }

    if(Ych>this.Y)
    {
      //dodaj wiersze z gory
      var addcountr = Math.abs(Ych-this.Y)
      for(var j=0;j<addcountr;j++)
      {
        this.grid.pushRow([funrow,this])
        this.grid.unshiftRow([funrow,this])
      }

    }
    if(Ych<this.Y)
    {
      //usun wiersze z gory
      var remcountr = Math.abs(Ych-this.Y)
      for(var j=0;j<remcountr;j++)
      {
        this.grid.popRow([funremove,this])
        this.grid.shiftRow([funremove,this])
      }
    }
    this.Y=Ych
    this.X=Xch

  }
});
/**
 * Zoomer
 */
//Zoomer = Class.create({
//  initialize:function(grid)
//  {
//
//  }
//})

/***************************************************************
funkcje do grida
***************************************************************/
/***** zoomowanie tilesow *****/
var funzoom = function(a,x,y)
{
  a.resize(this.dx*x,this.dy*y,this.size)
  return a
}
/******* tworzenie grida ******/
fun = function(x,y){
  var a = new TTile(this.zoom,this._rootx,this._rooty,x,y)
  a.bindAsEventListener(this,'load',this.tileCounter)
    a.bindAsEventListener(this,'start',this.tileCounter)
    a.initLoad()
    this.element.appendChild(a.element)
    return a
}
/** po set center ma sie wykonac to: **/
var funupdate = function(a,x,y){
  if(!a)
  {
   a =new TTile(this.zoom,this._rootx,this._rooty,x,y)
   //console.debug('made new tile')
  }

  a.refresh(this.zoom,this._rootx,this._rooty,x,y)

  return a
}
/*** preloadowanie obrazkow ***/
var funrowload = function(row,dy){
  for(var i=0;i<row.length;i++)
  {
    row[i].changeRow(dy)
  }

}

var funcolload = function(col,dx){
  for(var i=0;i<col.length;i++)
  {
    col[i].changeCol(dx)
  }

}


/***** dorzucanie obrazkow *****/
var funcol=function(col,row){
//wykonywana w kontekscie tilesa 0,0)
    try{
    var tiley = this.grid.getCell(0,0).tiley
    var tilex = this.grid.getCell(0,0).tilex
    if (col==0)
    {
      var cx = tilex-1
    }
  else
  {
    var cx = tilex+col
  }
   var a = new TTile(this.zoom,this._rootx,this._rooty,col,row)
    a.bindAsEventListener(this,'load',this.tileCounter)
    a.bindAsEventListener(this,'start',this.tileCounter)
    a.update(cx,tiley+row)
    this.element.appendChild(a.element)
    return a
    }
    catch(e){}
}
var funrow=function(col,row){
//wykonywana w kontekscie gridmanagera
 try{
    var tiley = this.grid.getCell(0,0).tiley
    var tilex = this.grid.getCell(0,0).tilex

    if (row==0)
    {
      var ry = tiley-1
    }
  else
  {
    var ry = tiley+row
  }
  var a = new TTile(this.zoom,this._rootx,this._rooty,col,row)
  a.bindAsEventListener(this,'load',this.tileCounter)
    a.bindAsEventListener(this,'start',this.tileCounter)
    a.update(tilex+col,ry)
    this.element.appendChild(a.element)
    return a
   }catch(e){}
}
var funremove = function(tiles)
{
  try{
  for(var i=0;i<tiles.length;i++)
  {
    this.element.removeChild(tiles[i].element)
    delete tiles[i].element
    delete tiles[i]
  }
  }catch(e){}
}

ZoomGridManager = Class.create(GridManager,{

  /**
   * metoda robiÄ…ce smoothzoomin
   * @param {Int} i plus minus [narazie ignorowany]
   * @param {TPoint} center centrum w mapdivie
   * @param {Int} tickcount ilosc tickow
   */
  zoomm:function(center,vpsize, lnglat, zoom)
  {
    this.zoomgrid = this.grid
    this.zoomelement = this.element

    delete this.grid
    delete this.element

    this.element=document.createElement("div");
    this.element.style['zIndex'] = '100'
    this._tilex=null; // numer x obiektu w lewym gÃ³rnym rogu
    this._tiley=null; // numer y obiektu w lewym gÃ³rnym rogu
    this._minx=0; // pozycja najbardziej wysunietego obrazka w lewo do gory
    this._miny=0;

    try
    {
      var tickcount = 8
      var dx = Math.ceil(center[0]/tickcount)
      var dy = Math.ceil(center[1]/tickcount)
      var md = parseInt(TileSize/tickcount)

      var fillarr=[funzoom,{'dx':-md ,'dy':-md,'size':md}]
      var context = {
        'tickcount':tickcount,
        'curtick':0,
        'grid':this.zoomgrid,
        'fillarr':fillarr,
        'mgr':this,
        'ddx':dx,
        'ddy':dy,
        'vpsize':vpsize,
        'lnglat':lnglat,
        'zoom':zoom
      }
      a = function()
      {
        try
        {
          this['mgr'].divmove(this['ddx'],this['ddy'])
          this.grid.updateGrid(this.fillarr)
          this['curtick']++;

          if(this['tickcount']<=this['curtick'])
          {
            window.clearInterval(this['handler'])
            this['mgr'].load(this.vpsize,this.lnglat,this.zoom,this.margin)
          }
        }
        catch(e)
        {
          //console.debug(e)
        }

      }.bind(context)
      context['handler'] = window.setInterval(a,20)
      var change = this.zoomgrid
      this.zoomgrid = this.grid
      this.grid = change
    }
    catch(e)
    {

    }
  }
});
TBoundingBox = Class.create({
  lls: null,

  // przyjmuje dwa LatLng muszÄ… byÄ‡ po ukosie, nie waÅ¼ne w ktÃ³rÄ… stronÄ™ i inne badziewie
  initialize: function(ll1, ll2) {
    this.lls = new Array(2);
    if(arguments.length==1) {
      if(ll1 instanceof Array) {
        this.getExtent(ll1)
      } else if(typeof(ll1)=="string") {
        var a= ll1.split(" ");
        this.lls[0]=new TLngLat(parseFloat(a[0]),parseFloat(a[1]));
        this.lls[1]=new TLngLat(parseFloat(a[2]),parseFloat(a[3]));
      }
    } else if(arguments.length==2) {
      if(ll1!=null)
        this.lls[0]=ll1;
      if(ll2!=null)
        this.lls[1]=ll2;
    }

  },
  getExtent:function(arr)
  {
    if(arr.length==0 ||!arr|| ( (arr.length==1 ) && !( arr[0] instanceof TBoundingBox ) ) )
    {
      throw new Error("Cannot initiate TBoundingBox without coords")
    }
    else
    {
      var NW = [90,180];
      var SE = [-90,-180];
      for(var i=1; i<arr.length; i++) {
        if(arr[i] instanceof TBoundingBox)
        {
          NW[0]=Math.min(NW[0], arr[i].getNorthWest().getLng()); // najmniejsza dÅ‚ugoÅ›Ä‡
          NW[1]=Math.max(NW[1], arr[i].getNorthWest().getLat()); // najwiÄ™ksza szerokoÅ›Ä‡
          SE[0]=Math.max(SE[0], arr[i].getSouthEast().getLng());
          SE[1]=Math.min(SE[1], arr[i].getSouthEast().getLat()); // i na odwrÃ³t Å¼eby dostaÄ‡ to co siÄ™ chce czyli prawy dolny rÃ³g
          continue;
        }
        else if(arr[i] instanceof TLngLat)
        {
          NW[0]=Math.min(NW[0], arr[i].getLng());
          NW[1]=Math.max(NW[1], arr[i].getLat());
          SE[0]=Math.max(SE[0], arr[i].getLng());
          SE[1]=Math.min(SE[1], arr[i].getLat());
          SE[0]=Math.max(SE[0], arr[i].getLng());
        }
      }
      this.lls[0]=new TLngLat(NW[0], NW[1]);
      this.lls[1]=new TLngLat(SE[0], SE[1]);
    }
  },
  getLngLats: function()
  {
    return this.lls;
  },

  toStr: function()
  {
    var i = 0;
    var ret = new Array();
    for(i=0;i<this.lls.length;i++)
    {
      ret.push(this.lls[i].toStr());
    }
    return ret.join(',');
  },

  // zwraca dwuelementowÄ… tablicÄ™ zawierajÄ…cÄ… TPointy lewego gÃ³rnego i prawego dolnego rogu. TBoundingBoxa.
  getMapdivPixel:function(z,rx,ry) // przydaÅ‚oby siÄ™ Å¼eby przyjmowaÅ‚ stan mapy a nie 3 arg znajdujÄ…ce siÄ™ w tym stanie.
  {
    return [this.getNorthWest().getMapdivPixel(z,rx,ry), this.getSouthEast().getMapdivPixel(z,rx,ry)];
  },

  getSouthWest:function()
  {
    var lat = Math.min(this.lls[0].getLat(), this.lls[1].getLat());
    var lng = Math.min(this.lls[0].getLng(), this.lls[1].getLng());
    return new TLngLat(lng, lat);
  },

  getNorthEast: function()
  {
    var lat = Math.max(this.lls[0].getLat(),this.lls[1].getLat());
    var lng = Math.max(this.lls[0].getLng(),this.lls[1].getLng());
    return new TLngLat(lng, lat);
  },

  getNorthWest: function() {
    return new TLngLat(Math.min(this.lls[0].getLng(), this.lls[1].getLng()), Math.max(this.lls[0].getLat(), this.lls[1].getLat()));
  },

  getSouthEast: function() {
    return new TLngLat(Math.max(this.lls[0].getLng(), this.lls[1].getLng()), Math.min(this.lls[0].getLat(), this.lls[1].getLat()));
  },

  // przyjmuje TBoundingBox a zwraca true jeÅ›li argument mieÅ›ci siÄ™ w TBoundingBox wywoÅ‚ujÄ…cy metodÄ™
  containsBounds:function(bb)
  {
    var la1 = this.getSouthEast();
    var lb1 = bb.getSouthEast();

    var la2 = this.getNorthWest();
    var lb2 = bb.getNorthWest();

    if(la1.getLng()>=lb1.getLng()&&la1.getLat()<=lb1.getLat()&&la2.getLat()>=lb2.getLat()&&la2.getLng()<=lb2.getLng())
    {
      return true;
    }
    return false;
  },

  getPixelWidth:function(curzoom)
  {
    var sl1 = this.getNorthEast().getMapPixel(curzoom) //wspolrzedne x,y punktu w pixelach przy srodkowym zoomie
    var sl2 = this.getSouthWest().getMapPixel(curzoom)
    return Math.abs(sl1[0]-sl2[0])
  },
  getPixelHeight:function(curzoom)
  {
    var sl1 = this.getNorthEast().getMapPixel(curzoom) //wspolrzedne x,y punktu w pixelach przy srodkowym zoomie
    var sl2 = this.getSouthWest().getMapPixel(curzoom)
    return Math.abs(sl1[1]-sl2[1])
  },
  getBestZoom:function(map)
  {
    var curzoom = 13
    var sl1 = this.getNorthEast().getMapPixel(curzoom) //wspolrzedne x,y punktu w pixelach przy srodkowym zoomie
    var sl2 = this.getSouthWest().getMapPixel(curzoom)
    var sw = parseFloat(Math.abs(sl1[0]-sl2[0])/(map.vpsize.getWidth()-150))//stosunek szerokosci takiego bboxa do szerokosci viewportu
    var sh = parseFloat(Math.abs(sl1[1]-sl2[1])/map.vpsize.getHeight())//jw. tylko wysokosci
    var a = Math.max(sw,sh)||1 //wez wiekszy stosunek, czyli to co sie bardziej nie miesci :-)
    if(a>1){ //13 zoom to zaduze przyblizenie, trzeba zmniejszyc zoom
      curzoom--
      while(a/2>1)//stosunek zoomow jest potega 2 wiec sprawdzamy czy zmiesci sie to szersze w viewporcie przy zoomie mniejszym o 1
      {
        a /=2
        curzoom--
      }
    }else
    {
      while(a*2<1.01)//tak jak wyzej
      {
        a *=2
        curzoom++
      }
    }
    return Math.min(Math.max(curzoom,7),18)//dorzucamy ograniczenia skali i zwracamy wynik
  },
  getCenter:function()
  {
    var la1 = this.getSouthWest();
    var la2 = this.getNorthEast();

    return new TLngLat((la1.getLng()+la2.getLng())/2, (la1.getLat()+la2.getLat())/2);
  }
});

TBoundingBoxLayer = Class.create(TMapMovableLayer, {
  /**
   * rysuje bbox na przesuwalnej warstwie mapy
   */
	group: "TBoundingBoxLayer",

	initialize: function($super, bbox, map) {
		this.BoundingBox=bbox;
		this.map=map;

		var style=$H(this.getStyle());
		var options={
			'style': style,
			'point': bbox.getNorthWest(),
			'size': new TSize(parseInt(style['width']), parseInt(style['height'])),
			'displacement': new TPoint(Math.ceil(parseInt(style['width'])/2), Math.ceil(parseInt(style['height'])/2)),
			'clickable':false
			}

		$super(options, "<div></div>", map);
	},

	getStyle: function() {
		var state=this.map.getState();
		var points=this.BoundingBox.getMapdivPixel(state['zoom'],state['rootx'],state['rooty']);
		var minx,miny,maxx,maxy;
		var sw = points[0];
		var ne = points[1];

		var minx = Math.min(sw.getX(),ne.getX());
		var maxx = Math.max(sw.getX(),ne.getX());

		var miny = Math.min(sw.getY(),ne.getY());
		var maxy = Math.max(sw.getY(),ne.getY());

		var width=Math.abs(maxx-minx);
		var height=Math.abs(maxy-miny);
		if(width<30) { // jak za Å›ledziowate to poszerz
			width+=50;
			minx-=25;
		} else  { // jak OK to dodaj marginesik Å¼eby elegansio byÅ‚o.
			width+=20;
			minx-=10;
		}
		if(height<30) {
			height+=50;
			miny-=25;
		} else {
			height+=20;
			miny-=10;
		}

		return {
			'width': width + "px",
			'height': height + "px",
			'top': miny + "px",
			'left': minx + "px",
			'position': "absolute",
			'border': "solid 3px #ff5a00"
		};
	},
	refresh:function()
	{
		$(this._element).setStyle(this.getStyle())
	}
});

//NAME:TMap
//DESCR:Podstawowa klasa mapy, dziÄ™ki niej mozna zaladowac i wycentrowac mapke o zadanych rozmiarach w dowolnym punkcie Polski
//REQ:TEvent

TMap = Class.create(TEventDispatcher,{
  _draggable: true,
  panObj : false,
  isStateDirty: true,
  /**
   *
   */
  getSize: function(slot)
  {
    var width=slot.getWidth();
    var height=slot.getHeight();

    if(width<30 && height<30) {
      //var ele=Element.absolutize(slot);
      var top=slot.offsetTop;
      var left=slot.offsetLeft;
      width=document.viewport.getWidth()-left-10; // 10px jest body margin
      height=document.viewport.getHeight()-top-15; // piÄ™tnaÅ›cie - tak na oko ;-)
     }

    Element.setStyle(slot, {'width': width + "px", 'height': height + "px"});
    this.vpsize = new TSize(width,height)
  },

  initialize:function(slot) {
    /**
     * Najpierw konfiguracja
     **/
    this.getSize($(slot));
    /**
     * slot to miejsce na stronie gdzie bedzie wpisana mapa
     **/
    this.slot = $(slot)
    this.createStaticLays();
    /**
     * mapdiv to warstwa wzgledem ktorej beda umieszczane Tile
     * ustalamy zarzadce mapy
     **/
    //this.mapdiv = new ZoomGridManager();
    this.mapdiv = new GridManager();
    this.createMovableLays();
    /**
     * Tworzy diva do ktorego beda dolaczane tile
     **/
    this.slot.appendChild(this.mapdiv.getMapDiv());//warstwa z obrazkami
    this.appendNonClickableSlot();                 //nieklikalne statyczne elementy
    this.appendNonClickableMovableLay();           //nieklikalne przesuwalne elementy
    this.createTransparentDiv();				   //transparent div
    this.appendClickableMovableLay();              //klikalne przesuwalne elementy
    this.appendClickableSlot();                    //klikalne nie przesuwalne elementy

    this.transp.style.zIndex = 100
    this.slot.style.zIndex = 1000
    this.mapdiv.element.style.zIndex = 0

    this.gp = new Projection(19);
    this.mapdiv.bindAsEventListener(this,'mapload',this.loadmap)

    this.dshArray=$A(new Array());
  },
  loadmap:function(){
    this.trigger({'e':'load'})
  },
  /**
  zmienia wielkosc obszaru roboczego
  **/
  resize: function(width, height) {
    this.isStateDirty = true
    // zmien wymiary divek ktore musza byc wymiarow jak viewport
    Element.setStyle(this.transp, {'width': width + "px", 'height': height + "px"});
    Element.setStyle(this.slot  , {'width': width + "px", 'height': height + "px"});

    this.vpsize = new TSize(width,height)
    // zmien wyglad i poloÅ¼enie kontrolek w slocie jesli tego wymagaja
    this.mapdiv.resize(this.vpsize)
    this.trigger({'e':'resize'})
    this.clickablestaticlay.refresh()
    this.isStateDirty = true
  },

  createStaticLays:function()
  {
    if(this.slot.getStyle("left")==null || this.slot.getStyle("top")==null)
    { // jesli to zdarzenie z duÅ¼ej mapki
      Element.setStyle(this.slot, {'position': "relative", 'left': "0px", 'top': "0px", 'overflow': "hidden", 'background': "#fff"});
    } else { // jesli to mala mapa to nie pozycjonuj slotu
      Element.setStyle(this.slot, {'position': "relative", 'overflow': "hidden", 'background': "#fff"}); // moÅ¼e nie musi byc relative dla slotu mini mapki.
    }
    this.clickablestaticlay = new TLaysContainer(new TResizeRefreshStrategy(),800)
    this.nonclickablestaticlay = new TLaysContainer(new TResizeRefreshStrategy(),80)

    this.bindAsEventListener(this.clickablestaticlay,'zoomin',this.clickablestaticlay.refresh);
    this.bindAsEventListener(this.nonclickablestaticlay,'zoomin',this.nonclickablestaticlay.refresh);
    this.bindAsEventListener(this.clickablestaticlay,'zoomout',this.clickablestaticlay.refresh);
    this.bindAsEventListener(this.nonclickablestaticlay,'zoomout',this.nonclickablestaticlay.refresh);
    this.bindAsEventListener(this.clickablestaticlay,'resize',this.clickablestaticlay.refresh);
    this.bindAsEventListener(this.nonclickablestaticlay,'resize',this.nonclickablestaticlay.refresh);
  },
  appendClickableSlot: function()
  {
    this.slot.appendChild(this.clickablestaticlay._element);
  },
  appendNonClickableSlot: function()
  {
    this.slot.appendChild(this.nonclickablestaticlay._element);
  },
  createMovableLays: function()
  {
    this.clickmovable=new TLaysContainer(new TZoomRefreshStrategy(this.mapdiv),600);
    this.nonclickmovable=new TLaysContainer(new TZoomRefreshStrategy(this.mapdiv),70);
    this.clickmovable.setToBeginning()
    this.nonclickmovable.setToBeginning()
    this.bindAsEventListener(this.clickmovable,'move',this.clickmovable.setToBeginning);
    this.bindAsEventListener(this.nonclickmovable,'move',this.nonclickmovable.setToBeginning);
    this.bindAsEventListener(this.clickmovable,'zoomin',this.clickmovable.refresh);
    this.bindAsEventListener(this.nonclickmovable,'zoomin',this.nonclickmovable.refresh);
    this.bindAsEventListener(this.clickmovable,'zoomout',this.clickmovable.refresh);
    this.bindAsEventListener(this.nonclickmovable,'zoomout',this.nonclickmovable.refresh);
    this.bindAsEventListener(this.clickmovable,'centerset',this.clickmovable.refresh);
    this.bindAsEventListener(this.nonclickmovable,'centerset',this.nonclickmovable.refresh);
    this.bindAsEventListener(this.clickmovable, 'resize', this.clickmovable.refresh);
    this.bindAsEventListener(this.nonclickmovable, 'resize', this.nonclickmovable.refresh);
  },

  appendClickableMovableLay: function()
  {
    this.slot.appendChild(this.clickmovable._element);
  },

  appendNonClickableMovableLay: function()
  {
    this.slot.appendChild(this.nonclickmovable._element);
  },
  /**
   * Dolacza przezroczysty div do mapslota i podpina pod niego obsluge przesuwania
   */
  createTransparentDiv:function() {
    this.transp=document.createElement("div");
    var h = {'position': "absolute",
        'overflow': "hidden",
 //       'zIndex': "1000",
        'top': "0px",
        'left': "0px",
        'width': this.vpsize.width + "px",
        'height': this.vpsize.height + "px",
        'background': "url(img.mapa/transparent.gif)"
    };
    Element.setStyle(this.transp, h);
    this.slot.appendChild(this.transp);

	if(this._draggable)
		this.dsh=new DragScrollableStandard(this.transp, null);
	else
		this.dsh=new DragScrollableFreez(this.transp, null);

	this.dsh.map=this;
	this.dsh.rebuild();
  },

  addStaticOverlay: function(element)
  {
    element.setMap(this)
    this.clickablestaticlay.attach(element);
   },
  setCenter: function(lnglat,zoom) {
     this.center = lnglat
     this.zoom   = parseInt(zoom)
     this.isStateDirty=false
     this.mapdiv.load(this.vpsize,lnglat,zoom)
     this.trigger({'e':'centerset','zoom':zoom,'center':lnglat})
  },
  panAndScaleToBBox:function(bbox)
  {
    this.setCenter(bbox.getCenter(),bbox.getBestZoom(this))
  },

	enableDragging: function() {
		// wypnij obecnÄ… DragScrollable
		this.dsh.destroy();
		// wepnij nowe DragScrollable
		this.dsh=new DragScrollableStandard(this.transp, null);
		this.dsh.map=map;
		this.dsh.rebuild();
	},

	disableDragging: function() {
		// wypnij obecnÄ… DragScrollable
		this.dsh.destroy();
		// wepnij nowe DragScrollable
		this.dsh=new DragScrollableFreez(this.transp, null);
		this.dsh.map=map;
		this.dsh.rebuild();
	},

	loadDsh: function(dsh) {
		if(this.dshArray.length==0)
			this.dsh.destroy();

		dsh.map=this;
		dsh.rebuild();
		this.dshArray.push(dsh);
	},

	unLoadDsh: function(dsh) {
		var index=this.dshArray.indexOf(dsh);
		if(index!=-1) {
			this.dshArray[index].destroy();
			delete this.dshArray[index];
			this.dshArray=this.dshArray.compact();
		}

		if(this.dshArray.length==0)
			this.dsh.rebuild();
	},

  addOverlay:function(lay) {
    if(lay instanceof TMapAbstract) {
      lay.setMap(this);
      lay.draw();
    }
    else
      this.addMsg("probujesz osadzic na mapie nieosadzalny obiekt");
    return lay;
  },

  getContainer: function() {
    return this.slot;
  },

  getViewPortBB: function(padding)
  { //padding obiekt typu TSize
      if (padding == undefined)
      {
        var padding = new TSize(0,0);
      }
    return new TBoundingBox((new TPoint(padding.getWidth(), padding.getHeight())).getViewPortLL(this), (new TPoint(this.vpsize.getWidth()-padding.getWidth(), this.vpsize.getHeight()-padding.getHeight())).getViewPortLL(this));
  },

  getCenter:function()
  {
    if(this.isStateDirty)
    {
      this.center = (new TPoint(this.vpsize.width/2,this.vpsize.height/2)).getViewPortLL(this)
      this.isStateDirty = false
    }
    return this.center
  },
  getTechState:function()
  {
    return {
    'zoom':this.mapdiv.zoom,
    'rootx':this.mapdiv._rootx,
    'rooty':this.mapdiv._rooty
    }
  },
  getZoom: function()
  {
    return this.mapdiv.zoom;
  },

  move: function(dx, dy)
  {
    this.mapdiv.move(dx, dy);
    this.isStateDirty = true;
    this.trigger({'e':'move','dx':dx,'dy':dy})
  },

/*  zoomin:function(center)
  {

      try{
        if(this.mapdiv.zoom>17) return
        if(!center)
        {
           var center = this.getCenter()
        }

        if(!(center instanceof TLngLat) )
        {
          var center = (new TPoint(center[0],center[1]).getViewPortLL(this))
        }

        var zoom = this.zoom+1
        var mapdivcenter = this.getCenter().getMapDivPixel(this.getTechState())

        var mintileposition = this.mapdiv.getMinTilePosition()
        var gridcenter = [mintileposition[0]-mapdivcenter.getX(),mintileposition[1]-mapdivcenter.getY()]
        if(mapdiv instanceof ZoomGridManager)
        {
          this.mapdiv.zoomm(gridcenter,this.vpsize,center,this.zoom+1)
        }

      }
      catch(e)
      {
        console.debug(e)
      }
      this.trigger(e)
  },*/

	zoomin:function(center) {
		if(this.mapdiv.zoom>17)
			return
		if(!center)
			var center = this.getCenter();
		if(!(center instanceof TLngLat))
			var center = (new TPoint(center[0],center[1]).getViewPortLL(this))
		var zoom = this.zoom+1;
		try {
			this.setCenter(center,zoom);
		} catch(e) {}
		this.trigger({	'e': 'zoomin',
						'zoom': zoom,
						'center': center});
	},

  zoomout:function(center)
  {
      try{
      if(this.mapdiv.zoom<7) return
      if(!center)
      {
         var center = this.getCenter()
      }
      if(!(center instanceof TLngLat))
      {
          var center = (new TPoint(center[0],center[1]).getViewPortLL(this))
      }
      var zoom = this.zoom-1
      e = {'e':'zoomout','zoom':zoom,'center':center}
      this.setCenter(center,zoom)
      this.trigger(e)
      }catch(e){}
  },
  pan: function(direction, px) {

    if(px>Math.max(this.vpsize.getWidth(),this.vpsize.getHeight()))
    {
      this.setCenter((new TPoint(px+this.vpsize.getWidth()/2,px+this.vpsize.getHeigth()/2)).getViewPortLL(this),this.zoom)
    }
    else{
      if(!this.panObj)
      {
        this.panObj = new TPanTo(this);
      };
      this.panObj.start(direction[0]*px, direction[1]*px);
    }
  },

  /*
   * Przesuwa mape do wyznaczonego punktu
   * Pobiera:
   * - wspolrzedne geograficzne miejsca do ktorego na sie przesunac mapa
   * - x i y [px] o ile bedzie przesuniete miejsce docelowe przesuniecia wzgledem srodka viewportu.
   * Nie zwraca wartosci
   */

  panTo: function(lnglat, x, y) {
    try{
    if(!x)
      x=0;
    if(!y)
      y=0;
    var start = new TPoint(this.vpsize.getWidth()/2-this.mapdiv._minx, this.vpsize.getHeight()/2-this.mapdiv._miny);
    var stop = lnglat.getMapdivPixel(this.zoom, this.mapdiv._rootx, this.mapdiv._rooty);
    var vector = new TPoint(start.getX()-stop.getX()+x, start.getY()-stop.getY()+y)

    if(vector.getX()>this.vpsize.getWidth()||vector.getY()>this.vpsize.getHeight())
    {
      var ll = (new TPoint(stop.getX()+x,stop.getY()+y)).getMapDivLL(this.getTechState())
      this.setCenter(ll,this.mapdiv.zoom)
      return
    }

    if(!this.panObj)
    {
      this.panObj = new TPanTo(this);
    }
    this.panObj.start(vector.getX(),vector.getY());
    }catch(e){}
  },

  getMapdivBB: function() {
    if(this.isStateDirty||!this.vpbb)
    {
    var tiles = [this.mapdiv.getMaxTile(),this.mapdiv.getMinTile()];
    var p = [this.tc.tileToGeo(tiles[0][0],tiles[0][1]),this.tc.tileToGeo(tiles[1][0],tiles[1][1])];
    var latlan1 = [p[0][1][0],p[0][0][1]];
    var latlan2 = [p[1][0][0],p[1][1][1]];
    this.vpbb = new TBoundingBox(new TLngLat(latlan1[0],latlan1[1]),new TLngLat(latlan2[0],latlan2[1]));
    }
    return this.vpbb
  },

  getObjectsByGroup:function(GroupArray)
  {
    return this.clickmovable.getObjectsByGroup(GroupArray).concat(
         this.nonclickmovable.getObjectsByGroup(GroupArray)).concat(
         this.clickablestaticlay.getObjectsByGroup(GroupArray)).concat(
         this.nonclickablestaticlay.getObjectsByGroup(GroupArray))
  },
  clearGroup: function(arguments) {
    this.clickmovable.clearGroup(arguments)
    this.nonclickmovable.clearGroup(arguments)
      this.clickablestaticlay.clearGroup(arguments)
    this.nonclickablestaticlay.clearGroup(arguments)
  },

  /**
   * @return {zoom:int, rootx:int, rooty:int,center:TLatTlan,scale:int}
   */
  getState:function()
  {
    return {'center': this.getCenter(),'zoom': this.mapdiv.zoom};

  },

  getViewPortWidth: function() {
    return this.vpsize.getWidth();
  },

  getViewPortHeight: function() {
    return this.vpsize.getHeight();
  },

  getByClass:function()
  { // przyjmujÄ™ dowolnÄ… liczbÄ™ argumentÃ³w po przecinku, moÅ¼e teÅ¼ byÄ‡ tablica
    if(typeof(arguments[0][0])!="undefined") // uwaga:arguments nie jest tablicÄ… rozszerzonÄ… przez prototype'a
    { // jeÅ›li ktoÅ› podaÅ‚ za argument tablicÄ™ to arguments bÄ™dÄ… tablicÄ… jednoelementowÄ… zawierajÄ…ca tablicÄ™
      arguments=arguments[0];
    } //i na koniec pobieramy ze wszystkich
    return this.clickmovable.getByClass(arguments).concat(
            this.nonclickmovable.getByClass(arguments)).concat(
            this.clickablestaticlay.getByClass(arguments)).concat(
            this.nonclickablestaticlay.getByClass(arguments))
  },
  clearByClass:function()
  { // jest to czÄ™sto uÅ¼ywane wiÄ™c taka metoda jest przydatna
    this.getByClass(arguments).invoke('clear');
  },
  hideByClass:function()
  { // ukrywamy wszystkie elementy danej klasy
    this.getByClass(arguments).invoke('hide');
  },
  showByClass:function(classes, exclude)
  {
    this.getByClass(arguments).invoke('show');
  },

	// ta funkcja powinna byÄ‡ wyparta na rzecz zdarzeÅ„ syntetycznych oraz bezpoÅ›redniej obsÅ‚ugi zdarzeÅ„ myszy
  getMouseLngLat:function(event)
  {
    return this.dsh.mousedata(event)['lnglat']
  },
  centerOrReset:function()
  {
    if (this.getObjectsByGroup(['marker']).length > 0)
    {
      tmp = this.getObjectsByGroup(['marker'])[1];
      this.setCenter(tmp.options.get('point'),this.mapdiv.zoom);
    }
    else
    {
      this.setCenter(new TLngLat(19.50625,52.02510758314961),7);
    }
  }
});

/*
TPanTo = Class.create({
  initialize:function(obj)
  {
    this.obj=obj;
  },

  start :function(dx, dy)
  {
    this.dy0 = dy;
    this.dx0 = dx;
    this.dx    = dx;
    this.dy    = dy;
    this.prox  = 20;
    this.proy  = Math.abs(dy/dx)*this.prox;
    this.interv = window.setInterval(this.move.bind(this),1);
  },

  move:function()
  {
    var dx = parseInt(Math.min(Math.abs(this.dx/Math.sqrt(Math.abs(this.dx))),this.prox)*this.signum(this.dx))||0
    var dy = parseInt(Math.min(Math.abs(this.dy/Math.sqrt(Math.abs(this.dy))),this.proy)*this.signum(this.dy))||0

    if(Math.abs(dx)+Math.abs(dy)>=1) {
      this.obj.move(dx,dy);
      this.dx-=dx;
      this.dy-=dy;
    } else {
      this.obj.move(dx, dy);
    }
  },

  stop: function(obj)
  {
    window.clearInterval(this.interv);
    this.obj.trigger({'e':'panend'})
  },
  signum: function(number) {
    return number/Math.abs(number);
  }
});*/

TPanTo = Class.create({
	initialize:function(obj)
	{
		this.obj=obj;
	},

	start :function(dx, dy)
	{
		this.dy0 = dy;
		this.dx0 = dx;
		this.dx    = dx;
		this.dy    = dy;
		this.prox  = 20;
		this.proy  = Math.abs(dy/dx)*this.prox;
		this.move();
	},

	move:function()
	{
		var dx = parseInt(Math.min(Math.abs(this.dx/Math.sqrt(Math.abs(this.dx))),this.prox)*this.signum(this.dx))||0
		var dy = parseInt(Math.min(Math.abs(this.dy/Math.sqrt(Math.abs(this.dy))),this.proy)*this.signum(this.dy))||0

		if(Math.abs(dx)+Math.abs(dy)>=1) {
		this.obj.move(dx,dy);
		this.dx-=dx;
		this.dy-=dy;
		window.setTimeout(this.move.bind(this), 1);
		} else {
		this.obj.move(dx, dy);
		this.obj.trigger({	'e': 'panend',
							'vector': new Array(this.dy0, this.dx0)});
		return;
		}
	},

	signum: function(number) {
		return number/Math.abs(number);
	}
});


TUnload = function()
{

  // No tu powinno byc cos co zwalnia pamiec po sieczce spowodowanej nasza mapa.

}

TCounter = 1
TRequester = Class.create(
{
  options :
  {
    asynchronous:true,
    contentType:'application/x-www-form-urlencoded',
    encoding:"UTF-8",
    method:'GET'
  },
  baseurl : "/",
  initialize: function(sname, params,method)
  {
    if(method){
      this.options.method= method
    }
    this.options.parameters = params
    this.url = this.baseurl+sname
  },
  doRequest:function()
  {
    this.options.onSuccess=this.onSuccess
    this.options.onFailure=this.onFailure
    this.options.oid = this.oid

    var parameters = $H(this.options.parameters)
    parameters.set('v','j')

    this.options.parameters=parameters.toObject()
    if(this.options.method=="POST"|this.options.method=="GET")
    {
	    this.req = new Ajax.Request(this.url,this.options)
    }
	else
	{
		this.req = new THamletRequest(this.url, this.options)
	}
  }
}
)

TGETRequester = Class.create(TRequester,
{
  initialize: function($super,sname, params)
  {
    $super(sname,params,'GET')
  }
}
)

TPOSTRequester = Class.create(TRequester,
{
  initialize: function($super,sname, params)
  {
    $super(sname,params,'POST')
  }
}
)

TELSERequester = Class.create(TRequester,
{
  initialize: function($super,sname, params,server)
  {
	if(server) this.baseurl=server
    $super(sname,params,'ELSE')
  }
}
)

THamletRequest= Class.create({
	initialize: function(url,options)
	{
		options.parameters['v']='e'
		options.oid = TCounter++
		this.options = options
		parameters = $H(options.parameters).toQueryString()
		TEvent.bindAsEventListener(options,'load',options.onSuccess);
		TEvent.bindAsEventListener(options,'error',options.onFailure);
		e = document.createElement("script")
		e.src = url+'?'+parameters+'&oid='+options.oid
		document.body.appendChild(e);
	},
	onsuccess:function(e)
	{
 	  this.options.onSuccess(e)
	},
	onfailure:function(e)
	{
		this.options.onFailure(e)
	}

}
)



/**
 * Creates a control with buttons to pan in four directions,
 * and zoom in and zoom out.
 */
TLargeMapControl = Class.create(TMapAbstract,{
  sizex: 50,
  totalsizex: 10,
  group:'TLargeMapControl',
  controls: new Array(),

	initialize: function(zindex)
	{
		this.element = document.createElement("div");
		Element.setStyle(this.element, {'position': "absolute", 'left': "0px", 'top': "0px"});
	},

	setMap:function(map)
	{
		this.map = map;
		this.init();
	},

	init: function()
	{
		var r1 = new SubControlRowStyle(this.totalsizex,27,60,0)
		this.slider = new TScaleSliderControl(this.map, r1.getStyle(1,1,123))
		this.controls.push(new ControlElement(this.map,new SubControlType("up"),r1.getStyle(1,1,5),"this.map.pan([0,1],200)"))
		this.controls.push(new ControlElement(this.map,new SubControlType("left"),r1.getStyle(2,1,35),"this.map.pan([1,0],200)"))
		this.controls.push(new ControlElement(this.map,new SubControlType("fitmap"), r1.getStyle(1,1,35), "this.map.centerOrReset()"));
		this.controls.push(new ControlElement(this.map,new SubControlType("right"),r1.getStyle(2,2,35),"this.map.pan([-1,0],200)"))
		this.controls.push(new ControlElement(this.map,new SubControlType("down"),r1.getStyle(1,1,65),"this.map.pan([0,-1],200)"))
		this.controls.push(this.slider)
		this.controls.push(new ControlElement(this.map,new SubControlType("zoomin"),r1.getStyle(1,1,105),"this.map.zoomin()"))
		this.controls.push(new ControlElement(this.map,new SubControlType("zoomout"),r1.getStyle(1,1,240),"this.map.zoomout()"))
		for(i=0;i<this.controls.length;i++) {
			this.element.appendChild(this.controls[i].getElement());
		}
	},

	refresh: function()
	{
	  this.slider.refresh()
	},

	clear: function() {
		this.map.clickablestaticlay.detach(this);
	},
    draw: function() {
		this.map.clickablestaticlay.attach(this);
	},
	onSetCenter: function(MapState) {
		this.refresh();
	}

});


SubControlRowStyle = Class.create({
	initialize: function(width,height,ewidth,top)
	{
		this.width  = width;
		this.ewidth = ewidth;
		this.height = height;
		this.top    = top
	},
	/**
	 * podaje object hash z wyliczona pozycja w zaleznosci od ilosci
	 * elementow w wierszu i numerku pozycji
	 */
	getStyle: function(count,curr,offsettop)
	{
		var t = Math.floor((this.width-count*this.ewidth)/2);
		var x = t+curr*this.ewidth;
		var y = this.top+offsettop;
		return {"top":y+"px","left":x+"px","position":"absolute"};
	}

});

SubControlType = Class.create({
	initialize:function(type)
	{
		this.type = type;
		this.img = "img.mapa/mapcontrol.png";
		this.size=[27,28];
		switch(type)
		{
			case "up":
				this.pos = [-162,0];
			break;
			case "down":
				this.pos = [0,0];
			break;
			case "left":
				this.pos = [-54,0];
			break;
		  case "right":
				this.pos = [-108,0];
			break;
			case "zoomin":
				this.pos = [0,0];
				this.size=[27,20];
				this.img = "img.mapa/mapcontroll/zoom_in_active.png";
			break;
			case "zoomout":
				this.pos = [0,0];
				this.size=[27,20];
				this.img = "img.mapa/mapcontroll/zoom_out_active.png";
			break;
			case "fitmap":
				this.pos = [-216,0];
			break;
		}
		this.start = '<div style="width:'+this.size[0]+'px;height:'+this.size[1]+'px;background:url('+this.img+') no-repeat; background-position: ';
		this.end = '"></div>';
	},

	toHTML:function(passive)
	{ // trzeba to poprawiÃ„Â‡ a najlepiej przepisaÃ„Â‡ caÄ¹Â‚ego pajÄ¹Ä„ka
	    if (this.type == "zoomin" && passive)
	    {
	      return '<div style="width:'+this.size[0]+'px;height:'+this.size[1]+'px;background:url(img.mapa/mapcontroll/zoom_in_passive.png) no-repeat;"></div>';
	    }
	    if (this.type == "zoomout" && passive)
	    {
	      return '<div style="width:'+this.size[0]+'px;height:'+this.size[1]+'px;background:url(img.mapa/mapcontroll/zoom_out_passive.png) no-repeat;"></div>';
	    }

	    if (passive)
	    {
	      return this.start+(this.pos[0]-27)+"px "+(this.pos[1])+"px"+this.end;
	    }
		return this.start+(this.pos[0])+"px "+(this.pos[1])+"px"+this.end;
	}
});

ControlElement = Class.create(
{
	initialize: function(map,type,style,command)
	{
		this._element = document.createElement("div");
		this.map = map;
        this.passive = type.toHTML(true);
		this.active = type.toHTML(false);
		this.command = command;

		Element.update(this._element, this.active);
		Element.setStyle(this._element,style);

		Event.observe(this._element,'mousedown',this.mousedown.bindAsEventListener(this),false);
		Event.observe(this._element,'mouseup',this.mouseup.bindAsEventListener(this),false);
	},
	mousedown:function()
	{

    Element.update(this._element, this.passive);
	},
    mouseup:function(e)
	{
     Element.update(this._element, this.active);
     eval(this.command);
	},
	getElement:function()
	{
	    return this._element
	}
});

TScaleSliderControl = Class.create({
	group:'TScaleSliderControl',
	initialize:function(map, style)
	{
		this._element = document.createElement("div");
		var elementStyle = style;

		var handle = document.createElement("div");
		var handleStyle={width:"33px",height:"7px","background":"url(img.mapa/mapcontroll/slide_handler.png) no-repeat",cursor: "move"};

		var track   = document.createElement("div");
		var trackStyle = {"background":"url(img.mapa/mapcontroll/slide_vertical.png) 4px 0px no-repeat", width:"19px", height:"119px"};

		Element.setStyle(this._element, elementStyle);
		Element.setStyle(handle, handleStyle);
		Element.setStyle(track, trackStyle);

		track.appendChild(handle);
		this._element.appendChild(track);
		this.slider = new Control.Slider(handle,track,{
											range:$R(19,5),
											values: [18,17,16,15,14,13,12,11,10,9,8,7,6],
											axis:"vertical",
											onChange:function(v){
												if(v!=this.map.zoom)
													this.map.setCenter(this.map.getCenter(), v);
											}.bind(this)
		});
		this.map = map;
		this.slider.setValue(this.map.zoom);

		this.map.bindAsEventListener(this, 'zoomin', this.refresh);
		this.map.bindAsEventListener(this, 'zoomout', this.refresh);
		this.map.bindAsEventListener(this, 'centerset', this.refresh);
	},

	refresh: function()
	{
        this.slider.setValue(this.map.zoom);
	},

	getElement: function() {
		return this._element;
	}
});

TMarkerDragController = Class.create(TEventController,{
	initialize: function($super, model) {
		$super(model);
		this.dsh=new DragScrollableMarkerMove(document.body, this);
	},

	getEvents:function() {
		return {'mousedown': this.onMouseDown};
	},

	onMouseDown:function(event)
	{
	    this.fix = new TPoint(0, this.model.options.get('size').getHeight()/2);

		this.funcMouseMove=this.onMouseMove.bindAsEventListener(this);
		this.funcMouseUp=this.onMouseUp.bindAsEventListener(this);

		Event.observe(this.model.element, 'mousemove', this.funcMouseMove);
		Event.observe(this.model.element, 'mouseup', this.funcMouseUp);
		Event.observe(this.model.element, 'mouseout', this.funcMouseMove);
	},

	onMouseMove: function(event) {
		this.model.map.loadDsh(this.dsh);

		this.model.trigger({'e': 'dragstart'});
		Event.stopObserving(this.model.element, 'mousemove', this.funcMouseMove);
		Event.stopObserving(this.model.element, 'mouseup', this.funcMouseUp);
		Event.stopObserving(this.model.element, 'mouseout', this.funcMouseMove);
	},

	onMouseUp:function(event)
	{
		Event.stopObserving(this.model.element, 'mousemove', this.funcMouseMove);
		Event.stopObserving(this.model.element, 'mouseup', this.funcMouseUp);
		Event.stopObserving(this.model.element, 'mouseout', this.funcMouseMove);
		this.model.trigger({'e':'click'});
	}
});

DragScrollableMarkerMove=Class.create(DragScrollableAbstract, {
	defaultCursor: "url(img.mapa/cursors/closedhandmarker.cur), move",

	rebuild: function() {
		this.eventMouseMove=this.funcMouseMove.bindAsEventListener(	this.context,
																	this.arbitraryMouseData.bind(this.map),
																	this.triggerArtEvent.bind(this.map));
		this.eventMouseUp=this.funcMouseUp.bindAsEventListener(	this.context,
																this.arbitraryMouseData.bind(this.map),
																this.triggerArtEvent.bind(this.map));

		Event.observe(this.element, 'mousemove', this.eventMouseMove);
		Event.observe(this.element, 'mouseup', this.eventMouseUp);
	},

	destroy: function() {
		Event.stopObserving(this.element, 'mousemove', this.eventMouseMove);
		Event.stopObserving(this.element, 'mouseup', this.eventMouseUp);
	},

	funcMouseMove: function(event, mouseData, eventTrigger) {
		var tmp = TPoint.sum(mouseData(event)["mapdiv_point"], this.fix);
		this.model.options.set('point', tmp.getMapDivLL(this.model.map.getTechState()));
		this.model.refresh();
	},

	funcMouseUp: function(event, mouseData, eventTrigger) {
		this.element.style.cursor="url(img.mapa/cursors/openhandmarker.cur), default";

		var opx = this.model.options.get('point').getMapDivPixel(this.model.map.getTechState());
		var panto = TPoint.subtract(opx,new TPoint(0,150)).getMapDivLL(this.model.map.getTechState());
		this.model.map.panTo(panto);

		this.model.map.unLoadDsh(this.dsh);


		this.model.trigger({	'e': 'dragend',
								'g': this.model.options.get('point')});

	}
});

TMarkerTemplate = new Template('<div style="#{src};width:100%;height:100%"/>')

TClickController = Class.create(TEventController,
{
  getEvents:function()
  {
  	return {'click':this.onclick}
  },
  onclick:function()
  {
  	this.model.trigger({'e':'click'})
  }
})

TMarker = Class.create(TMapMovableLayer,
{
	defaultoptions:{
    'image':'http://wmapa.pl/img.mapa/marker.png',
    'shadowimage':'http://wmapa.pl/img.mapa/marker_shadow.png',
    'shadowsize':new TSize(43,26),
    'shadowdisplacement':new TPoint(16, 34),
    'clickable': true,
    'template':TMarkerTemplate,
    'size':new TSize(37,57),
    'displacement': new TPoint(19, 57)
    },

    group:"marker",

	initialize:function($super,g,options,map)
	{
		var options = $H(this.defaultoptions).merge($H(options))
		options.set('point',g)
		if(!options.get('pathcontroller'))
		{
			options.set('pathcontroller',[{'path':'','controller':TClickController}])
		}
		else
		{
			options.get('pathcontroller').push({'path':'','controller':TClickController})
		}

		if(options.get('draggable'))
		{
			options.get('pathcontroller').push({'controller':TMarkerDragController,'path':''})
		}

		options.set('substitute',{'src':AlphaImage.create(options.get('image'))})

        var shadowoptions = options.clone()
        shadowoptions.set('pathcontroller',[])
        shadowoptions.set('controller',[])
        shadowoptions.set('o2controllers',[])
        shadowoptions.set('clickable',false)
        shadowoptions.set('substitute',{'src':AlphaImage.create(options.get('shadowimage'))})
		shadowoptions.set('size',options.get('shadowsize'))
		shadowoptions.set('displacement',options.get('shadowdisplacement'))
        this.shadow = new TMapMovableLayer(shadowoptions,'',map)/**/
        this.shadow.group="marker"

		$super(options,'',map)
	},

	setMap:function($super,map)
	{
		this.shadow.setMap(map)
		$super(map)
	},
	draw:function($super)
	{
		this.shadow.draw();
		$super()
		if(this.options.get('draggable'))
			this.element.style.cursor="url(img.mapa/cursors/openhandmarker.cur), default";
	},

	clear:function($super)
	{
		this.shadow.clear()
		$super()
	},
	hide:function($super)
	{
		this.shadow.hide()
		$super()
	},
	toggle:function($super)
	{
		this.shadow.toggle()
		$super()
	},
	show:function($super,map)
	{
		this.shadow.show(map)
		$super(map)
	},
	refresh:function($super)
	{
		this.shadow.options.set('point',this.options.get('point'));
		this.shadow.refresh();
		$super();
	}

});

/**
controller do markera pokazujacy zawartosc dymka, po kliknieciu
*/
TMarkerWithContentController = Class.create(TElseController,{
   getEvents:function()
   {
     this.model.bindAsEventListener(this,'dragstart',this.ondragstart)
     return {'click':this.onclick}
   },
   onclick:function()
   {
     if (!this.cloud||!this.cloud.getElement())
     {
	    if(this.model.options.get('windowsize'))
	    {
    	  var size = this.model.options.get('windowsize')
	    }
    	else
     	{
       	  var size = new TSize(343,250)
	    }
    	 var options = {
     				'point':this.model.options.get('point'),
     		        'size':size,
            		'single':true,
              		'extradisplacement':new TPoint(-this.model.options.get('size').width/2,this.model.options.get('size').height),
              		'clickable':true
                };
	     this.cloud=(new TWindow(options,''+this.model.options.get('content')+'',this.model.map));
    	 this.cloud.draw();
    	 this.cloud.show();
	     return
     }
     else{

     	 this.cloud.options.set('point',this.model.options.get('point'))
       if (Prototype.Browser.IE)
       {
          if (this.cloud.getElement()&&Object.isElement(this.cloud.getElement()))
          {
            this.cloud.clear();
          }
          else
          {
            this.cloud.draw();
          }
       }
       this.cloud.refresh()
	     if (!Prototype.Browser.IE)
	     {
	       this.cloud.toggle();
	     }
	 }

   },
   ondragstart:function()
   {
      if (Prototype.Browser.IE && this.cloud)
      {
        this.cloud.clear();
        return
      }
	   	if(this.cloud)	{
	   		this.cloud.hide()
	   	}
   }
});

TWindowTemplate = new Template('<div style="width: 100%;height:100%; position: relative; margin: 0px; border: solid #fff; border-width: 1px 1px 0px 1px"><div style="border: solid #000; border-width: 1px 1px 0px 1px;height:100%;">'+
			'<table cellpadding="0px" cellspacing="0px" style="height:100%;width:100%;border:0;margin: 0px; background-color: #fff9c1">' +
			'	<tr><td style="font-size: 12px;height:100%" valign="top"><div style="margin:15px"><img src="http://wmapa.pl/img.mapa/dymek_zamykanie.png" alt="x" class="t_note_closer" style="float: right; cursor: pointer" />'+
			'#{content}</div></td></tr></table></div></div>')
TWindowShadowTemplate = '<img src="http://wmapa.pl/img.mapa/dymek/dymek_dol_zolty.png"/>'
TWindow=Class.create(TMapMovableLayer, {
	group:"TWindow",
	initialize: function($super, options,content,map) {
		var defaultoptions =  $H({
		     'size'		   : new TSize(343,250),
		     'clickable':true,
		     'extradisplacement':new TPoint(0,0),
		     'template':TWindowTemplate
		     })
		 if(content){
		 	defaultoptions.set('substitute',{'content':content})
		 }
		 var options = $H(defaultoptions).merge($H(options))
		 if(options.get('pathcontroller'))options.get('pathcontroller').push({'controller': TClosingController, 'path': "img.t_note_closer"})
		 else {options.set('pathcontroller',[{'controller': TClosingController, 'path': "img.t_note_closer"}])}
		 options.set('displacement',new TPoint(32+options.get('extradisplacement').getX(),options.get('size').height+31+options.get('extradisplacement').getY()))

 		 var shadowoptions = {   'point' : options.get('point'),
 		 						 'displacement': new TPoint(32+options.get('extradisplacement').getX(), 29+options.get('extradisplacement').getY()),
							     'size'		   : new TSize(343,30),
		     					 'clickable':false}
         this.shadow = new TMapMovableLayer(shadowoptions,TWindowShadowTemplate,map)

 		 $super(options,'', map);

	},
	draw:function($super)
	{
		this.shadow.draw()
		$super()
	},
	clear:function($super)
	{
		this.shadow.clear()
		$super()
	},
	setMap:function($super,map)
	{
		this.shadow.setMap(map)
		$super(map)
	},
	hide:function($super)
	{
		this.shadow.hide()
		$super()
	},
	toggle:function($super)
	{
		this.shadow.toggle()
		$super()
	},
	show:function($super,map)
	{
		this.shadow.show(map)
		$super(map)
	},

	refresh:function($super)
	{
		this.shadow.options.set('point',this.options.get('point'))
		this.shadow.refresh()
		$super()
	}
});


TMapMessageTemplate=new Template(TPanelTemplate.evaluate({'body':'<div style="text-align: center">#{msg}<br /><br /><img src="img.mapa/wait.gif" alt="Czekaj..."></div>'}));

TMapMessage = Class.create(TMapStaticLayer,
{
	group:"TMapMessage",
	defaultoptions:{'positioner':new TCenterMiddlePositionStrategy(),
					'template':TMapAlertTemplate,
					'pathcontroller':[{'controller': TClosingController,'path':'img.close'}],
					'style':{'border': 'solid white 1px','background':'#fff'},
					'size':new TSize(400,200),
					'clickable':true
					},
	initialize:function($super, msg, map, options)
	{
		options = $H(options)
		options.set('substitute',{'msg':msg})
		$super(options,'', map );
	}

});



TMapConfirmTemplate=new Template(TPanelTemplate.evaluate(
	{'body':'<div style="text-align: center">#{msg}<br /><br /><input class="false_button" type="image" src="img.mapa/anuluj.png" />'+
	'&nbsp; &nbsp;<input class = "true_button" type="image" src="img.mapa/dalej.png" /></div>'
	}
));


TConfirmController=Class.create(TEventController,{
	getEvents:function()
	{
		return {'click':this.onClick}
	},
	onClick:function(){
		this.model.confirm()
	}
});

TDeclineController=Class.create(TEventController,{
	getEvents:function()
	{
		return {'click':this.onClick}
	},
	onClick:function(){
		this.model.decline()
	}
});


TMapConfirm = Class.create(TMapStaticLayer,
{
	group:"TMapConfirm",
	defaultoptions:{'positioner':new TCenterMiddlePositionStrategy(),
					'template':TMapConfirmTemplate,
					'pathcontroller':[{'controller': TDeclineController,'path':'.false_button'},{'controller': TDeclineController,'path':'img.close'},
					{'controller':TConfirmController,'path':'.true_button'}
					],
					'style':{'border': 'solid white 1px','background':'#fff'},
					'size':new TSize(400,145),
					'clickable':true
					},
	initialize:function($super, msg, map, truefunct, falsefunct)
	{
		options = $H({})
		options.set('substitute',{'msg':msg})
		if(truefunct){
			this.truefunc=truefunct;
			}else{
				this.truefunc=function(){}
			}
		if(falsefunct){
			this.falsefunct=falsefunct;
			}else{
				this.truefunc=function(){}
			}
		$super(options,'',map );
	},
	confirm:function()
	{
		this.clear();
		this.trigger({'e':'confirm'})

	},
	decline:function()
	{
		this.clear()
		this.trigger({'e':'decline'})

	}
});

TZoomLayer = Class.create(TMapStaticLayer,
{
  skale:{6:200,7:100,8:50,9:25,10:10,11:5,12:2,13:1,14:1,15:0.5,16:0.2,17:0.1,18:0.05},
  initialize: function($super, map)
  {
        this.map=map;
        map.bindAsEventListener(this,'moveend',this.showScale);
        map.bindAsEventListener(this,'centerset',this.showScale);
        map.bindAsEventListener(this,'zoomin',this.showScale);
        map.bindAsEventListener(this,'zoomout',this.showScale);

        var options={
                 'style' : {'background':'none'},
                 'clickable': false,
                 'size': new TSize(150,37),
                 'positioner' : new TLeftBottomPositionStrategy()
                 };
        this.div = document.createElement('div');
        this.div.style.cssText='padding-left:8px;';
        this.div.innerHTML='<img src="/img.mapa/skala_left.png" /><img src="/img.mapa/skala_center.png" class="skala_center" /><img src="/img.mapa/skala_right.png" /><p class="skala_info"></p>';
        $super(options, this.div, map);
        this.draw();
        $$('.skala_info')[0].style.cssText='margin:1px;text-align:center;font-size:9px;';
  },
  draw:function($super)
  {
    $super();
    this.showScale();
  },
  showScale:function()
  {
    var x = this.getScale();
    this.options.set('size',new TSize((x[1]+21),37));
    this.refresh();
    $$('.skala_center')[0].setStyle({'width':x[1]+'px','height':'4px'}); // do rozwa¿enia jest animowanie zmian javascriptem
    this.skalapx=x[1];
    if (x[0] >= 1)
    {
      var text = x[0]+' km';
    }
    else
    {
      var text = (x[0]*1000)+' m';
    }
    $$('.skala_info')[0].innerHTML=text;
  },
  getScale:function()
  {
    var tmp = new TPoint(this.map.vpsize.width/2,this.map.vpsize.height/2);
    var p1 = tmp.getViewPortLL(this.map); // pobieramy centrum mapy w TLngLat
    tmp.setX(tmp.getX()+100); // punkt o 100px dalej ni¿ centrum mapy
    var p2 = tmp.getViewPortLL(this.map); // pobieramy TLngLat tego punktu

    var realdistance = TLngLat.getDistance(p1,p2);
    var dist = this.skale[this.map.zoom];
    var px = Math.round(dist*100/realdistance);

    return [dist,px];
  }
});
