/**
 * Creates a NavigatorView. This view shows a minified version of the mindmap +
 * controls for adjusting the zoom.
 * 
 * @constructor
 */
mindmaps.NavigatorView = function() {
  var self = this;

  var $content = $("#template-navigator").tmpl();
  var $contentActive = $content.children(".active").hide();
  var $contentInactive = $content.children(".inactive").hide();
  var $dragger = $("#navi-canvas-overlay", $content);
  var $canvas = $("#navi-canvas", $content);

  /**
   * Returns the content.
   * 
   * @returns {jQuery}
   */
  this.getContent = function() {
    return $content;
  };

  /**
   * Shows the active content.
   */
  this.showActiveContent = function() {
    $contentInactive.hide();
    $contentActive.show();
  };

  /**
   * Shows the inactive content.
   */
  this.showInactiveContent = function() {
    $contentActive.hide();
    $contentInactive.show();
  };

  /**
   * Adjusts the size of the red rectangle.
   * 
   * @param {Number} width
   * @param {Nubmer} height
   */
  this.setDraggerSize = function(width, height) {
    $dragger.css({
      width : width,
      height : height
    });
  };

  /**
   * Sets the position of the dragger rectangle.
   * 
   * @param {Number} x
   * @param {Number} y
   */
  this.setDraggerPosition = function(x, y) {
    $dragger.css({
      left : x,
      top : y
    });
  };

  /**
   * Sets the height of the mini canvas.
   * 
   * @param {Number} height
   */
  this.setCanvasHeight = function(height) {
    $("#navi-canvas", $content).css({
      height : height
    });
  };

  /**
   * Gets the width of the mini canvas.
   * 
   * @returns {Number}
   */
  this.getCanvasWidth = function() {
    return $("#navi-canvas", $content).width();
  };

  this.init = function(canvasSize) {
    $("#navi-slider", $content).slider({
      // TODO remove magic numbers. get values from presenter
      min : 0,
      max : 11,
      step : 1,
      value : 3,
      slide : function(e, ui) {
        if (self.sliderChanged) {
          self.sliderChanged(ui.value);
        }
      }
    });

    $("#button-navi-zoom-in", $content).button({
      text : false,
      icons : {
        primary : "ui-icon-zoomin"
      }
    }).click(function() {
      if (self.buttonZoomInClicked) {
        self.buttonZoomInClicked();
      }
    });

    $("#button-navi-zoom-out", $content).button({
      text : false,
      icons : {
        primary : "ui-icon-zoomout"
      }
    }).click(function() {
      if (self.buttonZoomOutClicked) {
        self.buttonZoomOutClicked();
      }
    });

    // make draggable
    $dragger.draggable({
      containment : "parent",
      start : function(e, ui) {
        if (self.dragStart) {
          self.dragStart();
        }
      },
      drag : function(e, ui) {
        if (self.dragging) {
          var x = ui.position.left;
          var y = ui.position.top;
          self.dragging(x, y);
        }
      },
      stop : function(e, ui) {
        if (self.dragStop) {
          self.dragStop();
        }
      }
    });
  };

  /**
   * Draws the complete mindmap onto the mini canvas.
   * 
   * @param {mindmaps.MindMap} mindmap
   * @param {Number} scaleFactor
   */
  this.draw = function(mindmap, scaleFactor) {
    var root = mindmap.root;
    var canvas = $canvas[0];
    var width = canvas.width;
    var height = canvas.height;
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, width, height);
    ctx.lineWidth = 1.8;

    drawNode(root, width / 2, height / 2);

    // draw rect for root
    ctx.fillRect(width / 2 - 4, height / 2 - 2, 8, 4);

    function scale(value) {
      return value / scaleFactor;
    }

    function drawNode(node, x, y) {
      ctx.save();
      ctx.translate(x, y);

      if (!node.collapseChildren) {
        node.forEachChild(function(child) {
          ctx.beginPath();
          ctx.strokeStyle = child.branchColor;
          ctx.moveTo(0, 0);
          var posX = scale(child.offset.x);
          var posY = scale(child.offset.y);
          // var textWidth =
          // ctx.measureText(child.getCaption()).width;
          var textWidth = 5;

          /**
           * draw two lines: one going up to the node, and a second
           * horizontal line for the node caption. if node is left of
           * the parent (posX < 0), we shorten the first line and draw
           * the rest horizontally to arrive at the node's offset
           * position. in the other case, we draw the line to the
           * node's offset and draw another for the text.
           */
          if (posX < 0) {
            var firstStop = posX + textWidth;
            var secondStop = posX;
          } else {
            var firstStop = posX;
            var secondStop = posX + textWidth;
          }
          ctx.lineTo(firstStop, posY);
          ctx.lineTo(secondStop, posY);

          ctx.stroke();
          drawNode(child, secondStop, posY);
        });
      }
      ctx.restore();
    }
  };

  /**
   * Shows the zoom level as percentage.
   * 
   * @param {String} zoom
   */
  this.showZoomLevel = function(zoom) {
    $("#navi-zoom-level").text(zoom);
  };

  /**
   * Sets the value of the zoom slider.
   * 
   * @param {Integer} value
   */
  this.setSliderValue = function(value) {
    $("#navi-slider").slider("value", value);
  };
};

