

// Pan is the default navigation modus. Therefore we activate it right on application start
initPanEvents();

var tilearray   = new Array();  //keeps tilemap, clickcatcher and poiMarker object and position
var dragObject  = null;
var mouseOffset = null;
var mousePos0   = null;
var d           = {x:0, y:0};
var viewbox     = null;

/* vars to manage pan */
var asyncArguments = null;  //arguments for asynchronus function call
var asyncCallback = null;   //function for asynchronus call

var request_im = null;   //last ajax requests to update imagemap
var request_ov = null;   //last ajax requests to update overview
var request_ms = null;   //last ajax requests to update map scale image and tooltip value
var request_rs = null;   //last ajax requests to update map after resize of browser window
var request_pc = null;   //last ajax requests to precache
var panStack = new Array();     //array to stack tiles needed to load, fills on every pan, empties on every ajax request
var tilesToUpdate = new Array();    //array of tiles to load in an ajax request

/**
* Set listener on mouse move and up. The mouse down listeners on the tilemap where already
* setted on load of the tilemap images.
*/
function initPanEvents()
{
    if (document.addEventListener) {
        document.addEventListener('mousemove', mouseMove, false);
        document.addEventListener('mouseup', panEnd, false);
    } else {
        document.attachEvent('onmousemove', mouseMove);
        document.attachEvent('onmouseup', panEnd);
    }
}



function removePanEvents(cc)
{
    if (document.removeEventListener) {
        document.removeEventListener('mousemove', mouseMove, false);
        document.removeEventListener('mouseup', panEnd, false);
        if(cc) cc.removeEventListener('mousedown', mouseDown, false);
    } else {
        document.detachEvent('onmousemove', mouseMove);
        document.detachEvent('onmouseup', panEnd);
        if(cc) cc.detachEvent('onmousedown', mouseDown);
    }
}



function getMouseOffset(target, ev)
{
    var docPos    = getPosition(target);
    var mousePos  = mouseCoords(ev);

    return {x:mousePos.x - docPos.x + viewbox.x, y:mousePos.y - docPos.y + viewbox.y};
}

function mouseCoords(ev)
{
    if(ev.pageX || ev.pageY){
        return {x:parseInt(ev.pageX), y:parseInt(ev.pageY)};
    }

    return {
        x:parseInt(ev.clientX + document.body.scrollLeft - document.body.clientLeft),
        y:parseInt(ev.clientY + document.body.scrollTop  - document.body.clientTop)
    };
}

function getPosition(e)
{
    var left = 0;
    var top  = 0;

    while (e.offsetParent){
        left += e.offsetLeft;
        top  += e.offsetTop;
        e     = e.offsetParent;
    }

    left += e.offsetLeft;
    top  += e.offsetTop;

    return {x:left, y:top};
}

/**
* @desc Initializes an image item for dragging. Called with onload.
* @param Object item to be dragged, e.g. image
* @param Array viewbox.x and viewbox.y give the upper left position
* of the viewbox
*/
function makeDraggable(item, viewboxXY)
{ 
    if(!item) return false;   //if no item was supplied

    viewbox = viewboxXY;    //bounding box of view
    
    if (item.addEventListener)
        item.addEventListener('mousedown', mouseDown, false);
    else
        item.attachEvent('onmousedown', mouseDown);
}



function mouseDown(ev)
{
    //don't pan while map update is in progress
    if(Map.updateInProgress == true) return;

    ev = ev || window.event;
    
    if (ev.target) targ = ev.target;
    else if (ev.srcElement) targ = ev.srcElement;
    if (targ.nodeType == 3) // defeat Safari bug
        targ = targ.parentNode;

    //create an array of all map tile images with their xy-position
    tilemap = targ.parentNode.getElementsByTagName("IMG");
    tilearray = new Array(tilemap.length);
    for(i=0;i<tilemap.length;i++)
    {
        tilearray[i] = new Array(3);
        tilearray[i][0] = tilemap[i];
        tilearray[i][1] = parseInt(tilemap[i].style.left);
        tilearray[i][2] = parseInt(tilemap[i].style.top);
    }

    dragObject  = targ;
    mousePos0   = mouseCoords(ev);
    mouseOffset = getMouseOffset(targ, ev);

    if(ev && ev.preventDefault) ev.preventDefault();
    return false;
}


/* Pan the map */
function mouseMove(ev)
{
    if(dragObject && mousePos0)
    {
        ev           = ev || window.event;
        var mousePos = mouseCoords(ev);
        
        d = {x:(mousePos.x - mousePos0.x),y:(mousePos.y - mousePos0.y)};

        if(dragObject.id.match(/clickcatcher/))
        {
            //drag tilemap, clickcatcher and poiMarker
            for(i=0;i<tilearray.length;i++)
            {   
                tilearray[i][0].style.left = (tilearray[i][1] + d.x)+'px';
                tilearray[i][0].style.top  = (tilearray[i][2] + d.y)+'px';
            }
            return false;
        }
        else    //poiMarker got mousedown
        {
            //drag marker
            for(i=0;i<tilearray.length;i++)
            {   
                if(tilearray[i][0].id.match(/poiMarker/))
                {
                    tilearray[i][0].style.left = (tilearray[i][1] + d.x)+'px';
                    tilearray[i][0].style.top  = (tilearray[i][2] + d.y)+'px';
                }
            }
            return false;
        }
    }
    return false;
}



/* Update tilemap after panning the map */
function panEnd()
{
    mousePos0 = null;

    if( (d.x == 0 && d.y == 0) || isNaN(d.x) || isNaN(d.y))
        return false;
        
    if(dragObject.id.match(/clickcatcher/))
    {
        var leftTopTile = null;
        var rightBottomTile = null;

        //get the images of viewbox before update
        var imagesBeforeUpdate = new Array();
        $(Map.viewbox).select('img').each(function(elem){ imagesBeforeUpdate[imagesBeforeUpdate.length] = elem.readAttribute('id'); });
        
        //new tilemap bounding box [px]
        Map.bboxp[0] = Map.bboxp[0] + d.x;
        Map.bboxp[1] = Map.bboxp[1] + d.y;
        Map.bboxp[2] = Map.bboxp[2] + d.x;
        Map.bboxp[3] = Map.bboxp[3] + d.y;
        
        //new viewbox bounding box [m]
        Map.bboxv[0] = Map.bboxv[0] - d.x * Map.mpp;
        Map.bboxv[1] = Map.bboxv[1] + d.y * Map.mpp;
        Map.bboxv[2] = Map.bboxv[2] - d.x * Map.mpp;
        Map.bboxv[3] = Map.bboxv[3] + d.y * Map.mpp;

        /* if tilearray was moved over the tilearray outer borders, new tiles should be loaded */
        if((Map.bboxp[0] > 0) || (Map.bboxp[1] > 0) ||
           (Map.bboxp[2] < Map.viewboxWidth) || (Map.bboxp[3] < Map.viewboxHeight))
        {
            Map.moveTilearray();
        }
        
        //detect tiles to load because of the last pan
        $(Map.viewbox).select('img').each(function(elem){ tilesToUpdate[tilesToUpdate.length] = elem.readAttribute('id'); });
        tilesToUpdate = tilesToUpdate.diff(imagesBeforeUpdate);

        //merge not yet loaded tiles from previous pans with the detected tiles to load because of the last pan
        panStack = panStack.concat(tilesToUpdate).unique();

        if(Map.updateInProgress == false) requestPanStack();
    }
    
    dragObject = null;
    d = {x:0, y:0};
    
    return true;
}


