
function PixboxTopRightControl() {
}

PixboxTopRightControl.prototype.printable = function() {
  return true;
}

PixboxTopRightControl.prototype.selectable = function() {
  return true;
}
PixboxTopRightControl.prototype.initialize = function(map) {
  this.box = pbu.$("<div class='pixboxtoprightcontrol' style='height:60px'><div class='togglebutton mapbutton toggled'>karta</div><div class='togglebutton satbutton'>satellit</div><div class='togglebutton hybbutton'>hybrid</div><form><input type='text' class='q' value='sök efter plats'/><img class='lookingglass' src='/geo/img/lookingglass.png'/></form></div>")[0];
  var ctrl = this;
  pbu.$("input[type=text]", this.box).bind("focus", function() {
    if (this.value == "sök efter plats")
        this.value = "";
  });
  pbu.$("form", this.box).bind("submit", function() {
    var q = pbu.$("input.q", ctrl.box)[0].value;
    if (q == "luna")
      map.setMapType(G_MOON_VISIBLE_MAP);
    else
      map.pixbox.goToLocation(q);
    return false;
  });
  pbu.$("img.lookingglass", this.box).bind("click", function() {
    var q = pbu.$("input.q", ctrl.box)[0].value;
    map.pixbox.goToLocation(q);
    return false;
  });
  pbu.$("div.mapbutton", this.box).bind("click", function() {
    map.setMapType(G_NORMAL_MAP);
    pbu.$("div.togglebutton", this.box).removeClass("toggled");
    pbu.$("div.mapbutton", this.box).addClass("toggled");
    return false;
  });
  pbu.$("div.satbutton", this.box).bind("click", function() {
    map.setMapType(G_SATELLITE_MAP);
    pbu.$("div.togglebutton", this.box).removeClass("toggled");
    pbu.$("div.satbutton", this.box).addClass("toggled");
    return false;
  });
  pbu.$("div.hybbutton", this.box).bind("click", function() {
    map.setMapType(G_HYBRID_MAP);
    pbu.$("div.togglebutton", this.box).removeClass("toggled");
    pbu.$("div.hybbutton", this.box).addClass("toggled");
    return false;
  });
  map.getContainer().appendChild(this.box);
  return this.box;
}

PixboxTopRightControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 7));
}


function PixboxFilterControl() {
}

PixboxFilterControl.prototype.printable = function() {
  return true;
}

PixboxFilterControl.prototype.selectable = function() {
  return true;
}
PixboxFilterControl.prototype.initialize = function(map) {
  this.box = pbu.$("<div class='mapfiltercontrol'><a href='#' class='dropcontrol droptext'>Filter</a><a href='#' class='dropcontrol dropbutton'>&#9650;</a><div class='selectorcontainer'></div></div>")[0];
  this.expanded = true;
  var ctrl = this;
  pbu.$("a.dropcontrol", this.box).bind("click", function() {
    if (ctrl.expanded) {
      ctrl.expanded = false;
      pbu.$("a.dropbutton", ctrl.box).html("&#9660;");
      pbu.$(ctrl.box).stop().animate({"height":"20px"});
    }
    else {
      ctrl.expanded = true;
      pbu.$("a.dropbutton", ctrl.box).html("&#9650;");
      pbu.$(ctrl.box).stop().animate({"height":"200px"});
    }
    return false;
  });
  pbu.postJSON("/geo/json.php", {"func":"GetFilterBoxData"}, function(json, status) {
    if (json) {
      var container = pbu.$("div.selectorcontainer", ctrl.box)[0];
      container.innerHTML = "";
      var sel = pbu.$("<div class='item selecteditem'><span class='tag'>allt</span> ("+json.total+")</div>")[0];
      pbu.$(sel).data("tag", 0);
      container.appendChild(sel);
      for (var i in json.tags) {
        var sel = pbu.$("<div class='item'>"+pbu.htmlescape(json.tags[i].tag)+" ("+json.tags[i].num+")</div>")[0];
        pbu.$(sel).data("tag", json.tags[i].tag);
        container.appendChild(sel);
      }
      pbu.$("div.item", ctrl.box).bind("click", function() {
        pbu.$("div.item", ctrl.box).removeClass("selecteditem");
        pbu.$(this).addClass("selecteditem");
        var ext = map.pixbox;
        ext.filtertag = pbu.$(this).data("tag");
        GEvent.trigger(ext.map, "tagchanged");
        if (ext.state == ext.STATE_BROWSE_MESH_PENDING || ext.state == ext.STATE_BROWSE_IDLE)
          ext.scheduleRefresh();
      });
    }
  });
  map.getContainer().appendChild(this.box);
  return this.box;
}

PixboxFilterControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 75));
}


function
PixboxImageMarker(coord, pid)
{
  this.coord = coord;
  this.pid = pid;
  this.size = 3;
  this.offset_x = 0;
  this.offset_y = 0;
}

function
PixboxGridMarker(grid)
{
  var lat = grid.lat / 32768.0 * 180.0 - 90.0;
  var lng = grid.lng / 65536.0 * 360.0 - 180.0;
  var len = grid.len / 32768.0 * 180.0;
  this.cookie = grid.cookie;
  this.sw = new GLatLng(lat, lng);
  this.ne = new GLatLng(lat + len, lng + len);
  this.center = new GLatLng(lat + len/2, lng + len/2);
  this.weight = grid.weight;
}

