/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 * Copyright 2002-2019, Carrot Search s.c, All Rights Reserved.
 *
 *
 * A simple utility for attaching showing and hiding a
 * data loading and preparation progress indicator. The utility
 * will create and attach all the necessary HTML to the page.
 *
 * Please see demos/loading.html for the usage example.
 *
 * @param foamtree the FoamTree instance whose loading and
 *        layout preparation progress should be monitored
 * @param indicatorHtml the HTML representing the loading message
 */
window.CarrotSearchFoamTree.loader = function(foamtree, indicatorHtml) {
  var deferDataObjectChange = false, dataObjectToSet = undefined;
  var dataObjectToSetProvided = false;

  // Duration of the loading element CSS transition. You may
  // need to modify this value if you modified the default CSS transitions.
  var duration = 350;

  // On Webkit CSS transitions seem to continue running during JS processing.
  // Other browsers need to wait for the transition to complete.
  var waitForTransition = !/chrome|webkit/.test(navigator.userAgent.toLowerCase());

  // Create the indicator element
  var indicator = document.createElement("div");
  indicator.className = "visualization-loading fadeout";
  indicator.innerHTML = indicatorHtml;
  foamtree.get("element").appendChild(indicator);

  // Hide the indicator when rollout starts
  foamtree.on("rolloutStart", hideIndicator);

  return {
    /**
     * Call this method before data loading is initiated to
     * show the loading indicator.
     *
     * If FoamTree is set to perform a pullback animation,
     * the loading indicator will show after pullback is complete.
     */
    started: function() {
      dataObjectToSetProvided = false;
      if (isPullbackEnabled()) {
        // We'll defer setting of the new data object
        // until pullback completes. FoamTree does that
        // internally too, but we need to show the indicator
        // before we set the new data object. If we didn't
        // do that, the element would actually show after
        // the diagram computation completes, which wouldn't
        // make much sense.
        deferDataObjectChange = true;
        once("modelChanged", showIndicatorAndSetDataObject);

        // Set an empty data object to trigger a pullback
        foamtree.set("dataObject", null);
      } else {
        deferDataObjectChange = false;
        showIndicator();
      }
    },

    /**
     * Call this method when data loading completes. This method
     * will take care of setting the new data object on FoamTree
     * and hiding the indicator when the diagram is ready to show.
     *
     * @param dataObject new data object to set
     */
    complete: function (dataObject) {
      dataObjectToSetProvided = true;
      if (deferDataObjectChange) {
        // Just remember the new data object, we'll set it
        // after the pullback completes and the loading indicator
        // is made visible.
        dataObjectToSet = dataObject;
      } else {
        // Defer the update to give the browser a chance
        // to show the loading indicator.
        setTimeout(function() {
          foamtree.set("dataObject", dataObject);
          hideIndicatorIfDataObjectEmpty(dataObjectToSet);
        }, 1 + (waitForTransition ? duration + 50 : 1));
      }
    }
  };

  function showIndicatorAndSetDataObject() {
    showIndicator();
    if (dataObjectToSetProvided && deferDataObjectChange) {
      // Defer the update to give the browser a chance
      // to show the loading indicator.
      setTimeout(function() {
        foamtree.set("dataObject", dataObjectToSet);
        hideIndicatorIfDataObjectEmpty(dataObjectToSet);
      }, 5 + (waitForTransition ? duration + 50 : 1));
    }
    deferDataObjectChange = false;
  }

  function hideIndicatorIfDataObjectEmpty(dataObject) {
    if (!dataObject || !dataObject.groups || dataObject.groups.length == 0) {
      hideIndicator();
    }
  }

  function hideIndicator() {
    addClass(indicator, "fadeout");
  }

  function showIndicator() {
    // It would be good to defer showing of the indicator a bit
    // so that it's not shown when the loading process completes
    // very quickly. However, it's not currently possible to implement
    // because FoamTree diagram computation is performed in one
    // go and blocks until the computation completes. In the future,
    // it would be good to split the computation into chunks between
    // which FoamTree would yield to the UI thread to redraw the
    // DOM updates.
    removeClass(indicator, "fadeout");
  }

  function addClass(element, clazz) {
    var classes = element.className.split(/\s+/);
    if (classes.indexOf(clazz) < 0) {
      classes.push(clazz);
      element.className = classes.join(" ");
    }
  }

  function removeClass(element, clazz) {
    element.className = element.className.split(/\s+/).filter(function (c) {
      return c != clazz;
    }).join(" ");
  }

  function isPullbackEnabled() {
    return foamtree.get("pullbackDuration") > 0 || foamtree.get("fadeDuration") > 0;
  }

  function once(event, fn) {
    foamtree.on(event, function() {
      foamtree.off(event);
      fn();
    });
  }
};