
/**
 * Google Map для www.divingfinder.com.
 *
 * Js кода "продвинутой" карты.
 *
 * @package google_map
 * @author Denis V Zaharov (CalmNad@gmail.com)
 */

// Инициализация переменных.
var map = null; // Объект карты.
var ltm = null; // Хранит id таймер используемого во избежание дублирования загрузки данных.
var lid = null; // Хранит id последнего ajax запроса данных по объектам.
var ol = document.getElementById('map_ol'); // Ссылка на DOM объект, содержащий список объектов в виде div блоков.
var olw = 110; // Ширина списка объектов.

var vFilter = {0: true, 51: true, 52: true, 53: true, 57: true, 81: true, 89: true, 164: true}; //Фильтр отображения объектов.
var icons = {}; // Иконки разных объектов.

// 0 - начальное значение
// 1 - ни один объект не загружен (выводим сообщение о необходимости zoom, move, ...)
// 2 - отображается индикотор загрузки
// 3 - выполнялся запрос на загрузку объектов, но нет ни одного объекта (выводим сообщение "сорри, ничего нет...").
// 4 - выполнялся запрос на загрузку объектов, объектов больше максимально допустимого количества.
var STATE_NONE = 0;
var STATE_INIT = 1;
var STATE_LOAD = 2;
var STATE_MAX  = 3;
var STATE_ZERO = 4;
var STATE_NORM = 5;

var state = STATE_NONE;

google.load("maps", "2.x");

/**
* Выполняет инициализацию карты.
*
* Выполняет действия:
*  - Создает карту.
*  - Создает объект иконки для маркера.
*  - Привязывает обработчики событий на изменение масштаба карты и на перемещение карты.
*
* @return void
*/
function init() {
  // Инициализируем хэш используемых иконок.
  icons[0]   = {'n': '/_/map/icon_others.png',  'a': '/_/map/icon_others_a.png',  'f': '/_/map/icon_others_f.png',  'd': 'Uncategorized'};
  icons[51]  = {'n': '/_/map/icon_dcenter.png', 'a': '/_/map/icon_dcenter_a.png', 'f': '/_/map/icon_dcenter_f.png', 'd': 'Dive Centre'};
  icons[52]  = {'n': '/_/map/icon_dclub.png',   'a': '/_/map/icon_dclub_a.png',   'f': '/_/map/iicon_dclub_f.png',  'd': 'Dive Club'};
  icons[53]  = {'n': '/_/map/icon_dshop.png',   'a': '/_/map/icon_dshop_a.png',   'f': '/_/map/icon_dshop_f.png',   'd': 'Dive Shops'};
  icons[57]  = {'n': '/_/map/icon_lboat.png',   'a': '/_/map/icon_lboat_a.png',   'f': '/_/map/icon_lboat_f.png',   'd': 'Liveaboard Diving'};
  icons[81]  = {'n': '/_/map/icon_cour.png',    'a': '/_/map/icon_cour_a.png',    'f': '/_/map/icon_cour_f.png',    'd': 'Diving Courses'};
  icons[89]  = {'n': '/_/map/icon_holi.png',    'a': '/_/map/icon_holi_a.png',    'f': '/_/map/icon_holi_f.png',    'd': 'Diving Holiday'};
  icons[164] = {'n': '/_/map/icon_cboat.png',   'a': '/_/map/icon_cboat_a.png',   'f': '/_/map/icon_cboat_f.png',   'd': 'Diving Charter Boat'};

  // Создаем карту.
  map = new google.maps.Map2(document.getElementById("map_canvas"));
  map.setMapType(G_HYBRID_MAP);
  map.setCenter(new google.maps.LatLng(mCLat, mCLng), mZoom);
  map.addControl(new GLargeMapControl());
  map.addControl(new GMapTypeControl());
  map.addControl(new GOverviewMapControl());
  
  // Обработчик события "изменение масштаба карты".
  GEvent.addListener(map, "zoomend", function(lvOld, lvNew) {
    getObjects(sFilter ? sFilter : "/libs/map/map_advanced_ajax.php?r=" + Math.random() + "&bounds=" + map.getBounds() + '&center=' + map.getCenter());
  });

  // Обработчик события "перемещение карты".
  GEvent.addListener(map, "moveend", function() {
    getObjects(sFilter ? sFilter : "/libs/map/map_advanced_ajax.php?r=" + Math.random() + "&bounds=" + map.getBounds() + '&center=' + map.getCenter());
  });

  if (mLoad) {
    getObjects(sFilter ? sFilter : "/libs/map/map_advanced_ajax.php?r=" + Math.random() + "&bounds=" + map.getBounds() + '&center=' + map.getCenter());
  }
  else {
    SetState(STATE_INIT);
  }
}

