"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _realm = require("../realm.js");

var _index = require("../values/index.js");

var _invariant = require("../invariant.js");

var _invariant2 = _interopRequireDefault(_invariant);

var _utils = require("./utils");

var _index2 = require("../methods/index.js");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

// ReactElementSet keeps records around of the values
// of ReactElement/JSX nodes so we can return the same immutable values
// where possible, i.e. <div /> === <div />
//
// Rather than uses hashes, this class uses linked Maps to track equality of objects.
// It does this by recursively iterating through objects, by their properties/symbols and using
// each property key as a map, and then from that map, each value as a map. The value
// then links to the subsequent property/symbol in the object. This approach ensures insertion
// is maintained through all objects.

class ReactElementSet {
  constructor(realm, equivalenceSet) {
    this.realm = realm;
    this.equivalenceSet = equivalenceSet;
    this.reactElementRoot = new Map();
    this.objectRoot = new Map();
    this.arrayRoot = new Map();
    this.emptyArray = new _index.ArrayValue(realm);
    this.emptyObject = new _index.ObjectValue(realm, realm.intrinsics.ObjectPrototype);
  }


  _createNode() {
    return {
      map: new Map(),
      value: null
    };
  }

  _getKey(key, map, visitedValues) {
    if (!map.has(key)) {
      map.set(key, new Map());
    }
    return map.get(key);
  }

  _getValue(val, map, visitedValues) {
    if (val instanceof _index.StringValue || val instanceof _index.NumberValue) {
      val = val.value;
    } else if (val instanceof _index.AbstractValue) {
      val = this.equivalenceSet.add(val);
    } else if (val instanceof _index.ArrayValue) {
      val = this._getArrayValue(val, visitedValues);
    } else if (val instanceof _index.ObjectValue && !(val instanceof _index.FunctionValue)) {
      val = this._getObjectValue(val, visitedValues);
    }
    if (!map.has(val)) {
      map.set(val, this._createNode());
    }
    return map.get(val);
  }

  // for objects: [key/symbol] -> [key/symbol]... as nodes
  _getObjectValue(object, visitedValues) {
    if (visitedValues.has(object)) return object;
    visitedValues.add(object);

    if ((0, _utils.isReactElement)(object)) {
      return this.add(object);
    }
    let currentMap = this.objectRoot;
    let result;

    for (let [propName] of object.properties) {
      currentMap = this._getKey(propName, currentMap, visitedValues);
      let prop = (0, _utils.getProperty)(this.realm, object, propName);
      result = this._getValue(prop, currentMap, visitedValues);
      currentMap = result.map;
    }
    for (let [symbol] of object.symbols) {
      currentMap = this._getKey(symbol, currentMap, visitedValues);
      let prop = (0, _utils.getProperty)(this.realm, object, symbol);
      result = this._getValue(prop, currentMap, visitedValues);
      currentMap = result.map;
    }
    if (result === undefined) {
      return this.emptyObject;
    }
    if (result.value === null) {
      result.value = object;
    }
    return result.value;
  }

  // for arrays: [0] -> [1] -> [2]... as nodes
  _getArrayValue(array, visitedValues) {
    if (visitedValues.has(array)) return array;
    visitedValues.add(array);
    let lengthValue = (0, _utils.getProperty)(this.realm, array, "length");
    (0, _invariant2.default)(lengthValue instanceof _index.NumberValue);
    let length = lengthValue.value;
    let currentMap = this.arrayRoot;
    let result;

    for (let i = 0; i < length; i++) {
      currentMap = this._getKey(i, currentMap, visitedValues);
      let element = (0, _utils.getProperty)(this.realm, array, "" + i);
      result = this._getValue(element, currentMap, visitedValues);
      currentMap = result.map;
    }
    if (result === undefined) {
      return this.emptyArray;
    }
    if (result.value === null) {
      result.value = array;
    }
    return result.value;
  }

  add(reactElement, visitedValues) {
    if (!visitedValues) visitedValues = new Set();
    let currentMap = this.reactElementRoot;

    // type
    currentMap = this._getKey("type", currentMap, visitedValues);
    let type = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let result = this._getValue(type, currentMap, visitedValues);
    currentMap = result.map;
    // key
    currentMap = this._getKey("key", currentMap, visitedValues);
    let key = (0, _utils.getProperty)(this.realm, reactElement, "key");
    result = this._getValue(key, currentMap, visitedValues);
    currentMap = result.map;
    // ref
    currentMap = this._getKey("ref", currentMap, visitedValues);
    let ref = (0, _utils.getProperty)(this.realm, reactElement, "ref");
    result = this._getValue(ref, currentMap, visitedValues);
    currentMap = result.map;
    // props
    currentMap = this._getKey("props", currentMap, visitedValues);
    let props = (0, _utils.getProperty)(this.realm, reactElement, "props");
    result = this._getValue(props, currentMap, visitedValues);
    currentMap = result.map;

    if (result.value === null) {
      result.value = reactElement;
    }
    (0, _invariant2.default)(result.value instanceof _index.ObjectValue);
    return result.value;
  }
}
exports.default = ReactElementSet; /**
                                    * Copyright (c) 2017-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.
                                    */
//# sourceMappingURL=ReactElementSet.js.map