var dmap = null;

function DMap() {
    
    // config params
    this.absoluteServerPath =       '';
    this.tilePath =                 this.absoluteServerPath + 'gif/';
    this.transpImgPath = 			this.absoluteServerPath + 'images/transp.gif';
    this.jsonPath =                 this.absoluteServerPath + 'json/';
    this.tileContainer =            mdvLib.$('tileContainer');
    this.dmapContainer =            mdvLib.$('dmapViewport');
    this.msgContainer = 			mdvLib.$('infoMsg-template');
    this.tile =                     {
                                        width: 740,
                                        height: 500
                                    };
    this.viewport =                 {
                                        width: 740,
                                        height: 500
                                    };
    this.zoomlevels =               3;
    this.numTilesX =                [1, 2, 4];
    this.numTilesY =                [1, 2, 4];
    this.controlIncr =				100; // move value in px, when dmapControl gets clicked 			
    this.reloadTimeout = 			30*1000; //milliseconds
    // params to be updated
    
    // holds mousePos at time of click/mousewheel in real (overall) pixel
    this.targetCentre =             null;
    
    // gets set *after* layer update via setZoomlevel() 
    this.currentZoomlevel =         null;
    
    // init with 0, update *before* layer update via setZoomlevel()
    this.currentDayIdx =            null;
    
    // obj literal with members row and col
    // gets set in getClickedTile() with zero-based indices
    this.currentTileIdx =           null;
    
    // obj literal with offset of dmap html container
    // regarding the browser's 0/0 corner. set in init() and onresize()
    this.dmapContainerPos =         null;
    
    // holds obj literals with overall px sizes of all zoom layers
    this.overallSize =              [];
    
    // dragging flag
    this.dragging =                 false;
   
    // holds obj literal with real (overall) px offset of 
    // tileContainer in relation to 0/0 corner of dmap viewport
    // init with 0/0 in initTileLayer()
    // update in setCentre()and onDragEnd()
    this.moveStartOffset =          null;
    
    // holds obj literal with mouse offset regarding the browsers 0/0 corner.
    // used only to calculate delta while dragging more easily,
    // gets set in onmousedown handler.
    this.moveStart =                null;
    
    // holds obj literal with real (overall) px offset of 
    // tileContainer in relation to 0/0 corner of dmap viewport
    // init with 0/0 in initTileLayer()
    // update in moveTileContainer()and setCentre()
    this.currentOffset =            null;
   
    // stores mouseCoords for setting of new centre
    // after zooming. gets set in getAbsPos()
    this.targetOffset = 			{ left: 0, top:  0 };
    
    // array holding json response for each zoomlevel
    this.jsonData =					[];
    
    this.ctrlImgs =					[];
    
    this.timeout_tt =               null;
}




DMap.prototype.fetchTile = function(filename) {

    var img = new Image();

    img.onerror = function() { 
                    this.debugMsg('ERROR<br/>loading ' + filename); 
                }.bind(this);

    img.onload = function() {
        
                }.bind(this);
    
    img.src = filename;
    
    return img;

};





DMap.prototype.moveTileContainer = function(posObj, level) {
    
    this.currentOffset = { left: this.moveStartOffset.left - posObj.left, 
                           top: this.moveStartOffset.top - posObj.top };
                           
    this.tileContainer.style.left = this.currentOffset.left + 'px';
    this.tileContainer.style.top = this.currentOffset.top + 'px';
    
};



DMap.prototype.setCentre = function(posObj, level) {
    
    // keep click / wheel mouse pos in target level (~ hotspotonmousewheel)
    this.currentOffset = { left: (posObj.left * -1) + this.targetOffset.left,
                           top: (posObj.top * -1) + this.targetOffset.top }; 
    
    this.moveStartOffset = this.currentOffset;
    this.tileContainer.style.left = this.currentOffset.left + 'px'; 
    this.tileContainer.style.top = this.currentOffset.top + 'px';
    
};





DMap.prototype.getAbsPos = function(e) {
    
    if (this.dmapContainerPos === null) {
        return;
    }
    
    var relPos = mdvLib.getMousePosition(e);
    var  tileContainerPos = {
    	left: parseInt(this.tileContainer.style.left, 10) * -1,
    	top: parseInt(this.tileContainer.style.top, 10) * -1
    };
    
    this.targetOffset = {
    	left: relPos.left - this.dmapContainerPos.left,
    	top: relPos.top - this.dmapContainerPos.top
    };
   
    return {
        left: (tileContainerPos.left + (relPos.left - this.dmapContainerPos.left)) / this.numTilesX[this.currentZoomlevel],
        top: (tileContainerPos.top + (relPos.top - this.dmapContainerPos.top)) / this.numTilesX[this.currentZoomlevel]
    }; 
};