google.setOnLoadCallback(init);

/**
* Устанавливает таймер для получение данных по объектам.
*
* Устанавливает таймер для вызова (AJAX) функции получения данных по объектам.
* Таймер используется для избежания двойных вызово (например, при увеличении карты происходят два события:
* изменение масштаба и движение карты, т.к. на каждое привязан вызов обновления данных - получим два вызова).
* При вызове функция устанавливает таймер с задержкой (в милисекундах) на вызов обновления данных.
* Если до срабатывания таймера - происходит повторный запрос - предыдущий вызов отменяется и таймер устанавливается по новой.
*
* @param string url URL AJAX запроса.
*
* @return void
*/
function getObjects(url) {
  if (ltm) {
    clearTimeout(ltm);
  }
  lid  = Math.random();
  ltm = setTimeout(function() {_getObjects(url)}, 400);
}

/**
* Запрашивает (AJAX) обновление данных. По получении данных - обновляет состояние карты.
*
* Действия:
*
* @param string url URL AJAX запроса.
*
* @return void
*/
function _getObjects(url) {
  SetState(STATE_LOAD);
  ltm = false;
  url+= '&lid=' + lid;
  GDownloadUrl(url, function(data) {
    var items = {};
    var xml = GXml.parse(data);
    
    // Проверяем, получены ли данные по последнему запросу.
    inLid = xml.documentElement.getElementsByTagName('lid');
    if (undefined == inLid.length || 1 != inLid.length || lid != inLid[0].firstChild.nodeValue) {
      return;
    }
    
    // Проверяем, произошло ли переполнение максимально допустимого количества объектов на карте.
    over = xml.documentElement.getElementsByTagName('over');
    if (undefined == over.length || 1 != over.length || 101 > over[0].firstChild.nodeValue) {
      SetState(STATE_NORM);
    }
    else {
      SetState(STATE_MAX);
    }

    // Иногда парсер проглючивает, поэтому небольшая "задержка" перед обращением к данным.
    for (i = 0; i < 10; i++) { i++; i--; }
    
    var ms = xml.documentElement.getElementsByTagName('marker');
    var info = {};

    for (i = 0; i < ms.length; i++) {
      info = {
      'id':      ms[i].childNodes[0].firstChild ? ms[i].childNodes[0].firstChild.nodeValue : '',
      'lng':     ms[i].childNodes[1].firstChild ? ms[i].childNodes[1].firstChild.nodeValue : 0,
      'lat':     ms[i].childNodes[2].firstChild ? ms[i].childNodes[2].firstChild.nodeValue : 0,
      'type':    ms[i].childNodes[3].firstChild ? ms[i].childNodes[3].firstChild.nodeValue : '',
      'addr':    ms[i].childNodes[4].firstChild ? ms[i].childNodes[4].firstChild.nodeValue : '',
      'city':    ms[i].childNodes[5].firstChild ? ms[i].childNodes[5].firstChild.nodeValue : '',
      'region':  ms[i].childNodes[6].firstChild ? ms[i].childNodes[6].firstChild.nodeValue : '',
      'zip':     ms[i].childNodes[7].firstChild ? ms[i].childNodes[7].firstChild.nodeValue : '',
      'country': ms[i].childNodes[8].firstChild ? ms[i].childNodes[8].firstChild.nodeValue : '',
      'img':     ms[i].childNodes[9].firstChild ? ms[i].childNodes[9].firstChild.nodeValue : '',
      'url':     ms[i].childNodes[10].firstChild ? ms[i].childNodes[10].firstChild.nodeValue : '',
      'company': ms[i].childNodes[11].firstChild ? ms[i].childNodes[11].firstChild.nodeValue : ''
      };
      items[info['id']] = true;
      if (ms[i].childNodes[12].firstChild) {
        ids = ms[i].childNodes[12];
        info['specs'] = [];
        for (j = 0; j < ids.childNodes.length; j++) {
          if (ids.childNodes[j].firstChild) {
            info['specs'][info['specs'].length] = ids.childNodes[j].firstChild.nodeValue; //specs[
          }
          else {
            //alert(j); //TODO - NO DATA???
          }
        }
      }
      objs.CreateObject(info);
    }
    objs.DropObjects(items);
    if (0 == objs.len) {
      SetState(STATE_ZERO);
    }
  });
}

