/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package org.webrtc.videoengine;

import java.io.IOException;
import java.util.List;

import android.content.Context;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;

import java.util.concurrent.CountDownLatch;

import org.mozilla.gecko.annotation.WebRTCJNITarget;

import org.webrtc.CameraEnumerator;
import org.webrtc.Camera1Enumerator;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraVideoCapturer;
import org.webrtc.CapturerObserver;
import org.webrtc.EglBase;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.VideoFrame;
import org.webrtc.VideoFrame.I420Buffer;

public class VideoCaptureAndroid implements CameraVideoCapturer.CameraEventsHandler, CapturerObserver {
  private final static String TAG = "WEBRTC-JC";

  private final String deviceName;
  private volatile long native_capturer;  // |VideoCaptureAndroid*| in C++.
  private Context context;
  private CameraVideoCapturer cameraVideoCapturer;
  private EglBase eglBase;
  private SurfaceTextureHelper surfaceTextureHelper;

  // This class is recreated everytime we start/stop capture, so we
  // can safely create the CountDownLatches here.
  private final CountDownLatch capturerStarted = new CountDownLatch(1);
  private boolean capturerStartedSucceeded = false;
  private final CountDownLatch capturerStopped = new CountDownLatch(1);

 @WebRTCJNITarget
 public VideoCaptureAndroid(String deviceName) {
    // Remove the camera facing information from the name.
    String[] parts = deviceName.split("Facing (front|back):");
    if (parts.length == 2) {
      this.deviceName = parts[1].replace(" (infrared)", "");
    } else {
      Log.e(TAG, "VideoCaptureAndroid: Expected facing mode as part of name: " + deviceName);
      this.deviceName = deviceName;
    }
    this.context = GetContext();

    CameraEnumerator enumerator;
    if (Camera2Enumerator.isSupported(context)) {
      enumerator = new Camera2Enumerator(context);
    } else {
      enumerator = new Camera1Enumerator();
    }
    try {
      cameraVideoCapturer = enumerator.createCapturer(this.deviceName, this);
      eglBase = EglBase.create();
      surfaceTextureHelper = SurfaceTextureHelper.create("VideoCaptureAndroidSurfaceTextureHelper", eglBase.getEglBaseContext());
      cameraVideoCapturer.initialize(surfaceTextureHelper, context, this);
    } catch (java.lang.IllegalArgumentException e) {
      Log.e(TAG, "VideoCaptureAndroid: Exception while creating capturer: " + e);
    }
  }

  // Return the global application context.
  @WebRTCJNITarget
  private static native Context GetContext();

  // Called by native code.  Returns true if capturer is started.
  //
  // Note that this actually opens the camera, and Camera callbacks run on the
  // thread that calls open(), so this is done on the CameraThread.  Since ViE
  // API needs a synchronous success return value we wait for the result.
  @WebRTCJNITarget
  private synchronized boolean startCapture(
      final int width, final int height,
      final int min_mfps, final int max_mfps,
      long native_capturer) {
    Log.d(TAG, "startCapture: " + width + "x" + height + "@" +
        min_mfps + ":" + max_mfps);

    if (cameraVideoCapturer == null) {
      return false;
    }

    cameraVideoCapturer.startCapture(width, height, max_mfps);
    try {
      capturerStarted.await();
    } catch (InterruptedException e) {
      return false;
    }
    if (capturerStartedSucceeded) {
      this.native_capturer = native_capturer;
    }
    return capturerStartedSucceeded;
  }

  // Called by native code.  Returns true when camera is known to be stopped.
  @WebRTCJNITarget
  private synchronized boolean stopCapture() {
    Log.d(TAG, "stopCapture");
    if (cameraVideoCapturer == null) {
      return false;
    }

    native_capturer = 0;
    try {
      cameraVideoCapturer.stopCapture();
      capturerStopped.await();
    } catch (InterruptedException e) {
      return false;
    }
    Log.d(TAG, "stopCapture done");
    return true;
  }

  @WebRTCJNITarget
  private int getDeviceOrientation() {
    int orientation = 0;
    if (context != null) {
      WindowManager wm = (WindowManager) context.getSystemService(
          Context.WINDOW_SERVICE);
      switch(wm.getDefaultDisplay().getRotation()) {
        case Surface.ROTATION_90:
          orientation = 90;
          break;
        case Surface.ROTATION_180:
          orientation = 180;
          break;
        case Surface.ROTATION_270:
          orientation = 270;
          break;
        case Surface.ROTATION_0:
        default:
          orientation = 0;
          break;
      }
    }
    return orientation;
  }

  @WebRTCJNITarget
  private native void ProvideCameraFrame(
      int width, int height,
      java.nio.ByteBuffer dataY, int strideY,
      java.nio.ByteBuffer dataU, int strideU,
      java.nio.ByteBuffer dataV, int strideV,
      int rotation, long timeStamp, long captureObject);

  //
  // CameraVideoCapturer.CameraEventsHandler interface
  //

  // Camera error handler - invoked when camera can not be opened
  // or any camera exception happens on camera thread.
  public void onCameraError(String errorDescription) {}

  // Called when camera is disconnected.
  public void onCameraDisconnected() {}

  // Invoked when camera stops receiving frames.
  public void onCameraFreezed(String errorDescription) {}

  // Callback invoked when camera is opening.
  public void onCameraOpening(String cameraName) {}

  // Callback invoked when first camera frame is available after camera is started.
  public void onFirstFrameAvailable() {}

  // Callback invoked when camera is closed.
  public void onCameraClosed() {}

  //
  // CapturerObserver interface
  //

  // Notify if the capturer have been started successfully or not.
  public void onCapturerStarted(boolean success) {
    capturerStartedSucceeded = success;
    capturerStarted.countDown();
  }

  // Notify that the capturer has been stopped.
  public void onCapturerStopped() {
    capturerStopped.countDown();
  }

  // Delivers a captured frame.
  public void onFrameCaptured(VideoFrame frame) {
    if (native_capturer != 0) {
      I420Buffer i420Buffer = frame.getBuffer().toI420();
      ProvideCameraFrame(i420Buffer.getWidth(), i420Buffer.getHeight(),
          i420Buffer.getDataY(), i420Buffer.getStrideY(),
          i420Buffer.getDataU(), i420Buffer.getStrideU(),
          i420Buffer.getDataV(), i420Buffer.getStrideV(),
          frame.getRotation(),
          frame.getTimestampNs() / 1000000, native_capturer);

      i420Buffer.release();
    }
  }
}