/**
 * Creates a new NavigatorPresenter.
 * 
 * @constructor
 * @param {mindmaps.EventBus} eventBus
 * @param {mindmaps.NavigatorView} view
 * @param {mindmaps.CanvasContainer} container
 * @param {mindmaps.ZoomController} zoomController
 */
mindmaps.NavigatorPresenter = function(eventBus, view, container,
    zoomController) {
  var self = this;
  var $container = container.getContent();
  var viewDragging = false;
  var scale = zoomController.DEFAULT_ZOOM;
  var canvasSize = new mindmaps.Point();
  var docSize = null;
  var mindmap = null;

  /**
   * Calculates and sets the size of the dragger element.
   */
  function calculateDraggerSize() {
    var cw = $container.width() / scale;
    var ch = $container.height() / scale;
    // doc.x / container.x = canvas.x / dragger.x
    var width = (cw * canvasSize.x) / docSize.x;
    var height = (ch * canvasSize.y) / docSize.y;

    // limit size to bounds of canvas
    if (width > canvasSize.x) {
      width = canvasSize.x;
    }

    if (height > canvasSize.y) {
      height = canvasSize.y;
    }

    view.setDraggerSize(width, height);
  }

  /**
   * Calculates and sets the size of the mini canvas.
   */
  function calculateCanvasSize() {
    var width = view.getCanvasWidth();
    var _scale = docSize.x / width;
    var height = docSize.y / _scale;

    view.setCanvasHeight(height);

    canvasSize.x = width;
    canvasSize.y = height;
  }

  /**
   * Calculates and sets the possition of the dragger element.
   */
  function calculateDraggerPosition() {
    var sl = $container.scrollLeft() / scale;
    var st = $container.scrollTop() / scale;

    // sl / dox = dl / cw
    // dl = sl * cw / dox
    var left = sl * canvasSize.x / docSize.x;
    var top = st * canvasSize.y / docSize.y;
    view.setDraggerPosition(left, top);
  }

  /**
   * Calculates and sets the zoom level.
   */
  function calculateZoomLevel() {
    var zoomlevel = scale * 100 + " %";
    view.showZoomLevel(zoomlevel);
  }

  /**
   * Calculates and sets the slider value for the zoom level.
   */
  function calculateSliderValue() {
    var val = scale / zoomController.ZOOM_STEP - 1;
    view.setSliderValue(val);
  }

  /**
   * Initialize view when a document was opened.
   */
  function documentOpened(doc) {
    docSize = doc.dimensions;
    mindmap = doc.mindmap;

    calculateCanvasSize();
    calculateDraggerPosition();
    calculateDraggerSize();
    calculateZoomLevel();
    calculateSliderValue();
    renderView();

    view.showActiveContent();

    // move dragger when container was scrolled
    $container.bind("scroll.navigator-view", function() {
      if (!viewDragging) {
        calculateDraggerPosition();
      }
    });
  }

  /**
   * Update the canvas of the view component.
   */
  function renderView() {
    // draw canvas
    var scale = docSize.x / canvasSize.x;
    view.draw(mindmap, scale);
  }

  /**
   * Reset when document was closed.
   */
  function documentClosed() {
    docSize = null;
    mindmap = null;
    scale = 1;
    // clean up
    // remove listeners
    $container.unbind("scroll.navigator-view");

    view.showInactiveContent();
  }

  /**
   * View callbacks.
   * 
   * @ignore
   */

  view.dragStart = function() {
    viewDragging = true;
  };

  // scroll container when the dragger is dragged
  view.dragging = function(x, y) {
    var scrollLeft = scale * docSize.x * x / canvasSize.x;
    var scrollTop = scale * docSize.y * y / canvasSize.y;
    $container.scrollLeft(scrollLeft).scrollTop(scrollTop);
  };

  view.dragStop = function() {
    viewDragging = false;
  };

  view.buttonZoomInClicked = function() {
    zoomController.zoomIn();
  };

  view.buttonZoomOutClicked = function() {
    zoomController.zoomOut();
  };

  view.sliderChanged = function(value) {
    zoomController.zoomTo((value + 1) * zoomController.ZOOM_STEP);
  };

  // set dragger size when container was resized
  container.subscribe(mindmaps.CanvasContainer.Event.RESIZED, function() {
    if (docSize) {
      calculateDraggerSize();
    }
  });

  // document events
  eventBus.subscribe(mindmaps.Event.DOCUMENT_OPENED, documentOpened);
  eventBus.subscribe(mindmaps.Event.DOCUMENT_CLOSED, documentClosed);

  // node events
  eventBus.subscribe(mindmaps.Event.NODE_MOVED, renderView);
  eventBus.subscribe(mindmaps.Event.NODE_BRANCH_COLOR_CHANGED, renderView);
  eventBus.subscribe(mindmaps.Event.NODE_CREATED, renderView);
  eventBus.subscribe(mindmaps.Event.NODE_DELETED, renderView);
  eventBus.subscribe(mindmaps.Event.NODE_OPENED, renderView);
  eventBus.subscribe(mindmaps.Event.NODE_CLOSED, renderView);

  eventBus.subscribe(mindmaps.Event.ZOOM_CHANGED, function(zoomFactor) {
    scale = zoomFactor;
    calculateDraggerPosition();
    calculateDraggerSize();
    calculateZoomLevel();
    calculateSliderValue();
  });

  this.go = function() {
    view.init();
    view.showInactiveContent();
  };
};
