var MapMode = new function()
{
  this.BOX = "BOX";
  this.PAN = "PAN";
  this.CLICK = "CLICK";
}

function Map( id, coordBox, url )
{
  this._id = id;
  this._url = url;
  
  this._element = null;
  this._width = null;
  this._height = null;

  this._coordElementX = null;
  this._coordElementY = null;

  this._coordBox = coordBox;
  this._color = null;

  this._anchor = null;
  this._box = null;
    
  this._precisionX = null;
  this._precisionY = null;

  this._listener = new EventListener( this );
  
  this._image = new Image();

  this._onload( this._init );

  this._listener.add( window, EventType.UNLOAD, this._unload );
}

Map.prototype._onload = function( method )
{
  if( document.body == undefined )
    this._listener.add( window, EventType.LOAD, method );
  else
    method.call( this );
};

Map.prototype._init = function()
{
  this._element = document.getElementById( this._id );

  this._element.style.clip = 'rect(auto auto auto auto)';


  this._canvas = document.createElement( "div" );
  this._canvas.style.position = "absolute";
  this._canvas.style.borderStyle = "solid";
  this._canvas.style.borderWidth = 2;
  this._canvas.style.visibility = "hidden";
  if( this._color != null )
    this._canvas.style.color = this._color;
  document.body.appendChild( this._canvas );

  this._top = getTop( this._element );
  this._left = getLeft( this._element );
  this._width = this._element.width == undefined ? parsePosition( this._element.style.width ) : this._element.width;
  this._height = this._element.height == undefined ? parsePosition( this._element.style.height ) : this._element.height;

  if( this._coordBox != null )
  {
    this._precisionX = Math.abs( Math.floor( Math.log( Math.abs(
        ( this._coordBox.maxx - this._coordBox.minx ) / this._width ) ) / Math.log( 10 ) ) );
    this._precisionY = Math.abs( Math.floor( Math.log( Math.abs(
        ( this._coordBox.maxy - this._coordBox.miny ) / this._height ) ) / Math.log( 10 ) ) );
  }


  this._listener.add( this._image, EventType.LOAD, this._loadImage );
  this._image.src = this._url;

  new Image().src = "images/busy.gif";
  this._isReady = true;
};

Map.prototype.isReady = function()
{
  return this._isReady;
};

Map.prototype.reload = function( nocache )
{
  this._url = this._url.substring( 0, this._url.indexOf( "noCache" ) ) + nocache;
  this._element.src = this._url;
  this._busy();
  this._listener.add(  this._element, EventType.LOAD, this._ready );
};

Map.prototype._busy = function()
{
  if( this._busyImage == null )
  {
    this._busyImage = document.createElement( "img" );
    this._busyImage.src = "images/busy.gif";
    this._busyImage.style.position = "absolute";
    this._busyImage.style.top = ( this._height - 128 ) / 2;
    this._busyImage.style.left = ( this._width - 128 ) / 2;
    this._initPolyCanvas();
    this._polyCanvas.appendChild( this._busyImage );
  }
}

Map.prototype._ready = function()
{
  if( this._polyCanvas != null && this._busyImage != null )
  {
    this._polyCanvas.removeChild( this._busyImage );
    this._busyImage = null;
  }
}


Map.prototype._loadImage = function()
{
  this._element.src = this._image.src;
  this._listener.add( document, EventType.MOUSEUP, this._mouseup );
  this._listener.add( document, EventType.MOUSEMOVE, this._mousemove );
  this._listener.add( document, EventType.MOUSEDOWN, this._mousedown );
  this._listener.add( document, EventType.CLICK, this._click );
};

Map.prototype._unload = function()
{
  this._listener.remove( document, EventType.MOUSEMOVE, this._setCoord );
  this._listener.remove( document, EventType.MOUSEUP, this._mouseup );
  this._listener.remove( document, EventType.MOUSEMOVE, this._mousemove );
  this._listener.remove( document, EventType.MOUSEDOWN, this._mousedown );
  this._listener.remove( document, EventType.CLICK, this._click );  
};


Map.prototype._setCoordinateDisplay = function()
{
  this._coordElementX = document.getElementById( this._xId );
  this._coordElementY = document.getElementById( this._yId );
  this._listener.add( document, EventType.MOUSEMOVE, this._setCoord );
};

Map.prototype.setCoordinateDisplay = function( xId, yId )
{
  this._xId = xId;
  this._yId = yId;
  this._onload( this._setCoordinateDisplay );
};


Map.prototype._inRange = function( coord )
{
  return coord.x >= Math.max( this._coordBox.minx, -180 )
      && coord.x <= Math.min( this._coordBox.maxx, 180 )
      && coord.y >= Math.max( this._coordBox.miny, -90 )
      && coord.y <= Math.min( this._coordBox.maxy, 90 );
};

