/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * 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.chris.video;

import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.opengl.GLES20;
import android.os.Bundle;
import android.os.Trace;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import com.android.grafika.gles.EglCore;
import com.android.grafika.gles.WindowSurface;

/**
 * Exercises some less-commonly-used aspects of SurfaceView.  In particular:
 * <ul>
 * <li> We have three overlapping SurfaceViews.
 * <li> One is at the default depth, one is at "media overlay" depth, and one is on top of the UI.
 * <li> One is marked "secure".
 * </ul>
 * <p>
 * To watch this in systrace, use
 * <code>systrace.py --app=com.android.grafika gfx view sched dalvik</code>
 * (most interesting while bouncing).
 */
public class MultiSurfaceActivity extends Activity implements SurfaceHolder.Callback {
    private static final String TAG = PlayMovieActivity.class.getSimpleName();

    // Number of steps in each direction.  There's actually N+1 positions because we
    // don't re-draw the same position after a rebound.
    private static final int BOUNCE_STEPS = 30;

    private SurfaceView mSurfaceView1;
    private SurfaceView mSurfaceView2;
    private SurfaceView mSurfaceView3;
    private volatile boolean mBouncing;
    private Thread mBounceThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_multi_surface_test);

        // #1 is at the bottom; mark it as secure just for fun.  By default, this will use
        // the RGB565 color format.
        mSurfaceView1 = (SurfaceView) findViewById(R.id.multiSurfaceView1);
        mSurfaceView1.getHolder().addCallback(this);
        mSurfaceView1.setSecure(true);

        // #2 is above it, in the "media overlay"; must be translucent or we will totally
        // obscure #1 and it will be ignored by the compositor.  The addition of the alpha
        // plane should switch us to RGBA8888.
        mSurfaceView2 = (SurfaceView) findViewById(R.id.multiSurfaceView2);
        mSurfaceView2.getHolder().addCallback(this);
        mSurfaceView2.getHolder().setFormat(PixelFormat.TRANSLUCENT);
        mSurfaceView2.setZOrderMediaOverlay(true);

        // #3 is above everything, including the UI.  Also translucent.
        mSurfaceView3 = (SurfaceView) findViewById(R.id.multiSurfaceView3);
        mSurfaceView3.getHolder().addCallback(this);
        mSurfaceView3.getHolder().setFormat(PixelFormat.TRANSLUCENT);
        mSurfaceView3.setZOrderOnTop(true);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mBounceThread != null) {
            stopBouncing();
        }
    }

    /**
     * onClick handler for "bounce" button.
     */
    public void clickBounce(@SuppressWarnings("unused") View unused) {
        Log.d(TAG, "clickBounce bouncing=" + mBouncing);
        if (mBounceThread != null) {
            stopBouncing();
        } else {
            startBouncing();
        }
    }

    private void startBouncing() {
        final Surface surface = mSurfaceView2.getHolder().getSurface();
        if (surface == null || !surface.isValid()) {
            Log.w(TAG, "mSurfaceView2 is not ready");
            return;
        }
        mBounceThread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    long startWhen = System.nanoTime();
                    for (int i = 0; i < BOUNCE_STEPS; i++) {
                        if (!mBouncing) return;
                        drawBouncingCircle(surface, i);
                    }
                    for (int i = BOUNCE_STEPS; i > 0; i--) {
                        if (!mBouncing) return;
                        drawBouncingCircle(surface, i);
                    }
                    long duration = System.nanoTime() - startWhen;
                    double framesPerSec = 1000000000.0 / (duration / (BOUNCE_STEPS * 2.0));
                    Log.d(TAG, "Bouncing at " + framesPerSec + " fps");
                }
            }
        };
        mBouncing = true;
        mBounceThread.setName("Bouncer");
        mBounceThread.start();
    }

    /**
     * Signals the bounce-thread to stop, and waits for it to do so.
     */
    private void stopBouncing() {
        Log.d(TAG, "Stopping bounce thread");
        mBouncing = false;      // tell thread to stop
        try {
            mBounceThread.join();
        } catch (InterruptedException ignored) {}
        mBounceThread = null;
    }

    /**
     * Returns an ordinal value for the SurfaceHolder, or -1 for an invalid surface.
     */
    private int getSurfaceId(SurfaceHolder holder) {
        if (holder.equals(mSurfaceView1.getHolder())) {
            return 1;
        } else if (holder.equals(mSurfaceView2.getHolder())) {
            return 2;
        } else if (holder.equals(mSurfaceView3.getHolder())) {
            return 3;
        } else {
            return -1;
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        int id = getSurfaceId(holder);
        if (id < 0) {
            Log.w(TAG, "surfaceCreated UNKNOWN holder=" + holder);
        } else {
            Log.d(TAG, "surfaceCreated #" + id + " holder=" + holder);

        }
    }

    /**
     * SurfaceHolder.Callback method
     * <p>
     * Draws when the surface changes.  Since nothing else is touching the surface, and
     * we're not animating, we just draw here and ignore it.
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.d(TAG, "surfaceChanged fmt=" + format + " size=" + width + "x" + height +
                " holder=" + holder);

        int id = getSurfaceId(holder);
        boolean portrait = height > width;
        Surface surface = holder.getSurface();

        switch (id) {
            case 1:
                // default layer: circle on left / top
                if (portrait) {
                    drawCircleSurface(surface, width / 2, height / 4, width / 4);
                } else {
                    drawCircleSurface(surface, width / 4, height / 2, height / 4);
                }
                break;
            case 2:
                // media overlay layer: circle on right / bottom
                if (portrait) {
                    drawCircleSurface(surface, width / 2, height * 3 / 4, width / 4);
                } else {
                    drawCircleSurface(surface, width * 3 / 4, height / 2, height / 4);
                }
                break;
            case 3:
                // top layer: alpha stripes
                if (portrait) {
                    int halfLine = width / 16 + 1;
                    drawRectSurface(surface, width/2 - halfLine, 0, halfLine*2, height);
                } else {
                    int halfLine = height / 16 + 1;
                    drawRectSurface(surface, 0, height/2 - halfLine, width, halfLine*2);
                }
                break;
            default:
                throw new RuntimeException("wha?");
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // ignore
        Log.d(TAG, "Surface destroyed holder=" + holder);
    }

    /**
     * Clears the surface, then draws some alpha-blended rectangles with GL.
     * <p>
     * Creates a temporary EGL context just for the duration of the call.
     */
    private void drawRectSurface(Surface surface, int left, int top, int width, int height) {
        EglCore eglCore = new EglCore();
        WindowSurface win = new WindowSurface(eglCore, surface, false);
        win.makeCurrent();
        GLES20.glClearColor(0, 0, 0, 0);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
        for (int i = 0; i < 4; i++) {
            int x, y, w, h;
            if (width < height) {
                // vertical
                w = width / 4;
                h = height;
                x = left + w * i;
                y = top;
            } else {
                // horizontal
                w = width;
                h = height / 4;
                x = left;
                y = top + h * i;
            }
            GLES20.glScissor(x, y, w, h);
            switch (i) {
                case 0:     // 50% blue at 25% alpha, pre-multiplied
                    GLES20.glClearColor(0.0f, 0.0f, 0.125f, 0.25f);
                    break;
                case 1:     // 100% blue at 25% alpha, pre-multiplied
                    GLES20.glClearColor(0.0f, 0.0f, 0.25f, 0.25f);
                    break;
                case 2:     // 200% blue at 25% alpha, pre-multiplied (should get clipped)
                    GLES20.glClearColor(0.0f, 0.0f, 0.5f, 0.25f);
                    break;
                case 3:     // 100% white at 25% alpha, pre-multiplied
                    GLES20.glClearColor(0.25f, 0.25f, 0.25f, 0.25f);
                    break;
            }
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        }
        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);

        win.swapBuffers();
        win.release();
        eglCore.release();
    }

    /**
     * Clears the surface, then draws a filled circle with a shadow.
     * <p>
     * The Canvas drawing we're doing may not be fully implemented for hardware-accelerated
     * renderers (shadow layers only supported for text).  However, Surface#lockCanvas()
     * currently only returns an unaccelerated Canvas, so it all comes out looking fine.
     */
    private void drawCircleSurface(Surface surface, int x, int y, int radius) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.FILL);
        paint.setShadowLayer(radius / 4 + 1, 0, 0, Color.RED);

        Canvas canvas = surface.lockCanvas(null);
        try {
            Log.v(TAG, "drawCircleSurface: isHwAcc=" + canvas.isHardwareAccelerated());
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            canvas.drawCircle(x, y, radius, paint);
        } finally {
            surface.unlockCanvasAndPost(canvas);
        }
    }

    /**
     * Clears the surface, then draws a filled circle with a shadow.
     * <p>
     * Similar to drawCircleSurface(), but the position changes based on the value of "i".
     */
    private void drawBouncingCircle(Surface surface, int i) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.FILL);

        Canvas canvas = surface.lockCanvas(null);
        try {
            Trace.beginSection("drawBouncingCircle");
            Trace.beginSection("drawColor");
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            Trace.endSection(); // drawColor

            int width = canvas.getWidth();
            int height = canvas.getHeight();
            int radius, x, y;
            if (width < height) {
                // portrait
                radius = width / 4;
                x = width / 4 + ((width / 2 * i) / BOUNCE_STEPS);
                y = height * 3 / 4;
            } else {
                // landscape
                radius = height / 4;
                x = width * 3 / 4;
                y = height / 4 + ((height / 2 * i) / BOUNCE_STEPS);
            }

            paint.setShadowLayer(radius / 4 + 1, 0, 0, Color.RED);

            canvas.drawCircle(x, y, radius, paint);
            Trace.endSection(); // drawBouncingCircle
        } finally {
            surface.unlockCanvasAndPost(canvas);
        }
    }

}