var MIN_NOTE_SIZE = 15;

var PhotoNotes = Class.create({
  initialize: function(img, opts) {
    this.img = $(img);
    this.notes = [];
    this._create_container();

    Event.observe(document, 'keydown', this._on_document_keyupdown.bindAsEventListener(this));
    Event.observe(document, 'keyup', this._on_document_keyupdown.bindAsEventListener(this));

    this.options = Object.extend({}, opts);
  },

  add_note: function(note) {
    this.notes.push(note);
    this._create_note_div(note);
  },

  add_new_note: function() {
    if (!this.editing) {
      var img_dim = this.img.getDimensions();

      var new_note = {
        top: 50 / img_dim.height,
        left: 50 / img_dim.width,
        width: 50 / img_dim.width,
        height: 50 / img_dim.height,
        editable: true,
        editableText: 'Add your note here',
        text: 'Add your note here'
      };

      this.add_note(new_note);
      this.edit_note(null, new_note);
      this.img.scrollTo();
    }
  },

  // Private Methods
  _create_container: function() {
    var container_div = $(document.createElement('div'));
    this.img.parentNode.appendChild(container_div);
    container_div.className = 'photo_notes';
    container_div.style.position = 'absolute';
    container_div.clonePosition(this.img);
    this.container = container_div;

    var notes_text_div = $(document.createElement('div'));
    notes_text_div.className = 'notes_text_div';
    notes_text_div.style.position = 'absolute';
    notes_text_div.style.display = 'none';
    container_div.appendChild(notes_text_div);
    this.notes_text_div = notes_text_div;

    var notes_text_yeller = $(document.createElement('div'));
    notes_text_yeller.className = 'notes_text_yeller';
    notes_text_yeller.style.display = 'none';
    notes_text_div.appendChild(notes_text_yeller);
    this.notes_text_span_yeller = notes_text_yeller;

    var notes_text_span = $(document.createElement('div'));
    notes_text_span.className = 'notes_text_span';
    notes_text_yeller.appendChild(notes_text_span);
    this.notes_text_span = notes_text_span;

    var notes_text_form = $(document.createElement('form'));
    notes_text_form.id = 'notes_text_form';
    notes_text_form.style.display = 'none';
    notes_text_div.appendChild(notes_text_form);
    this.notes_text_form = notes_text_form;

    var hidden_id_field = $(document.createElement('input'));
    hidden_id_field.setAttribute('type', 'hidden');
    notes_text_form.appendChild(hidden_id_field);

    var notes_text_table = $(document.createElement('table'));
    notes_text_form.appendChild(notes_text_table);
    var notes_text_area_row = notes_text_table.insertRow(-1);
    var notes_text_area_cell = notes_text_area_row.insertCell(-1);

    var notes_text_area_yeller = $(document.createElement('div'));
    notes_text_area_yeller.className = 'notes_text_yeller editable';
    notes_text_area_cell.appendChild(notes_text_area_yeller);
    this.notes_text_area_yeller = notes_text_area_yeller;

    var notes_text_area = PhotoNotes.createFormElement('textarea', 'text');
    notes_text_area.id = 'notes_text_area';
    notes_text_area.setAttribute('maxlength', 140);
    notes_text_area.onkeyup = textarea_maxlength.bind(this, notes_text_area);
    notes_text_area.onchange = textarea_maxlength.bind(this, notes_text_area);
    notes_text_area_yeller.appendChild(notes_text_area);
    this.notes_text_area = notes_text_area;

    var buttons_row = notes_text_table.insertRow(-1);
    var buttons_cell = buttons_row.insertCell(-1);

    var button_save = PhotoNotes.createFormElement('input', 'save');
    button_save.setAttribute('type', 'button');
    button_save.setAttribute('value', 'Save');
    button_save.className = 'button button_save';
    buttons_cell.appendChild(button_save);

    var button_cancel = PhotoNotes.createFormElement('input', 'cancel');
    button_cancel.setAttribute('type', 'button');
    button_cancel.setAttribute('value', 'Cancel');
    button_cancel.className = 'button button_cancel';
    buttons_cell.appendChild(button_cancel);

    var button_delete = PhotoNotes.createFormElement('input', 'delete');
    button_delete.setAttribute('type', 'button');
    button_delete.setAttribute('value', 'Delete!');
    button_delete.className = 'button button_delete';
    buttons_cell.appendChild(button_delete);

    Event.observe(window, 'resize', this._on_window_resize.bindAsEventListener(this));
    Event.observe(button_save, 'click', this.save_edit.bindAsEventListener(this));
    Event.observe(button_cancel, 'click', this.cancel_edit.bindAsEventListener(this));
    Event.observe(button_delete, 'click', this.delete_note.bindAsEventListener(this));
    return container_div;
  },

  _create_note_div: function(note) {
    // This is the containing div
    var note_div = $(document.createElement('div'));
    this.container.appendChild(note_div);
    note_div.className = 'photo_note';
    note_div.id = 'photo_note_' + (note.id ? note.id : 'unsaved');

    var img_dim = this.img.getDimensions();
    note.left_in_px   = (note.left * img_dim.width).toFixed();
    note.top_in_px    = (note.top * img_dim.height).toFixed();
    note.width_in_px  = (note.width * img_dim.width).toFixed();
    note.height_in_px = (note.height * img_dim.height).toFixed();

    note_div.style.position = 'absolute';
    note_div.style.top = note.top_in_px + 'px';
    note_div.style.left = note.left_in_px + 'px';
    note_div.style.width = note.width_in_px + 'px';
    note_div.style.height = note.height_in_px + 'px';

    note.container_div = note_div;

    var note_div_dimensions = note_div.getDimensions();

    // This is the outer border, highlighted when the user mouses over the note
    var outer_div = $(document.createElement('div'));
    note_div.appendChild(outer_div);
    outer_div.style.position = 'absolute';
    outer_div.style.width = (note_div_dimensions.width) + 'px';
    outer_div.style.height = (note_div_dimensions.height) + 'px';
    outer_div.className = 'photo_note_box_div';
    outer_div.style.border = '0';

    // This is the inner, black border
    var inner_div = $(document.createElement('div'));
    outer_div.appendChild(inner_div);
    inner_div.style.position = 'absolute';
    inner_div.style.width = (note_div_dimensions.width - 2) + 'px';
    inner_div.style.height = (note_div_dimensions.height - 2) + 'px';
    inner_div.className = 'photo_note_box_inner_div';

    // This is the inner, white border
    var inner_inner_div = $(document.createElement('div'));
    inner_div.appendChild(inner_inner_div);
    inner_inner_div.style.position = 'absolute';
    inner_inner_div.style.width = (note_div_dimensions.width - 4) + 'px';
    inner_inner_div.style.height = (note_div_dimensions.height - 4) + 'px';
    inner_inner_div.className = 'photo_note_box_inner_inner_div';

    // Events & Behaviors
    Event.observe(note_div, 'mouseover', this._note_div_mouseover.bind(this, note));
    Event.observe(note_div, 'mouseout', this._note_div_mouseout.bind(this, note));

    if (note.editable) {
      var handle_nw = $(document.createElement('div'));
      note_div.appendChild(handle_nw);
      handle_nw.className = 'photo_note_resize_handle photo_note_resize_handle_nw';
      handle_nw.style.position = 'absolute';
      handle_nw.style.cursor = 'nw-resize';
      handle_nw.style.visibility = 'hidden';

      var handle_ne = $(document.createElement('div'));
      note_div.appendChild(handle_ne);
      handle_ne.className = 'photo_note_resize_handle photo_note_resize_handle_ne';
      handle_ne.style.position = 'absolute';
      handle_ne.style.cursor = 'ne-resize';
      handle_ne.style.visibility = 'hidden';

      var handle_sw = $(document.createElement('div'));
      note_div.appendChild(handle_sw);
      handle_sw.className = 'photo_note_resize_handle photo_note_resize_handle_sw';
      handle_sw.style.position = 'absolute';
      handle_sw.style.cursor = 'sw-resize';
      handle_sw.style.visibility = 'hidden';

      var handle_se = $(document.createElement('div'));
      note_div.appendChild(handle_se);
      handle_se.className = 'photo_note_resize_handle photo_note_resize_handle_se';
      handle_se.style.position = 'absolute';
      handle_se.style.cursor = 'se-resize';
      handle_se.style.visibility = 'hidden';

      this._position_handles(note);
      Event.observe(note_div, 'mousedown', this.edit_note.bindAsEventListener(this, note));
    }

    return note_div;
  },

  _move_resize_note: function(note, left, top, width, height) {
    var note_div = note.container_div;

    note_div.style.left = left + 'px';
    note_div.style.top = top + 'px';
    note_div.style.width = width + 'px';
    note_div.style.height = height + 'px';

    var outer_div = note_div.down('.photo_note_box_div');
    outer_div.style.width = width + 'px';
    outer_div.style.height = height + 'px';

    var inner_div = outer_div.down('.photo_note_box_inner_div');
    inner_div.style.width = (width - 2) + 'px';
    inner_div.style.height = (height - 2) + 'px';

    var inner_inner_div = inner_div.down('.photo_note_box_inner_inner_div');
    inner_inner_div.style.width = (width - 4) + 'px';
    inner_inner_div.style.height = (height - 4) + 'px';

    this._position_handles(note);
    this._position_notes_text_div(note);
  },

  _position_handles: function(note) {
    var note_div = note.container_div;
    var note_div_dimensions = note_div.getDimensions();

    var handle_nw = note_div.down('.photo_note_resize_handle_nw');
    handle_nw.style.top = 0;
    handle_nw.style.left = 0;

    var handle_ne = note_div.down('.photo_note_resize_handle_ne');
    handle_ne.style.top = 0;
    handle_ne.style.left = (note_div_dimensions.width - handle_ne.getWidth()) + 'px';

    var handle_sw = note_div.down('.photo_note_resize_handle_sw');
    handle_sw.style.top = (note_div_dimensions.height - handle_sw.getHeight()) + 'px';
    handle_sw.style.left = 0;

    var handle_se = note_div.down('.photo_note_resize_handle_se');
    handle_se.style.top = (note_div_dimensions.height - handle_se.getHeight()) + 'px';
    handle_se.style.left = (note_div_dimensions.width - handle_se.getWidth()) + 'px';
  },

  _position_notes_text_div: function(note) {
    var note_pos = note.container_div.positionedOffset();
    var note_dim = note.container_div.getDimensions();
    var img_dim = this.img.getDimensions();

    if (note_pos[0] < img_dim.width - 200) {
      this.notes_text_div.style.left = note_pos[0] + 'px';
      this.notes_text_div.style.right = '';
    } else {
      this.notes_text_div.style.right = (img_dim.width - note_dim.width - note_pos[0] ) + 'px';
      this.notes_text_div.style.left = '';
    }

    this.notes_text_div.style.top = (note_pos[1] + note_dim.height + 2) + 'px';
  },

  // Event callbacks
  _note_div_mouseover: function(note) {
    if (!(this.editing || this.shift_key_down)) {
      this._note_div_unfocus(this.hide_note);
      this._note_div_clear_timeout();

      var outer_div = note.container_div.down('.photo_note_box_div');
      outer_div.style.border = '1px solid #D4D82D';
      outer_div.style.top = '-1px';
      outer_div.style.left = '-1px';

      if (note.editable) {
        this.notes_text_span_yeller.addClassName("editable");
      } else {
        this.notes_text_span_yeller.removeClassName("editable");
      }

      this._position_notes_text_div(note);
      this.notes_text_span.innerHTML = note.text;
      this.notes_text_span_yeller.show();
      this.notes_text_div.show();
    }
  },

  _note_div_mouseout: function(note) {
    if (!(this.editing || this.shift_key_down)) {
      this._note_div_start_timeout(note);
    }
  },

  _note_div_start_timeout: function(note) {
    this._note_div_clear_timeout();

    this.hide_note = note;
    this.hide_note_timeout = window.setTimeout(this._notes_text_hide.bind(this), 500);

    this.notes_text_div.onmouseover = this._note_div_clear_timeout.bind(this);
    this.notes_text_div.onmouseout  = this._note_div_mouseout.bind(this, note);
  },

  _note_div_clear_timeout: function() {
    if (this.hide_note_timeout) {
      window.clearTimeout(this.hide_note_timeout);
      this.hide_note_timeout = null;
      this.hide_note = null;
    }
  },

  _note_div_unfocus: function(note) {
    if (note) {
      var outer_div = note.container_div.down('.photo_note_box_div');
      outer_div.style.borderStyle = 'none';
      outer_div.style.top = '0';
      outer_div.style.left = '0';
    }
  },

  _notes_text_hide: function() {
    if (!this.editing) {
      this._note_div_unfocus(this.hide_note);
      this.notes_text_div.hide();
    }
  },

  edit_note: function(event, note) {
    if (!this.editing) {
      var handles = note.container_div.select('.photo_note_resize_handle');
      handles.each(function(handle) {
        handle.style.visibility = 'visible';
      });

      note.container_div.down('.photo_note_box_inner_div').style.borderStyle = 'dashed';
      note.container_div.down('.photo_note_box_inner_inner_div').style.borderStyle = 'dashed';

      var note_style = note.container_div.style;

      note.saved_pos = {
        left: parseInt(note_style.left),
        top: parseInt(note_style.top),
        width: parseInt(note_style.width),
        height: parseInt(note_style.height)
      };

      this.notes_text_span_yeller.hide();
      this.notes_text_area.value = note.editableText;
      this.notes_text_form['delete'].style.display = note.id ? '' : 'none';
      this.notes_text_form.show();

      this._position_notes_text_div(note);
      this.notes_text_div.show();

      this.notes_text_area.focus();
      this.notes_text_area.select();

      this._create_draggables(note, event);
      this.current_note = note;
      this.editing = true;
    }
  },

  _create_draggables: function(note, event) {
    var div = note.container_div;
    var nw_handle = div.down(".photo_note_resize_handle_nw");
    var ne_handle = div.down(".photo_note_resize_handle_ne");
    var sw_handle = div.down(".photo_note_resize_handle_sw");
    var se_handle = div.down(".photo_note_resize_handle_se");

    var div_draggable = new Draggable(div, {change: this._note_draggable_change.bind(this, note)});
    var nw_draggable = new Draggable(nw_handle, {change: this._handle_draggable_change.bind(this, note, 'n', 'w')});
    var ne_draggable = new Draggable(ne_handle, {change: this._handle_draggable_change.bind(this, note, 'n', 'e')});
    var sw_draggable = new Draggable(sw_handle, {change: this._handle_draggable_change.bind(this, note, 's', 'w')});
    var se_draggable = new Draggable(se_handle, {change: this._handle_draggable_change.bind(this, note, 's', 'e')});

    note.draggables = [div_draggable, nw_draggable, ne_draggable, sw_draggable, se_draggable];

    if (event)
      div_draggable.initDrag(event);
  },

  _destroy_draggables: function(note) {
    note.draggables.invoke('destroy');
    note.draggables = null;
  },

  _note_draggable_change: function(note, draggable) {
    var image_dim = this.img.getDimensions();
    var note_dim = draggable.element.getDimensions();
    var note_pos = draggable.element.positionedOffset();

    draggable.element.style.left = Math.min(Math.max(note_pos[0], 0), image_dim.width - note_dim.width) + 'px';
    draggable.element.style.top = Math.min(Math.max(note_pos[1], 0), image_dim.height - note_dim.height) + 'px';

    this._position_notes_text_div(note);
  },

  _handle_draggable_change: function(note, vdir, hdir, draggable) {
    var note_div_pos = note.container_div.positionedOffset();
    var note_div_dim = note.container_div.getDimensions();
    var handle_pos = draggable.element.positionedOffset();
    var handle_dim = draggable.element.getDimensions();
    var image_dim = this.img.getDimensions();

    var left = note_div_pos[0];
    var top = note_div_pos[1];
    var width = note_div_dim.width;
    var height = note_div_dim.height;

    switch (vdir) {
      case 'n':
        if (top + handle_pos[1] > 0) {
          top += handle_pos[1];
          height -= handle_pos[1];
        }
        break;
      case 's':
        if (note_div_pos[1] + handle_pos[1] + handle_dim.height < image_dim.height)
          height += (handle_pos[1] - note_div_dim.height + handle_dim.height);
    }

    switch (hdir) {
      case 'w':
        if (left + handle_pos[0] > 0) {
          left += handle_pos[0];
          width -= handle_pos[0];
        }
        break;
      case 'e':
        if (note_div_pos[0] + handle_pos[0] + handle_dim.width < image_dim.width)
          width += (handle_pos[0] - note_div_dim.width + handle_dim.width);
    }

    if (width < MIN_NOTE_SIZE) {
      width = MIN_NOTE_SIZE; left = note_div_pos[0];
    }

    if (height < MIN_NOTE_SIZE) {
      height = MIN_NOTE_SIZE; top = note_div_pos[1];
    }

    left = Math.min(Math.max(left, 0), image_dim.width - MIN_NOTE_SIZE);
    top = Math.min(Math.max(top, 0), image_dim.height - MIN_NOTE_SIZE);

    this._move_resize_note(note, left, top, width, height);
  },

  cancel_edit: function() {
    var note = this.current_note;

    if (note.id != undefined) {
      var saved_pos = note.saved_pos;
      this._move_resize_note(note, saved_pos.left, saved_pos.top, saved_pos.width, saved_pos.height);
      this._note_finish_edit(note);
    } else {
      this.delete_note();
    }
  },

  _note_finish_edit: function(note) {
    var handles = note.container_div.select('.photo_note_resize_handle');
    handles.each(function(handle) {
      handle.style.visibility = 'hidden';
    });

    note.container_div.down('.photo_note_box_inner_div').style.borderStyle = 'solid';
    note.container_div.down('.photo_note_box_inner_inner_div').style.borderStyle = 'solid';

    this._destroy_draggables(note);
    this.notes_text_span_yeller.show();
    this.notes_text_form.hide();
    this.notes_text_div.hide();
    this.editing = false;
  },

  save_edit: function() {
    var note = this.current_note;
    var note_pos = note.container_div.positionedOffset();
    var note_dim = note.container_div.getDimensions();
    var img_dim = this.img.getDimensions();

    var new_note = Object.clone(note);
    new_note.editableText = this.notes_text_area.value;
    new_note.text = new_note.editableText;

    new_note.left_in_px   = note_pos[0];
    new_note.top_in_px    = note_pos[1];
    new_note.width_in_px  = note_dim.width;
    new_note.height_in_px = note_dim.height;

    new_note.left   = note_pos[0] / img_dim.width;
    new_note.top    = note_pos[1] / img_dim.height;
    new_note.width  = note_dim.width / img_dim.width;
    new_note.height = note_dim.height / img_dim.height;

    if (this.options.onsave) {
      this.options.onsave(note, new_note);
    } else {
      Object.extend(note, new_note);
    }

    this._note_finish_edit(note);
  },

  delete_note: function() {
    var note = this.current_note;

    if (note.id != undefined && this.options.ondelete) {
      this.options.ondelete(note);
    }

    var index = this.notes.indexOf(note);

    if (index > 0) {
      this.notes.splice(index, 1);
    }

    this._note_finish_edit(note);
    note.container_div.parentNode.removeChild(note.container_div);
  },

  _on_window_resize: function() {
    this.container.clonePosition(this.img);
  },

  _on_document_keyupdown: function(event) {
    this.shift_key_down = event.shiftKey;
  }
});

Object.extend(PhotoNotes, {
  createFormElement: function(type, name) {
    var element = null;

    try {
      // The IE way. This should throw an exception on respectable browsers
      element = document.createElement("<" + type + " name='" + name + "'>");
    } catch (e) {
    }

    // The standard way to construct an element.
    if (!element || element.nodeName != type.toUpperCase()) {
      element = document.createElement(type);
      element.setAttribute('name', name);
    }

    return $(element);
  }
});
