/*
 *  Copyright 2013 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;


/**
 * Java wrapper of native AndroidVideoTrackSource.
 */
public class VideoSource extends MediaSource {
    /**
     * Simple aspect ratio clas for use in constraining output format.
     */
    public static class AspectRatio {
        public static final AspectRatio UNDEFINED = new AspectRatio(/* width= */ 0, /* height= */ 0);

        public final int width;
        public final int height;

        public AspectRatio(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }

    private final NativeAndroidVideoTrackSource nativeAndroidVideoTrackSource;
    private final Object videoProcessorLock = new Object();
    private VideoProcessor videoProcessor;
    private boolean isCapturerRunning;

    private final CapturerObserver capturerObserver = new CapturerObserver() {
        @Override
        public void onCapturerStarted(boolean success) {
            nativeAndroidVideoTrackSource.setState(success);
            synchronized (videoProcessorLock) {
                isCapturerRunning = success;
                if (videoProcessor != null) {
                    videoProcessor.onCapturerStarted(success);
                }
            }
        }

        @Override
        public void onCapturerStopped() {
            nativeAndroidVideoTrackSource.setState(/* isLive= */ false);
            synchronized (videoProcessorLock) {
                isCapturerRunning = false;
                if (videoProcessor != null) {
                    videoProcessor.onCapturerStopped();
                }
            }
        }

        @Override
        public void onFrameCaptured(VideoFrame frame) {
            final NativeAndroidVideoTrackSource.FrameAdaptationParameters parameters =
                    nativeAndroidVideoTrackSource.adaptFrame(frame);
            if (parameters == null) {
                // Drop frame.
                return;
            }

            final VideoFrame.Buffer adaptedBuffer =
                    frame.getBuffer().cropAndScale(parameters.cropX, parameters.cropY, parameters.cropWidth,
                            parameters.cropHeight, parameters.scaleWidth, parameters.scaleHeight);
            final VideoFrame adaptedFrame =
                    new VideoFrame(adaptedBuffer, frame.getRotation(), parameters.timestampNs);

            synchronized (videoProcessorLock) {
                if (videoProcessor != null) {
                    videoProcessor.onFrameCaptured(adaptedFrame);
                    adaptedBuffer.release();
                    return;
                }
            }
            nativeAndroidVideoTrackSource.onFrameCaptured(adaptedFrame);
            adaptedBuffer.release();
        }
    };

    public VideoSource(long nativeSource) {
        super(nativeSource);
        this.nativeAndroidVideoTrackSource = new NativeAndroidVideoTrackSource(nativeSource);
    }

    /**
     * Calling this function will cause frames to be scaled down to the requested resolution. Also,
     * frames will be cropped to match the requested aspect ratio, and frames will be dropped to match
     * the requested fps. The requested aspect ratio is orientation agnostic and will be adjusted to
     * maintain the input orientation, so it doesn't matter if e.g. 1280x720 or 720x1280 is requested.
     */
    public void adaptOutputFormat(int width, int height, int fps) {
        final int maxSide = Math.max(width, height);
        final int minSide = Math.min(width, height);
        adaptOutputFormat(maxSide, minSide, minSide, maxSide, fps);
    }

    /**
     * Same as above, but allows setting two different target resolutions depending on incoming
     * frame orientation. This gives more fine-grained control and can e.g. be used to force landscape
     * video to be cropped to portrait video.
     */
    public void adaptOutputFormat(
            int landscapeWidth, int landscapeHeight, int portraitWidth, int portraitHeight, int fps) {
        adaptOutputFormat(new AspectRatio(landscapeWidth, landscapeHeight),
                /* maxLandscapePixelCount= */ landscapeWidth * landscapeHeight,
                new AspectRatio(portraitWidth, portraitHeight),
                /* maxPortraitPixelCount= */ portraitWidth * portraitHeight, fps);
    }

    /**
     * Same as above, with even more control as each constraint is optional.
     */
    public void adaptOutputFormat(AspectRatio targetLandscapeAspectRatio,
                                  Integer maxLandscapePixelCount, AspectRatio targetPortraitAspectRatio,
                                  Integer maxPortraitPixelCount, Integer maxFps) {
        nativeAndroidVideoTrackSource.adaptOutputFormat(targetLandscapeAspectRatio,
                maxLandscapePixelCount, targetPortraitAspectRatio, maxPortraitPixelCount, maxFps);
    }

    /**
     * Hook for injecting a custom video processor before frames are passed onto WebRTC. The frames
     * will be cropped and scaled depending on CPU and network conditions before they are passed to
     * the video processor. Frames will be delivered to the video processor on the same thread they
     * are passed to this object. The video processor is allowed to deliver the processed frames
     * back on any thread.
     */
    public void setVideoProcessor(VideoProcessor newVideoProcessor) {
        synchronized (videoProcessorLock) {
            if (videoProcessor != null) {
                videoProcessor.setSink(/* sink= */ null);
                if (isCapturerRunning) {
                    videoProcessor.onCapturerStopped();
                }
            }
            videoProcessor = newVideoProcessor;
            if (newVideoProcessor != null) {
                newVideoProcessor.setSink(nativeAndroidVideoTrackSource::onFrameCaptured);
                if (isCapturerRunning) {
                    newVideoProcessor.onCapturerStarted(/* success= */ true);
                }
            }
        }
    }

    public CapturerObserver getCapturerObserver() {
        return capturerObserver;
    }

    /**
     * Returns a pointer to webrtc::VideoTrackSourceInterface.
     */
    long getNativeVideoTrackSource() {
        return getNativeMediaSource();
    }

    @Override
    public void dispose() {
        setVideoProcessor(/* newVideoProcessor= */ null);
        super.dispose();
    }
}
