text: /**
   * Copyright 2013-2014 Facebook, Inc.
   *
   * 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.
   *
   * @providesModule ReactDefaultPerfAnalysis
   */
  
  var merge = require('merge');
  
  // Don't try to save users less than 1.2ms (a number I made up)
  var DONT_CARE_THRESHOLD = 1.2;
  var DOM_OPERATION_TYPES = {
    'mountImageIntoNode': 'set innerHTML',
    INSERT_MARKUP: 'set innerHTML',
    MOVE_EXISTING: 'move',
    REMOVE_NODE: 'remove',
    TEXT_CONTENT: 'set textContent',
    'updatePropertyByID': 'update attribute',
    'deletePropertyByID': 'delete attribute',
    'updateStylesByID': 'update styles',
    'updateInnerHTMLByID': 'set innerHTML',
    'dangerouslyReplaceNodeWithMarkupByID': 'replace'
  };
  
  function getTotalTime(measurements) {
    // TODO: return number of DOM ops? could be misleading.
    // TODO: measure dropped frames after reconcile?
    // TODO: log total time of each reconcile and the top-level component
    // class that triggered it.
    var totalTime = 0;
    for (var i = 0; i < measurements.length; i++) {
      var measurement = measurements[i];
      totalTime += measurement.totalTime;
    }
    return totalTime;
  }
  
  function getDOMSummary(measurements) {
    var items = [];
    for (var i = 0; i < measurements.length; i++) {
      var measurement = measurements[i];
      var id;
  
      for (id in measurement.writes) {
        measurement.writes[id].forEach(function(write) {
          items.push({
            id: id,
            type: DOM_OPERATION_TYPES[write.type] || write.type,
            args: write.args
          });
        });
      }
    }
    return items;
  }
  
  function getExclusiveSummary(measurements) {
    var candidates = {};
    var displayName;
  
    for (var i = 0; i < measurements.length; i++) {
      var measurement = measurements[i];
      var allIDs = merge(measurement.exclusive, measurement.inclusive);
  
      for (var id in allIDs) {
        displayName = measurement.displayNames[id].current;
  
        candidates[displayName] = candidates[displayName] || {
          componentName: displayName,
          inclusive: 0,
          exclusive: 0,
          render: 0,
          count: 0
        };
        if (measurement.render[id]) {
          candidates[displayName].render += measurement.render[id];
        }
        if (measurement.exclusive[id]) {
          candidates[displayName].exclusive += measurement.exclusive[id];
        }
        if (measurement.inclusive[id]) {
          candidates[displayName].inclusive += measurement.inclusive[id];
        }
        if (measurement.counts[id]) {
          candidates[displayName].count += measurement.counts[id];
        }
      }
    }
  
    // Now make a sorted array with the results.
    var arr = [];
    for (displayName in candidates) {
      if (candidates[displayName].exclusive >= DONT_CARE_THRESHOLD) {
        arr.push(candidates[displayName]);
      }
    }
  
    arr.sort(function(a, b) {
      return b.exclusive - a.exclusive;
    });
  
    return arr;
  }
  
  function getInclusiveSummary(measurements, onlyClean) {
    var candidates = {};
    var inclusiveKey;
  
    for (var i = 0; i < measurements.length; i++) {
      var measurement = measurements[i];
      var allIDs = merge(measurement.exclusive, measurement.inclusive);
      var cleanComponents;
  
      if (onlyClean) {
        cleanComponents = getUnchangedComponents(measurement);
      }
  
      for (var id in allIDs) {
        if (onlyClean && !cleanComponents[id]) {
          continue;
        }
  
        var displayName = measurement.displayNames[id];
  
        // Inclusive time is not useful for many components without knowing where
        // they are instantiated. So we aggregate inclusive time with both the
        // owner and current displayName as the key.
        inclusiveKey = displayName.owner + ' > ' + displayName.current;
  
        candidates[inclusiveKey] = candidates[inclusiveKey] || {
          componentName: inclusiveKey,
          time: 0,
          count: 0
        };
  
        if (measurement.inclusive[id]) {
          candidates[inclusiveKey].time += measurement.inclusive[id];
        }
        if (measurement.counts[id]) {
          candidates[inclusiveKey].count += measurement.counts[id];
        }
      }
    }
  
    // Now make a sorted array with the results.
    var arr = [];
    for (inclusiveKey in candidates) {
      if (candidates[inclusiveKey].time >= DONT_CARE_THRESHOLD) {
        arr.push(candidates[inclusiveKey]);
      }
    }
  
    arr.sort(function(a, b) {
      return b.time - a.time;
    });
  
    return arr;
  }
  
  function getUnchangedComponents(measurement) {
    // For a given reconcile, look at which components did not actually
    // render anything to the DOM and return a mapping of their ID to
    // the amount of time it took to render the entire subtree.
    var cleanComponents = {};
    var dirtyLeafIDs = Object.keys(measurement.writes);
    var allIDs = merge(measurement.exclusive, measurement.inclusive);
  
    for (var id in allIDs) {
      var isDirty = false;
      // For each component that rendered, see if a component that triggerd
      // a DOM op is in its subtree.
      for (var i = 0; i < dirtyLeafIDs.length; i++) {
        if (dirtyLeafIDs[i].indexOf(id) === 0) {
          isDirty = true;
          break;
        }
      }
      if (!isDirty && measurement.counts[id] > 0) {
        cleanComponents[id] = true;
      }
    }
    return cleanComponents;
  }
  
  var ReactDefaultPerfAnalysis = {
    getExclusiveSummary: getExclusiveSummary,
    getInclusiveSummary: getInclusiveSummary,
    getDOMSummary: getDOMSummary,
    getTotalTime: getTotalTime
  };
  
  module.exports = ReactDefaultPerfAnalysis;
  