DMap.prototype.debugMsg = function(msg) {
    //log.info(msg);
};





DMap.prototype.clearTileContainer = function() {
    while (this.tileContainer.hasChildNodes()) {
        this.tileContainer.removeChild(this.tileContainer.lastChild);
    }
};





DMap.prototype.initTileLayer = function(level) {
    
    var i, j, tmpImg;
    
    this.clearTileContainer();
    
    // prepare tileContainer for new zoomlevel
    // set dimensions...
    mdvLib.style([this.tileContainer], {
        width: this.overallSize[level].width + 'px',
        height: this.overallSize[level].height + 'px',
        left: 0,
        top: 0
    });
    
    // reset top left corner value
    this.moveStartOffset = { left: 0, top: 0 };
    this.currentOffset = { left: 0, top: 0 };
    
    // and init tiles with transp.gif
    for (i=0; i<this.numTilesX[level]; i+=1) {
        for (j=0; j<this.numTilesY[level]; j+=1) {
            tmpImg = this.fetchTile(this.transpImgPath);
            tmpImg.id = 'tile-' + i + '-' + j;
            tmpImg.width = this.tile.width;
            tmpImg.height = this.tile.height;
            tmpImg.style.position = 'absolute';
            tmpImg.style.left = ((i+1)*this.tile.width - this.tile.width) + 'px';
            tmpImg.style.top = ((j+1)*this.tile.height - this.tile.height) + 'px';
            this.tileContainer.appendChild(tmpImg);
        }
    }
};





DMap.prototype.setZoomlevel = function(level, dayIdx, posObj) {
    
    var i, tn, zoomImg = mdvLib.$('zoomImg' + level);
    
    if (typeof this.refMessageTimeout==='number') {
        window.clearTimeout(this.refMessageTimeout); 
        this.clearInfoMsg();      
    }
    
    if (level === this.currentZoomlevel && dayIdx === this.currentDayIdx) {
        return;
    }
    // reset jsonData upon date change
    if (dayIdx !== this.currentDayIdx) {
    	this.jsonData.length = 0;
    }
    
    this.currentDayIdx = dayIdx;
    
    dmap_setCookie('dayIdx', this.currentDayIdx);
    
    this.initTileLayer(level);
    
    for (i=0; i<this.ctrlImgs.length; i+=1) {
    	this.ctrlImgs[i].src = this.ctrlImgs[i].src.replace('Hover', 'Normal');
    }; 
    zoomImg.src = zoomImg.src.replace('Normal', 'Hover');
    
    if (level === 0) {
        // no tile path calculation needed.
        // just get the overview tile for the requested day. 
        this.tileContainer.appendChild(this.fetchTile(this.tilePath + this.currentDayIdx + '/RD-' + this.currentDayIdx + '-0-0-0.gif'));
    } else {
       tn = this.getClickedTile(level, posObj);
       this.loadTileSet(level, tn);
    }
    
    this.currentZoomlevel = level;
   
    this.getJsonForLevel();
    
};


DMap.prototype.getJsonForLevel = function() {
	
	if (typeof this.jsonData[this.currentZoomlevel] !== 'undefined') {
		
		this.createImageMap();
		
		return;		
	
	}
	
	var ajax = mdvLib.ajax({ 
					'host': this.getJsonPathStr(),
					'method': 'get', 
					'onComplete': this.onGetJsonComplete.bind(this),
					'onFailure': function(e) {
					}
				});	
	
}

DMap.prototype.getInfoCreationDate = function(creationStr) {
	return /^([\d|\/|\s|:]+)\/.*/.exec(creationStr)[1] || 'error extracting date';
};

DMap.prototype.onGetJsonComplete = function(resp) {
	
	var data,
		_response = resp.responseText || resp;
	
	eval('data = ' + _response + ';');
	
	this.jsonData[this.currentZoomlevel] = data;
	mdvLib.$('lastUpdated').innerHTML = 'Last updated: '  + this.getInfoCreationDate(data.creation);
	
	this.createImageMap();

};

