package org.thoughtcrime.redphone.call;

import android.os.Build;
import android.os.PowerManager;
import android.util.Log;

import org.whispersystems.libaxolotl.util.guava.Optional;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Controls access to the proximity lock.
 * The proximity lock is not part of the public API.
 *
 * @author Stuart O. Anderson
*/
class ProximityLock {

  private static final String TAG = ProximityLock.class.getSimpleName();

  private final Method wakelockParameterizedRelease = getWakelockParamterizedReleaseMethod();
  private final Optional<PowerManager.WakeLock> proximityLock;

  private static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
  private static final int WAIT_FOR_PROXIMITY_NEGATIVE    = 1;

  ProximityLock(PowerManager pm) {
    proximityLock = getProximityLock(pm);
  }

  private Optional<PowerManager.WakeLock> getProximityLock(PowerManager pm) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      if (pm.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
        return Optional.fromNullable(pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
                                                    "Signal Proximity Lock"));
      } else {
        return Optional.absent();
      }
    } else {
      try {
        return Optional.fromNullable(pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, "RedPhone Incall"));
      } catch (Throwable t) {
        Log.e(TAG, "Failed to create proximity lock", t);
        return Optional.absent();
      }
    }
  }

  public void acquire() {
    if (!proximityLock.isPresent() || proximityLock.get().isHeld()) {
      return;
    }

    proximityLock.get().acquire();
  }

  public void release() {
    if (!proximityLock.isPresent() || !proximityLock.get().isHeld()) {
      return;
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      proximityLock.get().release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
    } else {
      boolean released = false;

      if (wakelockParameterizedRelease != null) {
        try {
          wakelockParameterizedRelease.invoke(proximityLock.get(), WAIT_FOR_PROXIMITY_NEGATIVE);
          released = true;
        } catch (IllegalAccessException e) {
          Log.w(TAG, e);
        } catch (InvocationTargetException e) {
          Log.w(TAG, e);
        }
      }

      if (!released) {
        proximityLock.get().release();
      }
    }

    Log.d(TAG, "Released proximity lock:" + proximityLock.get().isHeld());
  }

  private static Method getWakelockParamterizedReleaseMethod() {
    try {
      return PowerManager.WakeLock.class.getDeclaredMethod("release", Integer.TYPE);
    } catch (NoSuchMethodException e) {
      Log.d(TAG, "Parameterized WakeLock release not available on this device.");
    }
    return null;
  }
}
