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 ReactDefaultPerf
   * @typechecks static-only
   */
  
  "use strict";
  
  var DOMProperty = require('DOMProperty');
  var ReactDefaultPerfAnalysis = require('ReactDefaultPerfAnalysis');
  var ReactMount = require('ReactMount');
  var ReactPerf = require('ReactPerf');
  
  var performanceNow = require('performanceNow');
  
  function roundFloat(val) {
    return Math.floor(val * 100) / 100;
  }
  
  function addValue(obj, key, val) {
    obj[key] = (obj[key] || 0) + val;
  }
  
  var ReactDefaultPerf = {
    _allMeasurements: [], // last item in the list is the current one
    _mountStack: [0],
    _injected: false,
  
    start: function() {
      if (!ReactDefaultPerf._injected) {
        ReactPerf.injection.injectMeasure(ReactDefaultPerf.measure);
      }
  
      ReactDefaultPerf._allMeasurements.length = 0;
      ReactPerf.enableMeasure = true;
    },
  
    stop: function() {
      ReactPerf.enableMeasure = false;
    },
  
    getLastMeasurements: function() {
      return ReactDefaultPerf._allMeasurements;
    },
  
    printExclusive: function(measurements) {
      measurements = measurements || ReactDefaultPerf._allMeasurements;
      var summary = ReactDefaultPerfAnalysis.getExclusiveSummary(measurements);
      console.table(summary.map(function(item) {
        return {
          'Component class name': item.componentName,
          'Total inclusive time (ms)': roundFloat(item.inclusive),
          'Exclusive mount time (ms)': roundFloat(item.exclusive),
          'Exclusive render time (ms)': roundFloat(item.render),
          'Mount time per instance (ms)': roundFloat(item.exclusive / item.count),
          'Render time per instance (ms)': roundFloat(item.render / item.count),
          'Instances': item.count
        };
      }));
      // TODO: ReactDefaultPerfAnalysis.getTotalTime() does not return the correct
      // number.
    },
  
    printInclusive: function(measurements) {
      measurements = measurements || ReactDefaultPerf._allMeasurements;
      var summary = ReactDefaultPerfAnalysis.getInclusiveSummary(measurements);
      console.table(summary.map(function(item) {
        return {
          'Owner > component': item.componentName,
          'Inclusive time (ms)': roundFloat(item.time),
          'Instances': item.count
        };
      }));
      console.log(
        'Total time:',
        ReactDefaultPerfAnalysis.getTotalTime(measurements).toFixed(2) + ' ms'
      );
    },
  
    getMeasurementsSummaryMap: function(measurements) {
      var summary = ReactDefaultPerfAnalysis.getInclusiveSummary(
        measurements,
        true
      );
      return summary.map(function(item) {
        return {
          'Owner > component': item.componentName,
          'Wasted time (ms)': item.time,
          'Instances': item.count
        };
      });
    },
  
    printWasted: function(measurements) {
      measurements = measurements || ReactDefaultPerf._allMeasurements;
      console.table(ReactDefaultPerf.getMeasurementsSummaryMap(measurements));
      console.log(
        'Total time:',
        ReactDefaultPerfAnalysis.getTotalTime(measurements).toFixed(2) + ' ms'
      );
    },
  
    printDOM: function(measurements) {
      measurements = measurements || ReactDefaultPerf._allMeasurements;
      var summary = ReactDefaultPerfAnalysis.getDOMSummary(measurements);
      console.table(summary.map(function(item) {
        var result = {};
        result[DOMProperty.ID_ATTRIBUTE_NAME] = item.id;
        result['type'] = item.type;
        result['args'] = JSON.stringify(item.args);
        return result;
      }));
      console.log(
        'Total time:',
        ReactDefaultPerfAnalysis.getTotalTime(measurements).toFixed(2) + ' ms'
      );
    },
  
    _recordWrite: function(id, fnName, totalTime, args) {
      // TODO: totalTime isn't that useful since it doesn't count paints/reflows
      var writes =
        ReactDefaultPerf
          ._allMeasurements[ReactDefaultPerf._allMeasurements.length - 1]
          .writes;
      writes[id] = writes[id] || [];
      writes[id].push({
        type: fnName,
        time: totalTime,
        args: args
      });
    },
  
    measure: function(moduleName, fnName, func) {
      return function(...args) {
        var totalTime;
        var rv;
        var start;
  
        if (fnName === '_renderNewRootComponent' ||
            fnName === 'flushBatchedUpdates') {
          // A "measurement" is a set of metrics recorded for each flush. We want
          // to group the metrics for a given flush together so we can look at the
          // components that rendered and the DOM operations that actually
          // happened to determine the amount of "wasted work" performed.
          ReactDefaultPerf._allMeasurements.push({
            exclusive: {},
            inclusive: {},
            render: {},
            counts: {},
            writes: {},
            displayNames: {},
            totalTime: 0
          });
          start = performanceNow();
          rv = func.apply(this, args);
          ReactDefaultPerf._allMeasurements[
            ReactDefaultPerf._allMeasurements.length - 1
          ].totalTime = performanceNow() - start;
          return rv;
        } else if (moduleName === 'ReactDOMIDOperations' ||
          moduleName === 'ReactComponentBrowserEnvironment') {
          start = performanceNow();
          rv = func.apply(this, args);
          totalTime = performanceNow() - start;
  
          if (fnName === 'mountImageIntoNode') {
            var mountID = ReactMount.getID(args[1]);
            ReactDefaultPerf._recordWrite(mountID, fnName, totalTime, args[0]);
          } else if (fnName === 'dangerouslyProcessChildrenUpdates') {
            // special format
            args[0].forEach(function(update) {
              var writeArgs = {};
              if (update.fromIndex !== null) {
                writeArgs.fromIndex = update.fromIndex;
              }
              if (update.toIndex !== null) {
                writeArgs.toIndex = update.toIndex;
              }
              if (update.textContent !== null) {
                writeArgs.textContent = update.textContent;
              }
              if (update.markupIndex !== null) {
                writeArgs.markup = args[1][update.markupIndex];
              }
              ReactDefaultPerf._recordWrite(
                update.parentID,
                update.type,
                totalTime,
                writeArgs
              );
            });
          } else {
            // basic format
            ReactDefaultPerf._recordWrite(
              args[0],
              fnName,
              totalTime,
              Array.prototype.slice.call(args, 1)
            );
          }
          return rv;
        } else if (moduleName === 'ReactCompositeComponent' && (
          fnName === 'mountComponent' ||
          fnName === 'updateComponent' || // TODO: receiveComponent()?
          fnName === '_renderValidatedComponent')) {
  
          var rootNodeID = fnName === 'mountComponent' ?
            args[0] :
            this._rootNodeID;
          var isRender = fnName === '_renderValidatedComponent';
          var isMount = fnName === 'mountComponent';
  
          var mountStack = ReactDefaultPerf._mountStack;
          var entry = ReactDefaultPerf._allMeasurements[
            ReactDefaultPerf._allMeasurements.length - 1
          ];
  
          if (isRender) {
            addValue(entry.counts, rootNodeID, 1);
          } else if (isMount) {
            mountStack.push(0);
          }
  
          start = performanceNow();
          rv = func.apply(this, args);
          totalTime = performanceNow() - start;
  
          if (isRender) {
            addValue(entry.render, rootNodeID, totalTime);
          } else if (isMount) {
            var subMountTime = mountStack.pop();
            mountStack[mountStack.length - 1] += totalTime;
            addValue(entry.exclusive, rootNodeID, totalTime - subMountTime);
            addValue(entry.inclusive, rootNodeID, totalTime);
          } else {
            addValue(entry.inclusive, rootNodeID, totalTime);
          }
  
          entry.displayNames[rootNodeID] = {
            current: this.constructor.displayName,
            owner: this._owner ? this._owner.constructor.displayName : '<root>'
          };
  
          return rv;
        } else {
          return func.apply(this, args);
        }
      };
    }
  };
  
  module.exports = ReactDefaultPerf;
  