/**
 * @file
 * wnoMap JS file.
 */
// Some base distance conversion constants.
var klinly = 31535094.6829333;
var lyinpc = 3.26156;

/**
* Zoom limits to use.
* 
* Zoom < 10: Hide objects within 200k
* Zoom < 13: Hide objects within 100k
* Zoom < 17: Hide objects within 60k
* Zoom < 21: Hide objects within 20k
* Zoom < 25: Hide objects within 10k
* Zoom < 29: Hide objects within 5k
* Zoom = 29: Hide objects within 3k
*/
var zoomLimits = {
  10: 300000.0,
  12: 250000.0,
  15: 200000.0,
  17: 60000.0,
  21: 20000.0,
  25: 10000.0,
  29: 5000.0,
  30: 3000.0
};

// Add some of our stellar distance units to OpenLayers.
OpenLayers.INCHES_PER_UNIT.kl = OpenLayers.INCHES_PER_UNIT.km;
OpenLayers.INCHES_PER_UNIT.kkl = OpenLayers.INCHES_PER_UNIT.kl * 1000.0;
OpenLayers.INCHES_PER_UNIT.ly = OpenLayers.INCHES_PER_UNIT.kl * klinly;
OpenLayers.INCHES_PER_UNIT.pc = OpenLayers.INCHES_PER_UNIT.ly * lyinpc;

// Allow alternate renderers via GET query.
var renderer = OpenLayers.Util.getParameters(window.location.href).renderer
renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;

function onPopupClose(evt) {
  // 'this' is the popup.
  map.selectControl.unselect(this.feature);
}

function onFeatureSelect(feature) {
  popup = new OpenLayers.Popup.FramedCloud(
    "featurePopup",
    feature.geometry.getBounds().getCenterLonLat(),
    new OpenLayers.Size(100, 100),
    popupContents(feature),
    null, true, onPopupClose
  );
  feature.popup = popup;
  popup.feature = feature;
  map.addPopup(popup);
}

function onFeatureUnselect(feature) {
  if (feature.popup) {
    popup.feature = null;
    map.removePopup(feature.popup);
    feature.popup.destroy();
    feature.popup = null;
  }
}

function popupContents(feature) {
  var c = 0, out = "<h3>" + feature.attributes.label + "</h3>";
  var nearby = feature.attributes.nearby;
  out += "Also here:<ul>";
  for (var n in nearby) {
    c++;
    out += "<li>"+nearby[n]+"</li>";
  }
  if (!c) {
    out += "<li>Nothing</li>";
  }
  out += "</ul>";
  
  return out;
}

/**
 * wnoMap namespace.
 */