DMap.prototype.createImageMap = function() {
	
	var i, imgMap, oldImgMap, ts,
	    imgMapImg = mdvLib.$('imgMapImg');
	var	stopHs = this.jsonData[this.currentZoomlevel].stopHotSpots;
	var	linkHs = this.jsonData[this.currentZoomlevel].linkHotSpots;
	
	if (stopHs.length === 0 && linkHs.length === 0) {
		this.displayErrorMsg({
			hl: 'Information',
			color: '#00a651', // nice green
			messageBody: 'There are no messages.<br/><br/><a href="javascript:dmap.clearInfoMsg();"/>close</a>'		
		});
		
		return;
	}
	
	oldImgMap = document.body.getElementsByTagName('map');
    oldImgMap = oldImgMap.length > 0 ? oldImgMap[0] : null;
     
    // clear old image map after changing date
    if (oldImgMap) {
        //null out all event handlers
        dmap_purge(oldImgMap);
        // remove all areas
        while (oldImgMap.hasChildNodes()) {
            oldImgMap.removeChild(oldImgMap.firstChild);
        }
        //remove the image map
        document.body.removeChild(oldImgMap);
    }
    
    // clear old image map hook image
	if (imgMapImg) {
    	imgMapImg.useMap = null;
        this.tileContainer.removeChild(imgMapImg);    
	}
	
	// create a new image to hook the image map on
	imgMapImg = this.fetchTile(this.transpImgPath);
	imgMapImg.id = 'imgMapImg';
	imgMapImg.width = this.overallSize[this.currentZoomlevel].width;
    imgMapImg.height = this.overallSize[this.currentZoomlevel].height;
    
    mdvLib.style([imgMapImg], {
        position: 'absolute',
        border: 0,
        left: 0,
        top: 0,
        display: 'block',
        zIndex: 4
    });
    
    this.tileContainer.appendChild(imgMapImg);	
	
	// create the image map
	var ts = new Date().getTime();
	imgMap = document.createElement('map');
	// timestamp for WebKit-Browsers (Safari, Chrome, ...)
	// don't remove this!
    imgMap.name = 'dMapInfo' + ts;
    imgMap.id = 'dMapInfo' +  ts;
    document.body.appendChild(imgMap);	
	
	
	// fill it with hotspots
	for (i=0; i<stopHs.length; i++) {
		var area = this.createImgMapHotspot(stopHs[i])
		imgMap.appendChild(area);
	}
	
	for (i=0; i<linkHs.length; i++) {
		imgMap.appendChild(this.createImgMapHotspot(linkHs[i]));
	}
	
	// and assign it to our created image.
	// use the id instead of name, or IE won't show it.
	imgMapImg.useMap = '#' + imgMap.id;
	
};

DMap.prototype.createImgMapHotspot = function(areaElem) {
	
	var area = document.createElement('area');
	
	area.coords = areaElem.coords;
	area.shape = areaElem.coords.split(',').length > 4 ? 'poly' : 'rect';
	area.title = areaElem.title;
	area.alt = areaElem.alt;
	area.onmouseover = function(e) {
	                       var pos = this.getInfoMsgPosition(e);
	                        if (typeof this.refMessageTimeout==='number') {
                                window.clearTimeout(this.refMessageTimeout);       
                            }
                            if (typeof this.refMessageTimeoutOpen==='number') {
                                window.clearTimeout(this.refMessageTimeoutOpen);       
                            }
                            this.refMessageTimeoutOpen = window.setTimeout(function(){
                                                            this.displayInfoMsg(pos, areaElem.aiMsgNr)
                                                        }.bind(this), 250);
    						return false;
						}.bind(this);
						 
	area.onmouseout = this.messageTimeout.bind(this); 
    
	return area;
};

DMap.prototype.messageTimeout = function() {
   this.refMessageTimeout = window.setTimeout(function(){
                                                this.clearInfoMsg();
                                            }.bind(this), 1500);
};

DMap.prototype.getMsgItemById = function(id) {
	var idArr = id.split('|');
	var i, j, res=[];
	var aiMsgs = this.jsonData[this.currentZoomlevel].aiMessages;
	
	for (j=0; j < idArr.length; j+=1) {
		for (i=0; i < aiMsgs.length; i+=1) {
			if (aiMsgs[i].id === idArr[j]) {
				res.push(aiMsgs[i])
			}
		}
	} 
	return res; 
};



DMap.prototype.buildMsg = function(msgItems, colors) {
    
    var i, j, 
        msg = '<div class="msgBody">';
        
    if (colors && colors.constructor === Array && colors.length > 1) {
        msg += '<div class="msgLines">';
        msg += this.visualizeLineColors(colors);
        msg += '</div>'
    }    
        
    outer: for(i=0; i<msgItems.length; i++) {
        // check for duplicate text before adding current message
        j=i;
        while (msgItems[j-1]) {
            if (msgItems[j-1].text === msgItems[i].text) {
                continue outer;
            } else {
                j-=1;
            }
        }
    	msg += '<div class="msgText">' + msgItems[i].text + '</div>';
    	msg += '<div class="msgCreation">message created:' + msgItems[i].creation + '<br/>';
    	msg += 'last modified:' + msgItems[i].lastMod + '</div>';
    	msg += '<hr/>';
    }
    // remove trailing hr
    msg = msg.replace(/<hr\/>$/, '');
    //msg = this.colorizeLineNames(msg); 
    msg += '</div>';
    return msg;
};