Map.prototype._click = function( event )
{
  if( this._mode == MapMode.CLICK )
  {
    var mouseEvent = new MouseEvent( event );
    var targetId = mouseEvent.getTarget().id;
    if( targetId.indexOf( 'oCMenu' ) == -1 ) // don't catch menu item clicks
    {
      var coord = this._getCoord( mouseEvent );
      if( this._inRange( coord ) )
      {
        this.submit( mouseEvent.getX() - getLeft( this._element ),
                     mouseEvent.getY() - getTop( this._element ), 0, 0 );
        mouseEvent.stop();
      }
    }
  }
};

Map.prototype._mousedown = function( event )
{
  if( this._mode == MapMode.BOX || this._mode == MapMode.PAN )
  {
    var mouseEvent = new MouseEvent( event );
    if( mouseEvent.getButton() == MouseButton.LEFT )
    {
      var targetId = mouseEvent.getTarget().id;
      if( targetId == null || targetId.indexOf( 'oCMenu' ) == -1 ) // don't catch menu item clicks
      {
        var coord = this._getCoord( mouseEvent );
        if( this._inRange( coord ) )
        {
          this._anchor = new Point( mouseEvent.getX(), mouseEvent.getY() );
          mouseEvent.stop();
        }
      }
    }
  }
};

Map.prototype._mousemove = function( event )
{
  if( this._anchor != null )
  {
    var mouseEvent = new MouseEvent( event );

    if( this._mode == MapMode.BOX )
    {
      this._drawBox( mouseEvent.getX(), mouseEvent.getY() );
    }
    else if( this._mode == MapMode.PAN )
    {
      this._pan( mouseEvent.getX() - this._anchor.x, mouseEvent.getY() - this._anchor.y );
    }
    
    mouseEvent.stop();
  }
  else if( this._mode == MapMode.PAN  )
  {
    var mouseEvent = new MouseEvent( event );
    var coord = this._getCoord( mouseEvent );
    if( this._inRange( coord ) )
      document.body.style.cursor = "url(images/openhand.cur), default";
    else
      document.body.style.cursor = "default";
  }
};


Map.prototype._mouseup = function( event )
{
  if( this._anchor != null )
  {
    var mouseEvent = new MouseEvent( event );
    var targetId = mouseEvent.getTarget().id;
    if( targetId.indexOf( 'oCMenu' ) == -1 ) // don't catch menu item clicks
    {
      if( this._mode == MapMode.BOX )
      {
        var coord = this._getCoord( mouseEvent );
        if( this._inRange( coord ) )
        {
          var x, y, width, height;
          x = Math.min( this._anchor.x, mouseEvent.getX() ) - this._left;
          y = Math.min( this._anchor.y, mouseEvent.getY() ) - this._top;
          width = Math.abs( this._anchor.x - mouseEvent.getX() );
          height = Math.abs( this._anchor.y - mouseEvent.getY() );
          this.submit( x, y, width, height );
        }
        else
        {
          this._clearBox();
        }
      }
      else if( this._mode == MapMode.PAN )
      {
        var x, y, width, height;
        x = this._anchor.x - mouseEvent.getX();
        y = this._anchor.y - mouseEvent.getY();
        width = this._width;
        height = this._height;
        if( x != 0 || y != 0 )
          this.submit( x, y, width, height );
      }
      this._anchor = null;
      mouseEvent.stop();
    }
  }
};


Map.prototype._drawBox = function( stretchX, stretchY )
{
  this._canvas.style.left = Math.min( this._anchor.x, stretchX );
  this._canvas.style.top =  Math.min( this._anchor.y, stretchY );
  this._canvas.style.width = Math.abs( this._anchor.x - stretchX );
  this._canvas.style.height = Math.abs( this._anchor.y - stretchY );
  this._canvas.style.visibility = "visible";
}

Map.prototype._clearBox = function()
{
  this._canvas.style.visibility = "hidden";
}

Map.prototype._pan = function( deltaX, deltaY )
{
  this._element.style.top = this._top + deltaY;
  this._element.style.left = this._left + deltaX;

  if( this._polyCanvas != null )
  {
    this._polyCanvas.style.top = this._top + deltaY;
    this._polyCanvas.style.left = this._left + deltaX;
  }
  var clipTop = Math.abs( Math.min( 0, deltaY ) );
  var clipRight = this._width - Math.max( deltaX, 0 );
  var clipBottom = this._height - Math.max( deltaY, 0 );
  var clipLeft = Math.abs( Math.min( 0, deltaX ) );
  this._clip( clipTop, clipRight, clipBottom, clipLeft );
};

