/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * 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.android.server.wm;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase;
import android.view.cts.surfacevalidator.PixelChecker;
import android.widget.FrameLayout;
import android.window.SurfaceSyncGroup;

import androidx.annotation.NonNull;

/**
 * A validator class that will create a SurfaceView and then update its size over and over. The code
 * will request to sync the SurfaceView content with the main window and validate that there was
 * never an empty area (black color). The test uses {@link SurfaceSyncGroup} class to gather the
 * content it wants to synchronize.
 */
public class SurfaceViewSyncValidatorTestCase implements ISurfaceValidatorTestCase {
    private static final String TAG = "SurfaceSyncGroupValidatorTestCase";

    private final Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            updateSurfaceViewSize();
            mHandler.postDelayed(this, 100);
        }
    };

    private Handler mHandler;
    private SurfaceView mSurfaceView;
    private boolean mLastExpanded = true;

    private RenderingThread mRenderingThread;
    private FrameLayout mParent;

    private SurfaceSyncGroup mSyncGroup;

    final SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {
            final Canvas canvas = holder.lockCanvas();
            canvas.drawARGB(255, 100, 100, 100);
            holder.unlockCanvasAndPost(canvas);
            Log.d(TAG, "surfaceCreated");
            mRenderingThread = new RenderingThread(holder);
            mRenderingThread.start();
        }

        @Override
        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
                int height) {
            if (mSyncGroup != null) {
                mSyncGroup.add(mSurfaceView, frameCallback ->
                        mRenderingThread.setFrameCallback(frameCallback));
                mSyncGroup.markSyncReady();
                mSyncGroup = null;
            }
        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
            mRenderingThread.stopRendering();
        }
    };

    @Override
    public PixelChecker getChecker() {
        return new PixelChecker(Color.BLACK) {
            @Override
            public boolean checkPixels(int matchingPixelCount, int width, int height) {
                return matchingPixelCount == 0;
            }
        };
    }

    @Override
    public void start(Context context, FrameLayout parent) {
        mSurfaceView = new SurfaceView(context);
        mSurfaceView.getHolder().addCallback(mCallback);
        mParent = parent;

        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(MATCH_PARENT, 600);
        parent.addView(mSurfaceView, layoutParams);
        mHandler = new Handler(Looper.getMainLooper());
        mHandler.post(mRunnable);
    }

    @Override
    public void end() {
        mHandler.removeCallbacks(mRunnable);
    }

    public void updateSurfaceViewSize() {
        if (mRenderingThread == null || mSyncGroup != null || !mRenderingThread.isReadyToSync()) {
            return;
        }

        Log.d(TAG, "updateSurfaceViewSize");

        final int height;
        if (mLastExpanded) {
            height = 300;
        } else {
            height = 600;
        }
        mLastExpanded = !mLastExpanded;

        mRenderingThread.pauseRendering();
        mSyncGroup = new SurfaceSyncGroup(TAG);
        mSyncGroup.add(mParent.getRootSurfaceControl(), null /* runanble */);

        ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
        svParams.height = height;
        mSurfaceView.setLayoutParams(svParams);
    }

    private static class RenderingThread extends HandlerThread {
        private final SurfaceHolder mSurfaceHolder;
        private SurfaceSyncGroup.SurfaceViewFrameCallback mFrameCallback;
        private boolean mPauseRendering;
        private boolean mComplete;

        int mColorValue = 0;
        int mColorDelta = 10;

        @Override
        public void run() {
            try {
                while (true) {
                    sleep(10);
                    synchronized (this) {
                        if (mComplete) {
                            break;
                        }
                        if (mPauseRendering) {
                            continue;
                        }

                        if (mFrameCallback != null) {
                            Log.d(TAG, "onFrameStarted");
                            mFrameCallback.onFrameStarted();
                        }

                        mColorValue += mColorDelta;
                        if (mColorValue > 245 || mColorValue < 10) {
                            mColorDelta *= -1;
                        }

                        Canvas c = mSurfaceHolder.lockCanvas();
                        if (c != null) {
                            c.drawRGB(255, mColorValue, 255 - mColorValue);
                            mSurfaceHolder.unlockCanvasAndPost(c);
                        }

                        mFrameCallback = null;
                    }
                }
            } catch (InterruptedException e) {
            }
        }

        RenderingThread(SurfaceHolder holder) {
            super("RenderingThread");
            mSurfaceHolder = holder;
        }

        public void pauseRendering() {
            synchronized (this) {
                mPauseRendering = true;
            }
        }

        private boolean isReadyToSync() {
            synchronized (this) {
                return mFrameCallback == null;
            }
        }
        public void setFrameCallback(SurfaceSyncGroup.SurfaceViewFrameCallback frameCallback) {
            synchronized (this) {
                mFrameCallback = frameCallback;
                mPauseRendering = false;
            }
        }

        public void stopRendering() {
            synchronized (this) {
                mComplete = true;
            }
        }
    }
}