DMap.prototype.visualizeLineColors = function(colorArr) {
    var i, tc, 
        str='',
        tubeColors = {
          '#AE6118': ['Bakerloo', '#FFF'],
          '#E41F1F': ['Central', '#FFF'],
          '#F8D42D': ['Circle', '#113B92'],
          '#00A575': ['District', '#FFF'],
          '#F2AD41': ['East London', '#113B92'],
          '#E899A8': ['H\'smith & City', '#113B92'],
          '#8F989E': ['Jubilee', '#FFF'],
          '#893267': ['Metropolitan', '#FFF'],
          '#000000': ['Northern', '#FFF'],
          '#0450A1': ['Piccadilly', '#FFF'],
          '#009FE0': ['Victoria', '#FFF'],
          '#70C3CE': ['Waterloo & City', '#113B92'],
          '#FFF': ['', '#000']  
        };
        
    for (var i=0; i<colorArr.length; i++) {
        tc = tubeColors[colorArr[i]] || tubeColors['#FFF']; 
        str += '<span style="background:' + colorArr[i];
        str += ';color:' + tc[1] + ';padding: 1px 4px; line-height:20px;">';
        str += '<nobr>' + tc[0] + '</nobr></span> ';   	
    }
    return str;
};

DMap.prototype.colorizeLineNames = function(str) {
    var regExLines = /london overground/gi;
    return str.replace(regExLines, function(hit) {
       var m = {
           'london overground': '<span style="background:#F5822B;color:#fff;">London Overground</span>'
       }; 
       return m[hit.toLowerCase()];
    });
};

DMap.prototype.displayInfoMsg = function(pos, aiMsgNr){
	
	var headerColor = '#cdcdcd',
	    headerText = 'Service Information',
		msgItems = this.getMsgItemById(aiMsgNr);
	var templateInner = this.msgContainer.innerHTML;
	var outerContainer = mdvLib.$('msgContainer');
	var re = /^([\w|\s]+):(.*)/i;
	var res = re.exec(msgItems[0].text);
	
	// workaround. for some reason the style attr gets stripped in method innerHTML
	// when put in the html template div. 
	var h2 =  '<h2 style="background-color:---hlColor---; color: ---color---;">---hl---</h2>';
	
	// station info comes without color attribute
	var colorArr = msgItems[0].color && msgItems[0].color.split('|');
	
	if (colorArr === undefined) {
	   headerText = 'Station Information';
	} else if (colorArr.length === 1) {
	    headerColor = msgItems[0].color;
	    headerText = msgItems[0].lineName;
	}
	
	var msgObj = {
		hlSlot: h2, 
		hl: headerText,
		hlColor : headerColor,
		color: headerColor !== '#cdcdcd' ? '#fff' : '#000',
		msg: this.buildMsg(msgItems, colorArr) || '(no information)'
	};
	
	var msgContent = this.replaceTemplate(templateInner, msgObj);
	outerContainer.innerHTML = msgContent;
	
	mdvLib.style(['msgContainer'], {
		top: pos.top + 'px',
		left: pos.left + 'px',
		display: 'block'
	});
	
	outerContainer.onmouseover = function(e) {
	    if (typeof this.refMessageTimeout === 'number') {
	       window.clearTimeout(this.refMessageTimeout);
	    }
	    var evt = e ? e : window.event;
	    this.cancelEvent(evt);
	}.bind(this);
	
	outerContainer.onmouseout = function(e){
        var evt = e ? e : window.event;
        this.messageTimeout();
        this.cancelEvent(evt);
    }.bind(this);
};

DMap.prototype.displayErrorMsg = function(msgItem) {
	
	var templateInner = this.msgContainer.innerHTML;
	var h2 =  '<h2 style="background-color:---hlColor---; color: ---color---;">---hl---</h2>';
	var msgObj = {
		hlSlot: h2, 
		hl: msgItem.hl,
		hlColor : msgItem.color || 'cdcdcd',
		color: '#fff',
		msg: '<div class="msgBody">' + msgItem.messageBody + '</div>'
	};
	
	var msgContent = this.replaceTemplate(templateInner, msgObj);
	mdvLib.$('msgContainer').innerHTML = msgContent;
	
	mdvLib.style(['msgContainer'], {
		top: '200px',
		left: '220px',
		display: 'block'
	});
	
};

