//need prototype!
//class MapClientConfig
function WMSConfig()
{
    this.path = "";
    //map initial settings
    this.scale = 10000;
    this.scalesList = null;
    this.cX = 0;
    this.cY = 0;
    this.format = 'image/png'; 
	this.transparent = false;
    
    //layers
    this.useLayersControl = true;
    this.defaultLayers = null;
    
    //map
    this.width = 400;
    this.height = 400;
    
    this.mapDivId = 'mapContainer';
    
     //selection
    this.useSelection = true;
    this.selDivId = "selDiv";
    
    //history
    this.historyMaxCount = 20;
    
}
//class MapClient
function WMSClient(cfg)
{


    this.config = cfg||new WMSClientConfig();
    
    this.scale =  this.config.scale;

    this.scaleList = null;
    this.scaleIdx = null;
    this.initScaleList();

    this.center = new WMSPoint(this.config.cX,this.config.cY);  
	   
	this.coordSystem = new WMSCoordSystem("LOCAL");
    this.mapRequest = new WMSMapRequest(this.config.format, this.config.transparent);
    
    //history
    this.history = new WMSPosHistory(this.config.historyMaxCount);
    this.savePosition();
    
    //layers
    this.defLayers = null;
    if(this.config.defaultLayers)
    {
        this.defLayers = this.config.defaultLayers.split(",");        
    }
    
    this.layersControl = null;
    if(this.config.useLayersControl)
    this.layersControl = new WMSLayersControl(this.capabilities.rootLayer , this, this.defLayers);
    
    //selection rect
    if(this.config.useSelection)
    {
        this.selElement = document.getElementById(this.config.selDivId);
        this.selElement.selection = true;
    }
    
    //scale label
    this.scaleLabelId = "scaleLabel";
    this.updateScaleLabel();
    
    //init map
    this.moveMode = true;
    this.fillMapRequest();  
    this.tileContainer = new TileContainer(cfg.mapDivId,this,cfg.width,cfg.height);
	


}

WMSClient.prototype.fillMapRequest = function()
{
    if(!this.mapRequest) this.mapRequest = new WMSMapRequest();
    
    //layers
    this.fillRequestLayers();
    
    //crs
    this.mapRequest.crs = this.coordSystem.crsName;
    //format
    //if(this.capabilities.mapFormatList.length>0)
    //this.mapRequest.format = this.capabilities.mapFormatList[0];
}

WMSClient.prototype.fillRequestLayers = function()
{
   if(this.config.useLayersControl)
    this.mapRequest.layerNames = this.layersControl.getSelectedLayersNames();
    else this.mapRequest.layerNames = this.defLayers;
}

WMSClient.prototype.onLayerChange = function(layer,state)
{
    this.fillRequestLayers();
    this.tileContainer.updateTiles();   
}

WMSClient.prototype.initScaleList = function()
{
    if(!this.config.scaleList)return;
    this.scaleList = this.config.scaleList.split(',');
    for(var i=0;i<this.scaleList.length;i++)
    {
       var sc = parseFloat(this.scaleList[i]);
       if(this.scale==sc)this.scaleIdx = i;
       this.scaleList[i]= sc;
    }

    if(this.scaleIdx == null)
    {
        this.scaleIdx = (this.scaleList.length/2).floor();
        this.scale =  this.scaleList[this.scaleIdx];
    }
}

WMSClient.prototype.moveMapImage = function(pixDx,pixDy)
{    
    this.tileContainer.moveTiles(-pixDx,-pixDy);
    this.tileContainer.checkCorners(); 
    this.onViewUpdated();
}

WMSClient.prototype.zoomMapImage = function(mult)
{
    this.scale += Math.round(this.scale*(1 - mult));
    this.tileContainer.resetTiles();
    this.onViewUpdated();    
}

WMSClient.prototype.zoomPlus = function()
{
    if(this.scaleList)
    {
        if(this.scaleIdx<(this.scaleList.length-1))
        {
            this.scaleIdx++;
            this.scale = this.scaleList[this.scaleIdx];
            this.tileContainer.resetTiles();
            this.onViewUpdated();
        }
        else return;
    }
    else
    {
        this.zoomMapImage(1.4);
    }

}

WMSClient.prototype.zoomMinus = function()
{
    if(this.scaleList)
    {
        if(this.scaleIdx>0)
        {
            this.scaleIdx--;
            this.scale = this.scaleList[this.scaleIdx];
            this.tileContainer.resetTiles();
            this.onViewUpdated();
        }
        else return;
    }
    else
    {
        this.zoomMapImage(0.6);
    }

}