function
PixboxInfoWindow(origin, pid)
{
  this.origin = origin;
  this.pid = pid;
}

function
PixboxCrossHair(coord)
{
  this.coord = coord;
}

function ImageMarkerManager(ext)
{
  this.markers = {};
  this.ext = ext;
  this.redzone = [];
  this.fit_failed = [];
  this.timer = null;
}

ImageMarkerManager.prototype.startTimer = function() {
  var imm = this;
  if (!this.timer)
    this.timer = window.setInterval(function() {imm.fit_tick()}, 20);
}

ImageMarkerManager.prototype.stopTimer = function() {
  if (this.timer) {
    clearInterval(this.timer);
    this.timer = null;
  }
}

ImageMarkerManager.prototype.fit_tick = function() {
  if (this.fit_failed.length > 0) {
    var pid = this.fit_failed.pop();
    this.findAvailableSpot(pid);
    this.markers[pid].redraw(true);
    this.markers[pid].div.style.display="block";
  }
  else
    this.stopTimer();
}

ImageMarkerManager.prototype.preFullRedraw = function() {
  for (var pid in this.markers) {
    this.markers[pid].stale = true;
    this.markers[pid].offset_x = 0;
    this.markers[pid].offset_y = 0;
  }
  this.redzone = [];
  this.fit_failed = [];
}

ImageMarkerManager.prototype.add = function(pid, lat, lng) {
  var latlng = new GLatLng(lat, lng);
  if (this.markers[pid]) {
    // recycle
    this.markers[pid].stale = false;
    var size = this.fitToRedzone(pid);
    if (size == -1) {
      this.fit_failed.push(pid);
      this.startTimer();
      this.markers[pid].div.style.display = "none";
      this.markers[pid].size = 0;
    }
    else {
      this.markers[pid].div.style.display = "block";
      if (this.markers[pid].size != size) {
        this.markers[pid].size = size;
        this.markers[pid].redraw(true);
      }
    }
  }
  else {
    var marker = new PixboxImageMarker(latlng, pid);
    this.markers[pid] = marker;
    var size = this.fitToRedzone(pid);
    if (size == -1) {
      this.fit_failed.push(pid);
      this.startTimer();
      this.markers[pid].size = 0;
      this.ext.map.addOverlay(marker);
      this.markers[pid].div.style.display = "none";
    }
    else {
      this.markers[pid].size = size;
      this.ext.map.addOverlay(marker);
    }
  }
}

ImageMarkerManager.prototype.postFullRedraw = function() {
  for (var pid in this.markers) {
    if (this.markers[pid].stale) {
      // go away
      this.ext.map.removeOverlay(this.markers[pid]);
      delete this.markers[pid];
    }
  }
}

ImageMarkerManager.prototype.clear = function() {
  this.fit_failed = [];
  for (var pid in this.markers) {
    this.ext.map.removeOverlay(this.markers[pid]);
    delete this.markers[pid];
  }
}

ImageMarkerManager.prototype.fitToRedzone = function(pid) {
  var dx;
  var dy;
  var d;
  var c = this.ext.map.fromLatLngToDivPixel(this.markers[pid].coord);
  var size = 3;
  var def = marker_defs[size];
  var rz_x = c.x - def.anchor_left + def.rz_left;
  var rz_y = c.y - def.anchor_top  + def.rz_top;
  var rz_radius = def.rz_radius;
  for (var i = 0; i < this.redzone.length; i++) {
    while (true) {
      dx = this.redzone[i].x - rz_x;
      dy = this.redzone[i].y - rz_y;
      d = this.redzone[i].r + rz_radius;
      if (dx*dx + dy*dy < d*d) {
        // need to try a smaller size
        if (size == 0) {
          // couldn't fit. Give up.
          return -1;
        } else {
          size--;
          def = marker_defs[size];
          rz_x = c.x - def.anchor_left + def.rz_left;
          rz_y = c.y - def.anchor_top  + def.rz_top;
          rz_radius = def.rz_radius;
        }
      }
      else {
        break; // while
      }
    }
  }
  this.redzone.push({"x":rz_x,"y":rz_y,"r":rz_radius});
  return size;
}

ImageMarkerManager.prototype.findAvailableSpot = function(pid) {
    var marker = this.markers[pid];
    var c = this.ext.map.fromLatLngToDivPixel(marker.coord);
    var def = marker_defs[marker.size]; // always 0?
    var rz_x = c.x - def.anchor_left + def.rz_left;
    var rz_y = c.y - def.anchor_top  + def.rz_top;
    var rz_radius = def.rz_radius;
    var offset_x = 0;
    var offset_y = 0;
    var turn;
    /* search redzone in a spiral pattern outwards for an empty spot on the map */
    move_more:
    for(turn = 1; turn < 50; turn += 2 / turn) {
      offset_x = Math.round(Math.cos(2.0 * Math.PI * turn) * 2 * turn);
      offset_y = Math.round(Math.sin(2.0 * Math.PI * turn) * 2 * turn);
      /* 1/turn corresponds to one pixel unit along the perimeter */
      for (var i = 0; i < this.redzone.length; i++) {
        dx = this.redzone[i].x - (rz_x + offset_x);
        dy = this.redzone[i].y - (rz_y + offset_y);
        d = this.redzone[i].r + rz_radius;
        if (dx*dx + dy*dy < d*d)
          continue move_more;
      }
      break;
    }
    marker.offset_x = offset_x;
    marker.offset_y = offset_y;
    this.redzone.push({"x":(rz_x + offset_x),"y":(rz_y + offset_y),"r":rz_radius});
}