/**
* Executes all user initiated, incremental pans
* All pans are stored in var panStack. They are realized all at once when Map.updateInProgress == false.
* During Map.updateInProgress == true the next pans are stored in var panStack until the update is finished.
*/
function requestPanStack()
{
    if(panStack.length > 0)
    {
        //load tiles available in cache
        new Ajax.Request(
            'index.php?json=tileFromCache&prefix='+Map.prefix+'&tilearray='+panStack.join(","), {
                method: 'post',
                onCreate: function()
                {   
                    panStack = new Array();     //remove tiles to load from panStack
                    Map.updateInProgress = true;
                    Map.updateBbox = Map.bboxm.copy();
                    Map.updateLeftTop = new Array(Map.getTileIndex('LeftTop').rowid, Map.getTileIndex('LeftTop').colid);
                },
                onSuccess: function(response)
                {
                    if(response.responseJSON)   //set image src to image resource serving page url
                    {
                        for(i=0; i<response.responseJSON.cachedTiles.length; i++)
                            if($(response.responseJSON.cachedTiles[i]))     //the tile could be in the meantime moved out of the viewbox
                                $(response.responseJSON.cachedTiles[i]).writeAttribute('src', Map.pngImagePageUrl+'&key='+Map.prefix+'_'+response.responseJSON.cachedTiles[i]);

                        if(response.responseJSON.uncachedTiles.length > 0)
                        {                        
                            new Ajax.Request(
                                'index.php?json=tileFromMapserver&pan=true&prefix='+Map.prefix+'&tilearray='+response.responseJSON.uncachedTiles.join(",")+'&extent='+Map.updateBbox.join(',')+'&startrow='+Map.updateLeftTop[0]+'&startcol='+Map.updateLeftTop[1], {
                                    method: 'post',
                                    //onSuccess: function(response){},
                                    //onFailure: function(response){},
                                    onComplete: function(response)
                                    {   
                                        if(response.responseJSON)
                                            for(i=0; i<response.responseJSON.cachedTiles.length; i++)
                                                if($(response.responseJSON.cachedTiles[i]))     //the tile could be in the meantime moved out of the viewbox
                                                    $(response.responseJSON.cachedTiles[i]).writeAttribute('src', Map.pngImagePageUrl+'&key='+Map.prefix+'_'+response.responseJSON.cachedTiles[i]);
                                        
                                        if(panStack.length > 0) requestPanStack();
                                        else Map.updateInProgress = false;
                                    }
                                }
                            );
                        }
                        else
                            Map.updateInProgress = false;
                    }
                },
                onFailure: function(response){
                    Map.updateInProgress = false;
                }
            }
        );
        
        //cancel eventually running request, update imagemap (after changing of tilemap)
        if(request_im){
            request_im.transport.abort();
            request_im = null;
        }
        request_im = Map.setImageMap();
    }

    //cancel eventually running request, update map overview (after each navigation)
    if(request_ov){
        request_ov.transport.abort();
        request_ov = null;
    }
    request_ov = Map.setOverview();
    
    //cancel eventually running request, initialize precaching of tiles further in the same pan direction
    if(request_pc){
        request_pc.transport.abort();
        request_pc = null;
    }
    asyncArguments = d;     // Save a reference to the arguments
    asyncCallback = Map.preCache;   // Save a reference to the callback function
    window.setTimeout("AsynchronousPreCache();", 1);    // Call Function Asynchronously
    
}


//asynchronus precache function call (Map.precache)
function AsynchronousPreCache()
{
    request_pc = asyncCallback(asyncArguments);
}

    


//----------------------------