DMap.prototype.getInfoMsgPosition = function(e) {
	
	var pos = mdvLib.getMousePosition(e);
		pos.left -= this.dmapContainerPos.left;
	    pos.top -= this.dmapContainerPos.top;
	    pos.top += 40;
	    pos.left += 40;
	
	// container width hardcoded here...
	if (pos.left + 300 > this.tile.width) {
    	pos.left -= 300;
    	pos.left -= 60;
    }
    if (pos.top + 200 > this.tile.height) {
    	pos.top = 300;
    }
    
	return pos;

};

DMap.prototype.replaceTemplate = function(template, obj) {
	var result = template;
  	for (var key in obj) {
		result = this.replaceTemplateValue(result, key, obj[key]);
	}
	return result;
};

DMap.prototype.replaceTemplateValue = function(template, key, value) {
  return template.replace(new RegExp("---" + key + "---", "g"), value);
};

DMap.prototype.clearInfoMsg = function(){
	mdvLib.$('msgContainer').style.display = 'none';
};

DMap.prototype.getClickedTile = function(level, posObj) {
    
    var posInTargetZoomlevel, rowIdx, colIdx;
    
    posInTargetZoomlevel = { 
        left: posObj.left*this.numTilesX[level],
        top: posObj.top*this.numTilesY[level]
    };
    
    rowIdx =  Math.floor(posInTargetZoomlevel.top / this.tile.height);
    colIdx =  Math.floor(posInTargetZoomlevel.left / this.tile.width);
    
    this.targetCentre = posInTargetZoomlevel;
    
    this.currentTileIdx = {
        row: rowIdx,
        col: colIdx
    };
    
    return this.getPathStr(level, colIdx, rowIdx);
    
};





DMap.prototype.getPathStr = function(level, colIdx, rowIdx) {
    
    return this.tilePath + this.currentDayIdx + '/' + 'RD-' + this.currentDayIdx + 
    		'-' + level + '-' + colIdx + '-' + rowIdx + '.gif';

};


DMap.prototype.getJsonPathStr = function() {
	
	var timestamp = new Date();
	var rc = (this.currentZoomlevel===0) ? 0 : 'ALL';
    
    return this.jsonPath + this.currentDayIdx + '/' + 'RD-' + this.currentDayIdx + 
    		'-' + this.currentZoomlevel + '-' + rc + '-' + rc + '.json';
};



// load new centre tile first,
// then fetch the ones around it.

DMap.prototype.loadTileSet = function(level, tileName) {
    
    var tmpTile, isTopRow, isBottomRow, isLeftCol, isRightCol;
    
    // set centre tile and move tileContainer
    tmpTile = this.fetchTile(tileName);
    mdvLib.$('tile-' + this.currentTileIdx.col + '-' + this.currentTileIdx.row).src = tmpTile.src;

    this.setCentre(this.targetCentre, level);
    
    // load tiles around it
    isTopRow = this.currentTileIdx.row === 0;
    isBottomRow = this.currentTileIdx.row === this.numTilesY[level]-1;
    isLeftCol = this.currentTileIdx.col === 0;
    isRightCol = this.currentTileIdx.col === this.numTilesX[level]-1;
    
    // nw
    if (!isTopRow && !isLeftCol) {
        tmpTile = this.fetchTile(this.getPathStr(level, this.currentTileIdx.col-1, this.currentTileIdx.row-1));
        mdvLib.$('tile-' + (this.currentTileIdx.col-1) + '-' + (this.currentTileIdx.row-1)).src = tmpTile.src;
    }
    // n
    if (!isTopRow) {
        tmpTile = this.fetchTile(this.getPathStr(level, this.currentTileIdx.col, this.currentTileIdx.row-1));
        mdvLib.$('tile-' + this.currentTileIdx.col + '-' + (this.currentTileIdx.row-1)).src = tmpTile.src;
    }
    
    // ne
    if (!isTopRow && !isRightCol) {
        tmpTile = this.fetchTile(this.getPathStr(level, this.currentTileIdx.col+1, this.currentTileIdx.row-1));
        mdvLib.$('tile-' + (this.currentTileIdx.col+1) + '-' + (this.currentTileIdx.row-1)).src = tmpTile.src;
    }
    
    // e
    if (!isRightCol) {
        tmpTile = this.fetchTile(this.getPathStr(level, this.currentTileIdx.col+1, this.currentTileIdx.row));
        mdvLib.$('tile-' + (this.currentTileIdx.col+1) + '-' + this.currentTileIdx.row).src = tmpTile.src;
    }
    
    // se
    if (!isBottomRow && !isRightCol) {
        tmpTile = this.fetchTile(this.getPathStr(level, this.currentTileIdx.col+1, this.currentTileIdx.row+1));
        mdvLib.$('tile-' + (this.currentTileIdx.col+1) + '-' + (this.currentTileIdx.row+1)).src = tmpTile.src;
    }
    
    // s
    if (!isBottomRow) {
        tmpTile = this.fetchTile(this.getPathStr(level, this.currentTileIdx.col, this.currentTileIdx.row+1));
        mdvLib.$('tile-' + this.currentTileIdx.col + '-' + (this.currentTileIdx.row+1)).src = tmpTile.src;
    }
    
    // sw
    if (!isBottomRow && !isLeftCol) {
        tmpTile = this.fetchTile(this.getPathStr(level, this.currentTileIdx.col-1, this.currentTileIdx.row+1));
        mdvLib.$('tile-' + (this.currentTileIdx.col-1) + '-' + (this.currentTileIdx.row+1)).src = tmpTile.src;
    }
    
    // w
    if (!isLeftCol) {
        tmpTile = this.fetchTile(this.getPathStr(level, this.currentTileIdx.col-1, this.currentTileIdx.row));
        mdvLib.$('tile-' + (this.currentTileIdx.col-1) + '-' + this.currentTileIdx.row).src = tmpTile.src;
    }
    
};