// Класс маркера для списка объектов.
function DLMarker(info, prn) {
  // Создаем DOM объект маркера.
  this.o = document.createElement('DIV');
  this.o.style.display = 'none';
  this.o.className = 'map_oli_norm';
  this.o.id = 'map_ol_item_' + info['id'];
  this.o.innerHTML = '<img src="' + prn.nicon + '" style="float: left;" /><strong>' + info['company'] + '</strong><br />' + info['type'];

  // Вставляем маркер в список объектов.
  ol.insertBefore(this.o, null);
  
  // Привязываем обработчики событий.
  if (document.all) {
    this.o.attachEvent("onmouseover", function() {document.getElementById('map_ol_item_' + info['id']).dObject.setAicon(true)});
    this.o.attachEvent("onmouseout", function() {document.getElementById('map_ol_item_' + info['id']).dObject.setNicon(true)});
    this.o.attachEvent("onclick", function() {GEvent.trigger(document.getElementById('map_ol_item_' + info['id']).dObject.gMarker, "click")});
  }
  else {
    this.o.addEventListener("mouseover", function() {document.getElementById('map_ol_item_' + info['id']).dObject.setAicon(true)}, true);
    this.o.addEventListener("mouseout", function() {document.getElementById('map_ol_item_' + info['id']).dObject.setNicon(true)}, true);
    this.o.addEventListener("click", function() {GEvent.trigger(document.getElementById('map_ol_item_' + info['id']).dObject.gMarker, "click")}, true);
  }
  
  // Функция включения маркера.
  this.show = function () {
    if ('none' == this.o.style.display) {
      this.o.style.display = 'block';
      olw+= 160;
      ol.style.width = olw + 'px';
    }
  }
  
  // Фукнция выключения маркера.
  this.hide = function () {
    if ('block' == this.o.style.display) {
      this.o.style.display = 'none';
      olw-= 160;
      ol.style.width = olw + 'px';
    }
  }
}