WMSClient.prototype.navigateToSelection = function()
{
    var str = this.selElement.style.left;
    var left = parseInt(str.substr(0,str.indexOf("px")));
    str = this.selElement.style.top;
    var top = parseInt(str.substr(0,str.indexOf("px")));
    str = this.selElement.style.width;
    var w = parseInt(str.substr(0,str.indexOf("px")));
    str = this.selElement.style.height;
    var h = parseInt(str.substr(0,str.indexOf("px")));
    
    var pixCx = left+w/2 - this.tileContainer.width/2;
    var pixCy = top+h/2 - this.tileContainer.height/2;
    var dpoint = new WMSPoint(pixCx,pixCy);
    this.center = this.coordSystem.getPointCoord(dpoint,this.center,this.scale);
    var ratio;
    if(this.tileContainer.width < this.tileContainer.height)
    {
        ratio = (h > w)?(w/this.tileContainer.width):(h/this.tileContainer.height)
    }
    else
    {
        ratio = (w > h)?(w/this.tileContainer.width):(h/this.tileContainer.height)
    }
    this.scale = Math.round(this.scale*ratio);
    
    this.tileContainer.resetTiles();
    
    this.selElement.style.display = "none";
    
    this.onViewUpdated();    
}

/*
WMSClient.prototype.navigateAllMap = function()
{
    var bbox = this.capabilities.rootLayer.getBBoxForCRS(this.coordSystem.crsName);
    this.center.x = (bbox.right + bbox.left)/2;
    this.center.y = (bbox.top + bbox.bottom)/2;
    var uSize = Math.max(bbox.right - bbox.left,bbox.bottom - bbox.top);
    var pSize = Math.min(this.tileContainer.width,this.tileContainer.height);
    this.scale = Math.round(uSize/this.coordSystem.pixToUnitLength(pSize,1));
    
    this.tileContainer.resetTiles();
    
    this.onViewUpdated();
}*/

WMSClient.prototype.savePosition = function()
{
    this.history.savePosition(this.center.x,this.center.y,this.scale);
}

WMSClient.prototype.navigateToPoint = function(point)
{
    this.savePosition();
    this.center.x = point.x;
    this.center.y = point.y;
    
    this.tileContainer.resetTiles();
}

WMSClient.prototype.navigateToPos = function(cx,cy,sc,saveCurrent)
{
    if(saveCurrent) this.savePosition();
    this.center.x = cx;
    this.center.y = cy;
    this.scale = sc;

    this.tileContainer.resetTiles();
}

WMSClient.prototype.prevPosition = function()
{
    var pos = this.history.getPrevious();
    if(pos) this.navigateToPos(pos.cX,pos.cY,pos.scale);
}

WMSClient.prototype.nextPosition = function()
{
    var pos = this.history.getNext();
    if(pos) this.navigateToPos(pos.cX,pos.cY,pos.scale);
}

WMSClient.prototype.updateScaleLabel = function()
{
    if(this.scaleLabelId)
    {
        var label = document.getElementById(this.scaleLabelId);
        if(label) label.innerHTML = this.scale;
    }
}

WMSClient.prototype.onViewUpdated = function()
{
    this.savePosition(); 
    this.updateScaleLabel();
}

WMSClient.prototype.resizeMapImage = function(width,height)
{
    this.tileContainer.resize(width,height);
}

//class TileContainer
function TileContainer(id,parent,w,h)
{
    this.parent = parent;
    this.element = $(id);
    this.element.style.overflow='hidden';
    this.element.style.position='relative';
    this.position = this.element.cumulativeOffset();

    this.tileSize = 300;
    
    this.width = w?w:500;
    this.height = h?h:500; 
    
    this.zeroPosition = null;

    this.wTilesCount = 0;//(this.width/this.tileSize).floor()+2;
    this.hTilesCount = 0;//(this.height/this.tileSize).floor()+2;

   /**
    this.wTilesCount =  (this.width/2/this.tileSize).floor()*2 + 3;
    this.hTilesCount =  (this.height/2/this.tileSize).floor()*2 + 3;
    
    this.realWidth = this.wTilesCount*this.tileSize;
    this.realHeight = this.hTilesCount*this.tileSize;
    this.wOffset =  ((this.realWidth - this.width)/2).floor(); 
    this.hOffset =  ((this.realHeight - this.height)/2).floor(); **/
    
    this.moveHandler = new MouseMoveHandler();
    
    this.tiles = new Array();
    this.initTiles();
    this.addEventHandlers();
}

TileContainer.prototype.addEventHandlers = function()
{
    Event.observe(this.element,'click',this.onClickHandler.bindAsEventListener(this));
    Event.observe(this.element,'mousedown',this.onMouseDownHandler.bindAsEventListener(this));
    Event.observe(this.element,'mouseup',this.onMouseUpHandler.bindAsEventListener(this));
    Event.observe(this.element,'mouseout',this.onMouseOutHandler.bindAsEventListener(this));    
    Event.observe(this.element,'mousemove',this.onMouseMoveHandler.bindAsEventListener(this));
    //Event.observe(this.element,'DOMMouseScroll',this.onMouseScrollHandler.bindAsEventListener(this));
    //Event.observe(this.element,'mousewheel',this.onMouseScrollHandler.bindAsEventListener(this));
}

TileContainer.prototype.removeEventHandlers = function()
{
    Event.stopObserving(this.element);	
}

TileContainer.prototype.onClickHandler = function(event)
{
    event.stop();  
    return false;
}

