// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview First run UI.
 */

<include src="step.js">

// Transitions durations.
/** @const  */ var DEFAULT_TRANSITION_DURATION_MS = 400;
/** @const  */ var BG_TRANSITION_DURATION_MS = 800;

/**
 * Changes visibility of element with animated transition.
 * @param {Element} element Element which visibility should be changed.
 * @param {boolean} visible Whether element should be visible after transition.
 * @param {number=} opt_transitionDuration Time length of transition in
 *     milliseconds. Default value is DEFAULT_TRANSITION_DURATION_MS.
 * @param {function()=} opt_onFinished Called after transition has finished.
 */
function changeVisibility(
    element, visible, opt_transitionDuration, opt_onFinished) {
  var classes = element.classList;
  // If target visibility is the same as current element visibility.
  if (classes.contains('transparent') === !visible) {
    if (opt_onFinished)
      opt_onFinished();
    return;
  }
  var transitionDuration = (opt_transitionDuration === undefined) ?
    cr.FirstRun.getDefaultTransitionDuration() : opt_transitionDuration;
  var style = element.style;
  var oldDurationValue = style.getPropertyValue('transition-duration');
  style.setProperty('transition-duration', transitionDuration + 'ms');
  var transition = visible ? 'show-animated' : 'hide-animated';
  classes.add(transition);
  classes.toggle('transparent');
  element.addEventListener('webkitTransitionEnd', function f() {
    element.removeEventListener('webkitTransitionEnd', f);
    classes.remove(transition);
    if (oldDurationValue)
      style.setProperty('transition-duration', oldDurationValue);
    else
      style.removeProperty('transition-duration');
    if (opt_onFinished)
      opt_onFinished();
  });
  ensureTransitionEndEvent(element, transitionDuration);
}

