// PotoSIG -- SIG extensions for Rails. Written by Javier Goizueta
// IvyGIS --- Mapserver and PostGIS on Rails.  Written by Robert S. Thau.
// Copyright 2006, Japan Spatial Information Technology, Inc.
//
// Distributed without warranty under the terms of the GNU General Public
// License, version 2.  

var PotoNotes = Class.create();
PotoNotes.prototype = {

  initialize: function(map, options) 
  {
    this.map = map;
    this.layer = map.create_overlay();
    this.message_sources = [];
    this.select_message(null, null);
    this.bookmark_div_id = options.bookmark_div_id;

    $(this.layer).style.zIndex = 10;  // lest IE put us behind the tiles!

    map.landmarker = this;
    map.dom_associate (this.bookmark_div_id);
    map.add_listener (this);
    PageEvents.add_listener (this);  // reload bookmarks tag on login/logout
  },

  add_compose_widget: function (elt_id) {
    new Draggable (elt_id,
                   { revert: true,
                       reverteffect: function(elt, top_offset, left_offset){
                       new Effect.MoveBy (elt, -top_offset, -left_offset,
                                          {duration: 0})
                         }})},

  add_message_source: function (new_source) {
    this.message_sources.push (new_source);
  },

  // Event handling...

  on_new_map: function () { this.reload_bookmarks(); },

  on_extents_changed: function () { this.load_messages(); },

  on_drop: function (elt) { 
    if (elt.id.substr(0,4) == 'new_')
      this.compose_from_drop (elt); 
  },

  on_login: function () { this.reload_bookmarks(); },

  on_logout: function () { this.reload_bookmarks(); },

  // Message source stuff

  find_source: function (name_or_source) {
    for (var i = 0; i < this.message_sources.length; ++i)
      if (this.message_sources[i].short_name == name_or_source)
        return this.message_sources[i];
    return name_or_source;
  },

  message_create_url: function (message_source, posn)
  {
    return message_source.base + "/new?lat=" + 
      posn.latitude + "&lon=" + posn.longitude + "&map_tag=" + 
      this.map.current_map_tag;
  },

  message_list_url: function (message_source, distance, longitude, latitude) 
  {
    var url = message_source.base + "/list_json?" + this.map.vp_form_args();

    if (this.selected_message.source == message_source)
      url += "&selected_message=" + this.selected_message.id;

    if (message_source.compute_extra_args)
      url += message_source.compute_extra_args();

    return url;
  },

  message_summary_url: function (message_source, id)
  {
    return message_source.base + "/list_json?id=" + id;
  },

  message_detail_url: function (message_source, id)
  {
    return message_source.base + "/show?id=" + id;
  },

  // Display of a "selected
  // message" which might not otherwise come up --- for example, when
  // the user has selected a landmark whose tags aren't in their current
  // set...

  select_message: function (message_source, id) 
  {
    this.selected_message =
      { source: this.find_source (message_source), id: id, shown_yet: false }
  },

  // Returns "true" if message was selected...

  deselect_message: function (message_source, id) 
  {
    if (this.selected_message.source == this.find_source (message_source)
        && this.selected_message.id == id) 
    {
      this.select_message (null, null);
      return true;
    }

    return false;
  },

  // DOM Ids for message display...

  message_detail_id: function (message_source, message_id) {
    return message_id + "." + message_source.short_name + "." + "msg";
  },

  message_bullet_id: function (message_source, message_id) {
    return message_id + "." + message_source.short_name + "." + "dot";
  },

  message_tooltip_id: function (message_source, message_id) {
    return message_id + "." + message_source.short_name + "." + "tip";
  },

  message_source_name_from_elt_id: function (elt_id) {
    var dot1 = elt_id.indexOf (".") + 1;
    var dot2 = elt_id.indexOf (".", dot1);
    return elt_id.substr (dot1, dot2 - dot1);
  },

  message_id_from_elt_id: function (elt_id) {
    return parseInt (elt_id);
  },

  // Wiping out messages, in case the user switched maps and 
  // we want to be quickly rid of the landmarks on the old map...
  
  clear_all_messages: function() {
    for (var i = 0; i < this.message_sources.length; ++i)
      this.clear_messages (this.message_sources[i], new Array());
  },

  // Actually loading messages...
  
  load_messages: function ()
  {
    for (var i = 0; i < this.message_sources.length; ++i)
      if (this.message_sources[i].active)
        this.load_messages_from_source (this.message_sources[i]);
      else
        this.clear_messages (this.message_sources[i], new Array());
  },

  load_messages_from_source: function (message_source) 
  {
    // Compute viewport dimensions, and distance from center
    // we have to display.

    var url = this.message_list_url (message_source);

    var notepad = this;

    new Ajax.Request
    (url, { method: 'get', onSuccess: function(req) {
          
         var display_cache = new Array();

         notepad.clear_messages (message_source, display_cache);

         var to_show = null;
         var messages = eval (req.responseText);  // JSON

         for (var i = 0; i < messages.length; ++i) {
           var message = messages[i];

           if (!notepad.selected_message.shown_yet
               && messages[i].id == notepad.selected_message.id
               && message_source == notepad.selected_message.source)
           {
             notepad.selected_message.shown_yet = true;
             to_show = messages[i];
           }

           notepad.note_message (message_source, message, 
                                 display_cache[message.id]);
         }      

         if (to_show != null) 
           notepad.show_message (message_source, to_show);
       }});
  },

  note_message: function (message_source, message, cached_display)
  {
    var posn = this.map.world_to_screen (message);

    var bullet_id = this.message_bullet_id (message_source, message.id);
    var old_bullet = $(bullet_id);

    if (old_bullet == null || old_bullet.parentNode == null) {

      var notepad = this;

      var bullet_div =
        this.create_map_bullet(posn.x, posn.y,
                               ["marker", "bullet " + message.message_type],
                               function () { 
                                 notepad.show_message(message_source,message)},
                               function (elt, status) { 
                                  notepad.highlight_message(elt,
                                              message.tooltip,
                                              message.message_type,
                                              status)});

      bullet_div.id = this.message_bullet_id (message_source, message.id);
    }

    // If we already had a full message <div> up, must redisplay that
    // as well...

    if (cached_display != null) {
      this.show_message (message_source, message, cached_display);
    }
  },

  highlight_message: function (elt, tooltip, message_type, highlight_on)
  {
    if (highlight_on)
      PotoTooltips.show(elt, null, message_type, tooltip, highlight_on);
    else
      PotoTooltips.hide(elt);
  },

  show_message: function (message_source, message, cached_div)
  {
    var posn = this.map.world_to_screen (message);
    var bullet_id = this.message_bullet_id (message_source, message.id)
    var bullet = $(bullet_id);
    var offsets = {x: 10, y: 0};

    // Check to see if we already have a display elt, either up
    // or supplied as cached.  If so, just reposition it.  
    // Typical "already up" case: activating a bookmark to
    // something already opened.

    var elt_id = this.message_detail_id (message_source, message.id);
    var old_elt = cached_div || $(elt_id);

    if (old_elt != null) {
      PotoTooltips.disable_over(bullet);
      this.map.register_pos_object(old_elt, bullet, message, offsets);
      return;
    }

    // Create <div> for complete message display

    var notepad = this;
    var close_handler = function(elt) {
      notepad.deselect_message(message_source, message.id);
      PotoTooltips.enable_over($(bullet_id));
      return true;
    };

    var widgets = [{css_class: 'bookmark_widget', char: '>', title:"bookmark",
                    handler: function() {
                      notepad.handle_note_bookmark(message_source, message)}
                    }];

    var elt = map.create_pos_div (bullet, 
                                  this.message_detail_url(message_source,
                                                          message.id),
                                  message, offsets,
                                  { widget_specs: widgets,
                                    close_handler: close_handler });

    elt.id = elt_id;
    elt.className += " " + message.msg_type;
  },

  handle_note_bookmark: function (message_source, message)
  {
    this.bookmark_message (0, message_source.short_name, 
                           message.id, message.tooltip);
    return false;
  },

  clear_messages: function (message_source, cache) 
  {
    var src_name = message_source.short_name;
    var notepad = this;

    $(this.layer).select(".marker").each
    (function (elt) { 
      if (src_name == notepad.message_source_name_from_elt_id (elt.id)) {
        $(notepad.layer).removeChild (elt); 
        var msg_id = notepad.message_id_from_elt_id (elt.id);
        var detail_id = notepad.message_detail_id (message_source, msg_id);
        var detail_elt = $(detail_id);

        if (detail_elt != null) {
          this.map.delete_pos_object(detail_elt);
          cache [parseInt (elt.id)] = detail_elt;
        }
      }
    });
  },

  // Composing messages...

  compose_from_drop: function (elt) 
  {
    // There is a positioned "nmarker" within the dragged item which
    // indicates exactly where to put the item.  Figure out where it is.

    var marker = document.getElementsByClassName ("nmarker", elt)[0];
    var marker_pos_raw = Position.page (marker);
    var document_x = marker_pos_raw[0] + parseInt(marker.offsetWidth) / 2;
    var document_y = marker_pos_raw[1] + parseInt(marker.offsetHeight) / 2;

    var target_pos_raw = Position.page ($(this.layer));

    var x = document_x - target_pos_raw[0] - 1;
    var y = document_y - target_pos_raw[1] - 1;

    // elt.id will be new_[source short name]:

    var source_name = elt.id.substr(4);
    var message_source = this.find_source (source_name);

    if (this.composing_frame != null && 
        this.composing_frame.parentNode != null) 
    {
      alert (get_alert ("Already composing a message"));
      return;
    }

    var notepad = this;

    this.composing_bullet = 
      this.create_map_bullet (x, y, "marker composing " + source_name, 
                              null, null);

    this.composing_frame =
      this.map.create_pos_div (this.composing_bullet,
                               this.message_create_url
                                 (message_source,
                                  this.map.screen_to_world({x:x, y:y})),
                               {x:x, y:y}, {x:10, y:0},
                               {close_handler: function() {
                                   notepad.cancel_composing();
                                   return true;
                                 }}
                               );

    this.composing_frame.className += " composing " + source_name;
  },

  note_composed: function (source, id) {
    this.end_composing ();
    this.select_message (source, id);
    this.load_messages ();
  },

  end_composing: function () {
    if (this.composing_frame != null) {
      // Might want to do a Script.aculo.us fadeout here...
      this.map.delete_pos_object(this.composing_frame);
    }
    this.cancel_composing();
    this.load_messages();       // The new message may be to self
  },

  cancel_composing: function () {
    this.composing_frame = null;
    if (this.composing_bullet != null) {
      this.composing_bullet.parentNode.removeChild (this.composing_bullet);
      this.composing_bullet = null;
    }
  },

  // Getting rid of them ...

  note_delete: function (source, id) {
    this.deselect_message (source, id);
    this.load_messages ();
  },

  // General display manipulation...

  create_map_bullet: function (x, y, className, 
                               click_handler, highlight_handler)
  {
    var ovl = document.createElement ("div");
    var innerClass = '';

    if (className.constructor == Array) {
      innerClass = className[1];
      className = className[0];
    }

    ovl.className = className;

    var inner_elt=document.createElement("span");
    inner_elt.className = innerClass;
    inner_elt.innerHTML = "&#8226;";

    ovl.appendChild (inner_elt);

    // Arrange for heights to be correct for computing offsets,
    // without flashing the bullet up at (0,0)...

    ovl.style.zIndex = -1;
    $(this.map.viewport).appendChild(ovl);    

    this.map.register_pos_object(ovl, this.layer, {x: x, y: y},
                                 {x: -parseInt(ovl.offsetWidth)/2,
                                  y: -parseInt(ovl.offsetHeight)/2});

    // Use the "hilit" class and a couple of handlers to work around
    // IE's pathetic :hover support...

    inner_elt.onmouseover =
      function () { this.parentNode.className = className + " hilit";
                    if (highlight_handler) 
                      highlight_handler (this.parentNode, true); };

    inner_elt.onmouseout =
      function () { this.parentNode.className = className;
                    if (highlight_handler) 
                      highlight_handler (this.parentNode, false); };

    if (click_handler != null) {
      inner_elt.onclick = function (evt) {
        if (evt == null) evt = window.event;
        evt.cancelBubble = true;
        click_handler (evt);
      }
    }

    return ovl;
  },

  // "bookmarks"

  bookmark_create_url: function (message_source_name, id, tooltip)
  {
    return '/bookmark/' + this.map.current_map_tag + '/add?item_id=' + id +
    "&source_name=" + message_source_name +
    "&tooltip=" + encodeURIComponent(tooltip);
  },

  reload_bookmarks: function () {
    new Ajax.Updater (this.bookmark_div_id,
                      "/bookmark/" + this.map.current_map_tag + '/index');
  },

  delete_bookmark_entry: function (id) {
    new Ajax.Updater (this.bookmark_div_id,
                      "/bookmark/" + this.map.current_map_tag + 
                      "/remove?bookmark_id=" + id);
  },

  bookmark_message: function (pos, message_source_name, id, tooltip) {
    new Ajax.Updater (this.bookmark_div_id,
                      this.bookmark_create_url 
                        (message_source_name, id, tooltip));
  },

  activate_bookmark_entry: function (id, message_source_base)
  {
    var message_source = this.find_source (message_source_base);

    this.select_message (message_source, id);

    var url = this.message_summary_url (message_source, id);
    var notepad = this;

    new Ajax.Request
    (url, { method: 'get', onSuccess: function (req) {
         var messages = eval (req.responseText);  // JSON

         for (var i = 0; i < messages.length; ++i) {
           if (messages[i].id == id) {
             notepad.note_message (message_source, messages[i], null);
             notepad.show_message (message_source, messages[i], null);
           }
         }}});
  },

  // Semi-general utilities that might be shuffled off to a base class...

  add_widget_handler: function (elt, klass, handler) 
  {    
    Element.select (elt, '.'+klass).each
    (function (widget) { widget.onclick = handler; });
  },

  // Stub for l10n of alert messages...

  get_alert: function (x) { x }

};