var Map = Class.create()
{
    this.bboxm = null;   //tile map bounding box array in [m]
    this.bboxp = null;   //tile map bounding box array in [px]
    this.bboxv = null;   //viewbox bounding box in [m]
    
    this.mpp = null;    //meter per pixel
    this.dxm = null;    //tile width in [m]
    this.dym = null;    //tile height in [m]
    this.dxp = null;    //tile width in [px]
    this.dyp = null;    //tile height in [px]
    
    this.rows = null;   //tile rows count
    this.cols = null;   //tile columns count
    
    this.tileshiftX = null;   //x-shift of tilearray
    this.tileshiftY = null;   //y-shift of tilearray
    
    this.overview = null;   //overview object id
    this.imagemap = null;   //imagemap object id
    this.clickcatcher = null;   //image above map to receive mouse events 
    this.viewbox = null;    //viewbox object
    this.viewboxWidth = null;   //viewbox width [px]
    this.viewboxHeight = null;  //viewbox height [px]
    this.viewboxLeft = null;   //viewbox left pos [px]
    this.viewboxTop = null;  //viewbox top pos [px]
    
    this.markerSize = null; //array with width and height of used marker [px]
    
    this.imageBaseUrl = null;   //url to image base path
    this.pngImagePageUrl = null;    //PRADO friendly page url to PngImage.page
    this.prefix = null; //the prefix in the image filename before the row and column index
    
    this.elemLeftTop = null;   //left-toppest element
    
    this.updateInProgress = null;  //true when tiles where requested until request is complete
    this.updateLeftTop = null;  //the left top tile index of the current update
    this.updateBbox = null; //the map bbox of the current update

    
    //transforms coordinates from pixel to meter and vice versa
    Map.trans = function(input, output, pnt)
    {
        if(input == 'px' && output == 'm')
        {
            var xm = Map.bboxv[0] + (pnt.x - Map.viewboxLeft) * Map.mpp;
            var ym = Map.bboxv[3] - (pnt.y - Map.viewboxTop) * Map.mpp;

            return {x:xm, y:ym};
        }
        if(input == 'm' && output == 'px')
        {
            return xy;
        }
    }
    //returns left top or right bottom rowid and colid    
    Map.getTileIndex = function(which)
    {
        var tile = new Array(2);
        var indexLeftTop = this.elemLeftTop.getAttribute('id').split('_');

        switch(which){
            case 'LeftTop':
                tile[0] = parseInt(indexLeftTop[0]);
                tile[1] = parseInt(indexLeftTop[1]);    
                break;
            case 'RightBottom':
                tile[0] = parseInt(indexLeftTop[0]) + this.rows - 1;
                tile[1] = parseInt(indexLeftTop[1]) + this.cols - 1;    
                break;
            default:
                tile[0] = null;
                tile[1] = null;
        }

        return {rowid:tile[0], colid:tile[1]};
    }
    
    // requests server to precache maptiles for next pan, zoom or fit
    Map.preCache = function(d)
    {
        var lt = Map.getTileIndex('LeftTop');
        var rb = Map.getTileIndex('RightBottom');
        return new Ajax.Request(
            'index.php?page=PreCache&prefix='+Map.prefix+'&index='+lt.rowid+','+lt.colid+','+rb.rowid+','+rb.colid+'&vector='+d.x+','+d.y+'&vextent='+Map.bboxv.join(',')+'&mextent='+Map.bboxm.join(','), {
                method: 'post'
            }
        );
    }
    
    // updates overview image after navigation of the map
    Map.setOverview = function()
    {
        return new Ajax.Request(
            'index.php?json=getMapOverview&prefix='+Map.prefix+'&extent='+Map.bboxv.join(',')+'&bboxp_w='+(Map.bboxp[2]-Map.bboxp[0])+'&bboxp_h='+(Map.bboxp[3]-Map.bboxp[1]), {
                method: 'post',
                onSuccess: function(response){
                        if(response.responseJSON)  //set overview image src to image resource serving page
                            $(Map.overview).writeAttribute('src', Map.pngImagePageUrl+'&key='+response.responseJSON.key);
                }
            }
        );
    }
    
    /* updates clickcatcher and imagemap image (which supply tooltips and maptips on the map)
       after navigation of the map */
    Map.setImageMap = function()
    {
        return new Ajax.Request(
            'index.php?json=getImageMap&prefix='+Map.prefix+'&extent='+Map.bboxm.join(','), {
                method: 'post',
                onSuccess: function(response)
                {
                    if(response.responseJSON){
                        //update imagemap inner html and name
                        var currentTime = new Date();
                        currentTime = currentTime.getSeconds();   //used to construct a new, unique imagemap id
                        $(Map.imagemap).update('<map style="cursor:help; " name="imagemap'+currentTime+'">'+response.responseJSON.imagemap+'</map>');
                        
                        //update clickcatcher (transparent image) usemap, position (position it at the left top corner of the first tile)
                        $(Map.clickcatcher).writeAttribute('usemap', '#imagemap'+currentTime+((mapnavimod == 'Info')?'':'~'));
                        $(Map.clickcatcher).setStyle({ left:Map.bboxp[0] + 'px', top:Map.bboxp[1] + 'px' });
                    }
                }
            }
        );
    }
    
    // updates overview image after navigation of the map
    Map.setMapScale = function()
    {
        return new Ajax.Request(
            'index.php?json=getMapScale&prefix='+Map.prefix+'&extent='+Map.bboxv.join(','), {
                method: 'post',
                onSuccess: function(response){
                        if(response.responseJSON){  //set mapscale image src to image resource serving page and tooltip to scale value
                            $(Map.scalebar).writeAttribute('src', Map.pngImagePageUrl+'&key='+response.responseJSON.key);
                            $(Map.scalebar).writeAttribute('title', response.responseJSON.value);
                        }
                }
            }
        );
    }
    
    /* Zoom in, zoom out of tilemap
    */
    Map.zoomInOut = function(ev)
    {
        //don't zoom while map update is in progress
        if(Map.updateInProgress == true) return;
        
        var ev = ev || window.event;
        var pos = mouseCoords(ev);
        var zoom = (mapnavimod == 'ZoomIn') ? Map.zoomIn : Map.zoomOut;


        /* center tilearray at clicked position by panning without loading of new tiles from server */
        var xp = pos.x - Map.viewboxLeft;   //viewbox coordinate of mouseclick
        var yp = pos.y - Map.viewboxTop;
        var dxp = parseInt(Map.viewboxWidth/2 - xp);   //distance from new viewbox center (mouseclick pos) to old center [px]
        var dyp = parseInt(Map.viewboxHeight/2 - yp);

        var clone = $(Map.viewbox).clone(true);     //clone tilemap before centering it

        $(Map.viewbox).select('img').each(  
            function(elem)
            {
                var id = elem.readAttribute('id');
                
                if(!id.match(/poiMarker/)){
                    elem.setStyle({   //center tilemap and clickcatcher
                        left: (parseInt(elem.getStyle('left')) + dxp) + 'px',
                        top: (parseInt(elem.getStyle('top')) + dyp) + 'px'
                    });
                    elem.writeAttribute('src', Map.imageBaseUrl+'/images/0.png');   //show while loading an transparent image
                    elem.writeAttribute('onload');   //remove onload event handler who inits pan events
                }
            }
        );
        
        /* animated zoom of displayed old tilemap */
        $(clone).writeAttribute('id','viewbox_clone');
        $(clone).select('img').each(  //zoom tiles
            function(elem, index){
                if(index < (Map.rows * Map.cols))
                {
                    elem.writeAttribute('id', index);  //remove duplicate id
                    elem.setStyle({
                        left: ((parseInt(elem.getStyle('left')) - xp) / zoom + Map.viewboxWidth/2) + 'px',
                        top:  ((parseInt(elem.getStyle('top'))  - yp) / zoom + Map.viewboxHeight/2) + 'px',
                        width: (parseInt(elem.getStyle('width')) / zoom) + 'px',
                        height: (parseInt(elem.getStyle('height')) / zoom) + 'px'
                    });
                }
                else
                    elem.remove();
            }
        );
        $(clone).select('div').each(  //remove imagemap div panel
            function(elem){ elem.remove(); }
        );
        $(clone).select('script').each(  //remove script block
            function(elem){ elem.remove(); }
        );
        $(Map.viewbox).insert({ before:clone });

        //calculate mouse position [m]
        var pntm = Map.trans('px', 'm', {x:pos.x,y:pos.y});
        
        Map.bboxp[0] = Map.bboxp[0] + dxp;   //shift tilemap bounding box [px]
        Map.bboxp[1] = Map.bboxp[1] + dyp;
        Map.bboxp[2] = Map.bboxp[2] + dxp;
        Map.bboxp[3] = Map.bboxp[3] + dyp;
        
        Map.bboxv[0] = Map.bboxv[0] - dxp * Map.mpp;   //shift viewbox bounding box [m]
        Map.bboxv[1] = Map.bboxv[1] + dyp * Map.mpp;
        Map.bboxv[2] = Map.bboxv[2] - dxp * Map.mpp;
        Map.bboxv[3] = Map.bboxv[3] + dyp * Map.mpp;
        
        /* if tilearray was moved over the tilearray outer borders, tilemap should be arranged newly */
        if((Map.bboxp[0] > 0) || (Map.bboxp[1] > 0) || (Map.bboxp[2] < Map.viewboxWidth) || (Map.bboxp[3] < Map.viewboxHeight)) Map.moveTilearray();

                
        /* now zoom tilemap (resp. bounding boxes) */
        var w = (Map.bboxv[2] - Map.bboxv[0]) * zoom;   //zoomed viewbox width and height [m]
        var h = (Map.bboxv[3] - Map.bboxv[1]) * zoom;

        Map.bboxv[0] = pntm.x - w/2;    //calculate new viewbox extent [m] from new center [m]
        Map.bboxv[1] = pntm.y - h/2;
        Map.bboxv[2] = pntm.x + w/2;
        Map.bboxv[3] = pntm.y + h/2;
        
        Map.bboxm[0] = (Map.bboxm[0] - pntm.x) * zoom + pntm.x;   //move new center to coordinate system origin (0,0), zoom min/max
        Map.bboxm[1] = (Map.bboxm[1] - pntm.y) * zoom + pntm.y;   //and move it back
        Map.bboxm[2] = (Map.bboxm[2] - pntm.x) * zoom + pntm.x;
        Map.bboxm[3] = (Map.bboxm[3] - pntm.y) * zoom + pntm.y;

        /* update Map object changes */
        Map.dxm = (Map.bboxm[2] - Map.bboxm[0]) / Map.cols;
        Map.dym = (Map.bboxm[3] - Map.bboxm[1]) / Map.rows;
        Map.mpp = Map.dxm / Map.dxp;
        
        //calc tiles to update
        var tiles = new Array();
        $(Map.viewbox).select('img').each(
            function(elem){ 
                var id = elem.readAttribute('id');
                
                //get id of all tiles, but not the clickcatcher or poiMarker
                if(!(id.match(/clickcatcher/) || id.match(/poiMarker/))) tiles[tiles.length] = id; 
            }
        );
        
        new Ajax.Request(
            'index.php?json=tileFromMapserver&pan=false&prefix='+Map.prefix+'&tilearray='+tiles.join(",")+'&extent='+Map.bboxm.join(',')+'&startrow='+Map.getTileIndex('LeftTop').rowid+'&startcol='+Map.getTileIndex('LeftTop').colid, {
                method: 'post',
                onCreate: function(){   
                    Map.updateInProgress = true;
                },
                onSuccess: function(response){
                    if(response.responseJSON && response.responseJSON.cachedTiles.length)
                    {
                        Map.prefix = response.responseJSON.prefix;

                        for(i=0; i<response.responseJSON.cachedTiles.length; i++)
                        {
                            var elem = $(response.responseJSON.cachedTiles[i]);
                            
                            if(elem)     //the tile could be in the meantime moved out of the viewbox
                                $(elem).writeAttribute('src', Map.pngImagePageUrl+'&key='+Map.prefix+'_'+response.responseJSON.cachedTiles[i]);

                            if(i+1 == response.responseJSON.cachedTiles.length){
                                Map.updateInProgress = false;
                                clone.remove();   //remove clone after setting last image src
                            }
                        }
                    }
                    else
                    {
                        Map.updateInProgress = false;
                    }
                },
                onFailure: function(response){
                    Map.updateInProgress = false;
                }
            }
        );

        //cancel eventually running request, update map overview (after each navigation)
        if(request_ov){
            request_ov.transport.abort();
            request_ov = null;
        }
        request_ov = Map.setOverview();
        
        //cancel eventually running request, update imagemap (after changing of tilemap)
        if(request_im){
            request_im.transport.abort();
            request_im = null;
        }
        request_im = Map.setImageMap();
        
        //cancel eventually running request, update mapscale (after changing each zoom window)
        if(request_ms){
            request_ms.transport.abort();
            request_ms = null;
        }
        request_ms = Map.setMapScale();

        //cancel eventually running request, initialize precaching of tiles further in the same pan direction
        if(request_pc){
            request_pc.transport.abort();
            request_pc = null;
        }
        asyncArguments = d;     // Save a reference to the arguments
        asyncCallback = Map.preCache;   // Save a reference to the callback function
        window.setTimeout("AsynchronousPreCache();", 1);    // Call Function Asynchronously
    }
    
    
    /*
    * @desc Zoom window or fit
    * If the argument bounding box is not null, the tilemap gets zoomed to this bounding box, 
    * considering the aspect ratio of the viewbox.
    * Otherwise the tilemap gets fitted to the fit extent given by Map.zoomfit, defined in config.xml
    * @param Array - upper left zoom window in [px] and width / height of the zoom window [px]
    */
    Map.zoomWindow = function(box, unit)
    {
        //don't zoom while map update is in progress
        if(Map.updateInProgress == true) return;

        if(box != null && unit == 'px')   //zoom window tilemap
        {
            var x = parseFloat(box[0]);
            var y = parseFloat(box[1]);
            var w = parseFloat(box[2]);
            var h = parseFloat(box[3]);

            //viewbox extent [m]
            Map.bboxv[0] = Map.bboxv[0] + x * Map.mpp;   //left
            Map.bboxv[2] = Map.bboxv[0] + w * Map.mpp;
            Map.bboxv[3] = Map.bboxv[3] - y * Map.mpp;    //top
            Map.bboxv[1] = Map.bboxv[3] - h * Map.mpp;

            //fill viewbox area completly with the requested map, even if the viewbox extent
            //does not fit to viewbox width and height [px]
            if((w / h) > (Map.viewboxWidth / Map.viewboxHeight))
            {
                Map.mpp = w * Map.mpp / Map.viewboxWidth;
                var d = Map.mpp * Map.viewboxHeight - (Map.bboxv[3] - Map.bboxv[1]);

                //fill the gaps on the viewbox area at the bottom and top of the map
                Map.bboxv[1] = Map.bboxv[1] - d / 2;
                Map.bboxv[3] = Map.bboxv[3] + d / 2;
            }else{
                Map.mpp = h * Map.mpp / Map.viewboxHeight;
                var d = Map.mpp * Map.viewboxWidth - (Map.bboxv[2] - Map.bboxv[0]);

                //fill the gaps on the viewbox area at the left and right of the map
                Map.bboxv[0] = Map.bboxv[0] - d / 2;
                Map.bboxv[2] = Map.bboxv[2] + d / 2;
            }
        }else{
            if(box != null && unit == 'm')   //zoom the tilemap, new viewbox in [m]
            {
                var w = box[2] - box[0];
                var h = box[3] - box[1];

                //viewbox extent [m]
                Map.bboxv[0] = parseFloat(box[0]);   //left
                Map.bboxv[1] = parseFloat(box[1]);
                Map.bboxv[2] = parseFloat(box[2]);
                Map.bboxv[3] = parseFloat(box[3]);    //top
            }
            else //fit the tilemap
            {
                var w = Map.zoomFit[2] - Map.zoomFit[0];
                var h = Map.zoomFit[3] - Map.zoomFit[1];

                //viewbox extent [m]
                Map.bboxv[0] = Map.zoomFit[0];   //left
                Map.bboxv[1] = Map.zoomFit[1];
                Map.bboxv[2] = Map.zoomFit[2];
                Map.bboxv[3] = Map.zoomFit[3];    //top
            }
            
            //fill viewbox area completly with the requested map, even if the viewbox extent
            //does not fit to viewbox width and height [px]
            if((w / h) > (Map.viewboxWidth / Map.viewboxHeight))
            {
                Map.mpp = w / Map.viewboxWidth;
                var d = Map.mpp * Map.viewboxHeight - h;

                //fill the gaps on the viewbox area at the bottom and top of the map
                Map.bboxv[1] = Map.bboxv[1] - d / 2;
                Map.bboxv[3] = Map.bboxv[3] + d / 2;
            }else{
                Map.mpp = h / Map.viewboxHeight;
                var d = Map.mpp * Map.viewboxWidth - w;

                //fill the gaps on the viewbox area at the left and right of the map
                Map.bboxv[0] = Map.bboxv[0] - d / 2;
                Map.bboxv[2] = Map.bboxv[2] + d / 2;
            }
            
        }

        Map.dxm = Map.dxp * Map.mpp;
        Map.dym = Map.dyp * Map.mpp;
        
        //calculate desired map and viewbox extents [m]
        Map.bboxm[0] = Map.bboxv[0];   //tilemap bounding box [m]
        Map.bboxm[3] = Map.bboxv[3];
        Map.bboxm[1] = Map.bboxm[3] - Map.rows * Map.dym;
        Map.bboxm[2] = Map.bboxm[0] + Map.cols * Map.dxm;

        Map.bboxp[0] = Map.tileshiftX;   //tilemap bounding box [px]
        Map.bboxp[1] = Map.tileshiftY;
        Map.bboxp[2] = Map.bboxp[0] + Map.cols * Map.dxp;
        Map.bboxp[3] = Map.bboxp[1] + Map.rows * Map.dyp;
        
        //pan tilearray to left top of viewbox
        //!!!not yet regarding tileshift
        var dxp = parseInt(Map.elemLeftTop.getStyle('left'));
        var dyp = parseInt(Map.elemLeftTop.getStyle('top'));
        
        //calc tiles to update
        var tiles = new Array();
        $(Map.viewbox).select('img').each(
            function(elem){ 
                var id = elem.readAttribute('id');
                
                if(!(id.match(/clickcatcher/) || id.match(/poiMarker/))){
                    tiles[tiles.length] = id;   //get id of all tiles, but not the clickcatcher or poiMarker

                    elem.setStyle({   //center tilemap and clickcatcher
                        left: (parseInt(elem.getStyle('left')) - dxp) + 'px',
                        top: (parseInt(elem.getStyle('top')) - dyp) + 'px'
                    });
                    elem.writeAttribute('src', Map.imageBaseUrl+'/images/0.png');   //show while loading an transparent image
                    elem.writeAttribute('onload');   //remove onload event handler who inits pan events
                }
            }
        );
        
        new Ajax.Request(
            'index.php?json=tileFromMapserver&pan=false&prefix='+Map.prefix+'&tilearray='+tiles.join(",")+'&extent='+Map.bboxm.join(',')+'&startrow='+Map.getTileIndex('LeftTop').rowid+'&startcol='+Map.getTileIndex('LeftTop').colid, {
                method: 'post',
                onCreate: function(){   
                    Map.updateInProgress = true;
                },
                onSuccess: function(response){
                    if(response.responseJSON && response.responseJSON.cachedTiles.length)
                    {
                        Map.prefix = response.responseJSON.prefix;

                        for(i=0; i<response.responseJSON.cachedTiles.length; i++)
                        {
                            var elem = $(response.responseJSON.cachedTiles[i]);
                            
                            if(elem)     //the tile could be in the meantime moved out of the viewbox
                                $(elem).writeAttribute('src', Map.pngImagePageUrl+'&key='+Map.prefix+'_'+response.responseJSON.cachedTiles[i]);

                            if(i+1 == response.responseJSON.cachedTiles.length)
                                Map.updateInProgress = false;
                        }
                    }
                    else
                    {
                        Map.updateInProgress = false;
                    }
                },
                onFailure: function(response){
                    Map.updateInProgress = false;
                }
            }
        );
        
        //cancel eventually running request, update map overview (after each navigation)
        if(request_ov){
            request_ov.transport.abort();
            request_ov = null;
        }
        request_ov = Map.setOverview();
        
        //cancel eventually running request, update imagemap (after changing of tilemap)
        if(request_im){
            request_im.transport.abort();
            request_im = null;
        }
        request_im = Map.setImageMap();
        
        //cancel eventually running request, update mapscale (after changing each zoom window)
        if(request_ms){
            request_ms.transport.abort();
            request_ms = null;
        }
        request_ms = Map.setMapScale();
        
        //cancel eventually running request, initialize precaching of tiles further in the same pan direction
        if(request_pc){
            request_pc.transport.abort();
            request_pc = null;
        }
        asyncArguments = d;     // Save a reference to the arguments
        asyncCallback = Map.preCache;   // Save a reference to the callback function
        window.setTimeout("AsynchronousPreCache();", 1);    // Call Function Asynchronously
    }
    
    
    /*
    */
    Map.moveTilearray = function()
    {
        //move column(s) from right to left
        while(parseInt(Map.elemLeftTop.getStyle('left')) > 0)
        {
            //get row and column id from left top tile id
            leftTopTile = Map.getTileIndex('LeftTop');
            
            //move right column of tiles to the left
            for(i=leftTopTile.rowid; i<(leftTopTile.rowid + Map.rows); i++)
            {
                Map.viewbox.select('img#'+i+'_'+(leftTopTile.colid + Map.cols - 1)).each(function(elem)
                {
                    elem.writeAttribute('src', Map.imageBaseUrl+'/images/0.png');   //show while loading an empty image
                    elem.setStyle({left:(Map.bboxp[0]-Map.dxp)+'px'});  //reposition image
                    elem.writeAttribute('id', i+'_'+(leftTopTile.colid-1)); //change element id according to new row and column
                });
            }
            //set new left top tile image
            Map.elemLeftTop = $(leftTopTile.rowid+'_'+(leftTopTile.colid-1));

            //set new bounding boxes
            Map.bboxm[0] = Map.bboxm[0] - Map.dxm;
            Map.bboxm[2] = Map.bboxm[2] - Map.dxm;

            Map.bboxp[0] = Map.bboxp[0] - Map.dxp;
            Map.bboxp[2] = Map.bboxp[2] - Map.dxp;
        }

        //move row(s) from bottom to top
        while(parseInt(Map.elemLeftTop.getStyle('top')) > 0)
        {
            //get row and column id from left top tile id
            leftTopTile = Map.getTileIndex('LeftTop');
            
            //move bottom row of tiles to the top
            for(i=leftTopTile.colid; i<(leftTopTile.colid + Map.cols); i++)
            {
                Map.viewbox.select('img#'+(leftTopTile.rowid + Map.rows - 1)+'_'+i).each(function(elem)
                {
                    elem.writeAttribute('src', Map.imageBaseUrl+'/images/0.png');   //show while loading an empty image
                    elem.setStyle({top:(Map.bboxp[1]-Map.dyp)+'px'});  //reposition image
                    elem.writeAttribute('id', (leftTopTile.rowid-1)+'_'+i); //change element id according to new row and column
                });
            }
            //set new left top tile image
            Map.elemLeftTop = $((leftTopTile.rowid-1)+'_'+leftTopTile.colid);

            //set new bounding boxes
            Map.bboxm[1] = Map.bboxm[1] + Map.dym;
            Map.bboxm[3] = Map.bboxm[3] + Map.dym;

            Map.bboxp[1] = Map.bboxp[1] - Map.dyp;
            Map.bboxp[3] = Map.bboxp[3] - Map.dyp;
        }

        //move column(s) from left to right
        while((parseInt($(Map.getTileIndex('RightBottom').rowid+'_'+Map.getTileIndex('RightBottom').colid).getStyle('left')) + Map.dxp) < Map.viewboxWidth)
        {
            //get row and column id from left top tile id
            leftTopTile = Map.getTileIndex('LeftTop');
            rightBottomTile = Map.getTileIndex('RightBottom');

            //move left column of tiles to the right
            for(i=leftTopTile.rowid; i<(leftTopTile.rowid + Map.rows); i++)
            {
                Map.viewbox.select('img#'+i+'_'+leftTopTile.colid).each(function(elem)
                {
                    elem.writeAttribute('src', Map.imageBaseUrl+'/images/0.png');   //show while loading an empty image
                    elem.setStyle({left:Map.bboxp[2]+'px'});  //reposition image
                    elem.writeAttribute('id', i+'_'+(rightBottomTile.colid+1)); //change element id according to new row and column
                });
                
            }
            //set new left top tile image
            Map.elemLeftTop = $(leftTopTile.rowid+'_'+(leftTopTile.colid+1));

            //set new bounding boxes
            Map.bboxm[0] = Map.bboxm[0] + Map.dxm;
            Map.bboxm[2] = Map.bboxm[2] + Map.dxm;

            Map.bboxp[0] = Map.bboxp[0] + Map.dxp;
            Map.bboxp[2] = Map.bboxp[2] + Map.dxp;
        }
        
        //move rows from top to bottom
        while((parseInt($(Map.getTileIndex('RightBottom').rowid+'_'+Map.getTileIndex('RightBottom').colid).getStyle('top')) + Map.dyp) < Map.viewboxHeight)
        {
            //get row and column id from left top tile id
            leftTopTile = Map.getTileIndex('LeftTop');
            rightBottomTile = Map.getTileIndex('RightBottom');
            
            //move top row of tiles to the bottom
            for(i=leftTopTile.colid; i<(leftTopTile.colid + Map.cols); i++)
            {
                Map.viewbox.select('img#'+leftTopTile.rowid+'_'+i).each(function(elem)
                {
                    elem.writeAttribute('src', Map.imageBaseUrl+'/images/0.png');   //show while loading an empty image
                    elem.setStyle({top:Map.bboxp[3]+'px'});  //reposition image
                    elem.writeAttribute('id', (rightBottomTile.rowid+1)+'_'+i); //change element id according to new row and column
                });
            }
            //set new left top tile image
            Map.elemLeftTop = $((leftTopTile.rowid+1)+'_'+leftTopTile.colid);

            //set new bounding boxes
            Map.bboxm[1] = Map.bboxm[1] - Map.dym;
            Map.bboxm[3] = Map.bboxm[3] - Map.dym;

            Map.bboxp[1] = Map.bboxp[1] + Map.dyp;
            Map.bboxp[3] = Map.bboxp[3] + Map.dyp;
        }        
    }
    
    /*
    * Resizes the tilemap
    * After resizing the browser window, the viewbox and tile images get resized and reloaded. 
    * Map.resizeTilemap updates the tilemap in the current map scale by enlarging viewbox and the tiles
    * in east and south direction.
    * @param Integer additional available width
    * @param Integer additional available height
    */
    Map.resizeTilemap = function(dw, dh)
    {
        if(Map.viewbox == null) return;
        
        //cancel eventually running resize ajax call
        if(request_rs){
            request_rs.transport.abort();
            request_rs = null;
        }
        
        //update viewbox width/height, 
        Map.viewboxWidth = Map.viewboxWidth + dw; 
        Map.viewboxHeight = Map.viewboxHeight + dh;
        $(Map.viewbox).setStyle({ 'width':Map.viewboxWidth+'px', 'height':Map.viewboxHeight+'px' });

        //update viewbox bounding box in [m]
        Map.bboxv = new Array(Map.bboxv[0], Map.bboxv[1] - (dh * Map.mpp), Map.bboxv[2] + (dw * Map.mpp), Map.bboxv[3]);
        
        //one tile of the tilemap must be completly outside for proper panning:
        //enlarge width if width of (cols-1) is smaller viewbox width; same with height
        var dwp = Math.ceil(Map.viewboxWidth / (Map.cols-1)) * Map.cols;
        var dhp = Math.ceil(Map.viewboxHeight / (Map.rows-1)) * Map.rows;
        
        //update tile map bounding box array in [px]
        Map.bboxp = new Array(Map.bboxp[0], Map.bboxp[1], Map.bboxp[0] + dwp, Map.bboxp[1] + dhp);

        //update tile map bounding box array in [m]
        var dwm = dwp * Map.mpp;
        var dhm = dhp * Map.mpp;
        Map.bboxm = new Array(Map.bboxm[0], Map.bboxm[3] - dhm, Map.bboxm[0] + dwm, Map.bboxm[3]);
        
        //new tile sizes
        Map.dxp = (Map.bboxp[2]-Map.bboxp[0]) / Map.cols;    //tile width in [px]
        Map.dyp = (Map.bboxp[3]-Map.bboxp[1]) / Map.rows;    //tile height in [px]
        Map.dxm = Map.dxp * Map.mpp;    //tile width in [m]
        Map.dym = Map.dyp * Map.mpp;    //tile height in [m]

        //resize tile images
        leftTopTile = Map.getTileIndex('LeftTop');
        
        //
        for(i=leftTopTile.rowid; i<(leftTopTile.rowid + Map.rows); i++)
        {
            for(j=leftTopTile.colid; j<(leftTopTile.colid + Map.cols); j++)
            {
                Map.viewbox.select('img#'+i+'_'+j).each(function(elem)
                {
                    //elem.writeAttribute('src', Map.imageBaseUrl+'/images/0.png');   //show while loading an empty image
                    elem.setStyle({left:(Map.bboxp[0] + j * Map.dxp)+'px', 
                        top:(Map.bboxp[1] + i * Map.dyp)+'px',
                        width:Map.dxp+'px',
                        height:Map.dyp+'px' });
                });
            }
        }
        
        //reposition mapscale, overview and copyright info
        $('panelOverviewOn').setStyle({left:(Map.viewboxWidth-181)+'px'});
        $('panelOverviewOff').setStyle({left:(Map.viewboxWidth-18)+'px'});
        $(Map.scalebar).setStyle({top:(Map.viewboxHeight+Map.viewboxTop-21)+'px'});
        $('showCopyright').parentNode.setStyle({width:(Map.viewboxWidth-5)+'px', top:(Map.viewboxHeight-15)+'px'});
        
        //resize clickcatcher
        $(Map.clickcatcher).setStyle({ width:(Map.dxp * Map.cols)+'px', height:(Map.dyp * Map.rows)+'px' });
        
        //update cached map parameters and update map
        request_rs = new Ajax.Request(
            'index.php?json=updateMapParameter&prefix='+Map.prefix+'&tile_width='+Map.dxp+'&tile_height='+Map.dyp, {
                method: 'post',
                onSuccess: function(response){
                    if(response.responseJSON)
                    {
                        //get updated prefix clientside
                        Map.prefix = response.responseJSON.prefix;                    
                        
                        //update map
                        Map.zoomWindow(Map.bboxv, 'm');
                    }
                }
            }
        );        
        
    }
    
}


    
        