TileContainer.prototype.onMouseDownHandler = function(event)
{    
    event.stop();
    
    if(this.moveHandler.started) return;
    this.moveHandler.started = true;
    this.moveHandler.startPos.x = event.pointerX();
    this.moveHandler.startPos.y = event.pointerY();
    this.moveHandler.stopPos.x = event.pointerX();
    this.moveHandler.stopPos.y = event.pointerY();
    
    if(this.parent.moveMode)
    {
        this.element.style.cursor = "move";
    }
    else 
    {
        var mp = this.mouseXY(event);
        this.moveHandler.layerStartX = mp.x;
        this.moveHandler.layerStartY = mp.y;
        this.element.style.cursor = "crosshair";
        this.parent.selElement.style.width = "0px";
        this.parent.selElement.style.height = "0px";
        this.parent.selElement.style.top = mp.y + "px";
        this.parent.selElement.style.left = mp.x + "px";
        this.parent.selElement.style.display="block";
    }
    
}

TileContainer.prototype.onMouseUpHandler = function(event)
{
    this.element.style.cursor = "default";
    event.stop();
    if(!this.moveHandler.started)
    {
        return;
    }
    
    var x = event.pointerX();
    var y = event.pointerY();
    
    var dx = x - this.moveHandler.stopPos.x;
    var dy = y - this.moveHandler.stopPos.y;
    
    this.moveHandler = new MouseMoveHandler();

    if(this.parent.moveMode)
    {
        this.moveTiles(dx,dy);
        this.checkCorners();
        this.parent.onViewUpdated();
    }
    else
    {
        this.parent.selElement.style.width = dx+ 'px'; 
        this.parent.selElement.style.height = dy + 'px'; 
        this.parent.navigateToSelection();
    }
    
}

TileContainer.prototype.onMouseMoveHandler = function(event)
{
    event.stop();
    
    if(!this.moveHandler.started) return;
    
    var x = event.pointerX();
    var y = event.pointerY();
    
    
    //���� �������
    if(this.parent.moveMode)
    {
        var dx = x - this.moveHandler.stopPos.x;
        var dy = y - this.moveHandler.stopPos.y;
    
        var shiftX = x - this.moveHandler.checkPos.x;
        var shiftY = y - this.moveHandler.checkPos.y;  
        //���� �������� ������ ��� �� ���������� ��������� �� ����� �� ����������
        if(shiftX.abs()>this.tileSize/2||shiftY.abs()>this.tileSize/2)
            {
                this.checkCorners();
                this.moveHandler.checkPos = {x:x,y:y};
            }

        if(dx.abs()>10||dy.abs()>10)    
        //if(dx!=0||dy!=0)    
        {
            this.moveHandler.stopPos.x = x;
            this.moveHandler.stopPos.y = y;     
            this.moveTiles(dx,dy);
        }
    }
    //�������
    else
    {
            var sel = this.parent.selElement;
            var dx = x - this.moveHandler.startPos.x;
            var dy = y - this.moveHandler.startPos.y;
            if(dx<0)
            {                
                sel.style.left = this.moveHandler.layerStartX+dx+"px";
                dx=-dx;
            }
            if(dy<0)
            {
                sel.style.top = this.moveHandler.layerStartY+dy+"px";
                dy=-dy;
            }
            sel.style.width = dx+ "px"; 
            sel.style.height = dy + "px"; 
     }
    
}

TileContainer.prototype.onMouseOutHandler = function(event)
{
    var relTarg = event.relatedTarget || event.toElement;

    if(relTarg && (relTarg.tile||relTarg.selection))
    {
         event.stop();
    }
    else 
    {
        this.onMouseUpHandler(event);
    }
    event.stop();
}

TileContainer.prototype.onMouseScrollHandler = function(event)
{    
	
    event.stop();
    var delta = "wheelDelta" in event ? event.wheelDelta : event.detail * -40;
	if(delta>0)this.parent.zoomPlus();
    else this.parent.zoomMinus();
    
	/**
    this.parent.scale += Math.round(0.4 * sign(delta) * this.parent.scale);
    this.resetTiles();
    this.parent.onViewUpdated();
    **/
}

TileContainer.prototype.initTiles = function()
{
     this.resetZeroPos();
     var unitSize = this.parent.coordSystem.pixToUnitLength(this.tileSize,this.parent.scale).round();

     //��� ���������� ������ �������� ���� ����������
     var ltFis = this.parent.coordSystem.getPointCoord({x:-this.width/2,y:-this.height/2},this.parent.center,this.parent.scale);
     var rtFis = this.parent.coordSystem.getPointCoord({x:this.width/2,y:this.height/2},this.parent.center,this.parent.scale);
     
     //������� ����� ������� ������
     var ltI = (ltFis.x/unitSize).floor();
     var ltJ = (ltFis.y/unitSize).floor();

     //������� ������ ������ ������
     var rtI = (rtFis.x/unitSize).floor();
     var rtJ = (rtFis.y/unitSize).floor();
     

     this.wTilesCount= (rtI-ltI).abs()+1;
     this.hTilesCount = (rtJ-ltJ).abs()+1;
     
     for(var i=0;i<this.wTilesCount;i++)
     {
        this.tiles[i] = new Array();
        for(var j=0;j<this.hTilesCount;j++)
        {
           var _x=this.parent.coordSystem.xDirection>0?ltI+i:ltI-i;
           var _y=this.parent.coordSystem.yDirection>0?ltJ+j:ltJ-j;
           var globalPos = {x:_x, y:_y};
           var url = this.getGlobalTileUrl(globalPos,unitSize);
           var tile = new Tile(this,url,globalPos);
           this.tiles[i].push(tile);           
        }
     }
}