/* XXX scope please! */

var marker_defs = [];
marker_defs[0] = {
  url:"/geo/img/marker0.png",
  width:6,
  height:7,
  rz_left:2,
  rz_top:3,
  rz_radius:3,
  anchor_left:2,
  anchor_top:6
};
marker_defs[1] = {
  url:"/geo/img/marker1.png",
  width:10,
  height:11,
  rz_left:4,
  rz_top:5,
  rz_radius:6,
  anchor_left:4,
  anchor_top:10,
  thumb_left:1,
  thumb_top:1,
  thumb_width:8,
  thumb_height:8
};
marker_defs[2] = {
  url:"/geo/img/marker2.png",
  width:18,
  height:20,
  rz_left:8,
  rz_top:9,
  rz_radius:10,
  anchor_left:8,
  anchor_top:19,
  thumb_left:1,
  thumb_top:1,
  thumb_width:16,
  thumb_height:16
};
marker_defs[3] = {
  url:"/geo/img/marker3.png",
  width:34,
  height:38,
  rz_left:16,
  rz_top:18,
  rz_radius:20,
  anchor_left:16,
  anchor_top:37,
  thumb_left:1,
  thumb_top:1,
  thumb_width:32,
  thumb_height:32
};


function enablePixboxGMapExtensionPreloader(container) {
  var blackdrop = pbu.$("<div style='cursor:wait;position:absolute;top:0px;left:0px;width:"+container.offsetWidth+"px;height:"+container.offsetHeight+"px;background-color:#000000;opacity:0.8;'></div>")[0];
  var spinner = pbu.$("<img style='position:absolute;left:"+(container.offsetWidth-70)+"px;top:"+(container.offsetHeight-70)+"px;width:64px;height:48px;'' alt='' src='/geo/img/spinner64.png' />")[0];
  container.appendChild(blackdrop);
  container.appendChild(spinner);
  return {"blackdrop":blackdrop, "spinner":spinner};
}

function PixboxGMapExtension(map) {
  this.STATE_INIT = 0;
  this.STATE_ENTER_BROWSE_MODE = 1;
  this.STATE_BROWSE_MESH_PENDING = 2;
  this.STATE_BROWSE_MESH_APPLY = 3;
  this.STATE_BROWSE_IDLE = 4;
  this.STATE_ENTER_TAG_MODE = 5;
  this.STATE_TAG_MODE = 6;
  this.STATE_POSITION_MODE = 7;
  this.STATE_POSITION_COMMIT_PENDING = 8;
  this.STATE_MINIMAP_MODE = 9;
  this.state = this.STATE_INIT;
  this.map = map;
  this.container = this.map.getContainer();
  // XXX circular ref must be free'd
  map.pixbox = this;
  var tmp = enablePixboxGMapExtensionPreloader(this.container);
  this.blackdrop = tmp.blackdrop;
  this.spinner = tmp.spinner;
  this.seen_tiles_loaded = false;
  this.last_sent = 0;
  this.refresh_scheduled = false;
  this.crosshair = null;
  this.infowindow = null;
  this.selected_pic = null;
  this.picview = false;
  this.imm = new ImageMarkerManager(this);
  this.geocoder = null;
  this.debug = false;
  this.gridmarkers = [];
  this.filtertag = 0;
  var ext = this;
  this.dock = new PixboxFisheyeDock(this.container, function(elem) {
    if (elem.src) {
      ext.selected_pic = elem.src.match(/([0-9]*).jpg/)[1];
      ext.changeExtensionState(ext.STATE_POSITION_MODE);
    }
  });
  GEvent.addListener(ext.map, "moveend", function() {
  if (ext.state == ext.STATE_BROWSE_MESH_PENDING || ext.state == ext.STATE_BROWSE_IDLE)
    ext.scheduleRefresh();
  });

  GEvent.addListener(ext.map, "click", function(overlay, latlng) {
    // IE8 bugfix. The latlng from the click event is just plain wrong (wtf!) so use the most recent move event instead.
    latlng = new GLatLng(ext.mousemove_lat, ext.mousemove_lng);
    if (ext.state == ext.STATE_POSITION_MODE) {
      if (ext.map.getZoom() < 15)
        ext.map.setCenter(latlng, ext.map.getZoom() + 1);
      else {
        ext.clicked_coord = latlng;
        ext.changeExtensionState(ext.STATE_POSITION_COMMIT_PENDING);
      }
    }
      if (ext.infowindow && !ext.infowindow.dead) {
        ext.infowindow.forget();
        ext.infowindow = null;
      }
  });

  GEvent.addListener(ext.map, "mousemove", function(latlng) {
    ext.mousemove_lat = latlng.lat();
    ext.mousemove_lng = latlng.lng();
    if (ext.state == ext.STATE_POSITION_MODE && ext.crosshair) {
      ext.crosshair.move(latlng);
    }
  });

  // for some reason this can't be done from a keyboard event handler. It breaks GetMesh.
  //  pbu.$(document).bind("keydown", function (e) {
  //    if (ext.state == ext.STATE_POSITION_MODE && e.which == 27) {
  //      ext.changeExtensionState(ext.STATE_ENTER_BROWSE_MODE);
  //    }
  //  });
}

