/*
* Copyright (C) 2015 Pedro Paulo de Amorim.
*
* 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.github.ppamorim.prismview;

import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.facebook.rebound.SimpleSpringListener;
import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringConfig;
import com.facebook.rebound.SpringSystem;
import com.facebook.rebound.SpringUtil;
import com.github.ppamorim.PrismPosition;
import com.github.ppamorim.SpringType;
import com.github.ppamorim.creator.FragmentViewItemAdapter;

/**
 * Provides a Facebook like
 * transition for your activity, this uses Rebound to provide the animations.
 */
public class PrismActivity extends AppCompatActivity {

  public static final int DEFAULT_TENSION = 40;
  public static final int DEFAULT_FRICTION = 5;
  public static final int DEFAULT_BOUNCENESS = 0;
  public static final int DEFAULT_SPEED = 20;
  public static final double DEFAULT_SMALL_RATIO = 0.8;

  private boolean hideEnabled = true;

  public int firstValue;
  public int secondValue;
  public double smallRatio = DEFAULT_SMALL_RATIO;

  public PrismPosition prismPosition = PrismPosition.RIGHT;
  public SpringType springType = SpringType.ORIGAMI;

  private FrameLayout root;
  private View mainView;
  private FrameLayout prismView;

  private FragmentViewItemAdapter fragmentViewItemAdapter;

