MN.Note = Class.create();

// This is where instances register themselves;
MN.Note.instances = [ ];

// Execute this function during resize
MN.Note.resize = function(dr) {
  var id       = MN.id(dr.element);
  var note     = $('note.' + id);
  var body     = $(note.id + '.body');
  var footer   = $(note.id + '.footer');
  var resizer  = $(note.id + '.resizer');
  var form     = $(note.id + '.form');
  // var debug   = $('debug');
  // [width needs algebra]
  // note.width - 4     - 7         - 4           - resizer.offsetLeft == 0
  //            margin  resizer.wd  resizer.right

  // [height is easier because offsetTop starts at 0]

  var nsize = Element.getDimensions(note);
  var fsize = Element.getDimensions(footer);

  var margin =  4;  // footer.marginLeft + footer.marginRight
  var width  = 12;  // resizer.width
  var right  =  2;  // resizer.right
  var offsetLeft = resizer.offsetLeft + margin + width + right; // == nsize.width

  // debug.innerHTML = resizer.offsetLeft + ' x ' + (nsize.height - resizer.offsetTop);
  var bodyHeight = (nsize.height + resizer.offsetTop - 16 - 40);

  Element.setStyle(
    note,
    {
      width  : offsetLeft + 'px',
      height : nsize.height + resizer.offsetTop + 'px'
    }
  );
  Element.setStyle(
    footer,
    {
      width  : (offsetLeft - margin) + 'px'
    }
  );
  Element.setStyle(
    body,
    {
      width  : (offsetLeft - 16) + 'px',
      height : bodyHeight + 'px'
    }
  );
  if (form) {
    var textarea = form.firstChild;
    Element.setStyle(
      textarea,
      {
        width  : (offsetLeft - 16) + 'px',
        height : (bodyHeight - 20) + 'px'
      }
    )
  }
};

// Execute this function when resizing is finished.
MN.Note.resizeFinish = function(el) {
  // var debug = $('debug');
  var id    = MN.id(el);
  var note  = MN.Note.instances[id];
  var el    = note.element;
  // debug.innerHTML = el.offsetWidth + " x " + el.offsetHeight;
  note.organizeRelations();
  note.drawRelations();

  var url = '/note/resize/' + id;
  new Ajax.Request (
    url,
    {
      postBody: 'width=' + el.offsetWidth + '&height=' + el.offsetHeight
    }
  );
};

MN.Note.resizeTextArea = function() {
  var id     = MN.id(this.element);
  var note   = $('note.' + id);
  var body   = this.element;
  var form   = $(note.id + '.form');

  bodyWidth  = parseInt(Element.getStyle(body, 'width'));
  bodyHeight = parseInt(Element.getStyle(body, 'height'));

  if (form && body.id.match('body')) {
    var textarea = form.getElementsByTagName('TEXTAREA')[0];
    if (textarea) {
      Element.setStyle(
        textarea,
        {
          width  : (bodyWidth  -  4) + 'px',
          height : (bodyHeight - 20) + 'px'
        }
      );
    }
  }
  return true;
}

// Create a new Ajax.InPlaceEditor for the body
MN.Note.makeInPlaceEditorForBody = function(id) {
  var ipe = new Ajax.InPlaceEditor (
    $('note.' + id + '.body'),
    '/note/edit_body/' + id,
    {
      formId            : 'note.' + id + '.form',
      loadTextURL       : '/note/return_unformatted_body/' + id,
      cols              : 32,
      rows              : 8,
      highlightendcolor : '#ffee88',
      onComplete        : function(transport, element) {
        new Effect.Highlight(
          element,
          {
            startcolor : '#ffff99',
            endcolor   : '#ffee88'
          }
        );
      }
    }
  );
  // ipe.onEnterEditMode = MN.Note.resizeTextArea;
  return ipe;
}


// Create the DOM structure for a new Note.
// http://wiki.script.aculo.us/scriptaculous/show/Builder
MN.Note.build = function(note) {
  var n = Builder.node('div',
    {
      className : 'note',
      id        : 'note.' + note.id,
      style     : 'position: absolute; left: ' + note.x + 'px; top: ' + note.y + 'px; width: ' + note.width + 'px; height: ' + note.height + 'px; z-index: ' + 3000
    },
    [

      // header
      Builder.node('h2',
        {
          className : 'header',
          id        : 'note.' + note.id + '.header'
        },
        [

          // not sure what I'll use this for, yet.
          Builder.node('span',
            {
              className : 'menu',
              id        : 'note.' + note.id + '.menu'
            },
            'o'
          ),

          // name
          Builder.node('span',
            {
              className : 'name',
              id        : 'note.' + note.id + '.name'
            },
            note.name
          ),

          // close note
          Builder.node('span',
            {
              className : 'destroyer',
              id        : 'note.' + note.id + '.destroyer'
            },
            'x'
          )
        ]
      ),

      // body
      Builder.node('div',
        {
          className : 'body',
          id        : 'note.' + note.id + '.body',
          style     : 'width: ' + (note.width - 16) + 'px; height: ' + (note.height - 56) + 'px'
        },
        'placeholder'
      ),

      // footer
      Builder.node('h2',
        {
          className : 'footer',
          id        : 'note.' + note.id + '.footer',
          style     : 'width: ' + (note.width - 4) + 'px'
        },
        [
          // link to other notes
          Builder.node('span',
            {
              className : 'linker',
              id        : 'note.' + note.id + '.linker'
            },
            "\u21AA"
          ),

          Builder.node('span',
            {
              className : 'date',
              id        : 'note.' + note.id + '.date'
            },
            note.created_on
          ),

          // resize
          Builder.node('span',
            {
              className : 'resizer',
              id        : 'note.' + note.id + '.resizer'
            },
            "\u25E2"
          )

        ]
      )

    ]
  );
  $$('div.workspace').first().appendChild(n);
  $('note.' + note.id + '.body').innerHTML = note.body;
};

