/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 */

'use strict';

var _prodInvariant = require('./reactProdInvariant'),
    _assign = require('object-assign');

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var React = require('react/lib/React');
var ReactCompositeComponent = require('./ReactCompositeComponent');
var ReactDefaultBatchingStrategy = require('./ReactDefaultBatchingStrategy');
var ReactReconciler = require('./ReactReconciler');
var ReactReconcileTransaction = require('./ReactReconcileTransaction');
var ReactUpdates = require('./ReactUpdates');

var emptyObject = require('fbjs/lib/emptyObject');
var getNextDebugID = require('react/lib/getNextDebugID');
var invariant = require('fbjs/lib/invariant');

function injectDefaults() {
  ReactUpdates.injection.injectReconcileTransaction(ReactReconcileTransaction);
  ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
}

var NoopInternalComponent = function () {
  function NoopInternalComponent(element) {
    _classCallCheck(this, NoopInternalComponent);

    this._renderedOutput = element;
    this._currentElement = element;

    if (process.env.NODE_ENV !== 'production') {
      this._debugID = getNextDebugID();
    }
  }

  NoopInternalComponent.prototype.mountComponent = function mountComponent() {};

  NoopInternalComponent.prototype.receiveComponent = function receiveComponent(element) {
    this._renderedOutput = element;
    this._currentElement = element;
  };

  NoopInternalComponent.prototype.unmountComponent = function unmountComponent() {};

  NoopInternalComponent.prototype.getHostNode = function getHostNode() {
    return undefined;
  };

  NoopInternalComponent.prototype.getPublicInstance = function getPublicInstance() {
    return null;
  };

  return NoopInternalComponent;
}();

var ShallowComponentWrapper = function (element) {
  // TODO: Consolidate with instantiateReactComponent
  if (process.env.NODE_ENV !== 'production') {
    this._debugID = getNextDebugID();
  }

  this.construct(element);
};
_assign(ShallowComponentWrapper.prototype, ReactCompositeComponent, {
  _constructComponent: ReactCompositeComponent._constructComponentWithoutOwner,
  _instantiateReactComponent: function (element) {
    return new NoopInternalComponent(element);
  },
  _replaceNodeWithMarkup: function () {},
  _renderValidatedComponent: ReactCompositeComponent._renderValidatedComponentWithoutOwnerOrContext
});

function _batchedRender(renderer, element, context) {
  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
  renderer._render(element, transaction, context);
  ReactUpdates.ReactReconcileTransaction.release(transaction);
}

var ReactShallowRenderer = function () {
  function ReactShallowRenderer() {
    _classCallCheck(this, ReactShallowRenderer);

    this._instance = null;
  }

  ReactShallowRenderer.prototype.getMountedInstance = function getMountedInstance() {
    return this._instance ? this._instance._instance : null;
  };

  ReactShallowRenderer.prototype.render = function render(element, context) {
    // Ensure we've done the default injections. This might not be true in the
    // case of a simple test that only requires React and the TestUtils in
    // conjunction with an inline-requires transform.
    injectDefaults();

    !React.isValidElement(element) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactShallowRenderer render(): Invalid component element.%s', typeof element === 'function' ? ' Instead of passing a component class, make sure to instantiate ' + 'it by passing it to React.createElement.' : '') : _prodInvariant('12', typeof element === 'function' ? ' Instead of passing a component class, make sure to instantiate ' + 'it by passing it to React.createElement.' : '') : void 0;
    !(typeof element.type !== 'string') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactShallowRenderer render(): Shallow rendering works only with custom components, not primitives (%s). Instead of calling `.render(el)` and inspecting the rendered output, look at `el.props` directly instead.', element.type) : _prodInvariant('13', element.type) : void 0;

    if (!context) {
      context = emptyObject;
    }
    ReactUpdates.batchedUpdates(_batchedRender, this, element, context);

    return this.getRenderOutput();
  };

  ReactShallowRenderer.prototype.getRenderOutput = function getRenderOutput() {
    return this._instance && this._instance._renderedComponent && this._instance._renderedComponent._renderedOutput || null;
  };

  ReactShallowRenderer.prototype.unmount = function unmount() {
    if (this._instance) {
      ReactReconciler.unmountComponent(this._instance, false);
    }
  };

  ReactShallowRenderer.prototype.unstable_batchedUpdates = function unstable_batchedUpdates(callback, bookkeeping) {
    // This is used by Enzyme for fake-simulating events in shallow mode.
    injectDefaults();
    return ReactUpdates.batchedUpdates(callback, bookkeeping);
  };

  ReactShallowRenderer.prototype._render = function _render(element, transaction, context) {
    if (this._instance) {
      ReactReconciler.receiveComponent(this._instance, element, transaction, context);
    } else {
      var instance = new ShallowComponentWrapper(element);
      ReactReconciler.mountComponent(instance, transaction, null, null, context, 0);
      this._instance = instance;
    }
  };

  return ReactShallowRenderer;
}();

ReactShallowRenderer.createRenderer = function () {
  return new ReactShallowRenderer();
};

module.exports = ReactShallowRenderer;