function FeatureInfo(opts_boxStyle, opts_other, opts_callbacks) {

  // Holds all information needed globally
  // Not all globals are initialized here
  this.globals = {
    draggingOn: false,
    cornerTopDiv: null,
    cornerRightDiv: null,
    cornerBottomDiv: null,
    cornerLeftDiv: null,
    mapPosition: null,
    outlineDiv: null,
    mapWidth: 0,
    mapHeight: 0,
    mapRatio: 0,
    startX: 0,
    startY: 0,
    borderCorrection: 0
  };

  //box style options
  this.globals.style = {
    opacity: .1,
    fillColor: "#440",
    border: "2px solid blue"
  };

  var style = this.globals.style;
  for (var s in opts_boxStyle) {
    style[s]=opts_boxStyle[s];
  }

  var borderStyleArray = style.border.split(' ');
  style.outlineWidth = parseInt(borderStyleArray[0].replace(/\D/g,''));
  style.outlineColor = borderStyleArray[2];
  style.alphaIE = 'alpha(opacity=' + (style.opacity * 100) + ')';
 
  // map context stack for back button
  this.globals.backStack = [];

  // Other options
  this.globals.options={
    buttonHTML: 'Info ...',
    buttonStartingStyle: 
      {width: '52px', border: '1px solid black', padding: '2px'},
    buttonStyle: {background: '#FFF'},
    buttonZoomingHTML: 'Drag a region on the map',
    buttonZoomingStyle: {background: '#FF0'},
    overlayRemoveTime: 6000,
    stickyZoomEnabled: false
  };
  
  for (var s in opts_other) {
    this.globals.options[s] = opts_other[s]
  }

  // callbacks: buttonclick, dragstart, dragging, dragend
  if (opts_callbacks == null) {
    opts_callbacks = {}
  }
  this.globals.callbacks = opts_callbacks;

}

FeatureInfo.prototype = new GControl();

/**
 * Creates a new button to control ginfo and appends to the button container div.
 * @param {DOM Node} buttonContainerDiv created in main .initialize code
 */
FeatureInfo.prototype.initButton_ = function(buttonContainerDiv) {
  var G = this.globals;
  var buttonDiv = document.createElement('div');
  buttonDiv.innerHTML = G.options.buttonHTML;
  buttonDiv.id = 'ginfo-control';
  DIndentUtil.style([buttonDiv], {cursor: 'pointer', zIndex:200});
  DIndentUtil.style([buttonDiv], G.options.buttonStartingStyle);
  DIndentUtil.style([buttonDiv], G.options.buttonStyle);
  buttonContainerDiv.appendChild(buttonDiv);
  return buttonDiv;
};

/**
 * Sets button mode to zooming or otherwise, changes CSS & HTML.
 * @param {String} mode Either "zooming" or not.
 */
FeatureInfo.prototype.setButtonMode_ = function(mode){
  var G = this.globals;
  if (mode == 'info') {
    G.buttonDiv.innerHTML = G.options.buttonZoomingHTML;
    DIndentUtil.style([G.buttonDiv], G.options.buttonStartingStyle);
    DIndentUtil.style([G.buttonDiv], G.options.buttonZoomingStyle);
  } else {
    G.buttonDiv.innerHTML = G.options.buttonHTML;
    DIndentUtil.style([G.buttonDiv], G.options.buttonStartingStyle);
    DIndentUtil.style([G.buttonDiv], G.options.buttonStyle);
  }
};

/**
 * Is called by GMap2's addOverlay method. Creates the zoom control
 * divs and appends to the map div.
 * @param {GMap2} map The map that has had this DraginfoControl added to it.
 * @return {DOM Object} Div that holds the gzoomcontrol button
 */ 
