/*
 * Copyright (C) 2019 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.systemui.glwallpaper;

import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glUniform1f;
import static android.opengl.GLES20.glViewport;

import android.app.WallpaperManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
import android.view.DisplayInfo;
import android.view.WindowManager;

import com.android.systemui.R;

import java.io.FileDescriptor;
import java.io.PrintWriter;

/**
 * A GL renderer for image wallpaper.
 */
public class ImageWallpaperRenderer implements GLWallpaperRenderer,
        ImageRevealHelper.RevealStateListener {
    private static final String TAG = ImageWallpaperRenderer.class.getSimpleName();
    private static final float SCALE_VIEWPORT_MIN = 1f;
    private static final float SCALE_VIEWPORT_MAX = 1.1f;

    private final WallpaperManager mWallpaperManager;
    private final ImageGLProgram mProgram;
    private final ImageGLWallpaper mWallpaper;
    private final ImageProcessHelper mImageProcessHelper;
    private final ImageRevealHelper mImageRevealHelper;

    private SurfaceProxy mProxy;
    private final Rect mScissor;
    private final Rect mSurfaceSize = new Rect();
    private final Rect mViewport = new Rect();
    private Bitmap mBitmap;
    private boolean mScissorMode;
    private float mXOffset;
    private float mYOffset;

    public ImageWallpaperRenderer(Context context, SurfaceProxy proxy) {
        mWallpaperManager = context.getSystemService(WallpaperManager.class);
        if (mWallpaperManager == null) {
            Log.w(TAG, "WallpaperManager not available");
        }

        DisplayInfo displayInfo = new DisplayInfo();
        WindowManager wm = context.getSystemService(WindowManager.class);
        wm.getDefaultDisplay().getDisplayInfo(displayInfo);

        // We only do transition in portrait currently, b/137962047.
        int orientation = context.getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            mScissor = new Rect(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
        } else {
            mScissor = new Rect(0, 0, displayInfo.logicalHeight, displayInfo.logicalWidth);
        }

        mProxy = proxy;
        mProgram = new ImageGLProgram(context);
        mWallpaper = new ImageGLWallpaper(mProgram);
        mImageProcessHelper = new ImageProcessHelper();
        mImageRevealHelper = new ImageRevealHelper(this);

        if (loadBitmap()) {
            // Compute threshold of the image, this is an async work.
            mImageProcessHelper.start(mBitmap);
        }
    }

    @Override
    public void onSurfaceCreated() {
        glClearColor(0f, 0f, 0f, 1.0f);
        mProgram.useGLProgram(
                R.raw.image_wallpaper_vertex_shader, R.raw.image_wallpaper_fragment_shader);

        if (!loadBitmap()) {
            Log.w(TAG, "reload bitmap failed!");
        }

        mWallpaper.setup(mBitmap);
        mBitmap = null;
    }

    private boolean loadBitmap() {
        if (mWallpaperManager != null && mBitmap == null) {
            mBitmap = mWallpaperManager.getBitmap();
            mWallpaperManager.forgetLoadedWallpaper();
            if (mBitmap != null) {
                mSurfaceSize.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
            }
        }
        return mBitmap != null;
    }

    @Override
    public void onSurfaceChanged(int width, int height) {
        glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame() {
        float threshold = mImageProcessHelper.getThreshold();
        float reveal = mImageRevealHelper.getReveal();

        glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_AOD2OPACITY), 1);
        glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_PER85), threshold);
        glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_REVEAL), reveal);

        glClear(GL_COLOR_BUFFER_BIT);
        // We only need to scale viewport while doing transition.
        if (mScissorMode) {
            scaleViewport(reveal);
        } else {
            glViewport(0, 0, mSurfaceSize.width(), mSurfaceSize.height());
        }
        mWallpaper.useTexture();
        mWallpaper.draw();
    }

    @Override
    public void updateAmbientMode(boolean inAmbientMode, long duration) {
        mImageRevealHelper.updateAwake(!inAmbientMode, duration);
    }

    @Override
    public void updateOffsets(float xOffset, float yOffset) {
        mXOffset = xOffset;
        mYOffset = yOffset;
        int left = (int) ((mSurfaceSize.width() - mScissor.width()) * xOffset);
        int right = left + mScissor.width();
        mScissor.set(left, mScissor.top, right, mScissor.bottom);
    }

    @Override
    public Size reportSurfaceSize() {
        return new Size(mSurfaceSize.width(), mSurfaceSize.height());
    }

    @Override
    public void finish() {
        mProxy = null;
    }

    private void scaleViewport(float reveal) {
        int left = mScissor.left;
        int top = mScissor.top;
        int width = mScissor.width();
        int height = mScissor.height();
        // Interpolation between SCALE_VIEWPORT_MAX and SCALE_VIEWPORT_MIN by reveal.
        float vpScaled = MathUtils.lerp(SCALE_VIEWPORT_MIN, SCALE_VIEWPORT_MAX, reveal);
        // Calculate the offset amount from the lower left corner.
        float offset = (SCALE_VIEWPORT_MIN - vpScaled) / 2;
        // Change the viewport.
        mViewport.set((int) (left + width * offset), (int) (top + height * offset),
                (int) (width * vpScaled), (int) (height * vpScaled));
        glViewport(mViewport.left, mViewport.top, mViewport.right, mViewport.bottom);
    }

    @Override
    public void onRevealStateChanged() {
        mProxy.requestRender();
    }

    @Override
    public void onRevealStart(boolean animate) {
        if (animate) {
            mScissorMode = true;
            // Use current display area of texture.
            mWallpaper.adjustTextureCoordinates(mSurfaceSize, mScissor, mXOffset, mYOffset);
        }
        mProxy.preRender();
    }

    @Override
    public void onRevealEnd() {
        if (mScissorMode) {
            mScissorMode = false;
            // reset texture coordinates to use full texture.
            mWallpaper.adjustTextureCoordinates(null, null, 0, 0);
            // We need draw full texture back before finishing render.
            mProxy.requestRender();
        }
        mProxy.postRender();
    }

    @Override
    public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
        out.print(prefix); out.print("mProxy="); out.print(mProxy);
        out.print(prefix); out.print("mSurfaceSize="); out.print(mSurfaceSize);
        out.print(prefix); out.print("mScissor="); out.print(mScissor);
        out.print(prefix); out.print("mViewport="); out.print(mViewport);
        out.print(prefix); out.print("mScissorMode="); out.print(mScissorMode);
        out.print(prefix); out.print("mXOffset="); out.print(mXOffset);
        out.print(prefix); out.print("mYOffset="); out.print(mYOffset);
        out.print(prefix); out.print("threshold="); out.print(mImageProcessHelper.getThreshold());
        mWallpaper.dump(prefix, fd, out, args);
    }
}