cr.define('cr.FirstRun', function() {
  return {
    // Whether animated transitions are enabled.
    transitionsEnabled_: false,

    // SVG element representing UI background.
    background_: null,

    // Container for background.
    backgroundContainer_: null,

    // Mask element describing transparent "holes" in background.
    mask_: null,

    // Pattern used for creating rectangular holes.
    rectangularHolePattern_: null,

    // Pattern used for creating round holes.
    roundHolePattern_: null,

    // Dictionary keeping all available tutorial steps by their names.
    steps_: {},

    // Element representing step currently shown for user.
    currentStep_: null,

    /**
     * Initializes internal structures and preparing steps.
     */
    initialize: function() {
      disableTextSelectAndDrag();
      this.transitionsEnabled_ = loadTimeData.getBoolean('transitionsEnabled');
      this.background_ = $('background');
      this.backgroundContainer_ = $('background-container');
      this.mask_ = $('mask');
      this.rectangularHolePattern_ = $('rectangular-hole-pattern');
      this.rectangularHolePattern_.removeAttribute('id');
      this.roundHolePattern_ = $('round-hole-pattern');
      this.roundHolePattern_.removeAttribute('id');
      var stepElements = document.getElementsByClassName('step');
      for (var i = 0; i < stepElements.length; ++i) {
        var step = stepElements[i];
        cr.FirstRun.DecorateStep(step);
        this.steps_[step.getName()] = step;
      }
      this.setBackgroundVisible(true, function() {
        chrome.send('initialized');
      });
    },

    /**
     * Hides all elements and background.
     */
    finalize: function() {
      // At first we hide holes (job 1) and current step (job 2) simultaneously,
      // then background.
      var jobsLeft = 2;
      var onJobDone = function() {
        --jobsLeft;
        if (jobsLeft)
          return;
        this.setBackgroundVisible(false, function() {
          chrome.send('finalized');
        });
      }.bind(this);
      this.doHideCurrentStep_(function(name) {
        if (name)
          chrome.send('stepHidden', [name]);
        onJobDone();
      });
      this.removeHoles(onJobDone);
    },

    /**
     * Adds transparent rectangular hole to background.
     * @param {number} x X coordinate of top-left corner of hole.
     * @param {number} y Y coordinate of top-left corner of hole.
     * @param {number} widht Width of hole.
     * @param {number} height Height of hole.
     */
    addRectangularHole: function(x, y, width, height) {
      var hole = this.rectangularHolePattern_.cloneNode();
      hole.setAttribute('x', x);
      hole.setAttribute('y', y);
      hole.setAttribute('width', width);
      hole.setAttribute('height', height);
      this.mask_.appendChild(hole);
      setTimeout(function() {
        changeVisibility(hole, true);
      }, 0);
    },

    /**
     * Adds transparent round hole to background.
     * @param {number} x X coordinate of circle center.
     * @param {number} y Y coordinate of circle center.
     * @param {number} radius Radius of circle.
     */
    addRoundHole: function(x, y, radius) {
      var hole = this.roundHolePattern_.cloneNode();
      hole.setAttribute('cx', x);
      hole.setAttribute('cy', y);
      hole.setAttribute('r', radius);
      this.mask_.appendChild(hole);
      setTimeout(function() {
        changeVisibility(hole, true);
      }, 0);
    },

    /**
     * Removes all holes previously added by |addHole|.
     * @param {function=} opt_onHolesRemoved Called after all holes have been
     *     hidden.
     */
    removeHoles: function(opt_onHolesRemoved) {
      var mask = this.mask_;
      var holes = Array.prototype.slice.call(
          mask.getElementsByClassName('hole'));
      var holesLeft = holes.length;
      if (!holesLeft) {
        if (opt_onHolesRemoved)
          opt_onHolesRemoved();
        return;
      }
      holes.forEach(function(hole) {
        changeVisibility(hole, false, this.getDefaultTransitionDuration(),
            function() {
              mask.removeChild(hole);
              --holesLeft;
              if (!holesLeft && opt_onHolesRemoved)
                opt_onHolesRemoved();
            });
      }.bind(this));
    },

    /**
     * Hides currently active step and notifies chrome after step has been
     * hidden.
     */
    hideCurrentStep: function() {
      assert(this.currentStep_);
      this.doHideCurrentStep_(function(name) {
        chrome.send('stepHidden', [name]);
      });
    },

    /**
     * Hides currently active step.
     * @param {function(string)=} opt_onStepHidden Called after step has been
     *     hidden.
     */
    doHideCurrentStep_: function(opt_onStepHidden) {
      if (!this.currentStep_) {
        if (opt_onStepHidden)
          opt_onStepHidden();
        return;
      }
      var name = this.currentStep_.getName();
      this.currentStep_.hide(true, function() {
        this.currentStep_ = null;
        if (opt_onStepHidden)
          opt_onStepHidden(name);
      }.bind(this));
    },

    /**
     * Shows step with given name in given position.
     * @param {string} name Name of step.
     * @param {object} position Optional parameter with optional fields |top|,
     *     |right|, |bottom|, |left| used for step positioning.
     * @param {Array} pointWithOffset Optional parameter for positioning
     *     bubble. Contains [x, y, offset], where (x, y) - point to which bubble
     *     points, offset - distance between arrow and point.
     */
    showStep: function(name, position, pointWithOffset) {
      assert(!this.currentStep_);
      if (!this.steps_.hasOwnProperty(name))
        throw Error('Step "' + name + '" not found.');
      var step = this.steps_[name];
      if (position)
        step.setPosition(position);
      if (pointWithOffset)
        step.setPointsTo(pointWithOffset.slice(0, 2), pointWithOffset[2]);
      step.show(true, function(step) {
        step.focusDefaultControl();
        this.currentStep_ = step;
        chrome.send('stepShown', [name]);
      }.bind(this));
    },

    /**
     * Sets visibility of the background.
     * @param {boolean} visibility Whether background should be visible.
     * @param {function()=} opt_onCompletion Called after visibility has
     *     changed.
     */
    setBackgroundVisible: function(visible, opt_onCompletion) {
      changeVisibility(this.backgroundContainer_, visible,
          this.getBackgroundTransitionDuration(), opt_onCompletion);
    },

    /**
     * Returns default duration of animated transitions, in ms.
     */
    getDefaultTransitionDuration: function() {
      return this.transitionsEnabled_ ? DEFAULT_TRANSITION_DURATION_MS : 0;
    },

    /**
     * Returns duration of transitions of background shield, in ms.
     */
    getBackgroundTransitionDuration: function() {
      return this.transitionsEnabled_ ? BG_TRANSITION_DURATION_MS : 0;
    }
  };
});

/**
 * Initializes UI.
 */
window.onload = function() {
  cr.FirstRun.initialize();
};
