// -*-Mode: Java;-*- 

/*
JourneySat, a library to georeference a travel journal using Google Maps.
Copyright (C) 2005  Paolo Montrasio, paolo AT paolomontrasio.com

This file is part of JourneySat (the Program).

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

// This is a kind of package JourneySat declaration in Java
if (JourneySat == undefined) var JourneySat = {};

JourneySat.MayaSkin = function () {};

JourneySat.MayaSkin.prototype = {

  travel: null,
  
  /**
   * The GMap object
   * @private
   */
  map:                   null,
  getMap: function() {
    return this.map;
  },

  /**
   * The ResourceBundle object that contains the labels used in the UI.
   */
  lang: {},

  /**
   * The visibility status of the map
   */
  visibleMap: true,
  mapDisabled: false, // for browsers not supported by Google Maps

  /**
   * Toggles the visibility status of the map;
   */
  toggleMap: function () {

    if (this.mapDisabled) {
      return;
    }

    var btn = document.getElementById(this.elementToggle);
    if (this.visibleMap) {
      this.visibleMap = false;
      this.map = null;
      var mapDiv = document.getElementById(this.elementMap);
      mapDiv.innerHTML = "<p></p>";
      btn.innerHTML = this.lang.btnShow;
      // refreshMap is needed to rebuild the description of the 
      // place to include links to pictures
      this.refreshMap();
    } else {
      this.visibleMap = true;
      // refreshMap isn't enough because it doesn't rebuilds
      // the markers.
      this.initScreen();
      btn.innerHTML = this.lang.btnHide;
    }
    
    
  },
  
  /**
   * Changes the size of the map
   * @param w width
   * @param h height
   */
  resizeMap: function (w, h) {

    var mapDiv = document.getElementById(this.elementMap);
    mapDiv.style.width = w + "px";
    mapDiv.style.height = h + "px";
    this.map.onResize(); // undocumented API function
    // see http://groups-beta.google.com/group/Google-Maps-API/browse_thread/thread/355ebf337596ff02/
    //
    this.mapWidth = w;
    this.mapHeight = h;

    /*
      // This was code I was using before learning about onResize() but
      // it seems that the old map wasn't really destroyed. Just try
      // to load the page, click to zoom, drag the map somewhere else,
      // click to zoom again: the old map still gets zoomed (clearListeners
      // doesn't work?) before being overwritten by the new one.
      GEvent.clearListeners(this.map, "onclick");

      // deleting the map will force the creation of a new one
      // with the new height and width
      this.map = null;
      this.initScreen();
    */
  },
  
  /**
   * Creates a new GMap
   * @param w width
   * @param h height
   */
  newMap: function (w, h) { 
    var mapDiv = document.getElementById(this.elementMap);
    mapDiv.style.width = w + "px";
    mapDiv.style.height = h + "px";
    
    this.map = null;
    this.map = new GMap2(mapDiv);
    this.map.setMapType(G_SATELLITE_MAP);
    //this.map.addControl(new GLargeMapControl());
  },
  
  refreshMap: function () {
    // The old code from release 1.0.1 is commented out
    //var places = this.travel.getPlaces(); 
    var pl = this.travel.getCurrentPlace();
    //var pl = places[currentPlace];
    var contents;
    
    var imageList = "";
    
    if (this.visibleMap) {
      var point = this.travel.getCurrentPoint();
      
      if (this.map == null) {
	// Creates a map using the defaults
	// The zoom level is from the XML read by Travel
	this.newMap(this.mapWidth, this.mapHeight);
      }
      var zoom = pl.getZoomLevel();
      if (zoom == -1) {
	zoom = this.travel.getZoomLevel();
      }
      this.map.setCenter(new GLatLng(point.y, point.x), zoom);
    } else {
      // Builds a list of links to images because the normal
      // links in the map are hidden
      contents = this.travel.getContentArray();
      if (contents.length > 0) {
	imageList = "<p>";
      }
      for (i = 0; i < contents.length; i++) {
	var id = this.lang.labelPhoto + (i + 1);
	imageList = imageList
	+ '<img src="' + this.photoIcon + '" id="'
	+ id + '"/>' + id + "<br/>";
      }
      if (contents.length > 0) {
	imageList = imageList + "</p>";
      }
    }
    
		// Migration from 1.0.1: getCurrentPlace() doesn't return an
    // int anymore.
    var currentPlace = this.travel.getCurrentPlaceIndex();
    document.getElementById(this.elementGoto + "Top").value =
	currentPlace + 1;
    document.getElementById(this.elementGoto + "Bottom").value =
	currentPlace + 1;
    document.getElementById(this.elementPlace).innerHTML = pl.getName();
    var dt = document.getElementById(this.elementPlaceDate);

    var date = this.travel.getCurrentDay().getDate();
    dt.innerHTML = date.toString();
    /* The old code from release 1.0.1
    if (pl.getDay() != null && pl.getMonth() != null && pl.getYear() != null) {
      dt.innerHTML = pl.getDay() + "/" + pl.getMonth() + "/" + pl.getYear();
    } else {
      dt.innerHTML = "";
    }
    */

    // It's important that pd is a <span> and not a <p>.
    // pd.innerHTML = "<p>Hi!</p>" breaks IE 6 when pd is a <p>
    var pd = document.getElementById(this.elementPlaceDescription);
    pd.innerHTML = pl.getDescription() + imageList;

    if (!this.visibleMap) {
      for (i = 0; i < contents.length; i++) {
	var id = this.lang.labelPhoto + (i + 1);
	var photo = document.getElementById(id);
	var href = contents[i].getHref();
	var descr = contents[i].getCbody();
	var ph = new PhotoIcon(photo, href, descr);
      }
    }
    
  },
  
  linkToPopup: function(url, id) {
    return html;
  },
  
  /**
   * The name of the map toggle button
   */
  elementToggle: null,
  setElementToggle: function(name) {
    this.elementToggle = name;
  },
  getElementToggle: function() {
    return this.elementToggle;
  },
  
  /**
   * The name of the travelTitle HTML element, used to show the name
   * of the travel as a h1 element. Usually this is always visible at
   * the top of the page.
   * @private
   */
  elementTitle: null,
  setElementTitleName: function(name) {
    this.elementTitle = name;
  },
  
  
  /**
   * The name of the travelDescription HTML element. This usually follows
   * the title at the top of the page and is always visible.
   * @private
   */
  elementDescription: null,
  setElementDescriptionName: function(name) {
    this.elementDescription = name;
  },
  
  /**
   * The name of the travelDates HTML element, used to show the start
   * and end date of the travel, if available.
   * Usually this is always visible at
   * the top of the page.
   * @private
   */
  elementDates: null,
  setElementDatesName: function(name) {
    this.elementDates = name;
  },
  
  elementPlace: null,
  setElementPlaceName: function(name) {
    this.elementPlace = name;
  },
  
  elementPlaceDate: null,
  setElementPlaceDateName: function(name) {
    this.elementPlaceDate = name;
  },

  elementPlaceDescription: null,
  setElementPlaceDescriptionName: function(name) {
    this.elementPlaceDescription = name;
  },
  
  /**
   * The name of the goto HTML element, the text input box used
   * to let the user enter a day sequence number.
   * @private
   */
  elementGoto: null,
  setElementGotoName: function(name) {
    this.elementGoto = name;
  },
  
  /**
   * The name of the totalPlaces HTML element, the label used
   * to let the user know how many places there are in this presentation
   * @private
   */
  elementTotalPlaces: null,
  setElementTotalPlacesName: function(name) {
    this.elementTotalPlaces = name;
  },
  

  /**
   * The name of the map HTML element, used to show satellite images
   * @private
   */
  elementMap: null,
  setElementMap: function(name) {
    this.elementMap = name;
  },
  
  /**
   * The name of the message HTML element, used to log messages
   * @private
   */
  elementMessage: null,
  setElementMessage: function(name) {
    this.elementMessage = name;
  },
  
  photoIcon: null,
  setPhotoIcon: function (url) {
    this.photoIcon = url;
  },
  
  photoShadow: null,
  setPhotoShadow: function (url) {
    this.photoShadow = url;
  },
  
  /**
   * The default width of the map HTML element
   * @private
   */
  defaultWidth: 400,
  setDefaultWidth: function(size) {
    this.defaultWidth = size;
  },
  getDefaultWidth: function() {
    return this.defaultWidth;
  },
  
  /**
   * The default height of the map HTML element
   * @private
   */
  defaultHeight: 300,
  setDefaultHeight: function(size) {
    this.defaultHeight = size;
  },
  getDefaultHeight: function() {
    return this.defaultHeight;
  },
  
  /**
   * The current width of the map, as set by the user
   */
  mapWidth: null,
  
  
  /**
   * The current height of the map, as set by the user
   */
  mapHeight: null, 
  
  start: function (travel) {
    
    this.travel = travel;
    if (!GBrowserIsCompatible()) {
      document.getElementById("message").innerHTML = 
	this.lang.errorNotCompatible;
      this.mapDisabled = true;
      this.visibleMap = false;
      return;
    }
    
    this.mapWidth = this.defaultWidth;
    this.mapHeight = this.defaultHeight;

    // Loads the travel description
    travel.load();
  },
  
  /**
   * Url of blank.html
   */
  blankPageURL: null,
  
  setBlankPageURL: function(url) {
    this.blankPageURL = url;
  },
  
  /** 
   * A reference to the popup window used to display photos
   */
  contentWin: null,

  /**
   * Controls the behaviour of the onClick handler.
   * Must be set before initScreen is run.
   */
  showCoordinatesValue: false,
  showCoordinates: function(tf) {
    this.showCoordinatesValue = tf;
  },

  /**
   * Coordinates the job of populating the GUI.
   */
  initScreen: function() {
    
    this.refreshMap();
    
    // Displays the main information of the travel: title, description
    // and dates.
    var element;

    var title = this.travel.getTravelTitle();
    if (title == null) { title = ""; }
    element = document.getElementById(this.elementTitle);
    if (element != null) { element.innerHTML = title; }

    var description = this.travel.getTravelDescription();
    if (description == null) { description = ""; }
    element = document.getElementById(this.elementDescription);
    if (element != null) { element.innerHTML = description; }

    var dates = this.travel.getTravelDates();
    if (dates == null) { dates = ""; }
    element = document.getElementById(this.elementDates);
    if (element != null) { element.innerHTML = dates; }
    
    // Creates the icon
    var icon = new GIcon();
    icon.image = this.photoIcon;
    icon.shadow = this.photoShadow;
    icon.iconSize = new GSize(30, 18);
    icon.shadowSize = new GSize(1, 1);
    icon.iconAnchor = new GPoint(1, 1);
    
    // markerForContent need a reference to this skin object
    var skin = this;
    
    // Creates a marker inside a function to have a different
    // listener for each marker
    function markerForContent(c) {
      var lat = c.getLat();
      var lng = c.getLng();
      var p = new GPoint(lng, lat);
      var marker = new GMarker(p, icon);
      var href = c.getHref();
      var descr = c.getCbody();
      GEvent.addListener(marker, "click", function() {
	skin.openPopup(href, descr);
      });
      return marker;
    }

    if (this.visibleMap) {
      // Iterates places and contents
      /* The code from the release 1.0.1
      var places = this.travel.getPlaces();
      for (i = 0; i < places.length; i++) { 
      */
      this.travel.firstPlace();
      for (var i = 0; i < this.travel.getNumberOfPlaces(); i++) {
	var pl = this.travel.getCurrentPlace();
	var content = this.travel.getContentArray();
	for (j = 0; j < content.length; j++) { 
	  var marker = markerForContent(content[j]);
	  this.map.addOverlay(marker);
	}
	this.travel.nextPlace();
      }
    }

    // Registers a handler to get the lat/lng position.
    // Useful to find out locations to add to the XML file.
    GEvent.addListener(skin.getMap(), "click",
		       function (overlay, latlng) {
			 var m = skin.getMap();
			 var z = parseInt(m.getZoom());
			 if (skin.showCoordinatesValue) {
			   alert("LAT = " + latlng.lat() +
			         ", LNG = " + latlng.lng() +
			         ", ZOOM = " + z);
			 } else {
			   if (z < 17) {
			     z++;
			     m.setCenter(latlng, z);
			   } else {
			     m.panTo(latlng);
			   }
			 }
		       });

  },
  
  openPopup: function(href, descr) {

    function openWin() {
      //      alert("open " + skin.blankPageURL);
      return window.open(skin.blankPageURL, "contentWindow",
			 "width=400,height=300,scrollbars=yes,resize=yes,"
			 + "resizable=yes,toolbar=no,location=no,"
			 + "directories=no,status=no,menubar=no");
    }

    // Creates a pop-up window named "contentWindow" or reuse the existing one

    if (skin.contentWin != null) {
      if (!skin.contentWin.closed) {
	skin.contentWin.close();
      }
    }
    window.winHref = href;
    window.winDescr = descr;
    var win = openWin();
  },
  

  /**
   * Moves the state object to the first place in the travel.
   * Refreshs the user interface.
   */
  firstPlace: function () {
    this.travel.firstPlace();
    this.refreshMap();
  },
  
  prevPlace: function () {
    this.travel.prevPlace();
    this.refreshMap();
  },
  
  nextPlace: function () {
    this.travel.nextPlace();
    this.refreshMap();
  },
  
  lastPlace: function () {
    this.travel.lastPlace();
    this.refreshMap();
  },
  
  goToPlaceTop: function () {
    var gotoTop = document.getElementById(this.elementGoto + "Top");

    var p = parseInt(gotoTop.value);
    // check if input is not a number
    if (isNaN(p)) {
      gotoTop.value = this.travel.getCurrentPlaceIndex() + 1;
      return;
    }
    // range checks
    if (p <= 0) {
      gotoTop.value = this.travel.getCurrentPlaceIndex() + 1;
      return;
    }
    if (p > this.travel.getNumberOfPlaces()) {
      gotoTop.value = this.travel.getCurrentPlaceIndex() + 1;
      return;
    }

    this.travel.goToPlace(p);
    var gotoBottom = document.getElementById(this.elementGoto + "Bottom");
    gotoBottom.value = p;
    this.refreshMap();
  },
  
  goToPlaceBottom: function () {
    var gotoBottom = document.getElementById(this.elementGoto + "Bottom");

    var p = parseInt(gotoBottom.value);
    // check if input is not a number
    if (isNaN(p)) {
      gotoBottom.value = this.travel.getCurrentPlaceIndex() + 1;
      return;
    }
    // range checks
    if (p <= 0) {
      gotoBottom.value = this.travel.getCurrentPlaceIndex() + 1;
      return;
    }
    if (p > this.travel.getNumberOfPlaces()) {
      gotoBottom.value = this.travel.getCurrentPlaceIndex() + 1;
      return;
    }

    this.travel.goToPlace(p);
    var gotoTop = document.getElementById(this.elementGoto + "Top");
    gotoTop.value = p;
    this.refreshMap();
  },
  
  zoomPlus: function () {
    // parseInt is needed to workaround a possible bug
    // that sometimes returns a string
    var z = parseInt(this.map.getZoom());
    this.map.setZoom(z + 1);
  },
  
  zoomMinus: function () {
    var z = parseInt(this.map.getZoom());
    this.map.setZoom(z - 1);
  }
  
};