// Return elements which are in A but not in arg0 through argn
Array.prototype.diff =
  function() {
    var a1 = this;
    var a = a2 = null;
    var n = 0;
    while(n < arguments.length) {
      a = [];
      a2 = arguments[n];
      var l = a1.length;
      var l2 = a2.length;
      var diff = true;
      for(var i=0; i<l; i++) {
        for(var j=0; j<l2; j++) {
          if (a1[i] === a2[j]) {
            diff = false;
            break;
          }
        }
        diff ? a.push(a1[i]) : diff = true;
      }
      a1 = a;
      n++;
    }
    return a.unique();
  };

// Return new array with duplicate values removed
Array.prototype.unique =
  function() {
    var a = [];
    var l = this.length;
    for(var i=0; i<l; i++) {
      for(var j=i+1; j<l; j++) {
        // If this[i] is found later in the array
        if (this[i] === this[j])
          j = ++i;
      }
      a.push(this[i]);
    }
    return a;
  };
  
// Returns a copy (not a pointer)  
Array.prototype.copy = function () {
return ((new Array()).concat(this));
};  
/*
 * Copyright (c) 2006 Jonathan Weiss <jw@innerewut.de>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


/* tooltip-0.2.js - Small tooltip library on top of Prototype 
 * by Jonathan Weiss <jw@innerewut.de> distributed under the BSD license. 
 *
 * This tooltip library works in two modes. If it gets a valid DOM element 
 * or DOM id as an argument it uses this element as the tooltip. This 
 * element will be placed (and shown) near the mouse pointer when a trigger-
 * element is moused-over.
 * If it gets only a text as an argument instead of a DOM id or DOM element
 * it will create a div with the classname 'tooltip' that holds the given text.
 * This newly created div will be used as the tooltip. This is usefull if you 
 * want to use tooltip.js to create popups out of title attributes.
 * 
 *
 * Usage: 
 *   <script src="/javascripts/prototype.js" type="text/javascript"></script>
 *   <script src="/javascripts/tooltip.js" type="text/javascript"></script>
 *   <script type="text/javascript">
 *     // with valid DOM id
 *     var my_tooltip = new Tooltip('id_of_trigger_element', 'id_of_tooltip_to_show_element')
 *
 *     // with text
 *     var my_other_tooltip = new Tooltip('id_of_trigger_element', 'a nice description')
 *
 *     // create popups for each element with a title attribute
 *    Event.observe(window,"load",function() {
 *      $$("*").findAll(function(node){
 *        return node.getAttribute('title');
 *      }).each(function(node){
 *        new Tooltip(node,node.title);
 *        node.removeAttribute("title");
 *      });
 *    });
 *    
 *   </script>
 * 
 * Now whenever you trigger a mouseOver on the `trigger` element, the tooltip element will
 * be shown. On o mouseOut the tooltip disappears. 
 * 
 * Example:
 * 
 *   <script src="/javascripts/prototype.js" type="text/javascript"></script>
 *   <script src="/javascripts/scriptaculous.js" type="text/javascript"></script>
 *   <script src="/javascripts/tooltip.js" type="text/javascript"></script>
 *
 *   <div id='tooltip' style="display:none; margin: 5px; background-color: red;">
 *     Detail infos on product 1....<br />
 *   </div>
 *
 *   <div id='product_1'>
 *     This is product 1
 *   </div>
 *
 *   <script type="text/javascript">
 *     var my_tooltip = new Tooltip('product_1', 'tooltip')
 *   </script>
 *
 * You can use my_tooltip.destroy() to remove the event observers and thereby the tooltip.
 */