FeatureInfo.prototype.initialize = function(map) {
  var G = this.globals;
  var me = this;
  var mapDiv = map.getContainer();
 
  // Create div for both buttons  
  var buttonContainerDiv = document.createElement("div"); 
  DIndentUtil.style([buttonContainerDiv], {cursor: 'pointer', zIndex: 150});

  // create and init the zoom button
  //DOM:button
  var buttonDiv = this.initButton_(buttonContainerDiv);

  // create and init the back button        
  //DOM:button
  //var backButtonDiv = this.initBackButton_(buttonContainerDiv);
  
  // Add the two buttons to the map           
  mapDiv.appendChild(buttonContainerDiv);
 
  //DOM:map covers
  var infoDiv = document.createElement("div");
  infoDiv.id ='ginfo-map-cover';
  infoDiv.innerHTML ='<div id="ginfo-outline" style="position:absolute;display:block;"></div><div id="ginfo-cornerTopDiv" style="position:absolute;display:block;"></div><div id="ginfo-cornerLeftDiv" style="position:absolute;display:none;"></div><div id="ginfo-cornerRightDiv" style="position:absolute;display:block;"></div><div id="ginfo-cornerBottomDiv" style="position:absolute;display:block;"></div>';
  DIndentUtil.style([infoDiv], {position: 'absolute', display: 'none', overflow: 'hidden', cursor: 'crosshair', zIndex: 101});
  mapDiv.appendChild(infoDiv);
  
  // add event listeners
  GEvent.addDomListener(buttonDiv, 'click', function(e) {
    me.buttonclick_(e);
  });
  GEvent.addDomListener(infoDiv, 'mousedown', function(e) {
    me.coverMousedown_(e);
  });
  
  // get globals
  G.mapPosition = DIndentUtil.getElementPosition(mapDiv);
  G.outlineDiv = DIndentUtil.gE("ginfo-outline");  
  G.buttonDiv = DIndentUtil.gE("ginfo-control");
  G.mapCover = DIndentUtil.gE("ginfo-map-cover");
  G.cornerTopDiv = DIndentUtil.gE("ginfo-cornerTopDiv");
  G.cornerRightDiv = DIndentUtil.gE("ginfo-cornerRightDiv");
  G.cornerBottomDiv = DIndentUtil.gE("ginfo-cornerBottomDiv");
  G.cornerLeftDiv = DIndentUtil.gE("ginfo-cornerLeftDiv");
  G.map = map;

  G.borderCorrection = G.style.outlineWidth * 2;  
  this.setDimensions_();
  
  //Make external ref
  refFeatureInfo = this;
  
  //styles
  this.initStyles_();
  return buttonContainerDiv;
};

/**
 * Required by GMaps API for controls. 
 * @return {GControlPosition} Default location for control
 */
FeatureInfo.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(3, 120));
};

/**
 * Function called when mousedown event is captured.
 * @param {Object} e 
 */
FeatureInfo.prototype.coverMousedown_ = function(e){

  var G = this.globals;
  var pos = this.getRelPos_(e);

  var swpx = new GPoint(pos.left, pos.top);
  var llpt = G.map.fromContainerPixelToLatLng(swpx); 

  var pt = new GLatLng(llpt.y,llpt.x);
  addInfoTipMarker(pt,pos);

  this.resetInfo_();

  return false;

};



/**
 * Set the cover sizes according to the size of the map
 */
FeatureInfo.prototype.setDimensions_ = function() {
  var G = this.globals;
  var mapSize = G.map.getSize();
  G.mapWidth  = mapSize.width;
  G.mapHeight = mapSize.height;
  G.mapRatio  = G.mapHeight / G.mapWidth;
  DIndentUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv], 
    {width: G.mapWidth + 'px', height: G.mapHeight +'px'});
};

/**
 * Initializes styles based on global parameters
 */
FeatureInfo.prototype.initStyles_ = function(){
  var G = this.globals;
  DIndentUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv], 
    {filter: G.style.alphaIE, opacity: G.style.opacity, background:G.style.fillColor});
  G.outlineDiv.style.border = G.style.border;  
};

/**
 * Function called when the ident button's click event is captured.
 */
FeatureInfo.prototype.buttonclick_ = function(){
  var G = this.globals; 
  //G.backButtonDiv.style.display='none';
  if (G.mapCover.style.display == 'block') { // reset if clicked before dragging 
    this.resetInfo_();
  } else {
    this.initCover_();
  }
};


/**
 * Shows the cover over the map
 */
FeatureInfo.prototype.initCover_ = function(){
  var G = this.globals;
  G.mapPosition = DIndentUtil.getElementPosition(G.map.getContainer());
  this.setDimensions_();
  this.setButtonMode_('info');
  DIndentUtil.style([G.mapCover], {display: 'block', background: 'transparent'});
  DIndentUtil.style([G.outlineDiv], {width: '0px', height: '0px'});

  //invoke callback if provided
  if(G.callbacks['buttonclick'] != null){
    G.callbacks.buttonclick();
  }

};

