/*
 * Linkerクラス
 *
 *--------------------------------------------------------------------- */
var Linker = {

  LINKER_ID_PREFIX: 'linker_',
  LINKER_ID_SEPARATOR: '_to_',
  POSITION_TOP_LEFT: 1,
  POSITION_TOP_RIGHT: 2,
  POSITION_BOTTOM_LEFT: 3,
  POSITION_BOTTOM_RIGHT: 4,
  LINE_WIDTH: 2,

  /*
   * create
   *
   *--------------------------------------------------------------------- */
  create: function(fromTag, toTag) {

    /*
     * すでにタグ配置要素に指定のリンカがないかを調べ,
     * 配置済みであるならば, Nullを返し, 処理を終了する。
     */
    var fromTagId = fromTag.getTagId();
    var toTagId = toTag.getTagId();

    if (this.getElement(fromTagId, toTagId) != null) {
      return null;
    }

    /*
     * リンカ要素の位置, サイズ, 領域方向, 位置関係を取得する。
     */
    var bounds = this.calculateLinkerBounds(fromTag, toTag);
    var left = bounds.left;
    var top = bounds.top;
    var width = bounds.width;
    var height = bounds.height;

    /*
     * リンカ要素のスタイルを設定する。
     */
    var linkerId = Linker.LINKER_ID_PREFIX + fromTagId + Linker.LINKER_ID_SEPARATOR + toTagId;
    var linker = Builder.node('div', {id: linkerId, className: 'linker'});
    var style = linker.style;
    var leftString = left + 'px';
    var topString = top + 'px';
    var widthString = width + 'px';
    var heightString = height + 'px';
    var lineWidthString = Linker.LINE_WIDTH + 'px';

    style.left = leftString;
    style.top = topString;
    style.width = widthString;
    style.height = heightString;

    /*
     * リンカ要素を構成する線を生成し, リンカ要素を構築する。
     */
    if (bounds.isHorizontal) {

      /*
       * 水平線を生成する。
       */
      var horizontal = Builder.node('div');

      Element.setStyle(horizontal, {
        width: widthString,
        height: lineWidthString
      });

      linker.appendChild(horizontal);

    } else {

      /*
       * フロム要素とトゥ要素の位置関係を元に,
       * 2つの水平線の位置を求める。
       */
      var zero = '0';
      var halfWidthString = (width / 2 + 0.5) + 'px'; // IEなどは0.5pxを1pxと捕らえられないため

      switch (bounds.position) {
        case Linker.POSITION_TOP_LEFT:
          fromLeftString = zero;
          fromTopString = zero;
          toLeftString = halfWidthString;
          toTopString = heightString;
          break;
        case Linker.POSITION_TOP_RIGHT:
          fromLeftString = halfWidthString;
          fromTopString = zero;
          toLeftString = zero;
          toTopString = heightString;
          break;
        case Linker.POSITION_BOTTOM_LEFT:
          fromLeftString = zero;
          fromTopString = heightString;
          toLeftString = halfWidthString;
          toTopString = zero;
          break;
        case Linker.POSITION_BOTTOM_RIGHT:
          fromLeftString = halfWidthString;
          fromTopString = heightString;
          toLeftString = zero;
          toTopString = zero;
          break;
      }

      /*
       * 水平線(フロム要素)を生成する。
       */
      var horizontal1 = Builder.node('div');

      Element.setStyle(horizontal1, {
        position: 'absolute',
        left: fromLeftString,
        top: fromTopString,
        width: halfWidthString,
        height: lineWidthString
      });

      linker.appendChild(horizontal1);

      /*
       * 垂直線を生成する。
       */
      var vertical = Builder.node('div');

      Element.setStyle(vertical, {
        position: 'absolute',
        left: (width / 2 - 1) + 'px',
        top: zero,
        width: lineWidthString,
        height: (height + 2) + 'px'
      });

      linker.appendChild(vertical);

      /*
       * 水平線(トゥ要素)を生成する。
       */
      var horizontal2 = Builder.node('div');

      Element.setStyle(horizontal2, {
        position: 'absolute',
        left: toLeftString,
        top: toTopString,
        width: halfWidthString,
        height: lineWidthString
      });

      linker.appendChild(horizontal2);

    }

    return linker;

  },

  calculateLinkerBounds: function(fromTag, toTag) {

    /*
     * タグの要素を取得する。
     */
    var from = fromTag.getElement();
    var to = toTag.getElement();

    /*
     * フロムの要素の位置, サイズを取得する。
     */
    var fromLeft = Element.getLeft(from);
    var fromTop = Element.getTop(from);
    var fromWidth = Element.getWidth(from);
    var fromHeight = Element.getHeight(from);
    var fromRight = fromLeft + fromWidth;

    /*
     * トゥの要素の位置, サイズを取得する。
     */
    var toLeft = Element.getLeft(to);
    var toTop = Element.getTop(to);
    var toWidth = Element.getWidth(to);
    var toHeight = Element.getHeight(to);
    var toRight = toLeft + toWidth;

    /*
     * Mathクラスのメソッドをローカル変数に代入する(.演算子は処理が重いため)。
     */
    var max = Math.max;
    var min = Math.min;
    var abs = Math.abs;
    var floor = Math.floor;

    /*
     * リンカ要素の位置, サイズ, 領域方向(水平, 垂直),
     * フロムおよびトゥ要素の位置関係を求めて返す。
     */
    var fromPointY = floor(fromTop + fromHeight / 2);
    var toPointY = floor(toTop + toHeight / 2);
    var left = max(min(fromRight, toLeft), min(fromLeft, toRight));
    var top = min(fromPointY, toPointY);
    var width = min(abs(fromRight - toLeft), abs(fromLeft - toRight));
    var height = abs(fromPointY - toPointY);
    var isVertical = (width == 0);
    var isHorizontal = (height == 0);

    if (fromPointY < toPointY) {
      position = (fromLeft < left) ? Linker.POSITION_TOP_LEFT : Linker.POSITION_TOP_RIGHT;
    } else {
      position = (fromLeft < left) ? Linker.POSITION_BOTTOM_LEFT : Linker.POSITION_BOTTOM_RIGHT;
    }

    return {
      left: left,
      top: top,
      width: width,
      height: height,
      isVertical: isVertical,
      isHorizontal: isHorizontal,
      position: position
    };

  },

  /*
   * getElement
   *
   *--------------------------------------------------------------------- */
  getElement: function(fromTagId, toTagId) {

    /*
     * 与えられた引数に対するリンカを取得する。
     * ただし取得できなかった場合は, フロムとトゥを反転し,
     * もう一度取得を試みる。
     */
    var linkerIdPrefix = Linker.LINKER_ID_PREFIX;
    var linkerIdSeparator = Linker.LINKER_ID_SEPARATOR;
    var element = $(linkerIdPrefix + fromTagId + linkerIdSeparator + toTagId);

    if (!element) {
      element = $(linkerIdPrefix + toTagId + linkerIdSeparator + fromTagId);
    }

    /*
     * 要素を取得できた場合はその要素を, 取得できなかった場合はNullを返す。
     */
    return (element) ? element : null;

  }

};