var Tooltip = Class.create();
Tooltip.prototype = {
  initialize: function(element, tool_tip) {
    var options = Object.extend({
      default_css: false,
      margin: "0px",
	    padding: "5px",
	    backgroundColor: "#d6d6fc",
	    min_distance_x: 5,
      min_distance_y: 5,
      delta_x: 0,
      delta_y: 0,
      zindex: 1000
    }, arguments[2] || {});

    if($(element))
        this.element = $(element);
    else
        return;

    this.options = options;
    
    // use the supplied tooltip element or create our own div
    if($(tool_tip)) {
      this.tool_tip = $(tool_tip);
    } else {
      this.tool_tip = $(document.createElement("div")); 
      document.body.appendChild(this.tool_tip);
      this.tool_tip.addClassName("tooltip");
      this.tool_tip.appendChild(document.createTextNode(tool_tip));
    }

    // hide the tool-tip by default
    this.tool_tip.hide();

    this.eventMouseOver = this.showTooltip.bindAsEventListener(this);
    this.eventMouseOut   = this.hideTooltip.bindAsEventListener(this);
    this.eventMouseMove  = this.moveTooltip.bindAsEventListener(this);

    this.registerEvents();
  },

  destroy: function() {
    Event.stopObserving(this.element, "mouseover", this.eventMouseOver);
    Event.stopObserving(this.element, "mouseout", this.eventMouseOut);
    Event.stopObserving(this.element, "mousemove", this.eventMouseMove);
  },

  registerEvents: function() {
    Event.observe(this.element, "mouseover", this.eventMouseOver);
    Event.observe(this.element, "mouseout", this.eventMouseOut);
    Event.observe(this.element, "mousemove", this.eventMouseMove);
  },

  moveTooltip: function(event){
	  Event.stop(event);
	  // get Mouse position
    var mouse_x = Event.pointerX(event);
	  var mouse_y = Event.pointerY(event);
	
	  // decide if wee need to switch sides for the tooltip
	  var dimensions = Element.getDimensions( this.tool_tip );
	  var element_width = dimensions.width;
	  var element_height = dimensions.height;
	
	  if ( (element_width + mouse_x) >= ( this.getWindowWidth() - this.options.min_distance_x) ){ // too big for X
		  mouse_x = mouse_x - element_width;
		  // apply min_distance to make sure that the mouse is not on the tool-tip
		  mouse_x = mouse_x - this.options.min_distance_x;
	  } else {
		  mouse_x = mouse_x + this.options.min_distance_x;
	  }
	
	  if ( (element_height + mouse_y) >= ( this.getWindowHeight() - this.options.min_distance_y) ){ // too big for Y
		  mouse_y = mouse_y - element_height;
	    // apply min_distance to make sure that the mouse is not on the tool-tip
		  mouse_y = mouse_y - this.options.min_distance_y;
	  } else {
		  mouse_y = mouse_y + this.options.min_distance_y;
	  } 
	
	  // now set the right styles
	  this.setStyles(mouse_x, mouse_y);
  },
	
		
  showTooltip: function(event) {
    Event.stop(event);
    this.moveTooltip(event);
	  new Element.show(this.tool_tip);
  },
  
  setStyles: function(x, y){
    // set the right styles to position the tool tip
	  Element.setStyle(this.tool_tip, { position:'absolute',
	 								    top:y + this.options.delta_y + "px",
	 								    left:x + this.options.delta_x + "px",
									    zindex:this.options.zindex
	 								  });
	
	  // apply default theme if wanted
	  if (this.options.default_css){
	  	  Element.setStyle(this.tool_tip, { margin:this.options.margin,
		 		  						                    padding:this.options.padding,
		                                      backgroundColor:this.options.backgroundColor,
										                      zindex:this.options.zindex
		 								    });	
	  }	
  },

  hideTooltip: function(event){
	  new Element.hide(this.tool_tip);
  },

  getWindowHeight: function(){
    var innerHeight;
	  if (navigator.appVersion.indexOf('MSIE')>0) {
		  innerHeight = document.body.clientHeight;
    } else {
		  innerHeight = window.innerHeight;
    }
    return innerHeight;	
  },
 
  getWindowWidth: function(){
    var innerWidth;
	  if (navigator.appVersion.indexOf('MSIE')>0) {
		  innerWidth = document.body.clientWidth;
    } else {
		  innerWidth = window.innerWidth;
    }
    return innerWidth;	
  }

}

