
// CARBON.
// STAY DRY IN THE BRAINSTORM.

// Designed by Simon Bond, Joey Flynn, Drew Hamlin
// Engineered by Drew Hamlin


var version = '0.1';
var canvas, buffer, drawing, pen, current_editor;
var is_dragging, handled_click, last_placed_element, last_placed_click_time;
var double_click_time_threshold = 500;
var grid = 20;

setInterval(function() {
  if (!current_editor)
    window.getSelection().collapseToStart();
}, 10);

window.onload = function() {
  canvas = $('carbon');
  buffer = document.createElement('ul');
  buffer.id = 'buffer';
  canvas.appendChild(buffer);
  
  // Drawing
  drawing = new jsGraphics(document.getElementById('canvas'));
  pen = new jsPen(new jsColor('black'), 1);
  
  // Canvas
  canvas.setAttribute('version', version);
  canvas.oncontextmenu = function() { return false; }
  canvas.onmouseover   = function() { buffer.show(); };
  canvas.onmouseout    = function() { buffer.hide(); };
  canvas.onmousedown   = function supress_editor() {
    if (current_editor && current_editor != event.target)
      current_editor.blur();
  }
  canvas.onmouseup = function facilitate_temporary_drag_selection() {
    setTimeout(function() {
        is_dragging = false;
        canvas.select('.being_dragged').each(function(being_dragged) {
          being_dragged.removeClassName('being_dragged');
        });
    }, 100);
  }
  canvas.onmousemove = function position_buffer() {
    buffer.style.left = event.x + 'px';
    buffer.style.top = event.y + 'px';
  };
  canvas.onclick = function() {
    if (is_dragging)
      return;
    if (handled_click) {
      handled_click = false;
      return;
    }
    _deselect_all();
  };
  canvas.ondblclick = function drop_bullet() {
    if (!event.shiftKey || current_editor)
      return;
    var element = _create_element('&middot;');
    element.addClassName('bullet');
    snap_element_to_grid(element, event.x, event.y);
    canvas.appendChild(element);
  }
  
  // Buffer
  buffer.onselectstart = function() { return false; };
  buffer.onclick = function _drop_single_element() {
    var click_time = _current_time();
    if ((buffer.childNodes.length == 1) && (click_time - last_placed_click_time) < double_click_time_threshold) {
      last_placed_element.style.top = (parseInt(last_placed_element.style.top) - grid) + 'px';  // Prepare for drop all
      return;
    }
    last_placed_click_time = click_time;
    
    var label = buffer.childNodes[0].innerHTML;
    buffer.removeChild(buffer.childNodes[0]);
    var element = _create_element(label);
    snap_element_to_grid(element, buffer.style.left, buffer.style.top);
    canvas.appendChild(element);
    last_placed_element = element;
    if (buffer.childNodes.length && !buffer.childNodes[0].innerHTML.strip().length) // Trim trailing whitespace item
      buffer.innerHTML = '';
  };
  buffer.ondblclick = function _drop_all_elements() {
    var group_id = _unique_id('in_group');
    last_placed_element.addClassName(group_id);
    
    var line = parseInt(last_placed_element.style.top) + grid;
    last_placed_element.style.top = line + 'px';
    while (buffer.childNodes.length) {
      var label = buffer.childNodes[0].innerHTML;
      buffer.removeChild(buffer.childNodes[0]);
      if (!label.length)
        continue;
      var element = _create_element(label);
      element.style.left = last_placed_element.style.left;
      element.addClassName(group_id);
      line += grid;
      element.style.top = line + 'px';
      canvas.appendChild(element);
    }
  };
}

document.onkeypress = function() {
  if (current_editor)
    return;
  var key = event.keyCode;
  if (key == 32) { // Space = Swap
    var selected = canvas.select('.selected');
    if (selected.length == 2) {
      var first = selected.first();
      var last = selected.last();
      
      var temp = first.innerHTML;
      var tempWeight = first.style.fontWeight;
      first.innerHTML = last.innerHTML;
      first.style.fontWeight = last.style.fontWeight;
      last.innerHTML = temp;
      last.style.fontWeight = tempWeight;
      
      _redraw_attached_lines(first);
      _redraw_attached_lines(last);
    }
  }
  else if (key == 13) { // Return
    var selected = canvas.select('.selected');
    if (selected.length == 1 && !current_editor) {
      _display_editor(selected.first());
      return false;
    }
  }
  
  canvas.select('.selected').each(function(selected) {
    switch (key) {
      case 8:  // Delete
        _remove_attached_lines(selected);
        canvas.removeChild(selected);
        return false;
      case 27: // Esc
        selected.removeClassName('selected');
        return;
    }
  });
  
  switch (key) {
    case 8:  // Delete
      if (buffer.childNodes.length) {
        var node = $A(buffer.childNodes).last();
        if (node.innerHTML.length)
          node.innerHTML = node.innerHTML.substring(0, node.innerHTML.length - 1);
        else
          node.parentNode.removeChild(node);
      }
      return false;
    case 13: // Return
      if ($A(buffer.childNodes).length && $A(buffer.childNodes).last().innerHTML.length)
        buffer.innerHTML = buffer.innerHTML + '<li></li>';
      return;
    case 27: // Esc
      // BRING UP HISTORY PANEL
      return;
    case 32:  // Space
    case 160: // Nonbreaking space
      if (!($A(buffer.childNodes).length && $A(buffer.childNodes).last().innerHTML.strip().length))
        return;
    default:
      if (event.metaKey) {
        if (key == 97) { // Cmd-A = Select All
          canvas.select('.element').each(function(element) {
            element.addClassName('selected');
          });
        }
        return;
      }
      if (!buffer.childNodes.length)
        buffer.innerHTML = '<li></li>';
      _deselect_all();
      $A(buffer.childNodes).last().innerHTML += String.fromCharCode(key);
  }
}