PixboxGMapExtension.prototype.goToLocation = function(q) {
  var ext = this;
  if (!this.geocoder) {
    this.geocoder = new GClientGeocoder();
    this.geocoder.setBaseCountryCode("se"); // this doesn't seem to do anything.
  }
  this.geocoder.getLocations(q, function(response) {
    if (response.Status.code != 200) {
      alert("Hittade inte platsen :-(");
    } else {
      var place = response.Placemark[0];
      var zoom = place.AddressDetails.Accuracy * 2 + 3;
      if (zoom > 17) zoom = 17;
      ext.map.setCenter(new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]), zoom);
    }
  });
}

PixboxGMapExtension.prototype.commitGeoTag = function() {
  var ext = this;
  var coord = ext.clicked_coord;
  pbu.postJSON("/geo/json.php", {"func":"Tag","csrf_token":csrf_token,"pic":ext.selected_pic,"lat":coord.lat(),"lng":coord.lng() }, function(json, status) {
      if (json) {
        var tmp_marker = new PixboxImageMarker(coord, ext.selected_pic);
        ext.map.addOverlay(tmp_marker);
        ext.changeExtensionState(ext.STATE_TAG_MODE);
      }
  });
}

PixboxGMapExtension.prototype.addGridToMap = function(grid) {
  var bounds = this.map.getBounds();
  var lat = grid.lat / 32768.0 * 180.0 - 90.0;
  var lng = grid.lng / 65536.0 * 360.0 - 180.0;
  var len = grid.len / 32768.0 * 180.0;
  var i;
  var j;
  var dx;
  var dy;


  var seen = {};

  for(i = 0; i < grid.spotlights.length; i++) {
    if (seen[grid.spotlights[i][0]] || grid.spotlights[i][0] == this.selected_pic)
      continue;
    if (!bounds.containsLatLng(new GLatLng(grid.spotlights[i][1], grid.spotlights[i][2])))
      continue;
    seen[grid.spotlights[i][0]] = true;
    this.imm.add(grid.spotlights[i][0], grid.spotlights[i][1], grid.spotlights[i][2]);
  }

  for(i = 0; i < grid.pics.length; i++) {
    // here we need to be careful not to readd spotlight pics because they would become refitted against themselves!
    if (seen[grid.pics[i][0]] || grid.pics[i][0] == this.selected_pic)
      continue;
    if (!bounds.containsLatLng(new GLatLng(grid.pics[i][1], grid.pics[i][2])))
      continue;
    seen[grid.pics[i][0]] = true;
    this.imm.add(grid.pics[i][0], grid.pics[i][1], grid.pics[i][2]);
  }

  return;

}


PixboxGMapExtension.prototype.badStateTransition = function(newState) {
  alert("ERROR: attempt to change map state "+this.state+" -> "+newState);
  return false;
}

PixboxGMapExtension.prototype.changeExtensionState = function(newState, arg) {
  var ext = this;
  if (!newState)
    return this.badStateTransition(newState);
  switch (newState) {
  case this.STATE_ENTER_BROWSE_MODE:
    if (this.state == this.STATE_BROWSE_IDLE)
      return false;
    if (this.state != this.STATE_INIT && this.state != this.STATE_TAG_MODE && this.state != this.STATE_POSITION_MODE)
      return this.badStateTransition(newState);
    if (this.state == this.STATE_TAG_MODE) {
      this.dock.hide();
    }
    this.state = newState;
    this.imm.clear(); // should already be empty
    this.map.clearOverlays();
    this.scheduleRefresh();
    return true;
  case this.STATE_MINIMAP_MODE:
    if (this.state != this.STATE_INIT)
      return this.badStateTransition(newState);
    this.imm.clear();
    this.leaveBusyMode();
    this.state = newState;
    return true;
  case this.STATE_BROWSE_MESH_PENDING:
    if (this.state != this.STATE_ENTER_BROWSE_MODE && this.state != this.STATE_BROWSE_IDLE)
      return this.badStateTransition(newState);
    this.enterBusyMode();
    this.state = newState;
    this.fireJSONRequest();
    return true;
  case this.STATE_BROWSE_MESH_APPLY:
    if (this.state != this.STATE_BROWSE_MESH_PENDING)
      return this.badStateTransition(newState);
    this.state = newState;
    return true;
  case this.STATE_BROWSE_IDLE:
    if (this.state != this.STATE_BROWSE_MESH_APPLY)
      return this.badStateTransition(newState);
    this.leaveBusyMode();
    this.state = newState;
    return true;
  case this.STATE_ENTER_TAG_MODE:
    if (this.state == this.STATE_TAG_MODE)
      return false;
    if (this.state != this.STATE_BROWSE_IDLE && this.state != this.STATE_INIT)
      return this.badStateTransition(newState)
    this.imm.clear();
    this.state = newState;
    this.changeExtensionState(this.STATE_TAG_MODE);
    if (this.picview /* immediately transition to positioning mode */) {
      this.changeExtensionState(this.STATE_POSITION_MODE);
    }
    return true;
  case this.STATE_TAG_MODE:
    if (this.state != this.STATE_ENTER_TAG_MODE && this.state != this.STATE_POSITION_COMMIT_PENDING)
      return this.badStateTransition(newState);
    this.leaveBusyMode();
    this.dock.populate();
    this.dock.show();
    if (this.picview && this.state == this.STATE_POSITION_COMMIT_PENDING) {
      this.state = newState;
      this.changeExtensionState(this.STATE_ENTER_BROWSE_MODE);
      return true;
    }
    this.state = newState;
    return true;
  case this.STATE_POSITION_MODE:
    if (this.state != this.STATE_TAG_MODE)
      return this.badStateTransition(newState);
    this.dock.hide();
    this.crosshair = new PixboxCrossHair(this.map.getCenter());
    this.map.addOverlay(this.crosshair);
    this.state = newState;
    return true;
  case this.STATE_POSITION_COMMIT_PENDING:
    if (this.state != this.STATE_POSITION_MODE)
      return this.badStateTransition(newState);
    this.map.removeOverlay(this.crosshair);
    this.crosshair = null;
    this.enterBusyMode();
    this.state = newState;
    this.commitGeoTag();
    return true;
  }
  return this.badStateTransition(newState);
}

