// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.paintpreview.player.frame;

import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Size;

/**
 * Used to represent the viewport for a frame in the paint preview player. There should be one of
 * these objects per player frame and it should be shared between various classes that manipulated
 * the location. Should only be accessed on the UI thread to avoid the need for locks.
 */
public class PlayerFrameViewport {
    // Enforce a max tile size to avoid giant bitmaps. Most high-DPI displays are < 2500px in any
    // dimension so this is a reasonable upper bound. Alternatively, this could use screensize, but
    // then there is more complexity with rotation.
    private static final int MAX_TILE_DIMENSION = 2500;

    /** The size of the viewport. */
    private Size mViewportSize = new Size(0, 0);
    /** A 3x3 affine transformation matrix to track scale and translation. */
    private final Matrix mViewportTransform = new Matrix();
    /** The visible region of the viewport. */
    private final Rect mVisibleRegion = new Rect();
    /** The offset of the visible region of the viewport relative to the frame's origin. */
    private final Point mOffset = new Point();
    private boolean mTileSizeOverridden;
    private Size mTileSize = new Size(0, 0);
    /** Transient storage objects to avoid allocations. */
    private final Rect mViewportRect = new Rect();
    private final float[] mMatrixValues = new float[9];
    private final Rect mVisibleViewport = new Rect();

    /**
     * @return the width of the viewport.
     */
    public int getWidth() {
        return mViewportSize.getWidth();
    }

    /**
     * @return the height of the viewport.
     */
    public int getHeight() {
        return mViewportSize.getHeight();
    }

    /**
     * Returns the translation of the viewport in the X direction (AKA left).
     * @return the x translation of the viewport.
     */
    float getTransX() {
        mViewportTransform.getValues(mMatrixValues);
        return mMatrixValues[Matrix.MTRANS_X];
    }

    /**
     * Returns the translation of the viewport in the Y direction (AKA top).
     * @return the y translation of the viewport.
     */
    float getTransY() {
        mViewportTransform.getValues(mMatrixValues);
        return mMatrixValues[Matrix.MTRANS_Y];
    }

    /**
     * Returns the scale at which to show contents.
     * @return a scale factor for the viewport.
     */
    public float getScale() {
        mViewportTransform.getValues(mMatrixValues);
        return mMatrixValues[Matrix.MSCALE_X]; // x and y should be identical here.
    }

    /**
     * Returns the current viewport position as a rect. Use cautiously as this is an instantaneous
     * snapshot and is not continually updated.
     * @return a rect of the current viewport.
     * */
    public Rect asRect() {
        mViewportTransform.getValues(mMatrixValues);
        final int left = Math.round(mMatrixValues[Matrix.MTRANS_X]);
        final int top = Math.round(mMatrixValues[Matrix.MTRANS_Y]);
        mViewportRect.set(
                left, top, left + mViewportSize.getWidth(), top + mViewportSize.getHeight());
        return mViewportRect;
    }

    /**
     * Sets the size of the viewport.
     * @param width The width of the viewport.
     * @param height The height of the viewport.
     */
    void setSize(int width, int height) {
        mViewportSize = new Size(width, height);

        if (mTileSizeOverridden) return;

        setTileSize(width, Math.round(height / 2f));
    }

    /**
     * Sets the position x, y (left, top) of the viewport.
     * @param x The left side of the viewport.
     * @param y The top of the viewport.
     */
    void setTrans(float x, float y) {
        mViewportTransform.getValues(mMatrixValues);
        mMatrixValues[Matrix.MTRANS_X] = x;
        mMatrixValues[Matrix.MTRANS_Y] = y;
        mViewportTransform.setValues(mMatrixValues);
    }

    /**
     * Sets the scale of the viewport.
     * @param scaleFactor The scale of the viewport.
     */
    void setScale(float scaleFactor) {
        mViewportTransform.getValues(mMatrixValues);
        mMatrixValues[Matrix.MSCALE_X] = scaleFactor;
        mMatrixValues[Matrix.MSCALE_Y] = scaleFactor;
        mViewportTransform.setValues(mMatrixValues);
    }

    /**
     * Offsets/shifts the viewport by a set amount.
     * @param dx The distance to offset on the x-axis.
     * @param dy The distance to offset on the y-axis.
     */
    void offset(float dx, float dy) {
        mViewportTransform.postTranslate(dx, dy);
    }

    /**
     * Affine scaling of the viewport about a focal point/pivot.
     * @param scaleFactor The amount to scale by (relative to the current scale).
     * @param focalX The x-coordinate of the focal point.
     * @param focalY The y-coordinate of the focal point.
     */
    void scale(float scaleFactor, float focalX, float focalY) {
        mViewportTransform.postScale(scaleFactor, scaleFactor, -focalX, -focalY);
    }

    /**
     * Sets the visible portion of the viewport.
     * @param left offset of the viewport on the left relative to the frame's origin.
     * @param top offset of the viewport on the top relative to the frame's origin.
     * @param right right - left is the width of the viewport.
     * @param bottom bottom - top is the height of the viewport.
     */
    void setVisibleRegion(int left, int top, int right, int bottom) {
        mVisibleRegion.set(left, top, right, bottom);
        mOffset.set(left, top);
    }

    /**
     * Returns whether this frame is visible.
     * @param isSubframe whether this is a subframe.
     */
    boolean isVisible(boolean isSubframe) {
        if (!isSubframe) return true;

        return !mVisibleRegion.isEmpty();
    }

    /**
     * Offset of the visible portion of the viewport relative to the frame's origin.
     */
    Point getOffset() {
        return mOffset;
    }

    /**
     * Gets the visible viewport of the content.
     * @param isSubframe whether this is a subframe.
     * @return The rect of the visible viewport.
     */
    Rect getVisibleViewport(boolean isSubframe) {
        if (!isSubframe) return asRect();

        if (mVisibleRegion.isEmpty()) return mVisibleRegion;

        Rect vr = asRect();
        final int x = vr.left + mVisibleRegion.left;
        final int y = vr.top + mVisibleRegion.top;
        mVisibleViewport.set(x, y, x + mVisibleRegion.width(), y + mVisibleRegion.height());
        return mVisibleViewport;
    }

    /**
     * @return bitmap tile size.
     */
    Size getBitmapTileSize() {
        return mTileSize;
    }

    /**
     * Overrides the tile size.
     */
    void overrideTileSize(int width, int height) {
        setTileSize(width, height);
        mTileSizeOverridden = true;
    }

    private void setTileSize(int width, int height) {
        mTileSize =
                new Size(Math.min(width, MAX_TILE_DIMENSION), Math.min(height, MAX_TILE_DIMENSION));
    }
}