Map.prototype._clip = function( top, right, bottom, left )
{
  this._element.style.clip = "rect( " + top + "px, " + right + "px, " + bottom + "px, " + left + "px )";

  if( this._polyCanvas != null )
  {
    this._polyCanvas.style.clip = "rect( " + top + "px, " + right + "px, " + bottom + "px, " + left + "px )";
  }
};


Map.prototype._setCoord = function( event )
{
  var mouseEvent = new MouseEvent( event );
  var coord = this._getCoord( mouseEvent );
  
  if( this._inRange( coord ) )
  {
    this._coordElementX.value = this._round( coord.x, this._precisionX );
    this._coordElementY.value = this._round( coord.y, this._precisionY );
    this._isInRange = true;
  }
  else
  {
    if( this._isInRange ) // don't change the value unless necessary
    {
      this._coordElementX.value = "";
      this._coordElementY.value = "";
      this._isInRange = false;
    }
  }
};

Map.prototype.setColor = function( color )
{
  this._color = color;
  if( this._canvas != null )
    this._canvas.style.color = this._color;
};

Map.prototype.setMode = function( mode )
{
  this._mode = mode;
};

Map.prototype.getCoord = function( pixel )
{   
  return new Point( this._coordBox.minx + pixel.x * ( ( this._coordBox.maxx - this._coordBox.minx ) / this._width ),
                    this._coordBox.maxy - pixel.y * ( ( this._coordBox.maxy - this._coordBox.miny ) / this._height ) );
};

Map.prototype._getCoord = function( mouseEvent )
{
  var pixel = new Point( mouseEvent.getX() - this._left, mouseEvent.getY() - this._top );
  return new Point( this._coordBox.minx + pixel.x * ( ( this._coordBox.maxx - this._coordBox.minx ) / this._width ),
                    this._coordBox.maxy - pixel.y * ( ( this._coordBox.maxy - this._coordBox.miny ) / this._height ) );
};

Map.prototype._getPixel = function( coord )
{
  return new Point( Math.round( ( coord.x - this._coordBox.minx ) / ( this._coordBox.maxx - this._coordBox.minx ) * this._width ),
                    Math.round( ( this._coordBox.maxy - coord.y ) / ( this._coordBox.maxy - this._coordBox.miny ) * this._height ) );
};

Map.prototype._round = function( value, n )
{
  var factor = Math.pow( 10, n );
  return Math.round( factor * value ) / factor;
};

var LEFT_PAREN = "(";
var RIGHT_PAREN = ")";
var COMMA = ",";
var SPACE = " ";

Map.prototype._parse = function( wkt )
{
  var points = [];
  var coords = wkt.substring( wkt.lastIndexOf( LEFT_PAREN ) + 1, wkt.indexOf( RIGHT_PAREN ) ).split( COMMA );
  for( var i = 0; i < coords.length - 1; i++ )
  {
    if( coords[i].charAt( 0 ) == SPACE )
      coords[i] = coords[i].substring( 1 );
    var coord = coords[i].split( SPACE );
    points.push( this._getPixel( new Point( coord[0], coord[1] ) ) );
  }
  return points;
}

Map.prototype.paint = function( id, wkt )
{
  if( document.getElementById( id ) == null )
  {
    this._initPolyCanvas();
    var poly = this._drawPoly( id, wkt );
    this._polyCanvas.appendChild( poly );
  }
  else
  {
    document.getElementById( id ).style.visibility = "visible";
  }
};

Map.prototype.setCursor = function( cursor )
{
  this._originalCursor = document.body.style.cursor;
  document.body.style.cursor = cursor;
};

Map.prototype.resetCursor = function()
{
  document.body.style.cursor = this._originalCursor == null ? "default" : this._originalCursor;
};


Map.prototype.drawAll = function( idArray, wktArray )
{
  document.body.style.cursor = "wait";
  this._initPolyCanvas();
  var container = document.createElement( "div" );
  for( var i = 0; i < idArray.length; i++ )
  {
    var poly = this._drawPoly( idArray[i], wktArray[i] );
    container.appendChild( poly );
  }
  this._polyCanvas.appendChild( container );
  document.body.style.cursor = "default";
};


Map.prototype._hide = function( id )
{
  var poly = document.getElementById( id );
  if( poly != null )
  {
    poly.parentNode.removeChild( poly );
  }
};

Map.prototype.hide = function( idArray )
{
  if( this._polyCanvas != null )
  {
    for( var i = 0; i < idArray.length; i++ )
    {
      this._hide( idArray[i] );
    }
  }
};

Map.prototype.hideAll = function()
{
  this._initPolyCanvas();
  for( var i = this._polyCanvas.childNodes.length - 1; i >= 0 ; i-- )
    this._polyCanvas.removeChild( this._polyCanvas.childNodes[i] );
};