  private ActivityHelper activityHelper;
  private Spring moveSpring;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    activityHelper = new ActivityHelper();
    activityHelper.config(getWindow());
  }

  /**
   * Initializes the RootView loading the activity_prism layout
   * and add the view of the #layoutResID in the root view.
   *
   * @param layoutResID Main view layout.
   */
  @Override public void setContentView(int layoutResID) {
    super.setContentView(initializeRootView());
    initialize(LayoutInflater.from(getApplicationContext()).inflate(layoutResID, null, false));
  }

  /**
   * This initialize the RootView loading the activity_prism layout
   * and add the view of the #view in the root view.
   *
   * @param view Main view.
   */
  @Override public void setContentView(View view) {
    super.setContentView(initializeRootView());
    initialize(view);
  }

  @Override public void setContentView(View view, ViewGroup.LayoutParams params) {
    super.setContentView(initializeRootView(), params);
    initialize(view);
  }

  /**
   * This check the consistency of the #mainView object and add the #mainView view
   * at the root view, after create a new instance of prismView and add this at the
   * root too.
   * Then, move the #prismView to the right position.
   * @param savedInstanceState
   */
  @Override protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    if (mainView == null) {
      throw new IllegalStateException("mainView is null, did you set this view?");
    }
    root.addView(mainView);
    if (prismView == null) {
      prismView = new FrameLayout(getApplicationContext());
      prismView.setId(prismView.getClass().hashCode());
    }
    root.addView(prismView, FrameLayout.LayoutParams.MATCH_PARENT,
        FrameLayout.LayoutParams.MATCH_PARENT);
    initializePositions();
  }

  /**
   * Add listener for moveSpring when the activity is resumed.
   */
  @Override protected void onResume() {
    super.onResume();
    initializeListeners();
  }

  /**
   * Remove the listener for moveSpring when the activity is paused.
   */
  @Override protected void onPause() {
    super.onPause();
    moveSpring().removeAllListeners();
  }

  /**
   * Inflates the base layout if this library.
   * @return return the root view.
   */
  private FrameLayout initializeRootView() {
    root = (FrameLayout) LayoutInflater.from(getApplicationContext())
        .inflate(R.layout.activity_prism, null, false);
    return root;
  }

  /**
   * Instantiates the #mainView.
   * @param mainView Return a instantiated #mainView.
   */
  private void initialize(View mainView) {
    this.mainView = mainView;
  }

  /**
   * Sets X position of the main view.
   */
  private void initializePositions() {
    if (prismView != null && activityHelper != null) {
      if (prismPosition == PrismPosition.RIGHT) {
        ViewCompat.setTranslationX(prismView, activityHelper.getWidth());
      } else if (prismPosition == PrismPosition.LEFT) {
        ViewCompat.setTranslationX(prismView, -activityHelper.getWidth());
      } else if (prismPosition == PrismPosition.TOP) {
        ViewCompat.setTranslationY(prismView, -activityHelper.getHeight());
      } else if (prismPosition == PrismPosition.BOTTOM) {
        ViewCompat.setTranslationY(prismView, activityHelper.getHeight());
      }
    }
  }

  private void initializeListeners() {
    SimpleSpringListener simpleSpringListener = null;
    if (prismPosition == PrismPosition.RIGHT) {
      simpleSpringListener = translationRight;
    } else if (prismPosition == PrismPosition.LEFT) {
      simpleSpringListener = translationLeft;
    } else if (prismPosition == PrismPosition.TOP) {
      simpleSpringListener = translationTop;
    } else if (prismPosition == PrismPosition.BOTTOM) {
      simpleSpringListener = translationBottom;
    }
    if (simpleSpringListener != null) {
      moveSpring().removeAllListeners().addListener(simpleSpringListener);
    }
  }

  /**
   * Load and replace the #prismView with a fragment.
   * Then, lazily performs a animation (reveal).
   * @param fragmentViewItemAdapter Instance of FragmentViewItemAdapter.
   */
  public void setAdapter(FragmentViewItemAdapter fragmentViewItemAdapter) {
    this.fragmentViewItemAdapter = fragmentViewItemAdapter;
  }

  public void show(int position) {
    if (fragmentViewItemAdapter == null) {
      throw new IllegalStateException("adapter is null");
    }
    if (fragmentViewItemAdapter.getLoadedPosition() != position) {
      fragmentViewItemAdapter.setLoadedPosition(position);
      FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
      transaction.replace(prismView.getId(), fragmentViewItemAdapter.getItem(position));
      transaction.commit();
    }
    reveal();
  }

  /**
   * Active this method when you don't want to hide the mainView
   * when the animation is complete.
   * @param enabled set animation enabled.
   */
  public void setHideEnabled(boolean enabled) {
    this.hideEnabled = enabled;
  }

  /**
   * @return True if animation is enabled.
   */
  public boolean isHideEnabled() {
    return hideEnabled;
  }


  public void setOrigami(int tension, int friction) {
    if (springType == SpringType.ORIGAMI) {
      this.firstValue = tension;
      this.secondValue = friction;
    } else {
      throw  new IllegalStateException("You must use SpringType.ORIGAMI"
          + " if you want to use setOrigami");
    }
  }

  public void setBouncenessSpeed(int bounceness, int speed) {
    if (springType == SpringType.SPEEDBOUNCINESS) {
      this.firstValue = bounceness;
      this.secondValue = speed;
    } else {
      throw  new IllegalStateException("You must use SpringType.SPEEDBOUNCINESS"
          + " if you want to use setSpeedBounceness");
    }
  }

  /**
   * @return Min size of the #mainView when the PrismView is reveled.
   */
  public double getSmallRatio() {
    return smallRatio;
  }

  /**
   * Set the smallest ratio of the #mainView.
   * @param smallRatio Need to be smaller than or equals 1.
   */
  public void setSmallRatio(double smallRatio) {
    if (smallRatio <= 1) {
      this.smallRatio = smallRatio;
    }
  }

  /**
   * @return Type of the spring mode
   */
  public SpringType getSpringType() {
    return springType;
  }

  /**
   * Set the type of spring mode
   * @param springType Enum type
   */
  public void setSpringType(SpringType springType) {
    this.springType = springType;
  }

  /**
   * @return Position of the PrismView
   */
  public PrismPosition getPrismPosition() {
    return prismPosition;
  }

  /**
   * Set the side of the PrismView
   * @param prismPosition Enum position
   */
  public void setPrismPosition(PrismPosition prismPosition) {
    this.prismPosition = prismPosition;
    initializePositions();
    initializeListeners();
  }

  /**
   * Perform the reveal animation.
   */
  public void reveal() {
    reset();
    moveSpring().setCurrentValue(0);
    moveSpring().setEndValue(1);
  }

  /**
   * Perform the hide animation.
   */
  public void hide() {
    reset();
    moveSpring().setCurrentValue(1);
    moveSpring().setEndValue(0);
  }

  /**
   * @return True if getCurrentValue() is bigger than 0.
   */
  public boolean isRevelead() {
    return moveSpring().getCurrentValue() > 0;
  }

  private void showMainView() {
    prismView.setVisibility(View.GONE);
    mainView.setVisibility(View.VISIBLE);
  }

  private void showPrismView() {
    prismView.setVisibility(View.VISIBLE);
    mainView.setVisibility(View.GONE);
  }

  /**
   * Reset the view to perform the reveal or hide animation.
   */
  private void reset() {
    initializePositions();
    prismView.setVisibility(View.VISIBLE);
    mainView.setVisibility(View.VISIBLE);
  }

  /**
   * Create a new instance of SimpleSpringListener that performs
   * a translationX, ScaleX, ScaleY and alpha value change.
   *
   * At the finish of the animation, verify if the getCurrentValue()
   * is equals 1, if true, show the PrismView, else, show the MainView.
   */
  private SimpleSpringListener translationRight = new SimpleSpringListener() {
    @Override public void onSpringUpdate(Spring spring) {
      super.onSpringUpdate(spring);
      double currentValue = spring.getCurrentValue();
      ViewCompat.setTranslationX(prismView, (float) SpringUtil.mapValueFromRangeToRange(
          currentValue, 0, 1, activityHelper.getWidth(), 0));
      mainViewUpdate(currentValue);
    }

    @Override public void onSpringAtRest(Spring spring) {
      super.onSpringAtRest(spring);
      onSpringAtRestOut(spring);
    }
  };

  /**
   * Create a new instance of SimpleSpringListener that performs
   * a translationX, ScaleX, ScaleY and alpha value change.
   *
   * At the finish of the animation, verify if the getCurrentValue()
   * is equals 1, if true, show the PrismView, else, show the MainView.
   */
  private SimpleSpringListener translationLeft = new SimpleSpringListener() {
    @Override public void onSpringUpdate(Spring spring) {
      super.onSpringUpdate(spring);
      double currentValue = spring.getCurrentValue();
      ViewCompat.setTranslationX(prismView, (float) SpringUtil.mapValueFromRangeToRange(
          currentValue, 0, 1, -activityHelper.getWidth(), 0));
      mainViewUpdate(currentValue);
    }

    @Override public void onSpringAtRest(Spring spring) {
      super.onSpringAtRest(spring);
      onSpringAtRestOut(spring);
    }
  };

  /**
   * Create a new instance of SimpleSpringListener that performs
   * a translationX, ScaleX, ScaleY and alpha value change.
   *
   * At the finish of the animation, verify if the getCurrentValue()
   * is equals 1, if true, show the PrismView, else, show the MainView.
   */
  private SimpleSpringListener translationTop = new SimpleSpringListener() {
        @Override public void onSpringUpdate(Spring spring) {
          super.onSpringUpdate(spring);
          double currentValue = spring.getCurrentValue();
          ViewCompat.setTranslationY(prismView,
              (float) SpringUtil.mapValueFromRangeToRange(currentValue, 0, 1,
                  -activityHelper.getHeight(), 0));
          mainViewUpdate(currentValue);
        }

        @Override public void onSpringAtRest(Spring spring) {
          super.onSpringAtRest(spring);
          onSpringAtRestOut(spring);
        }
      };

  /**
   * Create a new instance of SimpleSpringListener that performs
   * a translationX, ScaleX, ScaleY and alpha value change.
   *
   * At the finish of the animation, verify if the getCurrentValue()
   * is equals 1, if true, show the PrismView, else, show the MainView.
   */
  private SimpleSpringListener translationBottom = new SimpleSpringListener() {
    @Override public void onSpringUpdate(Spring spring) {
      super.onSpringUpdate(spring);
      double currentValue = spring.getCurrentValue();
      ViewCompat.setTranslationY(prismView,
          (float) SpringUtil.mapValueFromRangeToRange(currentValue, 0, 1,
              activityHelper.getHeight(), 0));
      mainViewUpdate(currentValue);
    }

    @Override public void onSpringAtRest(Spring spring) {
      super.onSpringAtRest(spring);
      onSpringAtRestOut(spring);
    }
  };

  private void mainViewUpdate(double currentValue) {
    float ratio =  (float) SpringUtil.mapValueFromRangeToRange(currentValue, 0, 1,
        1, smallRatio);
    ViewCompat.setScaleX(mainView, ratio);
    ViewCompat.setScaleY(mainView, ratio);
    ViewCompat.setAlpha(mainView, ratio);
  }

  private void onSpringAtRestOut(Spring spring) {
    if (hideEnabled) {
      if (spring.getCurrentValue() == 1) {
        showPrismView();
      } else {
        showMainView();
      }
    }
  }

  /**
   * Create a new Instance of moveSpring if it's needed.
   * @return Instance of moveSpring.
   */
  private Spring moveSpring() {

    SpringConfig springConfig = null;
    if (springType == SpringType.ORIGAMI) {
      if (isValuesNotSet()) {
        firstValue = DEFAULT_TENSION;
        secondValue = DEFAULT_FRICTION;
      }
      springConfig = SpringConfig.fromOrigamiTensionAndFriction(firstValue, secondValue);
    } else if (springType == SpringType.SPEEDBOUNCINESS) {
      if (isValuesNotSet()) {
        firstValue = DEFAULT_BOUNCENESS;
        secondValue = DEFAULT_SPEED;
      }
      springConfig = SpringConfig.fromBouncinessAndSpeed(firstValue, secondValue);
    }

    if (moveSpring == null) {
      if (springConfig != null) {
        moveSpring = SpringSystem.create().createSpring().setSpringConfig(springConfig);
      }
    } else {
      moveSpring.setSpringConfig(springConfig);
    }
    return moveSpring;
  }

  private boolean isValuesNotSet() {
    return firstValue == -1 || secondValue == -1;
  }

}
