/**
 * Copyright (c) 2015-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.
 *
 * @providesModule ReactNativeBaseComponent
 * 
 */
'use strict';

var _assign = require('object-assign');

var NativeMethodsMixin = require('./NativeMethodsMixin');
var ReactNativeAttributePayload = require('./ReactNativeAttributePayload');
var ReactNativeComponentTree = require('./ReactNativeComponentTree');
var ReactNativeEventEmitter = require('./ReactNativeEventEmitter');
var ReactNativeTagHandles = require('./ReactNativeTagHandles');
var ReactMultiChild = require('./ReactMultiChild');
var UIManager = require('react-native/lib/UIManager');

var deepFreezeAndThrowOnMutationInDev = require('react-native/lib/deepFreezeAndThrowOnMutationInDev');

var registrationNames = ReactNativeEventEmitter.registrationNames;
var putListener = ReactNativeEventEmitter.putListener;
var deleteListener = ReactNativeEventEmitter.deleteListener;
var deleteAllListeners = ReactNativeEventEmitter.deleteAllListeners;

// require('UIManagerStatTracker').install(); // uncomment to enable

/**
 * @constructor ReactNativeBaseComponent
 * @extends ReactComponent
 * @extends ReactMultiChild
 * @param {!object} UIKit View Configuration.
 */
var ReactNativeBaseComponent = function (viewConfig) {
  this.viewConfig = viewConfig;
};

/**
 * Mixin for containers that contain UIViews. NOTE: markup is rendered markup
 * which is a `viewID` ... see the return value for `mountComponent` !
 */
ReactNativeBaseComponent.Mixin = {
  getPublicInstance: function () {
    // TODO: This should probably use a composite wrapper
    return this;
  },

  unmountComponent: function () {
    ReactNativeComponentTree.uncacheNode(this);
    deleteAllListeners(this);
    this.unmountChildren();
    this._rootNodeID = 0;
  },

  /**
   * Every native component is responsible for allocating its own `tag`, and
   * issuing the native `createView` command. But it is not responsible for
   * recording the fact that its own `rootNodeID` is associated with a
   * `nodeHandle`. Only the code that actually adds its `nodeHandle` (`tag`) as
   * a child of a container can confidently record that in
   * `ReactNativeTagHandles`.
   */
  initializeChildren: function (children, containerTag, transaction, context) {
    var mountImages = this.mountChildren(children, transaction, context);
    // In a well balanced tree, half of the nodes are in the bottom row and have
    // no children - let's avoid calling out to the native bridge for a large
    // portion of the children.
    if (mountImages.length) {

      // TODO: Pool these per platform view class. Reusing the `mountImages`
      // array would likely be a jit deopt.
      var createdTags = [];
      for (var i = 0, l = mountImages.length; i < l; i++) {
        var mountImage = mountImages[i];
        var childTag = mountImage;
        createdTags[i] = childTag;
      }
      UIManager.setChildren(containerTag, createdTags);
    }
  },

  /**
   * Updates the component's currently mounted representation.
   *
   * @param {object} nextElement
   * @param {ReactReconcileTransaction} transaction
   * @param {object} context
   * @internal
   */
  receiveComponent: function (nextElement, transaction, context) {
    var prevElement = this._currentElement;
    this._currentElement = nextElement;

    if (process.env.NODE_ENV !== 'production') {
      for (var key in this.viewConfig.validAttributes) {
        if (nextElement.props.hasOwnProperty(key)) {
          deepFreezeAndThrowOnMutationInDev(nextElement.props[key]);
        }
      }
    }

    var updatePayload = ReactNativeAttributePayload.diff(prevElement.props, nextElement.props, this.viewConfig.validAttributes);

    if (updatePayload) {
      UIManager.updateView(this._rootNodeID, this.viewConfig.uiViewClassName, updatePayload);
    }

    this._reconcileListenersUponUpdate(prevElement.props, nextElement.props);
    this.updateChildren(nextElement.props.children, transaction, context);
  },

  /**
   * @param {object} initialProps Native component props.
   */
  _registerListenersUponCreation: function (initialProps) {
    for (var key in initialProps) {
      // NOTE: The check for `!props[key]`, is only possible because this method
      // registers listeners the *first* time a component is created.
      if (registrationNames[key] && initialProps[key]) {
        var listener = initialProps[key];
        putListener(this, key, listener);
      }
    }
  },

  /**
   * Reconciles event listeners, adding or removing if necessary.
   * @param {object} prevProps Native component props including events.
   * @param {object} nextProps Next native component props including events.
   */
  _reconcileListenersUponUpdate: function (prevProps, nextProps) {
    for (var key in nextProps) {
      if (registrationNames[key] && nextProps[key] !== prevProps[key]) {
        if (nextProps[key]) {
          putListener(this, key, nextProps[key]);
        } else {
          deleteListener(this, key);
        }
      }
    }
  },

  /**
   * Currently this still uses IDs for reconciliation so this can return null.
   *
   * @return {null} Null.
   */
  getHostNode: function () {
    return this._rootNodeID;
  },

  /**
   * @param {ReactNativeReconcileTransaction} transaction
   * @param {?ReactNativeBaseComponent} the parent component instance
   * @param {?object} info about the host container
   * @param {object} context
   * @return {string} Unique iOS view tag.
   */
  mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
    var tag = ReactNativeTagHandles.allocateTag();

    this._rootNodeID = tag;
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;

    if (process.env.NODE_ENV !== 'production') {
      for (var key in this.viewConfig.validAttributes) {
        if (this._currentElement.props.hasOwnProperty(key)) {
          deepFreezeAndThrowOnMutationInDev(this._currentElement.props[key]);
        }
      }
    }

    var updatePayload = ReactNativeAttributePayload.create(this._currentElement.props, this.viewConfig.validAttributes);

    var nativeTopRootTag = hostContainerInfo._tag;
    UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload);

    ReactNativeComponentTree.precacheNode(this, tag);

    this._registerListenersUponCreation(this._currentElement.props);
    this.initializeChildren(this._currentElement.props.children, tag, transaction, context);
    return tag;
  }
};

/**
 * Order of mixins is important. ReactNativeBaseComponent overrides methods in
 * ReactMultiChild.
 */
_assign(ReactNativeBaseComponent.prototype, ReactMultiChild.Mixin, ReactNativeBaseComponent.Mixin, NativeMethodsMixin);

module.exports = ReactNativeBaseComponent;