/**
 * Gets position of the mouse relative to the map
 * @param {Object} e
 */
FeatureInfo.prototype.getRelPos_ = function(e) {
  var pos = DIndentUtil.getMousePosition(e);
  var G = this.globals;
  return {top: (pos.top - G.mapPosition.top), 
          left: (pos.left - G.mapPosition.left)};
};

/**
 * Figures out the rectangle the user's trying to draw
 * @param {Number} startX 
 * @param {Number} startY
 * @param {Object} pos
 * @param {Number} ratio
 * @return {Object} Describes the rectangle
 */
FeatureInfo.prototype.getRectangle_ = function(startX, startY, pos, ratio){
  var left = false;
  var top = false;
  var dX = pos.left - startX;
  var dY = pos.top - startY;  
  if (dX < 0) {
    dX = dX * -1;
    left = true;
  }
  if (dY < 0) {
    dY = dY * -1;
    top = true;
  }
  delta = dX > dY ? dX : dY;

  return {
    startX: startX,
    startY: startY,
    endX: startX + delta,
    endY: startY + parseInt(delta * ratio),
    width: delta,
    height: parseInt(delta * ratio),
    left:left,
    top:top
  }

};

/** 
 * Resets CSS and button display when info done
 */
FeatureInfo.prototype.resetInfo_ = function() {
  var G = this.globals;
  DIndentUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv], 
    {display: 'none', opacity: G.style.opacity, filter: G.style.alphaIE});
  G.outlineDiv.style.display = 'none';  
  this.setButtonMode_('normal');
};



/* utility functions in DIndentUtil.namespace */
var DIndentUtil={};

/**
 * Alias function for getting element by id
 * @param {String} sId
 * @return {Object} DOM object with sId id
 */
DIndentUtil.gE = function(sId) {
  return document.getElementById(sId);
}

/**
 * A general-purpose function to get the absolute position
 * of the mouse.
 * @param {Object} e  Mouse event
 * @return {Object} Describes position
 */
DIndentUtil.getMousePosition = function(e) {
  var posX = 0;
  var posY = 0;
  if (!e) var e = window.event;
  if (e.pageX || e.pageY) {
    posX = e.pageX;
    posY = e.pageY;
  } else if (e.clientX || e.clientY){
    posX = e.clientX + 
      (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
    posY = e.clientY + 
      (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
  } 
  return {left: posX, top: posY};  
};

/**
 * Gets position of element
 * @param {Object} element
 * @return {Object} Describes position
 */
DIndentUtil.getElementPosition = function(element) {
  var leftPos = element.offsetLeft;          // initialize var to store calculations
  var topPos = element.offsetTop;            // initialize var to store calculations
  var parElement = element.offsetParent;     // identify first offset parent element  
  while (parElement != null ) {                // move up through element hierarchy
    leftPos += parElement.offsetLeft;      // appending left offset of each parent
    topPos += parElement.offsetTop;  
    parElement = parElement.offsetParent;  // until no more offset parents exist
  }
  return {left: leftPos, top: topPos};
};

/**
 * Applies styles to DOM objects 
 * @param {String/Object} elements Either comma-delimited list of ids 
 *   or an array of DOM objects
 * @param {Object} styles Hash of styles to be applied
 */
DIndentUtil.style = function(elements, styles){
  if (typeof(elements) == 'string') {
    elements = DIndentUtil.getManyElements(elements);
  }
  for (var i = 0; i < elements.length; i++){
    for (var s in styles) { 
      elements[i].style[s] = styles[s];
    }
  }
};

/**
 * Gets DOM elements array according to list of IDs
 * @param {String} elementsString Comma-delimited list of IDs
 * @return {Array} Array of DOM elements corresponding to s
 */
DIndentUtil.getManyElements = function(idsString){   
  var idsArray = idsString.split(',');
  var elements = [];
  for (var i = 0; i < idsArray.length; i++){
    elements[elements.length] = DIndentUtil.gE(idsArray[i])
  };
  return elements;
};