/*
 *
 *  Ajax Autocomplete for Prototype, version 1.0.3
 *  (c) 2008 Tomas Kirda
 *
 *  Ajax Autocomplete for Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the web site: http://www.devbridge.com/projects/autocomplete/
 *
 */

var Autocomplete = function(el, options){
  this.el = $(el);
  this.id = this.el.identify();
  this.el.setAttribute('autocomplete','off');
  this.suggestions = [];
  this.data = [];
  this.badQueries = [];
  this.selectedIndex = -1;
  this.currentValue = this.el.value;
  this.intervalId = 0;
  this.cachedResponse = [];
  this.instanceId = null;
  this.onChangeInterval = null;
  this.ignoreValueChange = false;
  this.serviceUrl = options.serviceUrl;
  this.options = {
    autoSubmit:false,
    minChars:1,
    maxHeight:300,
    deferRequestBy:0,
    width:0,
    container:null
  };
  if(options){ Object.extend(this.options, options); }
  if(Autocomplete.isDomLoaded){
    this.initialize();
  }else{
    Event.observe(document, 'dom:loaded', this.initialize.bind(this), false);
  }
};

Autocomplete.instances = [];
Autocomplete.isDomLoaded = false;

Autocomplete.getInstance = function(id){
  var instances = Autocomplete.instances;
  var i = instances.length;
  while(i--){ if(instances[i].id === id){ return instances[i]; }}
};