PixboxGMapExtension.prototype.enterBusyMode = function() {
  var ext = this;
  ext.spinner.src = "/geo/img/spinner64.png";
  pbu.$(ext.spinner).stop().animate({"opacity":1.0}, 2000, function() {
    pbu.$(ext.blackdrop).stop().css({"display":"block"}).animate({"opacity":0.8}, 2000);
  });
}

PixboxGMapExtension.prototype.leaveBusyMode = function() {
  var ext = this;
  pbu.$(ext.spinner).stop().animate({"opacity":0.3}, 300, function() {ext.spinner.src="/geo/img/logo64.png";});
  pbu.$(ext.blackdrop).stop().animate({"opacity":0}, 1000, function() {pbu.$(ext.blackdrop).css({"display":"none"})});
}


PixboxGMapExtension.prototype.scheduleRefresh = function() {
  var d = new Date();
  var current = d.getTime();
  if (this.last_sent + 2000 <= current) {
    // fine, we can send a new one
    this.last_sent = current;
    if (this.state == this.STATE_BROWSE_MESH_PENDING)
      this.scheduleRefresh(); // try later
    else {
        this.changeExtensionState(this.STATE_BROWSE_MESH_PENDING);
    }
  }
  else {
    var ext = this;
    // install a timer to call us again when we can fire away
    if (!this.refresh_scheduled) {
      setTimeout(function() {ext.refresh_scheduled = false; ext.scheduleRefresh();}, ext.last_sent + 2000 - current);
      this.refresh_scheduled = true;
    }
  }
}

PixboxGMapExtension.prototype.fireJSONRequest = function() {
  var bounds = themap.getBounds();
  var sw = bounds.getSouthWest();
  var ne = bounds.getNorthEast();
  var sw_lat = sw.lat();
  var sw_lng = sw.lng();
  var ne_lat = ne.lat();
  var ne_lng = ne.lng();

  var span = bounds.toSpan();

  // one quarter of the size of the viewport in grid units
  var min_grid_len = parseInt(0.25 * (span.lat() + span.lng())/2.0 * (32768.0/180.0));
  var ext = this;
  pbu.postJSON("/geo/json.php", {func:'GetMesh',"sw_lat":sw_lat,"sw_lng":sw_lng,"ne_lat":ne_lat,"ne_lng":ne_lng, "min_grid_len":min_grid_len,"selected_pic":ext.selected_pic,"filtertag":ext.filtertag}, function(json, status) {
    if (json && json.success) {
      ext.changeExtensionState(ext.STATE_BROWSE_MESH_APPLY);
      if (ext.debug) {
        for(var i in ext.gridmarkers) {
          ext.map.removeOverlay(ext.gridmarkers[i]);
          delete ext.gridmarkers[i];
        }
      }
      ext.imm.preFullRedraw();
      if (json.selected_pic) {
        ext.imm.add(json.selected_pic[0], json.selected_pic[1], json.selected_pic[2]);
      }
      for (var i = 0; i < json.grids.length; i++) {
        if (ext.debug) {
          var m = new PixboxGridMarker(json.grids[i]);
          ext.gridmarkers.push(m);
          ext.map.addOverlay(m);
        }
        ext.addGridToMap(json.grids[i]);
      }
      ext.imm.postFullRedraw();
      ext.changeExtensionState(ext.STATE_BROWSE_IDLE);
    } else {
      if (ext.debug)
        alert("error in server communication; stuck in STATE_BROWSE_MESH_PENDING");
    }
  });
}