document.onkeydown = function handle_arorw_keys() {
  var selected = canvas.select('.selected');
  if (!selected.length || current_editor)
    return;
  
  var key = event.keyCode;
  selected.each(function(element) {
    switch (key) {
      case 37: // Left
        element.style.left = (parseInt(element.style.left) - grid) + 'px';
        break;
      case 38: // Up
        element.style.top = (parseInt(element.style.top) - grid) + 'px';
        break;
      case 39: // Right
        element.style.left = (parseInt(element.style.left) + grid) + 'px';
        break;
      case 40: // Down
        element.style.top = (parseInt(element.style.top) + grid) + 'px';
        break;
    }
    _redraw_attached_lines(element);
  });
}

// -------
// LINES

function _set_lines(element, lines) {
  element.setAttribute('lines', lines.join());
}

function _lines(element) {
  var lines = element.getAttribute('lines');
  if (!lines)
    return new Array();
  return lines.split(',');
}

function _connected_element_on_line(element, line) {
  line = $(line);
  if (!line)
    return null;
  var start = $(line.getAttribute('start'));
  var end = $(line.getAttribute('end'));
  if (element.id == start.id)
    return end;
  else if (element.id == end.id)
    return start;
  else
    return null;
}

function _remove_attached_lines(element) {
  _lines(element).each(function(line) {
    // Remove the line from the connection's lines
    var connection = _connected_element_on_line(element, line);
    var connections_lines = _lines(connection);
    var attached_line_index = connections_lines.indexOf(line);
    if (attached_line_index >= 0)
      connections_lines.splice(attached_line_index, 1);
    _set_lines(connection, connections_lines);
    
    // Remove the actual line
    line = $(line);
    if (line)
      canvas.removeChild(line);
  });
  _set_lines(element, new Array());
}

function _redraw_attached_lines(element) {
  _lines(element).each(function(line) {
    var connection = _connected_element_on_line(element, line);
    canvas.removeChild($(line));
    _draw_line_between(element, connection, line);
  });
}

function _draw_line_between(first, second, name) {
  if (first == second || _is_bullet(first) || _is_bullet(second))
    return;
  
  // Don't allow two identical lines
  var existing = false;
  _lines(first).each(function(line) {
    if (_connected_element_on_line(first, line) == second) {
      existing = true;
      throw $break;
    }
  });
  if (existing)
    return;
  
  var first_x, first_y, second_x, second_y;
  var start   = first;
  var start_x = first_x  = parseInt(first.style.left);
  var start_y = first_y  = parseInt(first.style.top);
  var end     = second;
  var end_x   = second_x = parseInt(second.style.left);
  var end_y   = second_y = parseInt(second.style.top);
  
  if (start_x > end_x || (start_x == end_x && start_y > end_y)) {
    start   = second;
    start_x = second_x;
    start_y = second_y;
    end     = first;
    end_x   = first_x;
    end_y   = first_y;
  }
  
  start_x += start.offsetWidth - (10 * 2);
  var adjustment_applied = false;
  var adjustment = (grid - start_x % grid) - 2;
  if (adjustment > (grid / 2)) {
    start_x -= grid;
    if (start_y > end_y && start_x != end_x) {
      start_y -= grid;
      adjustment_applied = true;
    }
  }
  start_x += adjustment;
  end_x -= 12;
  start_y += 3;
  end_y += 3;
  
  if (start_x >= end_x && !adjustment_applied) {
    if (start_y > end_y)
      start_y -= grid;
    else if (start_y < end_y)
      end_y -= grid;
  }
  
  var line = drawing.drawLine(pen, new jsPoint(start_x, start_y), new jsPoint(end_x, end_y));
  line.id = (name ? name : _unique_id('line'));
  line.className = 'line';
  line.setAttribute('start', start.id);
  line.setAttribute('end', end.id);
  
  if (!name) {
    new Array(first, second).each(function(element) {
      var elements_lines = _lines(element);
      elements_lines.push(line.id);
      _set_lines(element, elements_lines);
    });
  }
}