// Класс дайвинг-объекта.
function DObject(info) {
  this.info = info; // Ссылка на хэш информации по объекту.
  
  // Определяем привязку объекта к фильтру видимости.
  if (undefined == vFilter[info.specs[0]]) {
    this.visible = vFilter[0];
    this.mspec = 0;
    this.nicon = icons[0]['n'];
    this.aicon = icons[0]['a'];
  }
  else {
    this.visible = vFilter[info.specs[0]];
    this.mspec = info.specs[0];
    this.nicon = icons[info.specs[0]]['n'];
    this.aicon = icons[info.specs[0]]['a'];
  }
  
  // Создаем объект маркера карты.
  // Определяем иконку маркера.
  var i = new GIcon(G_DEFAULT_ICON);
  var opts = {};
  if (icons[info['specs'][0]]) {
    i.image = this.nicon;
    i.iconSize = new GSize(22, 20);
    opts = { icon: i };
  }
  else {
    i.image = icons[0]['n'];
    i.iconSize = new GSize(22, 20);
    opts = { icon: i };
  }
  var point = new GLatLng(info['lat'], info['lng']);
  this.gMarker = new GMarker(point, opts);
  this.gMarker.dObject = this;
  map.addOverlay(this.gMarker);
  if (this.visible) {
    this.gMarker.show();
  }
  else {
    this.gMarker.hide();
  }
  var marker = this.gMarker;
  GEvent.addListener(marker, "mouseover", function() {
    marker.dObject.setAicon();
  });
  GEvent.addListener(marker, "mouseout", function() {
    marker.dObject.setNicon();
  });
  
  // Создаем и привязываем к маркеру infowindow.
  var tabs = [];
  var html = '';
  var odd = 1;
  // Первая вкладка.
  html = "<div class='map_iw_div'><table cellspacing='0' cellpadding='0' border='0'>";
  if (info['img']) {
    html+= "<tr><td><a href='http://www.divingfinder.com/Scuba_Diving/" + info['url'] + "'><img src='/Scuba_Diving/ctfiles/" + info['img'] + "' style='border: solid 0px;' align='absmiddle' hspace='10' vspace='10' width='100' /></a></td><td class='map_iw_company'>" + info['company'] + " <span class='map_iw_main_spec'>(" + info['type'] + ")</span></td></tr>";
  }
  else {
    html+= "<tr><td colspan='2' class='map_iw_company'>" + info['company'] + " <span class='map_iw_main_spec'>(" + info['type'] + ")</span></td></tr>";
  }
  html+= "<tr><td colspan='2' class='map_iw_title'>Services:</td></tr>";
  for (i = 0; i < info['specs'].length; i++) {
    if (odd) {
      html+= "<tr><td class='map_iw_spec'>" + specs[info['specs'][i]];
      odd^=1;
    }
    else {
      html+= "</td><td class='map_iw_spec'>" + specs[info['specs'][i]] + "</td></tr>";
      odd^=1;
    }
  }
  if (!odd) {
    html+= "</td><td class='map_iw_spec'></td></tr>";
  }
  html+= "<tr><td colspan='2' class='map_iw_details'><a href='/Scuba_Diving/" + info['url'] + "'>details...</a></td></tr>";
  html+= "</table></div>";
  tabs[0] = new GInfoWindowTab('Info', html);
  // Вторая вкладка.
  html = "<iframe style='height: 180px; width: 300px;' frameborder='no' src='http://gmapplet.accuweather.com/widget/mapplet/weather-data.asp?lat=" + info['lat'] + "&lon=" + info['lng'] + "'></iframe>";
  tabs[1] = new GInfoWindowTab('Weather', html);
  // Третья вкладка.
  html = '<form action="http://maps.google.com/maps" target="_blank" method="get" style="text-align: center;"><p><b>Leaving from:</b> <input name="saddr" size="9" type="text"><input name="daddr" value="' + info['zip'] + '" type="hidden"></p><p><b>Going to:</b> <i>' + info['zip'] + '</i></p><p><input value="Popup Driving Directions" type="submit"></p></form>';//OH 43076
  tabs[2] = new GInfoWindowTab('Directions', html);
  this.gMarker.bindInfoWindowTabs(tabs);

  // Создаем маркер списка объектов.
  this.lMarker = new DLMarker(info, this);
  // Устанавливаем видимость маркера в списке объектов.
  this.lMarker.o.dObject = this;
  if (this.visible) {
    this.lMarker.show();
  }
  else {
    this.lMarker.hide();
  }

  this.setNicon = function (l) {
    this.gMarker.setImage(this.nicon);
    this.lMarker.o.className = 'map_oli_norm';
  }

  this.setAicon = function (l) {
    this.gMarker.setImage(this.aicon);
    this.lMarker.o.className = 'map_oli_act';
    if (!l) {
      if (document.all) {
        this.lMarker.o.parentNode.parentNode.scrollLeft = this.lMarker.o.offsetLeft;
      }
      else {
        this.lMarker.o.parentNode.parentNode.scrollLeft = this.lMarker.o.offsetLeft - this.lMarker.o.parentNode.parentNode.offsetLeft - Math.round(this.lMarker.o.parentNode.parentNode.offsetWidth / 3);
      }
    }
  }

  // Функция удаления объекта.
  this.unset = function Unset() {
    // Удаляем маркер карты.
    this.gMarker.dObject = null;
    map.removeOverlay(this.gMarker);
    delete this.gMarker;
    
    // Удаляем маркер списка объектов.
    this.lMarker.o.dObject = null;
    ol.removeChild(this.lMarker.o);
    delete this.lMarker.o;
    delete this.lMarker;
  }
  
  this.show = function() {
    this.lMarker.show();
    this.gMarker.show();
  }

  this.hide = function() {
    this.lMarker.hide();
    this.gMarker.hide();
  }
}

// Класс для работы со списком объектов.
function DObjects() {
  this.len = 0; // Количество зарегистрированных объектов.
  this.vLen = 0; // Количество видимых зарегистрированных объектов.
  this.objects = {};
  
  // Метод создания нового объекта.
  this.CreateObject = function(info) {
    if (undefined == this.objects[info['id']]) {
      this.objects[info['id']] = new DObject(info);
      this.len++;
    }
    else {
      //alert('skip');
    }
  }
  
  this.SetVFilter = function(flid, o) {
    vFilter[flid] = o.checked;
    for (var i in this.objects) {
      if (flid == this.objects[i].mspec) {
        if (o.checked) {
          this.objects[i].show();
        }
        else {
          this.objects[i].hide();
        }
      }
    }
  }
  
  this.DropObjects = function(items) {
    for (var i in this.objects) {
      if (undefined == items[i]) {
        this.objects[i].hide();
        this.objects[i].unset();
        delete this.objects[i];
        this.len--;
      }
    }
  }
}