DMap.prototype.checkOutOfBounds = function(pos) {
	
	return !(pos.left >= 0 && pos.left <= this.tile.width && 
			  pos.top >= 0 && pos.top <=this.tile.height);
};


DMap.prototype.zoom = function(dir, pos) {
    
    // map not initialised
    if (typeof this.currentZoomlevel !== 'number') {
        return;
    }
    
    if (this.checkOutOfBounds(pos)) {
		
		this.displayErrorMsg({
			hl: 'Error',
			color: '#f00',
			messageBody: 'You tried to zoom outside of the map.<br/><br/>Please move your mouse only within the map<br/>while using the mousewheel or clicking.<br/><br/><a href="javascript:dmap.clearInfoMsg();"/>close</a>'		
		});
		return;
	}
    
    // zoom in
    if (dir && dir==='in' && this.currentZoomlevel+1 < this.zoomlevels) { 
    	this.setZoomlevel(this.currentZoomlevel+1, this.currentDayIdx, pos);
        return;
    }
    
    //zoom out
    if (dir && dir==='out' && this.currentZoomlevel-1 >= 0) { 
    	this.setZoomlevel(this.currentZoomlevel-1, this.currentDayIdx, pos);
        return;
    }
    // out of zoomlevel range
    this.debugMsg('method zoom:\ndir not set or out of zoomlevel range.')
};





DMap.prototype.attachHandlers = function() {
    
    // the dmapContainer catches all mouse events
    // except mousemove and mouseup
    
    this.dmapContainer.onmousewheel = function(e) {
                                        var evt = e ? e : window.event;
                                        var pos = this.getAbsPos(e);
                                        var wheelDelta = evt.wheelDelta ? evt.wheelDelta : evt.detail*-1;
                                        if (wheelDelta < 0) {
                                            this.zoom('out', pos);
                                        } else {
                                            this.zoom('in', pos);
                                        }
                                        // stop bubbling in ff 
                                        this.cancelEvent(evt);
                                        return false;
                                    }.bind(this);
    
    this.dmapContainer.ondblclick = function(e) {
                                        var pos = this.getAbsPos(e);
                                        this.zoom('in', pos);
                                        return false;
                                    }.bind(this);
    
    this.dmapContainer.onmousedown = function(e) {
                                        if (this.currentZoomlevel === 0) {
                                            return;
                                        }
                                        this.dmapContainer.style.cursor = 'url("images/grabbing.cur"), move';
                                        this.dragging = true;
                                        this.moveStart = mdvLib.getMousePosition(e);
                                        
                                        if (this.currentZoomlevel > 1) {
                                            this.checkNeighbouringTiles(e);
                                        }
                                        return false;
                                    }.bind(this);

};

DMap.prototype.attachControlHandlers = function() {

	var i, ctrl = mdvLib.$('dmapControl');
	var imgs = ctrl.getElementsByTagName('img');
	for (i=0; i<imgs.length; i+=1) {
		imgs[i].onmouseover = function() { this.src = this.src.replace('Normal', 'Hover'); this.style.cursor = 'pointer'; };
		imgs[i].onmouseout = function() { 
								if (/Hover/.test(this.src) && this.id && this.id.indexOf(dmap.currentZoomlevel.toString()) > -1) {
									return false;
								}
								this.src = this.src.replace('Hover', 'Normal');
								this.style.cursor = 'default'; 
							};
		imgs[i].ondblclick = function(e) {  var evt = e ? e : window.event; this.cancelEvent(evt); }.bind(this);
		
		this.ctrlImgs.push(imgs[i]);
	}
};

