/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 * @emails oncall+draft_js
 */

'use strict';

import type {BlockNodeRecord} from 'BlockNodeRecord';
import type ContentState from 'ContentState';
import type {DraftDecorator} from 'DraftDecorator';

const Immutable = require('immutable');

const {List} = Immutable;

const DELIMITER = '.';

/**
 * A CompositeDraftDecorator traverses through a list of DraftDecorator
 * instances to identify sections of a ContentBlock that should be rendered
 * in a "decorated" manner. For example, hashtags, mentions, and links may
 * be intended to stand out visually, be rendered as anchors, etc.
 *
 * The list of decorators supplied to the constructor will be used in the
 * order they are provided. This allows the caller to specify a priority for
 * string matching, in case of match collisions among decorators.
 *
 * For instance, I may have a link with a `#` in its text. Though this section
 * of text may match our hashtag decorator, it should not be treated as a
 * hashtag. I should therefore list my link DraftDecorator
 * before my hashtag DraftDecorator when constructing this composite
 * decorator instance.
 *
 * Thus, when a collision like this is encountered, the earlier match is
 * preserved and the new match is discarded.
 */
class CompositeDraftDecorator {
  _decorators: $ReadOnlyArray<DraftDecorator>;

  constructor(decorators: $ReadOnlyArray<DraftDecorator>) {
    // Copy the decorator array, since we use this array order to determine
    // precedence of decoration matching. If the array is mutated externally,
    // we don't want to be affected here.
    this._decorators = decorators.slice();
  }

  /**
   * Returns true if this CompositeDraftDecorator has the same decorators as
   * the given array. This does a reference check, so the decorators themselves
   * have to be the same objects.
   */
  isCompositionOfDecorators(arr: $ReadOnlyArray<DraftDecorator>): boolean {
    if (this._decorators.length !== arr.length) {
      return false;
    }
    for (let ii = 0; ii < arr.length; ii++) {
      if (this._decorators[ii] !== arr[ii]) {
        return false;
      }
    }
    return true;
  }

  getDecorations(
    block: BlockNodeRecord,
    contentState: ContentState,
  ): List<?string> {
    const decorations = Array(block.getText().length).fill(null);

    this._decorators.forEach((decorator: DraftDecorator, ii: number) => {
      let counter = 0;
      const strategy = decorator.strategy;
      function getDecorationsChecker(start: number, end: number) {
        // Find out if any of our matching range is already occupied
        // by another decorator. If so, discard the match. Otherwise, store
        // the component key for rendering.
        if (canOccupySlice(decorations, start, end)) {
          occupySlice(decorations, start, end, ii + DELIMITER + counter);
          counter++;
        }
      }
      strategy(block, getDecorationsChecker, contentState);
    });

    return List(decorations);
  }

  getComponentForKey(key: string): Function {
    const componentKey = parseInt(key.split(DELIMITER)[0], 10);
    return this._decorators[componentKey].component;
  }

  getPropsForKey(key: string): ?Object {
    const componentKey = parseInt(key.split(DELIMITER)[0], 10);
    return this._decorators[componentKey].props;
  }
}

/**
 * Determine whether we can occupy the specified slice of the decorations
 * array.
 */
function canOccupySlice(
  decorations: Array<?string>,
  start: number,
  end: number,
): boolean {
  for (let ii = start; ii < end; ii++) {
    if (decorations[ii] != null) {
      return false;
    }
  }
  return true;
}

/**
 * Splice the specified component into our decoration array at the desired
 * range.
 */
function occupySlice(
  targetArr: Array<?string>,
  start: number,
  end: number,
  componentKey: string,
): void {
  for (let ii = start; ii < end; ii++) {
    targetArr[ii] = componentKey;
  }
}

module.exports = CompositeDraftDecorator;

@@ -2324,24 +2324,110 @@
  true;%0A  %7D%0A%0A
+  getDecorators(): $ReadOnlyArray%3CDraftDecorator%3E %7B%0A    return this._decorators;%0A  %7D%0A%0A
   getDecorat

Add getDecorators to CompositeDraftDecorator