MN.Note.create = function(xhr) {
  var response = eval('(' + xhr.responseText + ')');
  MN.Note.build(response);
  var note = new MN.Note($('note.' + response.id));
  note.toFront();
}

// Draw all the arrows -- this will definitely be refactored later
MN.Note.drawAllRelations = function() {
  var notes = MN.Note.instances;

  // In the future, let's organizeRelations lazily.
  notes.each (
    function(note, i) {
      if (note) {
        note.organizeRelations();
      }
    }
  );

  // let's draw every note's from_realtions
  notes.each (
    function(note, i) {
      var edges = ['top', 'right', 'bottom', 'left'];
      if (note) {
        var j;
        for (j = 0; j < edges.length; j++) {
          var relations = note.relationsByEdge[edges[j]];
          var k;
          for (k = 0; k < relations.length; k++) {
            var r = relations[k];
            if (r.from != note.id) {
              continue;
            }
            r.draw();
          }
        }
      }
    }
  );
};

MN.Note.prototype = {

  // attributes
  id      : null,
  element : null,

  // long name that needs to be shrunken down ;-)
  deleteConfirmationMessage : "Are you sure you want to delete this note?",

  fromRelations   : null,
  toRelations     : null,

  // This is a hash whose keys are edge names and
  // whose values are arrays of relations.
  relationsByEdge : null,

  // This indicates whether this.edge needs updating.
  edgeNeedsUpdate : true,

  draggables      : null,
  editors         : null,

  // methods

  initialize: function(element, options) {
    Object.extend(this, options);

    this.fromRelations   = [ ];
    this.toRelations     = [ ];
    this.relationsByEdge = { };

    this.draggables = { };
    this.editors    = { };

    this.element = element;
    var el       = element;
    var id       = MN.id(el);
    this.id      = id;

    // register self
    MN.Note.instances[id] = this;

    $(el.id + '.header').onclick = this.toFront.bind(this);

    // make the note draggable in many ways
    this.draggables['note'] = new Draggable(
      el, {
        handle      : el.id + '.header',
        starteffect : false,
        endeffect   : false,
        zindex      : 6000
      }
    );
    this.draggables['resizer'] = new Draggable(
      $(el.id + '.resizer'),
      {
        revert : MN.Note.resizeFinish,
        change : MN.Note.resize,
        snap   : function(x,y) {


          var note = MN.Note.instances[id];

          // current
          var width  = x
          var height = Element.getStyle(note.element, 'height');
          height.replace(/px/, '');
          height = parseInt(height);
          // $('debug').innerHTML = width + 'x' + height;

          // set bounds
          var xMin = 200;
          var yMin = 100;

          // adjusted if necessary
          var xNow;
          var yNow;
          xNow = (width        <= xMin) ? xMin : x;
          yNow = ((height + y) <= yMin) ? 0    : y;

          return [xNow, yNow];
        }
      }
    );
    this.draggables['linker'] = new Draggable(
      $(el.id + '.linker'),
      {
        revert : true,
        zindex : 6000
      }
    );

    // make the note accept drops from linkers
    Droppables.add (
      el,
      {
        accept     : [ 'linker' ],
        hoverclass : 'linker-hover',
        onDrop     : MN.Relation.createOnDrop
      }
    );

    // editor for the title
    this.editors['name'] = new Ajax.InPlaceEditor (
      $(el.id + '.name'),
      '/note/edit_name/' + id,
      {
        highlightendcolor : '#eecc77',
        cols              : 9,
        onComplete        : function(transport, element) {
          new Effect.Highlight(
            element,
            {
              startcolor : '#ffff99',
              endcolor   : '#eecc77'
            }
          );
        }
      }
    );

    // destroyer for the note
    var destroyer = $(el.id + '.destroyer');
    var obj       = this;
    destroyer.onclick = function(event) {
      if (confirm(obj.deleteConfirmationMessage)) {
        obj.destroy();
      }
    }

    // editor for the body
    this.editors['body'] = MN.Note.makeInPlaceEditorForBody(id)

    // relations between notes
    var r;
    if (r = MN.Relation.fromRelations[id]) {
      r.each(
        function(relation, index) {
          this.addToFromRelations(relation);
        }.bind(this)
      );
    }
    if (r = MN.Relation.toRelations[id]) {
      r.each(
        function(relation, index) {
          this.addToToRelations(relation);
        }.bind(this)
      );
    }

  },

  // destroy note
  // TODO: destroy client-side variables
  destroy: function() {
    var el   = this.element;
    var id   = MN.id(el);
    var note = this;
    new Effect.Puff(
      el,
      {

        beforeStart: function() {
          new Ajax.Request(
            '/note/destroy/' + id,
            {

              onSuccess : function(xhr) {
                note.fromRelations.each(
                  function(relation, index) {
                    relation.destroy();
                  }
                );
                note.toRelations.each(
                  function(relation, index) {
                    relation.destroy();
                  }
                );
              }

            }
          );
        }

      }
    );
  },

  toFront: function() {
    // this might be inefficient, but we'll figure something better
    // out later.
    var z = $$('div.note').max(function(el) { return el.getStyle('z-index') });
    z++;
    Element.setStyle(this.element, { 'z-index': z });
  },

  // get width and height of note in an object
  dimension: function() {
    return Element.getDimensions(this.element);
  },

  // get x and y in an object
  position: function() {
    var offset = Position.cumulativeOffset(this.element);
    return {
      x: offset[0],
      y: offset[1]
    };
  },

  // This method figures out whether our relations are connected to
  // the (top, right, bottom, or left) edge of the note
  // XXX - Note that you can't call this method until all notes and relations
  // have been loaded on the client side.
  organizeRelations: function() {

    // init relation lists
    this.relationsByEdge['top']    = [ ];
    this.relationsByEdge['right']  = [ ];
    this.relationsByEdge['bottom'] = [ ];
    this.relationsByEdge['left']   = [ ];

    // organize relations by edge
    this.fromRelations.each(
      function(relation, index) {
        var result = relation.findNoteEdges();
        if (result) {
          this.relationsByEdge[result.from].push(relation);
        }
      }.bind(this)
    );

    this.toRelations.each(
      function(relation, index) {
        var result = relation.findNoteEdges();
        if (result) {
          this.relationsByEdge[result.to].push(relation);
        }
      }.bind(this)
    );

    // sort relations by id
    // XXX - sort by target coordinate (or angle) would be better
    var byId = function(a, b) {
      return (a - b);
    };

    this.relationsByEdge['top'].sort(byId);
    this.relationsByEdge['right'].sort(byId);
    this.relationsByEdge['bottom'].sort(byId);
    this.relationsByEdge['left'].sort(byId);

    ['top', 'right', 'bottom', 'left'].each(
      function(edge, index) {

        this.relationsByEdge[edge].each(
          function(relation, i) {
            if (relation.from == this.id) {
              relation.fromIndex = i;
            } else {
              relation.toIndex   = i;
            }
          }.bind(this)
        )

      }.bind(this)
    );

    this.edgeNeedsUpdate = false;
  },

  addToFromRelations: function(relation) {
    this.fromRelations.push(relation);
  },

  addToToRelations: function(relation) {
    this.toRelations.push(relation);
  },

  // removeRelation: function() {
  // },

  drawRelations: function() {
    var edges = ['top', 'right', 'bottom', 'left'];
    var j;

    for (j = 0; j < edges.length; j++) {
      var relations = this.relationsByEdge[edges[j]];
      var k;
      for (k = 0; k < relations.length; k++) {
        var r = relations[k];
        r.draw();
      }
    }
  },

  // not sure if I'll really need this.
  loadRelations: function() {
  },

  // coordinates of the center of this note
  midpoint: function() {
    var el = this.element;
    return {
      x : parseInt(el.offsetLeft + el.clientWidth  / 2),
      y : parseInt(el.offsetTop  + el.clientHeight / 2)
    };
  },

  // coordinates of the top-left corner of this note
  topLeft: function() {
    var el = this.element;
    return { x: el.offsetLeft, y: el.offsetTop };
  },

  // coordinates of the top-right corner of this note
  topRight: function() {
    var el = this.element;
    return { x: el.offsetLeft + el.clientWidth, y: el.offsetTop };
  },

  // coordinates of the bottom-right corner of this note
  bottomRight: function() {
    var el = this.element;
    return { x: el.offsetLeft + el.clientWidth, y: el.offsetTop + el.clientHeight };
  },

  // coordinates of the bottom-left corner of this note
  bottomLeft: function() {
    var el = this.element;
    return { x: el.offsetLeft, y: el.offsetTop + el.clientHeight };
  },

  // coordinate pair from top-left to top-right
  topEdge: function() {
    return [ this.topLeft(), this.topRight() ];
  },

  // coordinates pair from top-right to bottom-right
  rightEdge: function() {
    return [ this.topRight(), this.bottomRight() ];
  },

  // coordinates pair from bottom-left to bottom-right
  bottomEdge: function() {
    return [ this.bottomLeft(), this.bottomRight() ];
  },

  // coordinate pair from top-left to bottom-left
  leftEdge: function() {
    return [ this.topLeft(), this.bottomLeft() ];
  },

  last: 1
};

// vim:sw=2 sts=2 expandtab