// Показать/скрыть сообщение.
function OlMsg(on, id) {
  var o = null;
  if (on) {
    o = document.createElement('DIV');
    o.id = 'map_msg';
    switch (id) {
      case STATE_LOAD:
      o.innerHTML = "<p><img id='map_loader_main' src='/_/map/loader_main.gif' height='22' width='126'></p>";
      olw+= 200;
      o.className = 'map_msg_load';
      break;
      
      case STATE_MAX:
      o.innerHTML = '<p><img src="/_/map/bulb.png" height="32" width="32" style="float: left;">Too many objects. Only 100 of them (closest to the centre of the map) are shown.</p>';
      o.style.width = '200px';
      olw+= 200;
      o.className = 'map_msg_std';
      break;

      case STATE_INIT:
      o.innerHTML = '<p><img src="/_/map/bulb.png" height="32" width="32" style="float: left;">You need to zoom in further to see results.</p>';
      o.style.width = '200px';
      olw+= 200;
      o.className = 'map_msg_std';
      break;
      
      case STATE_ZERO:
      o.innerHTML = '<p><img src="/_/map/bulb.png" height="32" width="32" style="float: left;">Your search -  did not match any documents</p>';
      o.style.width = '200px';
      olw+= 200;
      o.className = 'map_msg_std';
      break;
      
      default:
      return;
    }
    ol.insertBefore(o, ol.firstChild ? ol.firstChild : null);
    ol.style.width = olw + 'px';
  }
  else {
    o = document.getElementById('map_msg');
    if (undefined != o) {
      olw-= 200; //o.offsetWidth;
      ol.style.width = olw + 'px';
      ol.removeChild(o);
    }
  }
}

// Установить фильтр для поиска объектов.
function SetSFilter() {
  var k = document.getElementById('map_sf_keyw');
  var m = document.getElementById('map_sf_miles');
  sFilter = '/libs/map/map_advanced_ajax.php?r=' + Math.random() + '&center=' + map.getCenter();
  if (k.value) {
    sFilter+= '&map_sf_keyw=' + k.value + '&map_sf_miles=' + document.getElementById('map_sf_miles').value;
    SetClear(true);
  }
  else {
    if (50000 != m.value) {
      sFilter+= '&map_sf_miles=' + m.value;
      SetClear(true);
    }
    else {
      sFilter+= '&bounds=' + map.getBounds();
      SetClear(false);
    }
  }
  getObjects(sFilter);
}

// Очистить фильтр поиска.
function ClearSf() {
  sFilter = false;
  document.getElementById('map_sf_keyw').value = '';
  document.getElementById('map_sf_miles').selectedIndex = 0;
  SetClear();
  getObjects("/libs/map/map_advanced_ajax.php?r=" + Math.random() + "&bounds=" + map.getBounds() + '&center=' + map.getCenter());
}

// Запретить/разрешить работу кнопки "Очистить Все".
function SetClear() {
  var o = document.getElementById('map_sf_clear');
  if (undefined != o) {
    if (sFilter) {
      o.disabled = false;
      o.className = 'map_sf_clear_on';
    }
    else {
      o.disabled = true;
      o.className = 'map_sf_clear_off';
    }
  }
}
SetClear();

// Отобразить информацию о статусе карты.
function SetState(s) {
  if (state != STATE_NORM) OlMsg(false);
  OlMsg(true, s);
  state = s;
}

// Пересчитать ширину списка объектов.
function SetOlWidth() {
  document.getElementById('map_ol_wrapper').style.width = (document.getElementById('map_canvas').offsetWidth - 5) + 'px';
  setTimeout(SetOlWidth, 500);
}

objs = new DObjects();

// Если браузер IE - надо пересчитывать ширину списка объектов при изменении размеров окна.
if (document.all) {
  SetOlWidth();
  bodyResize[bodyResize.length] = SetOlWidth;
}

