/*
 * Copyright (c) 2013-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *  This file was pulled from the facebook/rebound repository.
 */
package com.facebook.react.modules.core;

import android.annotation.TargetApi;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.Choreographer;

/**
 * Wrapper class for abstracting away availability of the JellyBean Choreographer. If Choreographer
 * is unavailable we fallback to using a normal Handler.
 */
public class ChoreographerCompat {

  private static final long ONE_FRAME_MILLIS = 17;
  private static final boolean IS_JELLYBEAN_OR_HIGHER =
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
  private static final ChoreographerCompat INSTANCE = new ChoreographerCompat();

  private Handler mHandler;
  private Choreographer mChoreographer;

  public static ChoreographerCompat getInstance() {
    return INSTANCE;
  }

  private ChoreographerCompat() {
    if (IS_JELLYBEAN_OR_HIGHER) {
      mChoreographer = getChoreographer();
    } else {
      mHandler = new Handler(Looper.getMainLooper());
    }
  }

  public void postFrameCallback(FrameCallback callbackWrapper) {
    if (IS_JELLYBEAN_OR_HIGHER) {
      choreographerPostFrameCallback(callbackWrapper.getFrameCallback());
    } else {
      mHandler.postDelayed(callbackWrapper.getRunnable(), 0);
    }
  }

  public void postFrameCallbackDelayed(FrameCallback callbackWrapper, long delayMillis) {
    if (IS_JELLYBEAN_OR_HIGHER) {
      choreographerPostFrameCallbackDelayed(callbackWrapper.getFrameCallback(), delayMillis);
    } else {
      mHandler.postDelayed(callbackWrapper.getRunnable(), delayMillis + ONE_FRAME_MILLIS);
    }
  }

  public void removeFrameCallback(FrameCallback callbackWrapper) {
    if (IS_JELLYBEAN_OR_HIGHER) {
      choreographerRemoveFrameCallback(callbackWrapper.getFrameCallback());
    } else {
      mHandler.removeCallbacks(callbackWrapper.getRunnable());
    }
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private Choreographer getChoreographer() {
    return Choreographer.getInstance();
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void choreographerPostFrameCallback(Choreographer.FrameCallback frameCallback) {
    mChoreographer.postFrameCallback(frameCallback);
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void choreographerPostFrameCallbackDelayed(
    Choreographer.FrameCallback frameCallback,
    long delayMillis) {
    mChoreographer.postFrameCallbackDelayed(frameCallback, delayMillis);
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void choreographerRemoveFrameCallback(Choreographer.FrameCallback frameCallback) {
    mChoreographer.removeFrameCallback(frameCallback);
  }

  /**
   * This class provides a compatibility wrapper around the JellyBean FrameCallback with methods
   * to access cached wrappers for submitting a real FrameCallback to a Choreographer or a Runnable
   * to a Handler.
   */
  public static abstract class FrameCallback {

    private Runnable mRunnable;
    private Choreographer.FrameCallback mFrameCallback;

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    Choreographer.FrameCallback getFrameCallback() {
      if (mFrameCallback == null) {
        mFrameCallback = new Choreographer.FrameCallback() {
          @Override
          public void doFrame(long frameTimeNanos) {
            FrameCallback.this.doFrame(frameTimeNanos);
          }
        };
      }
      return mFrameCallback;
    }

    Runnable getRunnable() {
      if (mRunnable == null) {
        mRunnable = new Runnable() {
          @Override
          public void run() {
            doFrame(System.nanoTime());
          }
        };
      }
      return mRunnable;
    }

    /**
     * Just a wrapper for frame callback, see {@link android.view.Choreographer.FrameCallback#doFrame(long)}.
     */
    public abstract void doFrame(long frameTimeNanos);
  }
}