TileContainer.prototype.resetZeroPos = function()
{
try
{

    //������� ������ ������ ������������ ������ � ���.��������
    var unitZero = new WMSPoint(-this.parent.center.x,-this.parent.center.y);
    this.zeroPosition =  this.parent.coordSystem.getPixPointCoord(unitZero,new WMSPoint(this.width/2,this.height/2),this.parent.scale);
    this.zeroPosition.x = this.zeroPosition.x.round();
    this.zeroPosition.y = this.zeroPosition.y.round();
    /**var pixLT;
    if(this.tiles[0] && this.tiles[0][0])
    {    
       pixLT = this.tiles[0][0].position;
    }
    else
    {
        var unitSize = this.parent.coordSystem.pixToUnitLength(this.tileSize,this.parent.scale).round();
        var cI = (this.parent.center.x/unitSize).floor();
        var cJ = (this.parent.center.y/unitSize).floor();
        pixLT = new WMSPoint(- this.wOffset,- this.hOffset);
    }
    pixLT = new WMSPoint(pixLT.x - this.width/2,pixLT.y - this.height/2);
    this.ltPos = this.parent.coordSystem.getPointCoord(pixLT,this.parent.center,this.parent.scale);
    this.ltPos.x = this.ltPos.x.round();
    this.ltPos.y = this.ltPos.y.round();
    **/
}
catch(err){}

}

TileContainer.prototype.removeTiles = function()
{
try
{
    for(var i=0;i<this.wTilesCount;i++)
    {
        for(var j=0;j<this.hTilesCount;j++)
        {
            var tile = this.tiles[i][j];
			this.element.removeChild(tile.element);
        }
    }
    this.tiles = new Array();
}
catch(err){}

}

TileContainer.prototype.updateTiles = function()
{
try
{
    var unitSize = this.parent.coordSystem.pixToUnitLength(this.tileSize,this.parent.scale).round();
    for(var i=0;i<this.wTilesCount;i++)
    {
        for(var j=0;j<this.hTilesCount;j++)
        {
            var tile = this.tiles[i][j];
            //var url = this.getTileUrl(tile.position.x,tile.position.y);
            var url = this.getGlobalTileUrl(tile.globalPosition,unitSize);
							
            tile.element.style.display = 'none';
            tile.element.src = url;
            tile.url = url;
        }
     }
}
catch(err){}

}

TileContainer.prototype.resetTiles = function()
{
try
{
    this.removeTiles();
    this.initTiles();
}
catch(err){}

}

TileContainer.prototype.getGlobalTileUrl = function(globalPos,unitSize)
{
try
{
    if(!unitSize) unitSize = this.parent.coordSystem.pixToUnitLength(this.tileSize,this.parent.scale).round();
    var left = unitSize*globalPos.x;
    var right = left+unitSize;

    
    var top = unitSize*globalPos.y;
    var bottom = top + unitSize;
    
    var bbox = new WMSBBox(Math.min(left, right),Math.min(top, bottom),Math.max(left, right),Math.max(top, bottom));
   
    return this.parent.config.path+'?'+this.parent.mapRequest.getQueryStringForBBox(bbox,this.tileSize,this.tileSize);
}
catch(err){}

}

TileContainer.prototype.getTileUrl = function(i,j)
{
try
{    
    var unitSize = this.parent.coordSystem.pixToUnitLength(this.tileSize,this.parent.scale).round();
    
    
    var left = this.ltPos.x+this.parent.coordSystem.xDirection*unitSize*i;
    var right = left+this.parent.coordSystem.xDirection*unitSize;
    
    var top = this.ltPos.y+this.parent.coordSystem.yDirection*unitSize*j;
    var bottom = top + this.parent.coordSystem.yDirection*unitSize;
    
    var bbox = new WMSBBox(left,top,right,bottom);    
   
    return this.parent.config.path+'?'+this.parent.mapRequest.getQueryStringForBBox(bbox,this.tileSize,this.tileSize);
}
catch(err){}

}

TileContainer.prototype.moveTiles = function(dx,dy)
{    
try
{
    var dpoint=new WMSPoint(-dx,-dy);
    this.zeroPosition.x += dx;
    this.zeroPosition.y += dy;
    this.parent.center = this.parent.coordSystem.getPointCoord(dpoint,this.parent.center,this.parent.scale); 
    for(var i=0;i<this.wTilesCount;i++)       
    for(var j=0;j<this.hTilesCount;j++)
    {
         var t = this.tiles[i][j];
         t.move(dx,dy);
    }
}
catch(err){}

}

TileContainer.prototype.checkCorners = function()
{
try
{
    var left = this.tiles[0][0].position.x;
    var top = this.tiles[0][0].position.y;
	var right = this.tiles[this.wTilesCount-1][0].position.x + this.tileSize;
    var bottom = this.tiles[this.wTilesCount-1][this.hTilesCount-1].position.y + this.tileSize;
	if(left>=0) this.addColumn(true);
    if(right<=this.width) this.addColumn(false);
    if(top>=0) this.addRow(true);
    if(bottom<=this.height) this.addRow(false);
}
catch(err){}

}

