/*
 * Copyright 2016-present The Material Motion Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.android.reactive.motion.rebound;

import com.facebook.rebound.OrigamiValueConverter;
import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringConfig;
import com.facebook.rebound.SpringSystem;
import com.google.android.indefinite.observable.IndefiniteObservable.Subscription;
import com.google.android.reactive.motion.MotionObservable;
import com.google.android.reactive.motion.MotionObservable.MotionObserver;
import com.google.android.reactive.motion.MotionObservable.SimpleMotionObserver;
import com.google.android.reactive.motion.interactions.MaterialSpring;
import com.google.android.reactive.motion.rebound.CompositeReboundSpring.CompositeSpringListener;
import com.google.android.reactive.motion.sources.SpringSource;

import java.util.ArrayList;
import java.util.List;

/**
 * A source for rebound springs.
 * <p>
 * Rebound springs only support animating between float values. This class supports arbitrary T
 * values by vectorizing the value into floats, and animating them individually using separate
 * rebound springs.
 */
public final class ReboundSpringSource<T> extends SpringSource<T> {

  public static final System SYSTEM = new System() {
    @Override
    public <T> SpringSource<T> create(MaterialSpring<?, T> spring) {
      return new ReboundSpringSource<>(spring);
    }
  };

  private final SpringSystem springSystem = SpringSystem.create();
  private final MaterialSpring<?, T> spring;

  private final Spring[] reboundSprings;
  private final SpringConnection<T> connection;
  private final List<CompositeSpringListener> springListeners = new ArrayList<>();

  private Subscription destinationSubscription;
  private Subscription frictionSubscription;
  private Subscription tensionSubscription;

  public ReboundSpringSource(MaterialSpring<?, T> spring) {
    super(spring);
    this.spring = spring;
    reboundSprings = new Spring[spring.vectorizer.getVectorLength()];
    for (int i = 0; i < reboundSprings.length; i++) {
      reboundSprings[i] = springSystem.createSpring();
    }

    connection = new SpringConnection<>(reboundSprings, new CompositeSpringListener() {
      @Override
      public void onCompositeSpringActivate() {
        for (int i = 0, count = springListeners.size(); i < count; i++) {
          springListeners.get(i).onCompositeSpringActivate();
        }
      }

      @Override
      public void onCompositeSpringUpdate(float[] values) {
        for (int i = 0, count = springListeners.size(); i < count; i++) {
          springListeners.get(i).onCompositeSpringUpdate(values);
        }
      }

      @Override
      public void onCompositeSpringAtRest() {
        for (int i = 0, count = springListeners.size(); i < count; i++) {
          springListeners.get(i).onCompositeSpringAtRest();
        }
      }
    });
  }

  @Override
  protected void onConnect(final MotionObserver<T> observer) {
    springListeners.add(new CompositeSpringListener() {

      @Override
      public void onCompositeSpringActivate() {
        observer.state(MotionObservable.ACTIVE);
      }

      @Override
      public void onCompositeSpringUpdate(float[] values) {
        T value = spring.vectorizer.compose(values);
        observer.next(value);
      }

      @Override
      public void onCompositeSpringAtRest() {
        observer.state(MotionObservable.AT_REST);
      }
    });
  }

  @Override
  protected void onEnable(MotionObserver<T> observer) {
    final SpringConfig springConfig = new SpringConfig(0, 0);
    tensionSubscription = spring.tension.subscribe(new SimpleMotionObserver<Float>() {
      @Override
      public void next(Float value) {
        springConfig.tension = OrigamiValueConverter.tensionFromOrigamiValue(value);
      }
    });
    frictionSubscription = spring.friction.subscribe(new SimpleMotionObserver<Float>() {
      @Override
      public void next(Float value) {
        springConfig.friction = OrigamiValueConverter.frictionFromOrigamiValue(value);
      }
    });

    final int count = reboundSprings.length;

    float[] initialValues = new float[count];
    spring.vectorizer.vectorize(spring.initialValue.read(), initialValues);

    float[] initialVelocities = new float[count];
    spring.vectorizer.vectorize(spring.initialVelocity.read(), initialVelocities);

    for (int i = 0; i < count; i++) {
      reboundSprings[i].setSpringConfig(springConfig);
      reboundSprings[i].setCurrentValue(initialValues[i]);
      reboundSprings[i].setVelocity(initialVelocities[i]);
    }

    final float[] endValues = new float[count];
    destinationSubscription = spring.destination.subscribe(new SimpleMotionObserver<T>() {
      @Override
      public void next(T value) {
        spring.vectorizer.vectorize(value, endValues);

        for (int i = 0; i < count; i++) {
          reboundSprings[i].setEndValue(endValues[i]);
        }
      }
    });
  }

  @Override
  protected void onDisable(MotionObserver<T> observer) {
    tensionSubscription.unsubscribe();
    frictionSubscription.unsubscribe();
    destinationSubscription.unsubscribe();

    for (int i = 0; i < reboundSprings.length; i++) {
      reboundSprings[i].setAtRest();
    }
  }

  @Override
  protected void onDisconnect(MotionObserver<T> observer) {
    connection.disconnect();
  }

  private static class SpringConnection<T> {

    private final CompositeReboundSpring spring;
    private final CompositeSpringListener springListener;

    private SpringConnection(
      Spring[] springs,
      CompositeSpringListener springListener) {
      this.spring = new CompositeReboundSpring(springs);
      this.springListener = springListener;

      this.spring.addListener(springListener);
    }

    private void disconnect() {
      spring.removeListener(springListener);
    }
  }
}

@@ -2142,38 +2142,46 @@
 nal 
-SpringConnection%3CT%3E connection
+CompositeReboundSpring compositeSpring
 ;%0A  
@@ -2701,58 +2701,100 @@
   co
-nnection = new SpringConnection%3C%3E(reboundSprings, 
+mpositeSpring = new CompositeReboundSpring(reboundSprings);%0A    compositeSpring.addListener(
 new 
@@ -5866,613 +5866,7 @@
 %0A  %7D
-%0A%0A  @Override%0A  protected void onDisconnect(MotionObserver%3CT%3E observer) %7B%0A    connection.disconnect();%0A  %7D%0A%0A  private static class SpringConnection%3CT%3E %7B%0A%0A    private final CompositeReboundSpring spring;%0A    private final CompositeSpringListener springListener;%0A%0A    private SpringConnection(%0A      Spring%5B%5D springs,%0A      CompositeSpringListener springListener) %7B%0A      this.spring = new CompositeReboundSpring(springs);%0A      this.springListener = springListener;%0A%0A      this.spring.addListener(springListener);%0A    %7D%0A%0A    private void disconnect() %7B%0A      spring.removeListener(springListener);%0A    %7D%0A  %7D
 %0A%7D%0A

Remove Connection class. Now obsoleted by superior Source type.