// -------
// ELEMENTS

function _is_bullet(element) {
  return $w(element.className).include('bullet');
}

function _handle_click(element) {
  if (is_dragging)
    return;
  var selected = canvas.select('.selected');
  if (event.shiftKey && selected.length == 1) {
    _draw_line_between(selected.first(), element);
    return;
  }
  if (!event.metaKey)
    _deselect_all();
  element.toggleClassName('selected');
  handled_click = true;
}

function snap_element_to_grid(element, _x, _y) {
  var location = {x: event.x, y: event.y};
  _snap_to_grid(location);
  element.style.left = location.x + 'px';
  element.style.top = location.y + 'px';
}

function _snap_to_grid(location) {
  location.x = Math.max(13, Math.round((location.x - 7) / grid) * grid + 10);
  location.y = Math.max(17, Math.round(location.y / grid) * grid - 5);
}

function _deselect_all() {
  canvas.select('.selected').each(function(selected) {
    selected.removeClassName('selected');
  });
}

function _display_editor(element) {
  if (_is_bullet(element))
    return;
  
  var editor = document.createElement('input');
  current_editor = editor;
  editor.className = 'element_editor';
  editor.id = element.id.replace('element_', 'element_editor_');
  editor.value = element.innerHTML;
  editor.style.fontWeight = element.style.fontWeight;
  editor.style.width = (element.offsetWidth - 7) + 'px';
  editor.style.top = element.style.top;
  editor.style.left = element.style.left;
  editor.onblur = function commit_editor_changes(event) {
    current_editor = null;
    var editor = event.target;
    var element = $(editor.id.replace('editor_', ''));
    element.innerHTML = editor.value;
    editor.parentNode.removeChild(editor);
    if (!element.innerHTML.length)
      element.parentNode.removeChild(element);
    return false;
  }
  editor.onkeypress = function textfield_key_press(event) {
    var key = event.keyCode;
    if (key == 13) { // Return
      setTimeout(function() { current_editor.blur(); }, 10);
      return false;
    }
    
    var editor = event.target;
    var element = $(editor.id.replace('editor_', ''));
    
    var selection = window.getSelection().getRangeAt(0);
    var before_selection = editor.value.substring(0, selection.startOffset);
    var after_selection = editor.value.substring(selection.endOffset, editor.value.length);
    var new_value = before_selection + String.fromCharCode(key) + after_selection;
    
    element.innerHTML = editor.value = new_value;
    editor.style.width = (element.offsetWidth - 7) + 'px';
    _redraw_attached_lines(element);
    return false;
  }
  editor.onkeydown = function test_for_delete(event) {
    if (event.keyCode != 8) // Delete
      return;
    var editor = event.target;
    var element = $(editor.id.replace('editor_', ''));
    
    var selection = window.getSelection().getRangeAt(0);
    var before_selection = editor.value.substring(0, selection.startOffset);
    var after_selection = editor.value.substring(selection.endOffset, editor.value.length);
    
    if (selection.startOffset != selection.endOffset)
      element.innerHTML = editor.value = before_selection + after_selection;
    else
      element.innerHTML = editor.value = element.innerHTML.substring(0, element.innerHTML.length - 1);
    
    editor.style.width = (element.offsetWidth - 7) + 'px';
    _redraw_attached_lines(element);
    return false;
  }
  canvas.appendChild(editor);
  editor.focus();
  editor.select();
}

function _toggle_bold(element) {
  if (_is_bullet(element))
    return;
  
  // Hold command or shift to maintain selection while bolding
  if (!(event.metaKey || event.shiftKey))
    _deselect_all();
  element.addClassName('selected');
  
  var shouldBold = canvas.select('.selected').collect(function(selected) {
    return selected.style.fontWeight == 'bold';
  }).all();
  canvas.select('.selected').each(function(selected) {
    selected.style.fontWeight = (shouldBold ? 'normal' : 'bold');
    _redraw_attached_lines(selected);
  });
}

function _create_element(label) {
  var element = document.createElement('div');
  element.className = 'element ';
  element.id = _unique_id('element');
  element.innerHTML = label;
  element.onselectstart = function() { return false; };
  element.onclick = function() { _handle_click(element); };
  element.ondblclick = function() { _display_editor(element); };
  element.oncontextmenu = function() { _toggle_bold(element); };
  Drag.init(element);
  element.onDrag = function(x, y) {
    handled_click = true;
    is_dragging = true;
    _deselect_all();
    element.addClassName('being_dragged');

    snap_element_to_grid(element, x, y);
    _redraw_attached_lines(element);
  }
  return element;
}

// -------
// HELPERS

function _unique_id(kind) {
  var stem = kind + '_' + _current_time();
  var unique = stem;
  var collision = 0;
  while ($(unique)) {
    unique = stem + '_' + (++collision);
  }
  return unique;
}

function _current_time() {
  return (new Date).getTime();
}