TileContainer.prototype.addColumn = function(left)
{
try
{
   var unitSize= this.parent.coordSystem.pixToUnitLength(this.tileSize,this.parent.scale).round();
   
   var col = new Array();

   var xDir = this.parent.coordSystem.xDirection;
   var yDir = this.parent.coordSystem.yDirection;
	
   var insI = left ? this.tiles[0][0].globalPosition.x-xDir : this.tiles[this.wTilesCount-1][0].globalPosition.x+xDir;
   var insJ = this.tiles[0][0].globalPosition.y;

   
   for(var j=0;j<this.hTilesCount;j++)
   {
       var globalPos = {x:insI, y:insJ+j*yDir};
       var url = this.getGlobalTileUrl(globalPos,unitSize);
       var tile = new Tile(this,url,globalPos);
       //if(needRemove)this.element.removeChild(this.tiles[remCol][j].element);
       col.push(tile);
   }
   if(left)
   {
       this.tiles.unshift(col);       
       //if(needRemove)this.tiles.pop();
   }
   else
   {
       this.tiles.push(col);
       //(needRemove)this.tiles.shift();
   }
//   if(!needRemove)
   this.wTilesCount++;

   var needRemove = this.wTilesCount >= (this.width/this.tileSize).floor()+3;
   if(needRemove)
   {
       this.removeColumn(!left);
   }

}
catch(err){}

}

TileContainer.prototype.removeColumn = function(left)
{
try
{
       var remCol = left?0:this.wTilesCount-1;
       for(var j=0;j<this.hTilesCount;j++)
       {
          this.element.removeChild(this.tiles[remCol][j].element);
       }
       if(left)
       {
           this.tiles.shift();
       }
       else
       {
           this.tiles.pop();
       }
       this.wTilesCount--; 
}
catch(err){}

}

TileContainer.prototype.addRow = function(up)
{
try
{

   var unitSize= this.parent.coordSystem.pixToUnitLength(this.tileSize,this.parent.scale).round(); 

   var xDir = this.parent.coordSystem.xDirection;
   var yDir = this.parent.coordSystem.yDirection;

   var insI = this.tiles[0][0].globalPosition.x;
   var insJ = up? this.tiles[0][0].globalPosition.y-yDir:this.tiles[0][this.hTilesCount-1].globalPosition.y+yDir;

   for(var i=0;i<this.wTilesCount;i++)   
   {
       var globalPos = {x:insI +i*xDir, y:insJ};
       var url = this.getGlobalTileUrl(globalPos,unitSize);
       var tile = new Tile(this,url,globalPos);
       if(up)
       {
           //this.element.removeChild(this.tiles[i][this.hTilesCount-1].element);
           this.tiles[i].unshift(tile);
           //this.tiles[i].pop();
       }
       else
       {
           //this.element.removeChild(this.tiles[i][0].element);
           this.tiles[i].push(tile);
           //if(needRemove)this.tiles[i].shift();
       }
   }   
   this.hTilesCount++;

   var needRemove = this.hTilesCount >= (this.height/this.tileSize).floor()+3;
   if(needRemove) this.removeRow(!up);

}
catch(err){}

}

TileContainer.prototype.removeRow = function(up)
{
try
{
   for(var i=0;i<this.wTilesCount;i++)
   {
       if(up)
       {
           this.element.removeChild(this.tiles[i][0].element);
           this.tiles[i].shift();
       }
       else
       {
           this.element.removeChild(this.tiles[i][this.hTilesCount-1].element);
           this.tiles[i].pop();
       }
   }
   this.hTilesCount--;
}
catch(err){}

}

TileContainer.prototype.mouseXY = function(event)
{ 
try
{
  var elem = event.element();
  var _x = 0;
  var _y = 0;
  
  if (event.layerX)//Gecko
  {
    _x = event.layerX - parseInt(elem.getStyle("border-left-width"));
    _y = event.layerY - parseInt(elem.getStyle("border-top-width"));
  }
  else if (event.offsetX)//IE, Opera
  {
    _x = event.offsetX;
    _y = event.offsetY;
  }
  
  //if event catched by tile img
  if(elem.tile)
  {      
      _x+=elem.tile.position.x;//parseInt(elem.getStyle("left"));
      _y+=elem.tile.position.y;//parseInt(elem.getStyle("top"));
  }
  return {x:_x,y:_y};
}
catch(err){}

}

TileContainer.prototype.resize = function(w,h)
{
    this.removeTiles();
    this.width = w;
    this.height = h;
    this.wTilesCount =  (this.width/2/this.tileSize).floor()*2 + 3;
    this.hTilesCount =  (this.height/2/this.tileSize).floor()*2 + 3;
    this.initTiles();
}


/**
   Tile class
**/
function Tile(parent,url,globalPos)
{
    this.tileContainer = parent;
    this.xDirection = parent.parent.coordSystem.xDirection;
    this.yDirection = parent.parent.coordSystem.yDirection;
    this.size = parent.tileSize;
    this.globalPosition = globalPos;
    
    this.position = null;
    this.url = url;  
    
    this.resetPosition();         
    this.createElement();
    
}

