/*
 * Copyright 2021 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

package org.skia.jetski.util;

import android.util.Log;
import android.view.SurfaceHolder;
import org.skia.jetski.Canvas;
import org.skia.jetski.Surface;

/**
  * Utility base class facilitating the implementation of Surface-bound animations.
  *
  * Provides a dedicated render thread and user content callbacks.
  */
public abstract class SurfaceRenderer implements SurfaceHolder.Callback, Runnable {
    private android.view.Surface mAndroidSurface;
    private Thread               mRenderThread;
    private boolean              mThreadRunning;
    private long                 mTimeBase;

    /**
      * Initialization callback.
      *
      * This can be invoked multiple times if the underlying surface changes.
      */
    protected abstract void onSurfaceInitialized(Surface surface);

    /**
      * Callback for frame content.
      *
      * Invoked once per (vsync'ed) frame.
      */
    protected abstract void onRenderFrame(Canvas canvas, long ms);

    @Override
    public void run() {
        Log.d("SurfaceRenderer", "Render thread started.");

        // TODO: Vulkan support?
        Surface surface = Surface.CreateGL(mAndroidSurface);
        onSurfaceInitialized(surface);

        mTimeBase = java.lang.System.currentTimeMillis();

        while (mThreadRunning) {
            long timestamp = java.lang.System.currentTimeMillis() - mTimeBase;
            onRenderFrame(surface.getCanvas(), timestamp);
            surface.flushAndSubmit();
        }

        // Ensure that the backing surface is released on the same thread.
        surface.release();

        Log.d("SurfaceRenderer", "Render thread finished.");
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // Initialization handled in surfaceChanged().
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mAndroidSurface = holder.getSurface();

        stopRenderThread();
        startRenderThread();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        stopRenderThread();
    }

    private void startRenderThread() {
        if (!mThreadRunning) {
            mThreadRunning = true;
            mRenderThread = new Thread(this);
            mRenderThread.start();
        }
    }

    private void stopRenderThread() {
        if (mThreadRunning) {
            mThreadRunning = false;
            try {
                mRenderThread.join();
            } catch (InterruptedException e) {}
        }
    }

    public void setBaseTime(long millis) {
        mTimeBase = millis;
    }

    public void release() {
        stopRenderThread();
    }
}