/**
 * class PhotoIcon, from the DhtmlObject class of
 * http://jibbering.com/faq/faq_notes/closures.html
 */

function PhotoIcon(element, href, descr){
  if (element){
    // 'this' is the the PhotoIcon, 'doOnClick' is a method of PhotoIcon
    element.onclick = enablePhotoIcon(this, "doOnClick", href, descr);
  }
}

PhotoIcon.prototype = {
  doOnClick: function(event, element, href, descr){  
    skin.openPopup(href, descr);
  }
  
};


// ------------ Functions that need to be in the global context start here

function enablePhotoIcon(obj, methodName, href, descr){
    return (function(e){
        e = e || window.event;
	// This return statement binds the PhotoIcon with the event
	// on the HTML element
	// 'obj' is a PhotoIcon
	// 'this' is the HTML element the event 'e' has been triggered about
        return obj[methodName](e, this, href, descr);
    });
}

// This function is called by the HTML file to start up everything
function startSkin(travel, skin, language) {

  var lang = new JourneySat.ResourceBundle("skin/Maya/" + language + ".xml",
					   i18n);
  skin.lang = lang;



  // Binds the relevant HTML elements to the code of the skin
  skin.setElementTitleName("title");
  skin.setElementDescriptionName("description");
  skin.setElementDatesName("dates");
  skin.setElementPlaceName("place");
  skin.setElementPlaceDateName("placeDate");
  skin.setElementPlaceDescriptionName("placeDescription");
  skin.setElementGotoName("goto");
  skin.setElementTotalPlacesName("totalPlacesLabel");
  skin.setElementMap("map");
  skin.setElementMessage("message");
  
  var btnToggleId = "btnToggle";
  skin.setElementToggle(btnToggleId);
  var btnToggle = document.getElementById(btnToggleId);
  btnToggle.src = skin.showMapURL;
  
  skin.setBlankPageURL("skin/Maya/blank.html");
  
  skin.setPhotoIcon("skin/Maya/icons/photo-icon.gif");
  skin.setPhotoShadow("skin/Maya/icons/no-shadow.gif");
  
  // Sets the function that will be called when 
  // Travel finishes loading the XML file 
  travel.onload = function () {
    
    // Updates the screen
    skin.initScreen();
    skin.travel.firstPlace();
    
    // Initializes the goto input box
    document.getElementById(skin.elementGoto + "Top").value = 1;
    document.getElementById(skin.elementGoto + "Bottom").value = 1;
    var totalPlaces = skin.travel.getNumberOfPlaces();
    document.getElementById(skin.elementTotalPlaces + "Top").innerHTML =
                                                  totalPlaces;
    document.getElementById(skin.elementTotalPlaces + "Bottom").innerHTML =
                                                  totalPlaces;

    // After the map has been created it's possible to
    // register a listener for events on it
    
  };
  
  // Register event handlers
  
  document.getElementById("controlBeginningTop").onclick = 
    function() { skin.firstPlace(); };
  
  document.getElementById("controlBackTop").onclick = 
    function() { skin.prevPlace(); };
  
  document.getElementById("controlForwardTop").onclick = 
    function() { skin.nextPlace(); };
  
  document.getElementById("controlEndTop").onclick = 
    function() { skin.lastPlace(); };
  
  document.getElementById("controlGoToPlaceTop").onclick = 
    function() { skin.goToPlaceTop(); };
  
  document.getElementById("controlZoomMinusTop").onclick = 
    function() { skin.zoomMinus(); };
  
  document.getElementById("controlZoomPlusTop").onclick = 
    function() { skin.zoomPlus(); };
  
  document.forms["controlsTop"].action = "javascript:skin.goToPlaceTop();";
  
  document.getElementById("controlBeginningBottom").onclick = 
    function() { skin.firstPlace(); };
  
  document.getElementById("controlBackBottom").onclick = 
    function() { skin.prevPlace(); };
  
  document.getElementById("controlForwardBottom").onclick = 
    function() { skin.nextPlace(); };
  
  document.getElementById("controlEndBottom").onclick = 
    function() { skin.lastPlace(); };
  
  document.getElementById("controlGoToPlaceBottom").onclick = 
    function() { skin.goToPlaceBottom(); };
  
  document.getElementById("controlZoomMinusBottom").onclick = 
    function() { skin.zoomMinus(); };
  
  document.getElementById("controlZoomPlusBottom").onclick = 
    function() { skin.zoomPlus(); };
  
  document.forms["controlsBottom"].action =
    "javascript:skin.goToPlaceBottom();";
  
  document.getElementById("btn400x300").onclick =
    function() { skin.resizeMap(400, 300); };
  
  document.getElementById("btn530x400").onclick =
    function() { skin.resizeMap(530, 400); };
  
  document.getElementById("btn660x500").onclick =
    function() { skin.resizeMap(660, 500); };
  
  document.getElementById("btn800x600").onclick =
    function() { skin.resizeMap(800, 600); };
  
  document.getElementById(btnToggleId).onclick =
    function() { skin.toggleMap(); };
  
  
  // Starts the application
  skin.start(travel);
  

  function i18n() {
    document.getElementById("zoomTop").innerHTML = skin.lang.labelZoom;
    document.getElementById("zoomBottom").innerHTML = skin.lang.labelZoom;
    document.getElementById("labelPoweredBy").innerHTML =
      skin.lang.labelPoweredBy;
    document.getElementById("ofLabelTop").innerHTML =
      skin.lang.totalPlacesLabelTop;
    document.getElementById("ofLabelBottom").innerHTML =
      skin.lang.totalPlacesLabelBottom;

    var btn = document.getElementById(skin.elementToggle);
    btn.innerHTML = skin.lang.btnHide;
    

  }	

}