Map.prototype._simplify = function( poly )
{
  for( var i = 0; i < poly.length && poly.length > 2; i++ )
  {
    var j = i == poly.length - 1 ? 0 : i + 1;
    var k = j == poly.length - 1 ? 0 : j + 1;

    if( ( poly[i].x == poly[j].x && poly[i].y == poly[j].y ) ||
        ( poly[i].x == poly[k].x && poly[i].x == poly[j].x ) ||
        ( poly[i].y == poly[k].y && poly[i].y == poly[j].y ) )
    {
      poly.splice( j, 1 );
      i--;
    }    
  }
};

Map.prototype._drawPoly = function( id, wkt )
{
  var element = document.createElement( "div" );
  element.setAttribute( "id", id );  
  var poly = this._parse( wkt );
  this._simplify( poly );
  if( poly.length == 4 &&
     (( poly[0].x == poly[1].x && poly[1].y == poly[2].y &&
        poly[2].x == poly[3].x && poly[3].y == poly[0].y ) ||
      ( poly[0].y == poly[1].y && poly[1].x == poly[2].x &&
        poly[2].y == poly[3].y && poly[3].x == poly[0].x ) ) )
  {
    var minx = Math.min( poly[0].x, poly[2].x );
    var miny = Math.min( poly[0].y, poly[2].y );
    var maxx = Math.max( poly[0].x, poly[2].x );
    var maxy = Math.max( poly[0].y, poly[2].y );

    element.style.left = minx;
    element.style.top = miny;
    element.style.width = maxx - minx;
    element.style.height = maxy - miny;
    element.style.borderStyle = "solid";
    element.style.borderWidth = 2;
    element.style.borderColor = "yellow";
    element.style.position = "absolute";    
  }
  else
  {
    var text = [];
    for( var i = 0; i < poly.length; i++ )
    {
      var next = i == poly.length - 1 ? 0 : i + 1;
      text.push( this._drawLine( poly[ i ], poly[ next ] ) );
    }
    element.innerHTML = text.join( '' );
  }

  return element;
};


Map.prototype._initPolyCanvas = function()
{
  if( this._polyCanvas == null )
  {
    this._polyCanvas = document.createElement( "div" );
    this._polyCanvas.style.position = "absolute";
    this._polyCanvas.style.left = this._left;
    this._polyCanvas.style.top =  this._top;
    this._polyCanvas.style.width = this._width;
    this._polyCanvas.style.height = this._height;
    this._polyCanvas.style.overflow = "hidden";
    this._polyCanvas.style.visibility = "visible";
    document.body.appendChild( this._polyCanvas );
  }
};

Map.prototype._drawLineSegment = function( x, y, w, h )
{
  return "<div style='position:absolute;overflow:hidden;background-color:yellow;top:" + y + ";left:" + x + ";width:" + w + ";height:" + h + "'></div>";
};

Map.prototype._drawLine = function( p1, p2 )
{
  var text = [];

  var stroke = 2;
  var x1 = p1.x;
  var x2 = p2.x;
  var y1 = p1.y;
  var y2 = p2.y;

  // draw left to right
  if( x1 > x2 )
  {
    x1 = p2.x;
    y1 = p2.y;
    x2 = p1.x;
    y2 = p1.y;
  }

  var width = x2 - x1;
  var height = y2 - y1;
  var signY = 1;
  if( height < 0 )
  {
    signY = -1;
    height = -height;
  }

  width = width << 1;
  height = height << 1;
  if( height <= width )
  {
    var fraction = height - ( width >> 1 );
    var mx = x1;
    while( x1 != x2 )
    {
      x1++;
      if( fraction >= 0 )
      {
        text.push( this._drawLineSegment( mx, y1, x1 - mx, stroke ) );
        y1 += signY;
        mx = x1;
        fraction -= width;
      }
      fraction += height;
    }
    if( x1 - mx > 0 )
      text.push( this._drawLineSegment( mx, y1, x1 - mx, stroke ) );
  }
  else
  {
    var fraction = width - ( height >> 1 );
    var my = y1;
    if( signY > 0 )
    {
      while( y1 != y2 )
      {
        y1++;
        if( fraction >= 0 )
        {
          text.push( this._drawLineSegment( x1++, my, stroke, y1 - my ) );
          my = y1;
          fraction -= height;
        }
        fraction += width;
      }
      if( y1 - my > 0 )
        text.push( this._drawLineSegment( x1, my, stroke, y1 - my ) );
    }
    else
    {
      while( y1 != y2 )
      {
        y1--;
        if( fraction >= 0 )
        {
          text.push( this._drawLineSegment( x1++, y1, stroke, my - y1 ) );
          my = y1;
          fraction -= height;
        }
        fraction += width;
      }
      if( my - y1 > 0 )
        text.push( this._drawLineSegment( x1, y1, stroke, my - y1 ) );
    }
  }

  if( text.length > 0 )
    return text.join( '' );
  else
    return '';
}