function
initPixboxGMapExtension()
{
  // Massive XXX: Why does it only work when the prototypes are wrapped in an init function?
  // (Why does the google maps api need to be initialised before prototypes are defined?)

  /* =================
     PixboxImageMarker
     ================= */

  PixboxImageMarker.prototype = new GOverlay();

  PixboxImageMarker.prototype.initialize = function(map) {
    this.map = map;
    this.div = document.createElement("div");
    this.div.style.position = "absolute";
    this.div.style.width = "0px";
    this.div.style.height = "0px";
    this.img = document.createElement("img");
    this.img.style.position="absolute";
    this.img.alt="";
    this.img.style.width="0px";
    this.img.style.height="0px";
    this.div.appendChild(this.img);
    this.thumb = document.createElement("img");
    this.thumb.style.position="absolute";
    this.thumb.alt="";
    this.thumb.style.width="0px";
    this.thumb.style.height="0px";
    this.div.appendChild(this.thumb);
    this.stale = false;
    var marker = this;
    GEvent.addDomListener(this.img, "click", function(overlay, latlng) {marker.clickHandler(latlng);});
    GEvent.addDomListener(this.thumb, "click", function(overlay, latlng) {marker.clickHandler(latlng);});
    this.map.getPane(G_MAP_MARKER_PANE).appendChild(this.div);
  }

  PixboxImageMarker.prototype.clickHandler = function(latlng) {
    if (this.map.pixbox.state == this.map.pixbox.STATE_MINIMAP_MODE) {
      document.location.href = "/pic"+this.pid;
      return;
    }
    if (this.map.pixbox.infowindow && !this.map.pixbox.infowindow.dead)
      this.map.pixbox.infowindow.forget();
    this.map.pixbox.infowindow = new PixboxInfoWindow(this.coord, this.pid);
    this.map.addOverlay(this.map.pixbox.infowindow);
    this.map.pixbox.infowindow.show();

  }

  PixboxImageMarker.prototype.remove = function() {
    this.div.removeChild(this.img);
    this.div.removeChild(this.thumb);
    this.div.parentNode.removeChild(this.div);
    this.div = null;
    this.img = null;
    this.thumb = null;
    this.map = null;
    this.coord = null;
  }

  PixboxImageMarker.prototype.copy = function() {
    alert("PixboxImageMarker copy not impl");
  }

  PixboxImageMarker.prototype.redraw = function(force) {
    if (!force) return;
    var def = marker_defs[this.size];
    var c = this.map.fromLatLngToDivPixel(this.coord);
    this.div.style.left = (c.x + this.offset_x) + "px";
    this.div.style.top = (c.y + this.offset_y) + "px";
    this.img.src = def.url;
    this.img.style.width=def.width+"px";
    this.img.style.height=def.height+"px";
    this.img.style.left=(-def.anchor_left)+"px";
    this.img.style.top=(-def.anchor_top)+"px";
    if (this.size == 0) {
      this.thumb.style.width = "0px";
      this.thumb.style.height = "0px";
    }
    else {
      this.thumb.src = pbu.picUrl(0, this.pid, "60x60", 0, 0);
      this.thumb.style.left = (-def.anchor_left+def.thumb_left)+"px";
      this.thumb.style.top =  (-def.anchor_top+def.thumb_top)+"px";
      this.thumb.style.width = def.thumb_width+"px";
      this.thumb.style.height = def.thumb_height+"px";
    }
    return;

  }

  /* ================
     PixboxInfoWindow
     ================ */

  PixboxInfoWindow.prototype = new GOverlay();

  PixboxInfoWindow.prototype.initialize = function(map) {
    this.map = map;
    this.div = document.createElement("div");
    this.div.style.position = "absolute";
    this.div.style.display = "none";
    this.div.style.width = "0px";
    this.div.style.height = "0px";
    this.bg = document.createElement("img");
    this.bg.style.position = "absolute";
    this.bg.style.top = "0px";
    this.bg.style.left = "0px";
    this.bg.style.width = "0px";
    this.bg.style.height = "0px";
    this.bg.src = "/geo/img/info.png";
    this.div.appendChild(this.bg);
    this.a = document.createElement("a");
    this.a.href = "/pic"+this.pid;
    this.thumb = document.createElement("img");
    this.thumb.style.position = "absolute";
    this.thumb.style.top = "0px";
    this.thumb.style.left = "0px";
    this.thumb.style.width = "0px";
    this.thumb.style.height = "0px";
    this.thumb.src = pbu.picUrl(0, this.pid, "120x120", 0, 0, {"noproxy":true});
    this.a.appendChild(this.thumb);
    this.div.appendChild(this.a);
    this.p = pbu.$("<p style='font-weight:bold;color:black;position:absolute;left:13px;top:135px;width:215px;height:17px'></p>")[0];
    this.div.appendChild(this.p);
    this.dead = false;
    var iw = this;
    this.map.getPane(G_MAP_FLOAT_PANE).appendChild(this.div);
    pbu.postJSON("/geo/json.php", {"func":"Popup", "pid":iw.pid}, function(json, status) {
      var title_html;
      var tooltip_html;
      if (json && json.title && json.title.length > 0) {
        if (json.title.length > 20)
          title_html = pbu.htmlescape(json.title.substring(0,20))+"...";
        else
          title_html = pbu.htmlescape(json.title);
        tooltip_html = pbu.htmlescape(json.title);
      }
      else {
        title_html = "bild";
        tooltip_html = "bild";
      }
      var nickname_html = (json && json.nickname)?pbu.htmlescape(json.nickname):"ok&auml;nd";
      pbu.$(iw.p).html("<a class='greenLink' title='"+tooltip_html+"' href='/pic"+iw.pid+"'>"+title_html+"</a> av "+nickname_html);
    });
  }

  PixboxInfoWindow.prototype.forget = function() {
    var iw = this;
    pbu.$(this.bg).stop().animate({"width":"0px", "height":"0px"}, 250);
    pbu.$(this.thumb).stop().animate({"top":"0px","left":"0px","width":"0px", "height":"0px"}, 250);
    pbu.$(this.div).stop().animate({"left":(iw.origin_pixel.x+"px"),"top":(iw.origin_pixel.y+"px"), "width":"0px", "height":"0px"}, 250,
      function() {
        if (!iw.dead) {
          iw.map.removeOverlay(iw);
        }
    });
  }

  PixboxInfoWindow.prototype.show = function(offset) {
    var iw = this;
    var center = this.map.getCenter();
    this.div.style.display ="block";
    this.destination_pixel = {};
    this.destination_pixel.x = this.origin_pixel.x - 240 / 2 + ((this.origin.lng() < center.lng())?1:-1)*(240 / 2 + 10);
    this.destination_pixel.y = this.origin_pixel.y - 160 / 2 + ((this.origin.lat() > center.lat())?1:-1)*(160 / 2 + 10);
    pbu.$(this.div).stop().animate({"top":iw.destination_pixel.y+"px","left":iw.destination_pixel.x+"px", "width":"240px", "height":"160px"}, 250);
    pbu.$(this.bg).stop().animate({"width":"240px", "height":"160px"}, 250);
    pbu.$(this.thumb).stop().animate({"top":"13px","left":"13px","width":"120px", "height":"120px"}, 250);
  }

  PixboxInfoWindow.prototype.remove = function() {
    pbu.$(this.div).stop();
    pbu.$(this.bg).stop();
    pbu.$(this.thumb).stop();
    this.div.removeChild(this.bg);
    this.bg = null;
    this.a.removeChild(this.thumb);
    this.thumb = null;
    this.div.removeChild(this.a);
    this.a = null;
    this.div.removeChild(this.p);
    this.p = null;
    this.div.parentNode.removeChild(this.div);
    this.div = null;
    this.map = null;
    this.coord = null;
    this.dead = true;
  }

  PixboxInfoWindow.prototype.copy = function() {
    alert("PixboxInfoWindow copy not impl");
  }

  PixboxInfoWindow.prototype.redraw = function(force) {
    if (!force)
      return;
    this.origin_pixel = this.map.fromLatLngToDivPixel(this.origin);
    if (this.div.style.display == "none") {
      this.div.style.left = this.origin_pixel.x + "px";
      this.div.style.top = this.origin_pixel.y + "px";
    }
  }

  /* ================
     PixboxGridMarker
     ================ */

  PixboxGridMarker.prototype = new GOverlay();

  PixboxGridMarker.prototype.initialize = function(map) {
    this.div = document.createElement("div");
    this.div.style.position = "absolute";
    this.div.style.border="1px dashed black";
    this.div.innerHTML= parseInt(this.cookie).toString(16);
    this.map = map;
    this.map.getPane(G_MAP_OVERLAY_LAYER_PANE).appendChild(this.div);
  }

  PixboxGridMarker.prototype.remove = function() {
    this.div.parentNode.removeChild(this.div);
    this.div = null;
    this.map = null;
    this.sw = null;
    this.ne = null;
    this.center = null;
  }

  PixboxGridMarker.prototype.copy = function() {
    alert("PixboxGridMarker copy not impl");
  }

  PixboxGridMarker.prototype.redraw = function(force) {
    if (!force) return;

//    var c = this.map.fromLatLngToDivPixel(this.center);
    var sw = this.map.fromLatLngToDivPixel(this.sw);
    var ne = this.map.fromLatLngToDivPixel(this.ne);

    this.div.style.top = ne.y + "px";
    this.div.style.left = sw.x + "px";
    this.div.style.width= (ne.x-sw.x) + "px";
    this.div.style.height = (sw.y-ne.y) + "px";
  }

  /* ================
     PixboxCrossHair
     ================ */

  PixboxCrossHair.prototype = new GOverlay();

  PixboxCrossHair.prototype.initialize = function(map) {
    this.map = map;
    this.img = document.createElement("img");
    this.img.style.position="absolute";
    this.img.style.width="31px";
    this.img.style.height="31px";
    this.img.style.cursor="none";
    this.thumb = document.createElement("img");
    this.thumb.style.position="absolute";
    this.thumb.style.width="24px";
    this.thumb.style.height="24px";
    this.thumb.style.border="1px solid black";
    this.thumb.src = pbu.picUrl(0, this.map.pixbox.selected_pic, "60x60", 0, 0, {"noproxy":true});
    if (this.map.getZoom() < 15)
      this.img.src="/geo/img/crosshair-zoom.png";
    else
      this.img.src="/geo/img/crosshair.png";
    this.map.getPane(G_MAP_FLOAT_PANE).appendChild(this.thumb);
    this.map.getPane(G_MAP_MARKER_PANE).appendChild(this.img);
  }

  PixboxCrossHair.prototype.remove = function() {
    this.img.parentNode.removeChild(this.img);
    this.img = null;
    this.thumb.parentNode.removeChild(this.thumb);
    this.thumb = null;
  }

  PixboxCrossHair.prototype.copy = function() {
    alert("PixboxCrossHair copy not impl");
  }

  PixboxCrossHair.prototype.redraw = function(force) {
    if (!force) return;
    if (this.map.getZoom() < 15)
      this.img.src="/geo/img/crosshair-zoom.png";
    else
      this.img.src="/geo/img/crosshair.png";
    var c = this.map.fromLatLngToDivPixel(this.coord);
    this.img.style.left = "" + (c.x-16) + "px";
    this.img.style.top = "" + (c.y-16) + "px";
    this.thumb.style.left = "" + (c.x+16) + "px";
    this.thumb.style.top = "" + (c.y+16) + "px";
  }

  PixboxCrossHair.prototype.move = function(coord) {
    this.coord = coord;
    this.redraw(true);
  }
}