Tile.prototype.resetPosition = function()
{
    var shiftX   = this.xDirection>0? this.globalPosition.x*this.size : -(this.globalPosition.x+1)*this.size;
    var shiftY   = this.yDirection>0? this.globalPosition.y*this.size : -(this.globalPosition.y+1)*this.size;
    var _x = shiftX + this.tileContainer.zeroPosition.x;
    var _y = shiftY + this.tileContainer.zeroPosition.y;
    this.position= {x:_x,y:_y};
}

Tile.prototype.createElement = function()
{
    var img = document.createElement('img');
    img.style.display = 'none';
    img.onload = function(){img.style.display='block';};
    img.style.position = 'absolute';
    img.style.left = this.position.x+'px';
    img.style.top = this.position.y+'px';
	//img.style.behavior = "url('/img/pngbehavior.htc')";
    img.style.margin = 0;
    img.style.padding = 0;
    img.src = this.url;
    img.tile=this;  
    img.title='';
    this.element = img;
    this.tileContainer.element.appendChild(this.element);
}

Tile.prototype.load = function(url)
{
    img.src = url;
}

Tile.prototype.move = function(dx,dy)
{
    this.position.x+=dx;
    this.position.y+=dy;
    this.element.style.left = this.position.x + 'px'
    this.element.style.top = this.position.y + 'px';
}

/**
    MouseMoveHandler
**/
function MouseMoveHandler()
{
    this.startPos = {x:0,y:0};
    this.stopPos = {x:0,y:0};
    this.started = false;
    this.checkPos = {x:0,y:0};
    
    this.layerStartX=0;
    this.layerStartY=0;
}

//class WMSCapabilities
function WMSCapabilities(xmlDoc)
{
    //init layers
    this.rootLayer = new WMSLayer();
    var lNodes=xmlDoc.getElementsByTagName("Layer");
    //find root layer
    var rootLNode;
    for(var i=0; i < lNodes.length; i++)
    {
        var lNode=lNodes[i];
        if(lNode.parentNode.nodeName.toLowerCase()!="layer")
        {
           rootLNode=lNode;           
           break;
        }        
    }
   
    this.rootLayer.init(rootLNode, true);   
   
    //this.mapFormatList = new Array();
    //this.initMapFormats(xmlDoc);
    
}

WMSCapabilities.prototype.initMapFormats = function(xmlDoc)
{
    var gmReqNode = xmlDoc.getElementsByTagName("GetMap");
    if(!gmReqNode||gmReqNode.length<1) return;
    var childs = gmReqNode[0].childNodes;
    for (var i=0;i<childs.length;i++)
    {
        var node=childs[i];
        if(node.nodeName.toLowerCase() =="format")
        {
           this.mapFormatList.push(getNodeTextContent(node));
        }        
    }
    
}

//class WMSLayer
function WMSLayer(title,name)
{
    this.name = name;
    this.title = title;
    this.crsList = new Array();
    this.bboxList = new Array();
    this.subLayers = null;
    this.level = null;
    this.root = null;
    this.maxLevel = 0;
    this.defaultChecked = true;
}

WMSLayer.prototype.addSubLayer = function(layer)
{
    if(!this.subLayers) this.subLayers = new Array();
    this.subLayers.push(layer);
}

WMSLayer.prototype.init = function(node,initSub,parentLayer)
{        
      if(parentLayer)
      {
          this.root = parentLayer.root;
          this.level = parentLayer.level + 1;
          if(this.root.maxLevel < this.level)this.root.maxLevel = this.level;
      }
      else
      {
          this.root = this;
          this.level = 0;
          this.maxlevel = 0;
      }
	  
	  if (parentLayer)
	  {
		  var chNodes=node.childNodes;
		  if(chNodes)
		  {
			  for(var i=0;i<chNodes.length;i++)
			  {
				var child = chNodes[i];
				var chTag=child.nodeName.toLowerCase();
				if( chTag == "name")
					this.name = getNodeTextContent(child);
				else if( chTag == "title")
					this.title = getNodeTextContent(child);
				else if(initSub && chTag == "layer")
				{            
					var layer = new WMSLayer();
					layer.init(child,true,this);
					this.addSubLayer(layer);
				}
				else if(chTag == "crs" || chTag == "srs")
				{
					this.crsList.push(getNodeTextContent(child).toUpperCase());
				}
				else if(chTag == "boundingbox")
				{
					var bbox = new WMSBBox(parseFloat(child.getAttribute("minx")),parseFloat(child.getAttribute("miny"))
						,parseFloat(child.getAttribute("maxx")),parseFloat(child.getAttribute("maxy")));
					bbox.crsName = child.getAttribute("CRS");
					if(!bbox.crsName) bbox.crsName = child.getAttribute("SRS");
					this.bboxList.push(bbox);
				}
			  }
		  } 
	  }  
}

WMSLayer.prototype.getBBoxForCRS = function(crsName)
{
    if(!this.bboxList||!crsName) return null;
    for(var i=0;i<this.bboxList.length;i++)
    {
        var bbox = this.bboxList[i];
        if(bbox.crsName && (bbox.crsName.toUpperCase() == crsName.toUpperCase()))
        {
            return bbox;
        }    
    }
    return null;
}