DMap.prototype.checkTileSrc = function(col, row) {
	
	var imgName = 'tile-' + col + '-' + row; 
	var imgSrc = mdvLib.$(imgName) && mdvLib.$(imgName).src || null;
	
	return /transp.gif/.test(imgSrc);
};

DMap.prototype.checkNeighbouringTiles = function(e) {
    
    var pos = this.getAbsPos(e);
    
   	// only set currentTileIdx, no pathstr needed.
   	this.getClickedTile(this.currentZoomlevel, pos);
   	
   	// col / row Idx out of bounds.
   	// this might be possible if user navigate
   	// only via dmapControl  
   	if (this.currentTileIdx.col < 0 || this.currentTileIdx.col > this.numTilesX[this.currentZoomlevel] || 
   		this.currentTileIdx.row < 0 || this.currentTileIdx.row > this.numTilesY[this.currentZoomlevel]) {
   		return;
   	}
    
    // check left col
    if (this.currentTileIdx.col - 1 > -1 && 
    	this.checkTileSrc(this.currentTileIdx.col-1, this.currentTileIdx.row)) {
    	this.loadNeighbouringCol(this.currentTileIdx.col-1);
    }
    // check right col
    if (this.currentTileIdx.col + 1 < this.numTilesX[this.currentZoomlevel] && 
    	this.checkTileSrc(this.currentTileIdx.col+1, this.currentTileIdx.row)) {
    	this.loadNeighbouringCol(this.currentTileIdx.col+1);
    }
    // check upper row
    if (this.currentTileIdx.row - 1 > -1 && 
    	this.checkTileSrc(this.currentTileIdx.col, this.currentTileIdx.row-1)) {
    	this.loadNeighbouringRow(this.currentTileIdx.row-1);
    }
    // check lower row
    if (this.currentTileIdx.row + 1 < this.numTilesY[this.currentZoomlevel] && 
    	this.checkTileSrc(this.currentTileIdx.col, this.currentTileIdx.row + 1)) {
    	this.loadNeighbouringRow(this.currentTileIdx.row+1);
    }
};




DMap.prototype.loadNeighbouringRow = function(rowIdx) {
	
	var i, tmpTile;
	
	for (var i=0; i<this.numTilesX[this.currentZoomlevel]; i+=1) {
		tmpTile = this.fetchTile(this.getPathStr(this.currentZoomlevel, i, rowIdx));
        mdvLib.$('tile-' + i + '-' + rowIdx).src = tmpTile.src;
   }
};




DMap.prototype.loadNeighbouringCol = function(colIdx) {
	
	var i, tmpTile;
	
	for (var i=0; i<this.numTilesY[this.currentZoomlevel]; i+=1) {
		tmpTile = this.fetchTile(this.getPathStr(this.currentZoomlevel, colIdx, i));
        mdvLib.$('tile-' + colIdx + '-' + i).src = tmpTile.src;
   }
};




DMap.prototype.onDragEnd = function(e) {
    
    if (this.dragging !== true) {
        return;
    }
    this.dragging = false;
    
    this.moveStartOffset = this.currentOffset;
    
    this.dmapContainer.style.cursor = 'default';
    
};


DMap.prototype.onDragging = function(e) {
   
    if (this.dragging !== true) {
        return;
    }
     
    if (typeof this.refMessageTimeout==='number') {
        window.clearTimeout(this.refMessageTimeout); 
        this.clearInfoMsg();      
    }
    
    var pos = mdvLib.getMousePosition(e);
    
    var delta = { 
	    	left: this.moveStart.left - pos.left, 
	    	top: this.moveStart.top - pos.top 
		};
    
    this.moveTileContainer(delta);    
    
    return false;
};



DMap.prototype.cancelEvent = function(evt) {
        
    evt.cancelBubble = true;
    evt.returnValue = false;

    if (evt.stopPropagation) {
        evt.stopPropagation();
    }    
    if (evt.preventDefault) {
        evt.preventDefault();
    }  
    return false;
};