var wnoMap = {

  // Sensor Uplink support.
  sensorUplink: 0,
  sensorUser: '',
  sensorPass: '',
  sensorRange: 0.0,
  sensorContacts: {},
  
  // Scale to apply to incoming coordinates.
  scale: 1.0,
  
  // Coordinate translation (changes origin to, for example, Earth).
  //translation: [3850000002.0, 199989.0],
  translation: [0.0, 0.0],
  
  labelLayer: new OpenLayers.Layer.Vector('Labels', {
    styleMap: new OpenLayers.StyleMap({
      'default': {
        strokeOpacity: 0,
        strokeWidth: 0,
        fillOpacity: 0,
        pointRadius: 0,
        label: "${label}",
        fontColor: "#FF5500",
        fontSize: "10px",
        fontFamily: "Arial, sans-serif",
        fontWeight: "normal",
        labelAlign: "lt"
      }
    })
  }),
  
  sensorLayer: new OpenLayers.Layer.Vector("Sensors", {
    styleMap: new OpenLayers.StyleMap({
      'default': {
        strokeColor: "#FFFF00",
        strokeOpacity: 1,
        strokeWidth: 2,
        fillColor: "#0055FF",
        fillOpacity: 1,
        pointRadius: 3,
        pointerEvents: "visiblePainted",
        label: "${label}",
        
        //fontColor: "${favColor}",
        fontSize: "12px",
        fontFamily: "Courier New, monospace",
        fontWeight: "bold",
        labelAlign: "lt"
      }
    })
  }),
  
  /**
   * Special function to convert WNO coordinates to standard cartesient coords,
   * because I wrote space before I had taken any algebra or geometry.
   */
  point: function(x, y){
    x = (parseFloat(x) + wnoMap.translation[0]) * wnoMap.scale;
    y = (parseFloat(y) + wnoMap.translation[1]) * wnoMap.scale;
    return new OpenLayers.Geometry.Point(y, x);
  },
  
  /**
   * Read borders from text.
   */
  processBorders: function(text, layer){
    var lines = text.replace("\r\n", "\n").split("\n");
    var els, x, y, radius, r, g, b, name, border;
    var borders = [];
    for (var i = 0; i < lines.length; i++) {
      els = lines[i].split(" ");
      x = els[0];
      y = els[1];
      radius = parseFloat(els[2]) * wnoMap.scale;
      r = els[3];
      g = els[4];
      b = els[5];
      name = els.slice(6).join(" ");
      border = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon.createRegularPolygon(wnoMap.point(x, y), radius, 50));
      border.attributes = {
        name: name,
        borderColor: "rgb(" + r + "," + g + "," + b + ")",
        order: i
      };
      borders.push(border);
    }
    layer.addFeatures(borders);
  },
  
  /**
   * Read locations from text.
   */
  processLocations: function(text, layer){
    var lines = text.replace("\r\n", "\n").split("\n");
    var els, x, y, type, name, loc, label, point;
    var locations = [];
    var labels = [];
    for (var i = 0; i < lines.length; i++) {
      els = lines[i].trim().replace(/ +/g, " ").split(" ");
      x = els[0];
      y = els[1];
      //z = els[2];
      type = els[3];
      name = els.slice(4).join(" ");
      point = wnoMap.point(x, y);
      loc = new OpenLayers.Feature.Vector(point);
      loc.attributes = {
        label: name,
        type: type,
        visible: "yes",
        nearby: {},
        lastZoomCheck: 1000
      };
      locations.push(loc);
      //label = new OpenLayers.Feature.Vector(point);
      //label.attributes = {label: name, type: type};
      //labels.push(label);
    }
    layer.addFeatures(locations);
    wnoMap.showHideLocations(true);
    //this.labelLayer.addFeatures(labels);
  },
  
  /**
   * Shows or hides features that are too close to other features based on zoom
   * level. Add name of hidden feature to a property on feature it is near to.
   */
  showHideLocations: function(refresh) {
    var minDist = 100000.0; // default
    var a, feature, other, zLevel, dist, show;
    var f, o;
    
    for (zLevel in zoomLimits) {
      if (map.zoom < zLevel) {
        var minDist = zoomLimits[zLevel];
        break;
      }
    }
    
    for (f in map.locLayer.features) {
      feature = map.locLayer.features[f];
      a = feature.attributes;
      // Only check if the object is already visible and we are zooming in or
      // is already hidden and we're zooming out.
      if (refresh || (a.visible == "yes" && map.zoom > a.lastZoomCheck)
          || (a.visible == "no" && map.zoom < a.lastZoomCheck)) {
        show = 1;
        for (o in map.locLayer.features) {
          other = map.locLayer.features[o];
          if (feature.id == other.id) {
            break;
          }
          dist = feature.geometry.distanceTo(other.geometry, {edge:false});
          if (dist < minDist) {
            show = 0;
            other.attributes.nearby[feature.id] = a.label;
            a.behind = other;
            a.lastZoomCheck = map.zoom;
            break;
          }
        }
        a.visible = show ? "yes" : "no";
        if (show && a.behind != undefined) {
          a.behind.attributes.nearby[feature.id] = undefined;
          a.behind = undefined;
        }
      }
    }
    
    map.locLayer.redraw();
  },
  
  /**
   * Returns a MousePosition object regeared to work with wno coordinates.
   */
  wnoMouseCoords: function(){
    var wnoCoords = new OpenLayers.Control.MousePosition();
    wnoCoords.formatOutput = function(lonLat){
      // Switch x and y.
      var digits = parseInt(this.numDigits);
      var newHtml = this.prefix +
      lonLat.lat.toFixed(digits) +
      this.separator +
      lonLat.lon.toFixed(digits) +
      this.suffix;
      return newHtml;
    };
    wnoCoords.prefix = 'Coordinates: ';
    wnoCoords.suffix = ', 0'; // Show Z even though we don't know anything about it.
    wnoCoords.numDigits = 0; // Skip the decimals since we aren't granular enough.
    return wnoCoords;
  },
  
  /**
   * Returns a ScaleLine object geared for wno distances.
   */
  wnoScaleLine: function(){
    var wnoScale = new OpenLayers.Control.ScaleLine();
    wnoScale.topOutUnits = "kkl";
    wnoScale.topInUnits = "kkl";
    wnoScale.bottomOutUnits = "kl";
    wnoScale.bottomInUnits = "kl";
    
    wnoScale.update = function(){
      var res = this.map.getResolution();
      if (!res) {
        return;
      }
      var curMapUnits = this.map.getUnits();
      var inches = OpenLayers.INCHES_PER_UNIT;
      var maxSizeData = this.maxWidth * res * inches[curMapUnits];
      var topUnits;
      var bottomUnits;
      if (maxSizeData >= 787400000000) {
        topUnits = this.topOutUnits;
        bottomUnits = this.bottomOutUnits;
      }
      else {
        topUnits = this.topInUnits;
        bottomUnits = this.bottomInUnits;
      }
      var topMax = maxSizeData / inches[topUnits];
      var bottomMax = maxSizeData / inches[bottomUnits];
      var topRounded = this.getBarLen(topMax);
      var bottomRounded = this.getBarLen(bottomMax);
      topMax = topRounded / inches[curMapUnits] * inches[topUnits];
      bottomMax = bottomRounded / inches[curMapUnits] * inches[bottomUnits];
      var topPx = topMax / res;
      var bottomPx = bottomMax / res;
      if (this.eBottom.style.visibility == "visible") {
        this.eBottom.style.width = Math.round(bottomPx) + "px";
        this.eBottom.innerHTML = bottomRounded + " " + bottomUnits;
      }
      if (this.eTop.style.visibility == "visible") {
        this.eTop.style.width = Math.round(topPx) + "px";
        this.eTop.innerHTML = topRounded + " " + topUnits;
      }
    }
    
    return wnoScale;
  },
  
  wnoEditBar: function(layer){
    var toolbar = new OpenLayers.Control.wnoToolbar(layer);
    
    return toolbar;
  },
  
  /**
   * Create main map.
   */
  init: function(){
    // Edit layer.
    var editLayer = new OpenLayers.Layer.Vector("Drawing");
    var editBar = wnoMap.wnoEditBar(editLayer);
    
    // Create map object. 
    var mapSize = 400 * wnoMap.scale * klinly;
    var map = new OpenLayers.Map({
      div: "map",
      maxExtent: new OpenLayers.Bounds(-mapSize, -mapSize, mapSize, mapSize),
      minResolution: 300,
      maxResolution: 500000,
      resolutions: [500000, 400000, 300000, 200000, 100000, 90000, 80000, 70000, 60000, 50000, 40000, 30000, 20000, 10000, 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, 900, 800, 700, 600, 500, 400, 300],
      //minResolution: 40000,
      //maxResolution: 50000000,
      //resolutions: [50000000,40000000,30000000,20000000,10000000,9000000,8000000,7000000,6000000,5000000,4000000,3000000,2000000,1000000,900000,800000,700000,600000,500000,400000,300000,200000,100000,90000,80000,70000,60000,50000,40000],
      units: 'kl',
      allOverlays: true,
      controls: [new OpenLayers.Control.Navigation(), new OpenLayers.Control.PanZoomBar(), new OpenLayers.Control.LayerSwitcher({
        'ascending': false
      }), wnoMap.wnoScaleLine(), wnoMap.wnoMouseCoords(), editBar//,
      //new OpenLayers.Control.KeyboardDefaults()
      ]
    });
    this.map = map;
    
    // Add borders layer.
    var borderLayer = new OpenLayers.Layer.Vector("Borders", {
      styleMap: new OpenLayers.StyleMap({
        'default': {
          strokeWidth: 0,
          fillColor: "${borderColor}",
          fillOpacity: 1,
          graphicZIndex: "${order}"
        }
      }),
      renderers: renderer,
      rendererOptions: {
          zIndexing: true
      }
    });
    borderLayer.setOpacity(0.5);
    map.addLayer(borderLayer);
    var req = OpenLayers.Request.GET({
      url: "borders.txt",
      success: function(request){
        wnoMap.processBorders(request.responseText, borderLayer);
      }
    });
    
    // Add locations layer.
    var locLayer = new OpenLayers.Layer.Vector("Locations", {
      styleMap: new OpenLayers.StyleMap({
        'default': {
          strokeColor: "#00FF00",
          strokeOpacity: 1,
          strokeWidth: 2,
          fillColor: "#FF5500",
          fillOpacity: 1,
          pointRadius: 3,
          pointerEvents: "visiblePainted",
          label: "${label}",
          fontColor: "#ff5500",
          fontSize: "10px",
          fontFamily: "Arial, sans-serif",
          fontWeight: "normal",
          labelAlign: "lt"
        }
      }),
      renderers: renderer
    });
    locLayer.styleMap.addUniqueValueRules(
      "default",
      "visible",
      {"yes": {display: ""}, "no": {display:"none"}}
    );
    map.locLayer = locLayer;
    var req = OpenLayers.Request.GET({
        url: "game_proxy.php/space/map",
        success: function(request){
            wnoMap.processLocations(request.responseText, locLayer);
        }
    });
    
    map.addLayer(locLayer);
    map.addLayer(editLayer);
    map.addLayer(wnoMap.labelLayer);
    //map.addLayer(wnoMap.sensorLayer);
    map.labelLayer = wnoMap.labelLayer;
    map.editLayer = editLayer;
    
    map.selectControl = new OpenLayers.Control.SelectFeature(locLayer,
      {onSelect: onFeatureSelect, onUnselect: onFeatureUnselect}
    );
    map.addControl(map.selectControl);
    map.selectControl.activate();
    
    // Center, zoom the map.
    var center = wnoMap.point(-3855000000, 1000000);
    map.setCenter(new OpenLayers.LonLat(center.x, center.y), 9);
    
    return map;
  },
  
  tryUplink: function(data){
    if (typeof data == 'number') {
      if (data < 1) {
        alert("Failed ship match");
      } else {
        wnoMap.sensorUplink = data;
        wnoMap.sensorUser = $('#user').val();
        wnoMap.sensorPass = $('#pass').val();
        wnoMap.refreshSensors();
      }
    }
  },
  
  refreshSensors: function() {
    if (this.sensorUplink) {
      var url = 'game_proxy.php/space/so/'+this.sensorUplink+'/sensors';
      var user = this.sensorUser;
      var pass = this.sensorPass;
      
      $.getJSON(url, {user:user, pass:pass}, function(data){
        var dbref, name, pos, refs = [], point, features = [];
        var contacts = wnoMap.sensorContacts;
        wnoMap.sensorRange = data[0];
        for (var i = 1; i < data.length; i++) {
          dbref = data[i][0];
          name = data[i][1];
          pos = data[i][2];
          refs.push(dbref);
          point = wnoMap.point(pos[0], pos[1]);
          if (contacts[dbref] == undefined) {
            contacts[dbref] = {
              dbref: dbref,
              name: name,
              pos: pos,
              feature: new OpenLayers.Feature.Vector(point)
            };
            contacts[dbref].feature.attributes = {label:name};
            features.push(contacts[dbref].feature);
          } else {
            contacts[dbref].feature.move(point.x, point.y);
          }
        }
        wnoMap.sensorLayer.addFeatures(features);
        for (var c in contacts) {
          if (!$.inArray(c, refs)) {
            contacts[c].feature.destroy();
            contacts[c] = undefined;
          }
        }
      });
    }
  }
}

$(document).ready(function(){
    window.map = wnoMap.init();
    map.events.register("zoomend", null, wnoMap.showHideLocations);
    
    $('#plot').click(function(){
      var coords = $('#coords').val().split(' ', 2);
      if (coords.length == 2) {
        var x = coords[0];
        var y = coords[1];
        var label = $('#user_label').val();
        var point = wnoMap.point(x, y);
        var feature = new OpenLayers.Feature.Vector(point);
        feature.attributes = {
            label: label.length ? label : (point.y + " " + point.x)
        };
        wnoMap.map.locLayer.addFeatures([feature]);
        $('#coords,#user_label').val('');
      }
    });
    
    $('#uplink').click(function(){
      $.getJSON(
        'game_proxy.php/space/find', 
        {name: $('#ship').val()}, 
        wnoMap.tryUplink
      );
    });
});