Autocomplete.highlight = function(value, re){
  return value.replace(re, function(match){ return '<strong>' + match + '<\/strong>' });
};

Autocomplete.prototype = {

  killerFn: null,

  initialize: function() {
    var me = this;
    this.killerFn = function(e) {
      if (!$(Event.element(e)).up('.autocomplete')) {
        me.killSuggestions();
        me.disableKillerFn();
      }
    } .bindAsEventListener(this);

    if (!this.options.width) { this.options.width = this.el.getWidth(); }

    var div = new Element('div', { style: 'position:absolute;' });
    div.update('<div class="autocomplete-w1"><div class="autocomplete-w2"><div class="autocomplete" id="Autocomplete_' + this.id + '" style="display:none; width:' + this.options.width + 'px;"></div></div></div>');

    this.options.container = $(this.options.container);
    if (this.options.container) {
      this.options.container.appendChild(div);
      this.fixPosition = function() { };
    } else {
      document.body.appendChild(div);
    }

    this.mainContainerId = div.identify();
    this.container = $('Autocomplete_' + this.id);
    this.fixPosition();
    
    Event.observe(this.el, window.opera ? 'keypress':'keydown', this.onKeyPress.bind(this));
    Event.observe(this.el, 'keyup', this.onKeyUp.bind(this));
    Event.observe(this.el, 'blur', this.enableKillerFn.bind(this));
    Event.observe(this.el, 'focus', this.fixPosition.bind(this));
    this.container.setStyle({ maxHeight: this.options.maxHeight + 'px' });
    this.instanceId = Autocomplete.instances.push(this) - 1;
  },

  fixPosition: function() {
    var offset = this.el.cumulativeOffset();
    $(this.mainContainerId).setStyle({ top: (offset.top + this.el.getHeight()) + 'px', left: offset.left + 'px' });
  },

  enableKillerFn: function() {
    Event.observe(document.body, 'click', this.killerFn);
  },

  disableKillerFn: function() {
    Event.stopObserving(document.body, 'click', this.killerFn);
  },

  killSuggestions: function() {
    this.stopKillSuggestions();
    this.intervalId = window.setInterval(function() { this.hide(); this.stopKillSuggestions(); } .bind(this), 300);
  },

  stopKillSuggestions: function() {
    window.clearInterval(this.intervalId);
  },

  onKeyPress: function(e) {
    if (!this.enabled) { return; }
    // return will exit the function
    // and event will not fire
    switch (e.keyCode) {
      case Event.KEY_ESC:
        this.el.value = this.currentValue;
        this.hide();
        break;
      case Event.KEY_TAB:
      case Event.KEY_RETURN:
        if (this.selectedIndex === -1) {
          this.hide();
          return;
        }
        this.select(this.selectedIndex);
        if (e.keyCode === Event.KEY_TAB) { return; }
        break;
      case Event.KEY_UP:
        this.moveUp();
        break;
      case Event.KEY_DOWN:
        this.moveDown();
        break;
      default:
        return;
    }
    Event.stop(e);
  },

  onKeyUp: function(e) {
    switch (e.keyCode) {
      case Event.KEY_UP:
      case Event.KEY_DOWN:
        return;
    }
    clearInterval(this.onChangeInterval);
    if (this.currentValue !== this.el.value) {
      if (this.options.deferRequestBy > 0) {
        // Defer lookup in case when value changes very quickly:
        this.onChangeInterval = setInterval((function() {
          this.onValueChange();
        }).bind(this), this.options.deferRequestBy);
      } else {
        this.onValueChange();
      }
    }
  },

  onValueChange: function() {
    clearInterval(this.onChangeInterval);
    this.currentValue = this.el.value;
    this.selectedIndex = -1;
    if (this.ignoreValueChange) {
      this.ignoreValueChange = false;
      return;
    }
    if (this.currentValue === '' || this.currentValue.length < this.options.minChars) {
      this.hide();
    } else {
      this.getSuggestions();
    }
  },

  getSuggestions: function() {
    var cr = this.cachedResponse[this.currentValue];
    if (cr && Object.isArray(cr.suggestions)) {
      this.suggestions = cr.suggestions;
      this.data = cr.data;
      this.suggest();
    } else if (!this.isBadQuery(this.currentValue)) {
      new Ajax.Request(this.serviceUrl, {
        parameters: { query: this.currentValue },
        onComplete: this.processResponse.bind(this),
        method: 'get'
      });
    }
  },

  isBadQuery: function(q) {
    var i = this.badQueries.length;
    while (i--) {
      if (q.indexOf(this.badQueries[i]) === 0) { return true; }
    }
    return false;
  },

  hide: function() {
    this.enabled = false;
    this.selectedIndex = -1;
    this.container.hide();
  },

  suggest: function() {
    if (this.suggestions.length === 0) {
      this.hide();
      return;
    }
    var content = [];
    //var re = new RegExp('\\b' + this.currentValue.match(/\w+/g).join('|\\b'), 'gi');
    var re = new RegExp(this.currentValue.match(/\w+/g).join('|'), 'gi');
    this.suggestions.each(function(value, i) {
      content.push((this.selectedIndex === i ? '<div class="selected"' : '<div'), ' title="', value, '" onclick="Autocomplete.instances[', this.instanceId, '].select(', i, ');" onmouseover="Autocomplete.instances[', this.instanceId, '].activate(', i, ');">', Autocomplete.highlight(value, re), '</div>');
    } .bind(this));
    this.enabled = true;
    this.container.update(content.join('')).show();
  },

  processResponse: function(xhr) {
    var response;
    try {
      response = xhr.responseText.evalJSON();
      if (!Object.isArray(response.data)) { response.data = []; }
    } catch (err) { return; }
    this.suggestions = response.suggestions;
    this.data = response.data;
    this.cachedResponse[response.query] = response;
    if (response.suggestions.length === 0) { this.badQueries.push(response.query); }
    if (response.query === this.currentValue) { this.suggest(); }
  },

  activate: function(index) {
    var divs = this.container.childNodes;
    var activeItem;
    // Clear previous selection:
    if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
      divs[this.selectedIndex].className = '';
    }
    this.selectedIndex = index;
    if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
      activeItem = divs[this.selectedIndex]
      activeItem.className = 'selected';
    }
    return activeItem;
  },

  deactivate: function(div, index) {
    div.className = '';
    if (this.selectedIndex === index) { this.selectedIndex = -1; }
  },

  select: function(i) {
    var selectedValue = this.suggestions[i];
    if (selectedValue) {
      this.el.value = selectedValue;
      if (this.options.autoSubmit && this.el.form) {
        this.el.form.submit();
      }
      this.ignoreValueChange = true;
      this.hide();
      this.onSelect(i);
    }
  },

  moveUp: function() {
    if (this.selectedIndex === -1) { return; }
    if (this.selectedIndex === 0) {
      this.container.childNodes[0].className = '';
      this.selectedIndex = -1;
      this.el.value = this.currentValue;
      return;
    }
    this.adjustScroll(this.selectedIndex - 1);
  },

  moveDown: function() {
    if (this.selectedIndex === (this.suggestions.length - 1)) { return; }
    this.adjustScroll(this.selectedIndex + 1);
  },

  adjustScroll: function(i) {
    var container = this.container;
    var activeItem = this.activate(i);
    var offsetTop = activeItem.offsetTop;
    var upperBound = container.scrollTop;
    var lowerBound = upperBound + this.options.maxHeight - 25;
    if (offsetTop < upperBound) {
      container.scrollTop = offsetTop;
    } else if (offsetTop > lowerBound) {
      container.scrollTop = offsetTop - this.options.maxHeight + 25;
    }
    this.el.value = this.suggestions[i];
  },

  onSelect: function(i) {
    (this.options.onSelect || Prototype.emptyFunction)(this.suggestions[i], this.data[i]);
  }

};