DMap.prototype.init = function() {
    
    var i, reloadDay=0; 
    
    if (this.dmapContainer === null) {
        this.debugMsg('ERROR<br/>init failed. dmapContainer is null.');
        return;        
    }
    this.dmapContainerPos = mdvLib.getElementPosition(this.dmapContainer);
    
    reloadDay = parseInt(dmap_readCookie('dayIdx'), 10) || 0;
    
    this.currentDayIdx = reloadDay;
    
    // init overall sizes
    for (i=0; i<this.zoomlevels; i+=1) {
        this.overallSize.push({
            width: this.tile.width*this.numTilesX[i],
            height: this.tile.height*this.numTilesY[i]
        });
    }
    // start today or with the last day viewed, with overview tile
    this.setZoomlevel(0, reloadDay);
    
    // setup dmap event handlers
    this.attachHandlers();
    
    this.attachControlHandlers();
    
    // and the documentwide ones 
    attachEventListener(window.document, 'mousemove', this.onDragging.bind(this), null);
    attachEventListener(window.document, 'mouseup', this.onDragEnd.bind(this), null);
    
    attachEventListener(window, 'resize', function() { 
	   this.dmapContainerPos = mdvLib.getElementPosition(this.dmapContainer); 
	 }.bind(this), null);
    
    //  accomodate FF
    if (window.addEventListener && navigator.product && navigator.product == 'Gecko') {
   		this.dmapContainer.addEventListener('DOMMouseScroll', this.dmapContainer.onmousewheel.bind(this) , false);
	}
	
	this.timeout = window.setTimeout(function() {
		if (mdvLib.$('msgContainer').style.display !== 'block') {
			dmap_setCookie('dayIdx', this.currentDayIdx);
			document.location.href = document.location.href;
		}
	}.bind(this), this.reloadTimeout);
	
	attachEventListener(window, 'unload', function() { 
     if (typeof this.timeout === 'number') {
        window.clearTimeout(this.timeout);    
     }; 
     if (typeof this.refMessageTimeout === 'number') {
        window.clearTimeout(this.refMessageTimeout);    
     }; 
     }.bind(this), null);
};

function dmap_move(where) {
	
	if (dmap === null) {
		return;
	}
	
	if(dmap.currentZoomlevel === 0) {
		return;
	}
	
	var pos = { left: 0, top: 0	};
	
	switch(where) {
		case 'north':	pos.top -= dmap.controlIncr;
						break;
		case 'east':	pos.left += dmap.controlIncr;
						break;
		case 'south':	pos.top += dmap.controlIncr;
						break;
		case 'west':	pos.left -= dmap.controlIncr;
						break;
	}
	
	dmap.moveTileContainer(pos, dmap.currentZoomlevel);
}

function dmap_zoom(cmd) {
	
	if (dmap === null) {
		return;
	}
	var pos = {
		left: dmap.tile.width / 2,
		top: dmap.tile.height / 2
	}; 
	
	switch(cmd) {
		case 'in':		dmap.zoom('in', pos);
						break;
		case 'out':		dmap.zoom('out', pos);
						break;
		case 2:			dmap.setZoomlevel(2, dmap.currentDayIdx, pos);
						break;
		case 1:			dmap.setZoomlevel(1, dmap.currentDayIdx, pos);
						break;
		case 0:			dmap.setZoomlevel(0, dmap.currentDayIdx, pos);
						break;
	}
	
}

function dmap_restore() {
	
	if (dmap === null) {
		return;
	}
	
	dmap.setZoomlevel(0, dmap.currentDayIdx, 0);
}
 
function dmap_setCookie(n, w, e) {
	
	var a; 
	
	if (e) {
		a = new Date();
		a = new Date(a.getTime() + e);
		a = '; expires=' + a.toGMTString();
	} else {
		a = '';
	}
	
	document.cookie = n + '=' + w + a + ';';
}

function dmap_readCookie(name) {
	
	var i, c, ca, 
		nameEQ = name + "=";
	
	ca = document.cookie.split(';');
	
	for(i=0; i<ca.length; i++) {
		c = ca[i];
		while (c.charAt(0)==' ') {
			 c = c.substring(1,c.length);
		}
		if (c.indexOf(nameEQ) == 0) {
			return c.substring(nameEQ.length,c.length);
		}
	}
	return null;
}

function dmap_uniqueArr(arr) {
   
   var i, o, 
       r = new Array();
   
   o:for (i=0, n=arr.length; i<n; i++) {
      for (x=i+1; x<n; x++) {
         if (arr[x]===arr[i]) {
              continue o;
         }
      }
      r.push(arr[i]);
   }
   return r;
}

function dmap_purge(d) {
    var a = d.attributes, i, l, n;
    if (a) {
        l = a.length;
        for (i = 0; i < l; i += 1) {
            n = a[i].name;
            if (typeof d[n] === 'function') {
                d[n] = null;
            }
        }
    }
    a = d.childNodes;
    if (a) {
        l = a.length;
        for (i = 0; i < l; i += 1) {
            dmap_purge(d.childNodes[i]);
        }
    }
}


// rely on mdvLib
attachEventListener(window, 'load', function() { 
    
				    var initDay = parseInt(dmap_readCookie('dayIdx'), 10) || 0;
				    
				    // init date picker
				    datePicker.init.apply(datePicker,[initDay]);
				    
				    // create and init a new disruption map
				    dmap = new DMap();
				    dmap.init();
				    
				 }, null);
