text: /**
   * 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 AnimatedImplementation
   * @flow
   * @format
   * @preventMunge
   */
  'use strict';
  
  const {AnimatedEvent, attachNativeEvent} = require('./AnimatedEvent');
  const AnimatedAddition = require('./nodes/AnimatedAddition');
  const AnimatedDiffClamp = require('./nodes/AnimatedDiffClamp');
  const AnimatedDivision = require('./nodes/AnimatedDivision');
  const AnimatedInterpolation = require('./nodes/AnimatedInterpolation');
  const AnimatedModulo = require('./nodes/AnimatedModulo');
  const AnimatedMultiplication = require('./nodes/AnimatedMultiplication');
  const AnimatedNode = require('./nodes/AnimatedNode');
  const AnimatedProps = require('./nodes/AnimatedProps');
  const AnimatedTracking = require('./nodes/AnimatedTracking');
  const AnimatedValue = require('./nodes/AnimatedValue');
  const AnimatedValueXY = require('./nodes/AnimatedValueXY');
  const DecayAnimation = require('./animations/DecayAnimation');
  const SpringAnimation = require('./animations/SpringAnimation');
  const TimingAnimation = require('./animations/TimingAnimation');
  
  const createAnimatedComponent = require('./createAnimatedComponent');
  
  import type {
    AnimationConfig,
    EndCallback,
    EndResult,
  } from './animations/Animation';
  import type {TimingAnimationConfig} from './animations/TimingAnimation';
  import type {DecayAnimationConfig} from './animations/DecayAnimation';
  import type {SpringAnimationConfig} from './animations/SpringAnimation';
  import type {Mapping, EventConfig} from './AnimatedEvent';
  
  type CompositeAnimation = {
    start: (callback?: ?EndCallback) => void,
    stop: () => void,
    reset: () => void,
    _startNativeLoop: (iterations?: number) => void,
    _isUsingNativeDriver: () => boolean,
  };
  
  const add = function(
    a: AnimatedNode | number,
    b: AnimatedNode | number,
  ): AnimatedAddition {
    return new AnimatedAddition(a, b);
  };
  
  const divide = function(
    a: AnimatedNode | number,
    b: AnimatedNode | number,
  ): AnimatedDivision {
    return new AnimatedDivision(a, b);
  };
  
  const multiply = function(
    a: AnimatedNode | number,
    b: AnimatedNode | number,
  ): AnimatedMultiplication {
    return new AnimatedMultiplication(a, b);
  };
  
  const modulo = function(a: AnimatedNode, modulus: number): AnimatedModulo {
    return new AnimatedModulo(a, modulus);
  };
  
  const diffClamp = function(
    a: AnimatedNode,
    min: number,
    max: number,
  ): AnimatedDiffClamp {
    return new AnimatedDiffClamp(a, min, max);
  };
  
  const _combineCallbacks = function(
    callback: ?EndCallback,
    config: AnimationConfig,
  ) {
    if (callback && config.onComplete) {
      return (...args) => {
        config.onComplete && config.onComplete(...args);
        callback && callback(...args);
      };
    } else {
      return callback || config.onComplete;
    }
  };
  
  const maybeVectorAnim = function(
    value: AnimatedValue | AnimatedValueXY,
    config: Object,
    anim: (value: AnimatedValue, config: Object) => CompositeAnimation,
  ): ?CompositeAnimation {
    if (value instanceof AnimatedValueXY) {
      const configX = {...config};
      const configY = {...config};
      for (const key in config) {
        const {x, y} = config[key];
        if (x !== undefined && y !== undefined) {
          configX[key] = x;
          configY[key] = y;
        }
      }
      const aX = anim((value: AnimatedValueXY).x, configX);
      const aY = anim((value: AnimatedValueXY).y, configY);
      // We use `stopTogether: false` here because otherwise tracking will break
      // because the second animation will get stopped before it can update.
      return parallel([aX, aY], {stopTogether: false});
    }
    return null;
  };
  
  const spring = function(
    value: AnimatedValue | AnimatedValueXY,
    config: SpringAnimationConfig,
  ): CompositeAnimation {
    const start = function(
      animatedValue: AnimatedValue | AnimatedValueXY,
      configuration: SpringAnimationConfig,
      callback?: ?EndCallback,
    ): void {
      callback = _combineCallbacks(callback, configuration);
      const singleValue: any = animatedValue;
      const singleConfig: any = configuration;
      singleValue.stopTracking();
      if (configuration.toValue instanceof AnimatedNode) {
        singleValue.track(
          new AnimatedTracking(
            singleValue,
            configuration.toValue,
            SpringAnimation,
            singleConfig,
            callback,
          ),
        );
      } else {
        singleValue.animate(new SpringAnimation(singleConfig), callback);
      }
    };
    return (
      maybeVectorAnim(value, config, spring) || {
        start: function(callback?: ?EndCallback): void {
          start(value, config, callback);
        },
  
        stop: function(): void {
          value.stopAnimation();
        },
  
        reset: function(): void {
          value.resetAnimation();
        },
  
        _startNativeLoop: function(iterations?: number): void {
          const singleConfig = {...config, iterations};
          start(value, singleConfig);
        },
  
        _isUsingNativeDriver: function(): boolean {
          return config.useNativeDriver || false;
        },
      }
    );
  };
  
  const timing = function(
    value: AnimatedValue | AnimatedValueXY,
    config: TimingAnimationConfig,
  ): CompositeAnimation {
    const start = function(
      animatedValue: AnimatedValue | AnimatedValueXY,
      configuration: TimingAnimationConfig,
      callback?: ?EndCallback,
    ): void {
      callback = _combineCallbacks(callback, configuration);
      const singleValue: any = animatedValue;
      const singleConfig: any = configuration;
      singleValue.stopTracking();
      if (configuration.toValue instanceof AnimatedNode) {
        singleValue.track(
          new AnimatedTracking(
            singleValue,
            configuration.toValue,
            TimingAnimation,
            singleConfig,
            callback,
          ),
        );
      } else {
        singleValue.animate(new TimingAnimation(singleConfig), callback);
      }
    };
  
    return (
      maybeVectorAnim(value, config, timing) || {
        start: function(callback?: ?EndCallback): void {
          start(value, config, callback);
        },
  
        stop: function(): void {
          value.stopAnimation();
        },
  
        reset: function(): void {
          value.resetAnimation();
        },
  
        _startNativeLoop: function(iterations?: number): void {
          const singleConfig = {...config, iterations};
          start(value, singleConfig);
        },
  
        _isUsingNativeDriver: function(): boolean {
          return config.useNativeDriver || false;
        },
      }
    );
  };
  
  const decay = function(
    value: AnimatedValue | AnimatedValueXY,
    config: DecayAnimationConfig,
  ): CompositeAnimation {
    const start = function(
      animatedValue: AnimatedValue | AnimatedValueXY,
      configuration: DecayAnimationConfig,
      callback?: ?EndCallback,
    ): void {
      callback = _combineCallbacks(callback, configuration);
      const singleValue: any = animatedValue;
      const singleConfig: any = configuration;
      singleValue.stopTracking();
      singleValue.animate(new DecayAnimation(singleConfig), callback);
    };
  
    return (
      maybeVectorAnim(value, config, decay) || {
        start: function(callback?: ?EndCallback): void {
          start(value, config, callback);
        },
  
        stop: function(): void {
          value.stopAnimation();
        },
  
        reset: function(): void {
          value.resetAnimation();
        },
  
        _startNativeLoop: function(iterations?: number): void {
          const singleConfig = {...config, iterations};
          start(value, singleConfig);
        },
  
        _isUsingNativeDriver: function(): boolean {
          return config.useNativeDriver || false;
        },
      }
    );
  };
  
  const sequence = function(
    animations: Array<CompositeAnimation>,
  ): CompositeAnimation {
    let current = 0;
    return {
      start: function(callback?: ?EndCallback) {
        const onComplete = function(result) {
          if (!result.finished) {
            callback && callback(result);
            return;
          }
  
          current++;
  
          if (current === animations.length) {
            callback && callback(result);
            return;
          }
  
          animations[current].start(onComplete);
        };
  
        if (animations.length === 0) {
          callback && callback({finished: true});
        } else {
          animations[current].start(onComplete);
        }
      },
  
      stop: function() {
        if (current < animations.length) {
          animations[current].stop();
        }
      },
  
      reset: function() {
        animations.forEach((animation, idx) => {
          if (idx <= current) {
            animation.reset();
          }
        });
        current = 0;
      },
  
      _startNativeLoop: function() {
        throw new Error(
          'Loops run using the native driver cannot contain Animated.sequence animations',
        );
      },
  
      _isUsingNativeDriver: function(): boolean {
        return false;
      },
    };
  };
  
  type ParallelConfig = {
    stopTogether?: boolean, // If one is stopped, stop all.  default: true
  };
  const parallel = function(
    animations: Array<CompositeAnimation>,
    config?: ?ParallelConfig,
  ): CompositeAnimation {
    let doneCount = 0;
    // Make sure we only call stop() at most once for each animation
    const hasEnded = {};
    const stopTogether = !(config && config.stopTogether === false);
  
    const result = {
      start: function(callback?: ?EndCallback) {
        if (doneCount === animations.length) {
          callback && callback({finished: true});
          return;
        }
  
        animations.forEach((animation, idx) => {
          const cb = function(endResult) {
            hasEnded[idx] = true;
            doneCount++;
            if (doneCount === animations.length) {
              doneCount = 0;
              callback && callback(endResult);
              return;
            }
  
            if (!endResult.finished && stopTogether) {
              result.stop();
            }
          };
  
          if (!animation) {
            cb({finished: true});
          } else {
            animation.start(cb);
          }
        });
      },
  
      stop: function(): void {
        animations.forEach((animation, idx) => {
          !hasEnded[idx] && animation.stop();
          hasEnded[idx] = true;
        });
      },
  
      reset: function(): void {
        animations.forEach((animation, idx) => {
          animation.reset();
          hasEnded[idx] = false;
          doneCount = 0;
        });
      },
  
      _startNativeLoop: function() {
        throw new Error(
          'Loops run using the native driver cannot contain Animated.parallel animations',
        );
      },
  
      _isUsingNativeDriver: function(): boolean {
        return false;
      },
    };
  
    return result;
  };
  
  const delay = function(time: number): CompositeAnimation {
    // Would be nice to make a specialized implementation
    return timing(new AnimatedValue(0), {toValue: 0, delay: time, duration: 0});
  };
  
  const stagger = function(
    time: number,
    animations: Array<CompositeAnimation>,
  ): CompositeAnimation {
    return parallel(
      animations.map((animation, i) => {
        return sequence([delay(time * i), animation]);
      }),
    );
  };
  
  type LoopAnimationConfig = {iterations: number};
  
  const loop = function(
    animation: CompositeAnimation,
    {iterations = -1}: LoopAnimationConfig = {},
  ): CompositeAnimation {
    let isFinished = false;
    let iterationsSoFar = 0;
    return {
      start: function(callback?: ?EndCallback) {
        const restart = function(result: EndResult = {finished: true}): void {
          if (
            isFinished ||
            iterationsSoFar === iterations ||
            result.finished === false
          ) {
            callback && callback(result);
          } else {
            iterationsSoFar++;
            animation.reset();
            animation.start(restart);
          }
        };
        if (!animation || iterations === 0) {
          callback && callback({finished: true});
        } else {
          if (animation._isUsingNativeDriver()) {
            animation._startNativeLoop(iterations);
          } else {
            restart(); // Start looping recursively on the js thread
          }
        }
      },
  
      stop: function(): void {
        isFinished = true;
        animation.stop();
      },
  
      reset: function(): void {
        iterationsSoFar = 0;
        isFinished = false;
        animation.reset();
      },
  
      _startNativeLoop: function() {
        throw new Error(
          'Loops run using the native driver cannot contain Animated.loop animations',
        );
      },
  
      _isUsingNativeDriver: function(): boolean {
        return animation._isUsingNativeDriver();
      },
    };
  };
  
  function forkEvent(
    event: ?AnimatedEvent | ?Function,
    listener: Function,
  ): AnimatedEvent | Function {
    if (!event) {
      return listener;
    } else if (event instanceof AnimatedEvent) {
      event.__addListener(listener);
      return event;
    } else {
      return (...args) => {
        typeof event === 'function' && event(...args);
        listener(...args);
      };
    }
  }
  
  function unforkEvent(
    event: ?AnimatedEvent | ?Function,
    listener: Function,
  ): void {
    if (event && event instanceof AnimatedEvent) {
      event.__removeListener(listener);
    }
  }
  
  const event = function(argMapping: Array<?Mapping>, config?: EventConfig): any {
    const animatedEvent = new AnimatedEvent(argMapping, config);
    if (animatedEvent.__isNative) {
      return animatedEvent;
    } else {
      return animatedEvent.__getHandler();
    }
  };
  
  /**
   * The `Animated` library is designed to make animations fluid, powerful, and
   * easy to build and maintain. `Animated` focuses on declarative relationships
   * between inputs and outputs, with configurable transforms in between, and
   * simple `start`/`stop` methods to control time-based animation execution.
   *
   * The simplest workflow for creating an animation is to to create an
   * `Animated.Value`, hook it up to one or more style attributes of an animated
   * component, and then drive updates via animations using `Animated.timing()`:
   *
   * ```javascript
   * Animated.timing(                            // Animate value over time
   *   this.state.fadeAnim,                      // The value to drive
   *   {
   *     toValue: 1,                             // Animate to final value of 1
   *   }
   * ).start();                                  // Start the animation
   * ```
   *
   * Refer to the [Animations](docs/animations.html#animated-api) guide to see
   * additional examples of `Animated` in action.
   *
   * ## Overview
   *
   * There are two value types you can use with `Animated`:
   *
   * - [`Animated.Value()`](docs/animated.html#value) for single values
   * - [`Animated.ValueXY()`](docs/animated.html#valuexy) for vectors
   *
   * `Animated.Value` can bind to style properties or other props, and can be
   * interpolated as well. A single `Animated.Value` can drive any number of
   * properties.
   *
   * ### Configuring animations
   *
   * `Animated` provides three types of animation types. Each animation type
   * provides a particular animation curve that controls how your values animate
   * from their initial value to the final value:
   *
   * - [`Animated.decay()`](docs/animated.html#decay) starts with an initial
   *   velocity and gradually slows to a stop.
   * - [`Animated.spring()`](docs/animated.html#spring) provides a simple
   *   spring physics model.
   * - [`Animated.timing()`](docs/animated.html#timing) animates a value over time
   *   using [easing functions](docs/easing.html).
   *
   * In most cases, you will be using `timing()`. By default, it uses a symmetric
   * easeInOut curve that conveys the gradual acceleration of an object to full
   * speed and concludes by gradually decelerating to a stop.
   *
   * ### Working with animations
   *
   * Animations are started by calling `start()` on your animation. `start()`
   * takes a completion callback that will be called when the animation is done.
   * If the animation finished running normally, the completion callback will be
   * invoked with `{finished: true}`. If the animation is done because `stop()`
   * was called on it before it could finish (e.g. because it was interrupted by a
   * gesture or another animation), then it will receive `{finished: false}`.
   *
   * ### Using the native driver
   *
   * By using the native driver, we send everything about the animation to native
   * before starting the animation, allowing native code to perform the animation
   * on the UI thread without having to go through the bridge on every frame.
   * Once the animation has started, the JS thread can be blocked without
   * affecting the animation.
   *
   * You can use the native driver by specifying `useNativeDriver: true` in your
   * animation configuration. See the
   * [Animations](docs/animations.html#using-the-native-driver) guide to learn
   * more.
   *
   * ### Animatable components
   *
   * Only animatable components can be animated. These special components do the
   * magic of binding the animated values to the properties, and do targeted
   * native updates to avoid the cost of the react render and reconciliation
   * process on every frame. They also handle cleanup on unmount so they are safe
   * by default.
   *
   * - [`createAnimatedComponent()`](docs/animated.html#createanimatedcomponent)
   *   can be used to make a component animatable.
   *
   * `Animated` exports the following animatable components using the above
   * wrapper:
   *
   * - `Animated.Image`
   * - `Animated.ScrollView`
   * - `Animated.Text`
   * - `Animated.View`
   *
   * ### Composing animations
   *
   * Animations can also be combined in complex ways using composition functions:
   *
   * - [`Animated.delay()`](docs/animated.html#delay) starts an animation after
   *   a given delay.
   * - [`Animated.parallel()`](docs/animated.html#parallel) starts a number of
   *   animations at the same time.
   * - [`Animated.sequence()`](docs/animated.html#sequence) starts the animations
   *   in order, waiting for each to complete before starting the next.
   * - [`Animated.stagger()`](docs/animated.html#stagger) starts animations in
   *   order and in parallel, but with successive delays.
   *
   * Animations can also be chained together simply by setting the `toValue` of
   * one animation to be another `Animated.Value`. See
   * [Tracking dynamic values](docs/animations.html#tracking-dynamic-values) in
   * the Animations guide.
   *
   * By default, if one animation is stopped or interrupted, then all other
   * animations in the group are also stopped.
   *
   * ### Combining animated values
   *
   * You can combine two animated values via addition, multiplication, division,
   * or modulo to make a new animated value:
   *
   * - [`Animated.add()`](docs/animated.html#add)
   * - [`Animated.divide()`](docs/animated.html#divide)
   * - [`Animated.modulo()`](docs/animated.html#modulo)
   * - [`Animated.multiply()`](docs/animated.html#multiply)
   *
   * ### Interpolation
   *
   * The `interpolate()` function allows input ranges to map to different output
   * ranges. By default, it will extrapolate the curve beyond the ranges given,
   * but you can also have it clamp the output value. It uses lineal interpolation
   * by default but also supports easing functions.
   *
   * - [`interpolate()`](docs/animated.html#interpolate)
   *
   * Read more about interpolation in the
   * [Animation](docs/animations.html#interpolation) guide.
   *
   * ### Handling gestures and other events
   *
   * Gestures, like panning or scrolling, and other events can map directly to
   * animated values using `Animated.event()`. This is done with a structured map
   * syntax so that values can be extracted from complex event objects. The first
   * level is an array to allow mapping across multiple args, and that array
   * contains nested objects.
   *
   * - [`Animated.event()`](docs/animated.html#event)
   *
   * For example, when working with horizontal scrolling gestures, you would do
   * the following in order to map `event.nativeEvent.contentOffset.x` to
   * `scrollX` (an `Animated.Value`):
   *
   * ```javascript
   *  onScroll={Animated.event(
   *    // scrollX = e.nativeEvent.contentOffset.x
   *    [{ nativeEvent: {
   *         contentOffset: {
   *           x: scrollX
   *         }
   *       }
   *     }]
   *  )}
   * ```
   *
   */
  module.exports = {
    /**
     * Standard value class for driving animations.  Typically initialized with
     * `new Animated.Value(0);`
     *
     * See also [`AnimatedValue`](docs/animated.html#animatedvalue).
     */
    Value: AnimatedValue,
    /**
     * 2D value class for driving 2D animations, such as pan gestures.
     *
     * See also [`AnimatedValueXY`](docs/animated.html#animatedvaluexy).
     */
    ValueXY: AnimatedValueXY,
    /**
     * exported to use the Interpolation type in flow
     *
     * See also [`AnimatedInterpolation`](docs/animated.html#animatedinterpolation).
     */
    Interpolation: AnimatedInterpolation,
    /**
     * Exported for ease of type checking. All animated values derive from this class.
     */
    Node: AnimatedNode,
  
    /**
     * Animates a value from an initial velocity to zero based on a decay
     * coefficient.
     *
     * Config is an object that may have the following options:
     *
     *   - `velocity`: Initial velocity.  Required.
     *   - `deceleration`: Rate of decay.  Default 0.997.
     *   - `useNativeDriver`: Uses the native driver when true. Default false.
     */
    decay,
    /**
     * Animates a value along a timed easing curve. The
     * [`Easing`](docs/easing.html) module has tons of predefined curves, or you
     * can use your own function.
     *
     * Config is an object that may have the following options:
     *
     *   - `duration`: Length of animation (milliseconds).  Default 500.
     *   - `easing`: Easing function to define curve.
     *     Default is `Easing.inOut(Easing.ease)`.
     *   - `delay`: Start the animation after delay (milliseconds).  Default 0.
     *   - `useNativeDriver`: Uses the native driver when true. Default false.
     */
    timing,
    /**
     * Spring animation based on Rebound and
     * [Origami](https://facebook.github.io/origami/).  Tracks velocity state to
     * create fluid motions as the `toValue` updates, and can be chained together.
     *
     * Config is an object that may have the following options. Note that you can
     * only define bounciness/speed or tension/friction but not both:
     *
     *   - `friction`: Controls "bounciness"/overshoot.  Default 7.
     *   - `tension`: Controls speed.  Default 40.
     *   - `speed`: Controls speed of the animation. Default 12.
     *   - `bounciness`: Controls bounciness. Default 8.
     *   - `useNativeDriver`: Uses the native driver when true. Default false.
     */
    spring,
  
    /**
     * Creates a new Animated value composed from two Animated values added
     * together.
     */
    add,
  
    /**
     * Creates a new Animated value composed by dividing the first Animated value
     * by the second Animated value.
     */
    divide,
  
    /**
     * Creates a new Animated value composed from two Animated values multiplied
     * together.
     */
    multiply,
  
    /**
     * Creates a new Animated value that is the (non-negative) modulo of the
     * provided Animated value
     */
    modulo,
  
    /**
     * Create a new Animated value that is limited between 2 values. It uses the
     * difference between the last value so even if the value is far from the bounds
     * it will start changing when the value starts getting closer again.
     * (`value = clamp(value + diff, min, max)`).
     *
     * This is useful with scroll events, for example, to show the navbar when
     * scrolling up and to hide it when scrolling down.
     */
    diffClamp,
  
    /**
     * Starts an animation after the given delay.
     */
    delay,
    /**
     * Starts an array of animations in order, waiting for each to complete
     * before starting the next.  If the current running animation is stopped, no
     * following animations will be started.
     */
    sequence,
    /**
     * Starts an array of animations all at the same time.  By default, if one
     * of the animations is stopped, they will all be stopped.  You can override
     * this with the `stopTogether` flag.
     */
    parallel,
    /**
     * Array of animations may run in parallel (overlap), but are started in
     * sequence with successive delays.  Nice for doing trailing effects.
     */
    stagger,
    /**
    * Loops a given animation continuously, so that each time it reaches the
    * end, it resets and begins again from the start. Can specify number of
    * times to loop using the key 'iterations' in the config. Will loop without
    * blocking the UI thread if the child animation is set to 'useNativeDriver'.
    */
    loop,
  
    /**
     * Takes an array of mappings and extracts values from each arg accordingly,
     * then calls `setValue` on the mapped outputs.  e.g.
     *
     *```javascript
     *  onScroll={Animated.event(
     *    [{nativeEvent: {contentOffset: {x: this._scrollX}}}],
     *    {listener},          // Optional async listener
     *  )}
     *  ...
     *  onPanResponderMove: Animated.event([
     *    null,                // raw event arg ignored
     *    {dx: this._panX},    // gestureState arg
     *  ]),
     *```
     *
     * Config is an object that may have the following options:
     *
     *   - `listener`: Optional async listener.
     *   - `useNativeDriver`: Uses the native driver when true. Default false.
     */
    event,
  
    /**
     * Make any React component Animatable.  Used to create `Animated.View`, etc.
     */
    createAnimatedComponent,
  
    /**
     * Imperative API to attach an animated value to an event on a view. Prefer using
     * `Animated.event` with `useNativeDrive: true` if possible.
     */
    attachNativeEvent,
  
    /**
     * Advanced imperative API for snooping on animated events that are passed in through props. Use
     * values directly where possible.
     */
    forkEvent,
    unforkEvent,
  
    __PropsOnlyForTests: AnimatedProps,
  };
  