Event.observe(document, 'dom:loaded', function(){ Autocomplete.isDomLoaded = true; }, false);




    function jsBrowserSniff()
    {
        if (document.layers) 
            return "NS";

        if (document.all) {
            var agt = navigator.userAgent.toLowerCase();
            var is_opera = (agt.indexOf("opera") != -1);
            var is_konq = (agt.indexOf("konqueror") != -1);

            if(is_opera)
            {
                return "OPR";
            }else{
                if(is_konq)
                {
                    return "KONQ";
                }else{
                    return "IE";
                }
            }
        }

        if (document.getElementById) 
            return "MOZ";

        return "OTHER";
      }
      
      
      
    function jsBrowserWidth(){
        var browser = jsBrowserSniff();
        switch (browser){
        case "MOZ":
        case "NS": 
            return window.innerWidth;
            break;
        case "OPR": 
            return -1;
            break;
        case "KONQ": 
            return -1;
            break;
        case "IE": 
            if (document.documentElement && document.documentElement.clientHeight)    // Explorer 6 Strict Mode
                w = document.documentElement.clientWidth;
            else 
                if (document.body) // other Explorers
                    w = document.body.clientWidth;
                return w;
                break;
        case "OTHER": 
            return -1;
            break;
        }
    }
    
    
    
    function jsBrowserHeight()
    {
        var browser = jsBrowserSniff();

        switch (browser){
            case "MOZ":
            case "NS": 
                return window.innerHeight;
                break;
            case "OPR": 
                return -1;
                break;
            case "KONQ": 
                return -1;
                break;
            case "IE": 
                if (document.documentElement && document.documentElement.clientHeight)    // Explorer 6 Strict Mode
                    h = document.documentElement.clientHeight;
                else
                    if (document.body) // other Explorers
                        h = document.body.clientHeight;
                return h;
                break;
            case "OTHER": 
                return -1;
                break;
        }
    }