function PixboxFisheyeDock(container, fisheye_click) {
  this.lastupdate = 0;
  this.container = container;

  this.num = 48;

  this.hidden = true;
  this.imgs = [];
  var dock = this;
  for (var i = 0; i < this.num; i++) {
    this.imgs[i] = document.createElement("img");
    this.imgs[i].style.position="absolute";
    this.imgs[i].style.display="none";
    this.imgs[i].style.cursor="pointer";
    pbu.$(this.imgs[i]).bind("click", function() {
      fisheye_click(this /* image element */);
    });
    this.container.appendChild(this.imgs[i]);
  }
  pbu.$(this.container).bind("mousemove", function(e) {
    if (dock.hidden)
      return;
    var d = new Date();
    var current = d.getTime();
    if (dock.lastupdate + 50 > current) {
      return;
    }
    dock.lastupdate = current;
    var p = pbu.getAbsolutePosition(dock.container);
    var mousex = e.pageX - p.left;
    var mousey = e.pageY - p.top;
    dock.refresh(mousex, mousey);
  });
}

PixboxFisheyeDock.prototype.populate = function() {
  var dock = this;
  var mode = dock.alb?"alb":"latest";
  pbu.postJSON("/geo/json.php", {"func":"GetUntagged", "csrf_token":csrf_token, "mode":mode, "alb":dock.alb}, function(json, status) {
    if (json.success) {
      for (i = 0; i < json.pics.length && i < dock.num; i++) {
        dock.imgs[i].src = pbu.picUrl(0, json.pics[i], "120x120", 0,0, {"noproxy":true});
        if (!dock.hidden)
          dock.imgs[i].style.display="inline";
      }
      for (; i < dock.num; i++) {
        docs.imgs[i].src = null;
        dock.imgs[i].style.display="none";
      }
    }
  });


}