//class WMSPoint
function WMSPoint(x,y)
{
    this.x = x;
    this.y = y;
}
//class WMSBBox
function WMSBBox(left,top,right,bottom,crsName)
{
    this.left = left;
    this.top = top;
    this.right = right;
    this.bottom = bottom;
    this.crsName = crsName;
}

//class WMSLayersControl
function WMSLayersControl(rootLayer,parentCtrl,defaultLayers,divId)
{   
    this.parentCtrl = parentCtrl;
    this.containerId = divId || 'LayersContainer';
    this.defaultLayers = defaultLayers;
    this.layerControls = new Array();
    this.element = null;    
    this.createElement( rootLayer);
    this.bind();
}

WMSLayersControl.prototype.onLayerChange = function(layer,state)
{
    if(this.parentCtrl) this.parentCtrl.onLayerChange(layer,state);    
}

WMSLayersControl.prototype.createElement = function(rootLayer)
{
    this.element = document.createElement("table");
    var tbody = document.createElement("tbody");
    this.element.appendChild(tbody);
    this.createLayerElement(rootLayer,true,tbody,true);
}

WMSLayersControl.prototype.createLayerElement = function(layer,initsub,parentElement,skipRender)
{
    var checked = true;
    if(this.defaultLayers&&this.defaultLayers.length>0)
    {
        if(this.defaultLayers.indexOf(layer.name)<0) checked = false;
    }
    var lTr = document.createElement("tr");
    var lTd = document.createElement("td");
    var lCtrl = new WMSLayerControl(layer,lTd,this,checked);
    lTr.appendChild(lTd);
    if(!skipRender)
    {
        parentElement.appendChild(lTr);
    }
    this.layerControls.push(lCtrl);
    if(initsub)
    {
        var subLayers = layer.subLayers;
        if(subLayers) for( var i=0;i<subLayers.length;i++)
            this.createLayerElement(subLayers[i],true,parentElement);
    }
}

WMSLayersControl.prototype.bind = function()
{
    if(this.element)
    {
        document.getElementById(this.containerId).appendChild(this.element);
    }
}

WMSLayersControl.prototype.getSelectedLayers = function()
{
    var res = new Array();
    for(var i=0;i<this.layerControls.length;i++)
    {
        var lCtrl = this.layerControls[i];
        if(lCtrl.checked) res.push(lCtrl.layer);
    }
    return res;
}

WMSLayersControl.prototype.getSelectedLayersNames = function()
{
    var res = new Array();
    for(var i=0;i<this.layerControls.length;i++)
    {
        var lCtrl = this.layerControls[i];
        var name = lCtrl.layer.name;
        if(lCtrl.checked && name) res.push(name);
    }
    return res;
}

//class WMSLayerControl
function WMSLayerControl(wmsLayer,parentElement,parentCtrl,checked)
{
    this.layer = wmsLayer;
    this.parentElement = parentElement;    
    this.parentCtrl = parentCtrl;
    this.checked = checked;
    this.element = null;
    this.createElement();
    this.bind();    
}
//todo: default function,creates div with checkbox with id cb_[layer name]turning enable property on/off and span with title text
WMSLayerControl.prototype.createElement = function()
{
    var  elem = document.createElement("div");
    for(var i=0;i<this.layer.level;i++)
        {
            elem.appendChild(document.createTextNode("- "));
        }
    if(this.layer.name&&this.layer.name!="")
    {        
        var cbox = document.createElement("input");
        cbox.setAttribute("type","checkbox");
        if(this.checked)
        {
            cbox.checked = true;
            cbox.defaultChecked = true;
        }
        cbox["dataObj"] = this;
        cbox.onclick = function()
        {
            var ctrl=this.dataObj;
            ctrl.checked = !ctrl.checked;
            ctrl.parentCtrl.onLayerChange(ctrl.layer, ctrl.checked);
        }
        elem.appendChild(cbox);
    }
    var titleTxt = document.createTextNode(this.layer.title);
    elem.appendChild(titleTxt);    
    this.element = elem;
}

WMSLayerControl.prototype.bind = function()
{
    if(this.element && this.parentElement)
    {
        this.parentElement.appendChild(this.element);
    }
}

//Get string that is concat of all text node childs
function getNodeTextContent(node)
{
    var res="";
    var childs = node.childNodes;
    for(var i = 0; i < childs.length; i++)
    {
        var child=childs[i];
        if( child.nodeType == 3) res += child.nodeValue;
    }
    return res;
}

//class WMSMapRequest
function WMSMapRequest(format, transparent)
{
    this.format = format?format:"image/png"; 
	this.transparent = transparent;
    this.request = "GetMap";
    this.layerNames = new Array();
    this.bbox = new WMSBBox;
    this.width=0;
    this.height=0;
    this.crs = null;
    this.version = "1.1.1";//todo
}

WMSMapRequest.prototype.getQueryString = function ()
{   
    var res = "";    
    res += "request=" + this.request;
    res += "&layers=" + this.layerNames.join(",");
    res += "&bbox=" + this.bbox.left + "," + this.bbox.top + "," + this.bbox.right + "," + this.bbox.bottom;
    res += "&width=" + this.width;
    res += "&height=" + this.height;
    res += "&format=" + this.format;
    res += "&crs=" + this.crs;
    res += "&version=" + this.version;
    return res;
}

WMSMapRequest.prototype.getFeatureQueryString = function ()
{
    var res = "";    
    res += "request=" + "GetFeatureInfo";
    res += "&layers=" + encodeURIComponent(this.layerNames.join(","));
    res += "&bbox=" + this.bbox.left + "," + this.bbox.top + "," + this.bbox.right + "," + this.bbox.bottom;
    res += "&width=" + this.width;
    res += "&height=" + this.height;
    res += "&format=" + this.format;
    res += "&crs=" + this.crs;
    res += "&version=" + this.version;
    return res;
}

WMSMapRequest.prototype.getQueryStringForBBox = function(bbox,w,h)
{    
    var res = "";    
    res += "request=" + this.request;
    res += "&layers=" + this.layerNames.join(",");
    res += "&bbox=" + bbox.left + "," + bbox.top + "," + bbox.right + "," + bbox.bottom;
    res += "&width=" + w;
    res += "&height=" + h;
    res += "&format=" + this.format;
    res += "&crs=" + this.crs;
    res += "&version=" + this.version;
    res += "&transparent=" + this.transparent;
    return res;
}
//class WMSCoordSystem
function WMSCoordSystem(crsName)
{
    this.crsName = crsName;
    var unit = this.defineUnit(crsName);
    this.dpi = 120;//???
    this.xDirection=1;
    this.yDirection=-1;
    //pixToMeters 
    this.metersToGrad = 360 / 2 / 6378137 / 3.1416;
    this.pixToMeters = 0.0254/this.dpi;    
    this.pixToGrad = this.pixToMeters * this.metersToGrad;    
    
    if(unit == "meter")
        this.pixToUnit = this.pixToMeters;    
    else if(unit == "grad")
        this.pixToUnit = this.pixToGrad;
    else this.pixToUnit = 1;
}

WMSCoordSystem.prototype.defineUnit = function()
{
    if(!this.crsName) return;
    if(this.crsName.indexOf("AUTO") > -1) return "meter";
    if(this.crsName.indexOf("EPSG:4326") > -1) return "grad";
    if(this.crsName.indexOf("EPSG:26918") > -1) return "meter";
    if(this.crsName.indexOf("LOCAL") > -1) return "meter";
    return null;
}

WMSCoordSystem.prototype.pixToUnitLength = function(pixLength, scale)
{
   if(!scale) scale = 1;
   return Math.abs(pixLength * this.pixToUnit * scale);
}

WMSCoordSystem.prototype.unitToPixLength = function(unitLength, scale)
{
   if(!scale||scale==0) scale = 1;   
   return Math.abs(Math.round(unitLength/this.pixToUnit/scale));
}
//���������� ����������, ��������������� ����������� � �������� pixPoint
//������������ ����� centerPoint, �������� � ��� ����������� ��� �������� �������� scale
WMSCoordSystem.prototype.getPointCoord = function(pixPoint,centerPoint,scale)
{
   //TODO: ����� ����� �������� ��� � �� �����
   var res = new WMSPoint();
   res.x = this.xDirection*sign(pixPoint.x)*this.pixToUnitLength(pixPoint.x, scale) + centerPoint.x;
   res.y = this.yDirection*sign(pixPoint.y)*this.pixToUnitLength(pixPoint.y, scale) + centerPoint.y;   
   return res;
}

WMSCoordSystem.prototype.getPixPointCoord = function(point,centerPoint,scale)
{
   //TODO: ����� ����� �������� ��� � �� �����
   var res = new WMSPoint();
   //res.x = this.xDirection*sign(pixPoint.x)*this.pixToUnitLength(pixPoint.x, scale) + centerPoint.x;
   res.x = this.xDirection*sign(point.x)*this.unitToPixLength(point.x, scale) + centerPoint.x;
   //res.y = this.yDirection*sign(pixPoint.y)*this.pixToUnitLength(pixPoint.y, scale) + centerPoint.y;   
   res.y = this.yDirection*sign(point.y)*this.unitToPixLength(point.y, scale) + centerPoint.y;   
   return res;
}

//class WMSPosHistory
function WMSPosHistory(maxCount)
{
    this.maxCount = maxCount;
    this.positions = new Array();    
    this.current = -1;
    
    this.getPrevious = function()
    {
        if(this.current < 1) return null;
        this.current--;
        return this.positions[this.current];
    }
    
    this.getNext = function()
    {
        if(this.current >= (this.positions.length - 1)) return null;
        this.current++;
        return this.positions[this.current];
    }
    
    this.savePosition = function( cx, cy, sc)
    {
        var pos = {cX:cx,cY:cy,scale:sc};
        if(this.maxCount && this.positions.length == this.maxCount)
        {
            this.positions.shift();
        }
        else this.current++;
        this.positions[this.current] = pos;
        var rest = this.positions.length - this.current - 1;
        if (rest > 0)this.positions.splice(this.current + 1, rest);
    }
    
}
//Math utils
function sign(x)
{
    if(x==0)return 0;
    return x>0?1:-1;
}