PixboxFisheyeDock.prototype.show = function() {
  this.refresh(0, 0);
  for(var i = 0; i < this.num; i++) {
    if (this.imgs[i].src)
      this.imgs[i].style.display="inline";
  }
  this.hidden = false;
}

PixboxFisheyeDock.prototype.hide = function() {
  for(var i = 0; i < this.num; i++)
    this.imgs[i].style.display="none";
  this.hidden = true;
}

PixboxFisheyeDock.prototype.refresh = function(mousex, mousey) {
  container_height = this.container.offsetHeight;
  var y = mousey / container_height;
  y = y * 3 - 2;
  y = 1 - y;
  if (y < 0) y = 0;
  if (y > 1) y = 1;

  var x = mousex / this.container.offsetWidth;
  var scale_array = this.getScales(this.num, y, 20, x);
  var p = 0;
  for(var i = 0 ; i<48; i++) {
    var img = this.imgs[i];
    var scale = scale_array[i];
    var w = 20 * scale;
    img.style.top = (container_height - w) + "px";
    img.style.left = p + "px";
    p = p + w;
    img.style.width =  w + "px";
    img.style.height = w + "px";
  }
}

// num number of elemens
// baseline (0-1) fraction of area that should not be part of bulge
// power (0-inf) steepness of bulge
// pos (0-1) position of bulge
PixboxFisheyeDock.prototype.getScales = function(num, baseline, power, pos) {
  var ret = [];
  var superimposed = [];
  var sum = 0;
  for (var i = 0; i < num; i++) {
    var a = (i / num - pos) * Math.PI;
    if (a < -Math.PI / 2) a = -Math.PI / 2;
    if (a >  Math.PI / 2) a =  Math.PI / 2;
    superimposed[i] =  Math.pow(Math.cos(a), power);
    sum = sum + superimposed[i];
  }
  for(i = 0; i < num; i++){
    ret[i] = baseline + (1-baseline) * (num * superimposed[i] / sum);
  }
  return ret;
}


