/*
 * Copyright (C) 2024 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 android.graphics.pdf;

import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.pdf.content.PdfPageGotoLinkContent;
import android.graphics.pdf.models.FormWidgetInfo;
import android.graphics.pdf.models.jni.LinkRects;
import android.graphics.pdf.models.jni.LoadPdfResult;
import android.graphics.pdf.models.jni.MatchRects;
import android.graphics.pdf.models.jni.PageSelection;
import android.graphics.pdf.models.jni.SelectionBoundary;
import android.graphics.pdf.utils.StrictModeUtils;
import android.os.ParcelFileDescriptor;

import java.util.List;

/**
 * This class accesses the PdfClient tools to manipulate and render a PDF document. One instance of
 * this class corresponds to one PDF document, loads it within PdfClient and keeps an internal
 * reference to the resulting object, to be re-used in subsequent calls.
 *
 * <p>This class is mostly a JNI gateway to PdfClient.
 *
 * @hide
 */
public class PdfDocumentProxy {
    private static final String TAG = "PdfDocument";

    private static final String LIB_NAME = "pdfclient";

    /** Internal reference to a native pointer to a Document object. */
    private final long mPdfDocPtr;

    private final int mNumPages;

    /** Constructs a PdfDocument. Do not call directly from java, use {@link #createFromFd}. */
    protected PdfDocumentProxy(long pdfDocPtr, int numPages) {
        this.mPdfDocPtr = pdfDocPtr;
        this.mNumPages = numPages;
    }

    /**
     * Tries to load a PdfDocument from native file descriptor.
     *
     * @return a LoadPdfResult of status LOADED containing the PdfDocument,
     * or, an empty LoadPdfResult of a different status indicating failure.
     */
    public static native LoadPdfResult createFromFd(int fd, String password);

    /**
     * Loads the PdfClient binary library used to render PDF documents. The library will only be
     * loaded once so subsequent calls after the first will have no effect. This may be used to
     * preload the library before use.
     */
    public static void loadLibPdf() {
        // TODO(b/324549320): Cleanup if bypassing is not required
        StrictModeUtils.bypass(() -> System.loadLibrary(LIB_NAME));
    }

    public long getPdfDocPtr() {
        return mPdfDocPtr;
    }

    public int getNumPages() {
        return mNumPages;
    }

    /** Destroys the PDF document and release resources held by PdfClient. */
    public native void destroy();

    /**
     * Tries to save this PdfDocument to the given native file descriptor, which must be open for
     * write or append.
     *
     * @return true on success
     */
    public native boolean saveToFd(int fd);

    /**
     * Saves the current state of this {@link PdfDocument} to the given, writable, file descriptor.
     * The given file descriptor is closed by this function.
     *
     * @param destination the file descriptor to write to
     * @return true on success
     */
    public boolean saveAs(ParcelFileDescriptor destination) {
        return saveToFd(destination.detachFd());
    }

    /**
     * Returns the width of the given page of the PDF. This is measured in points, but we
     * zoom-to-fit, so it doesn't matter.
     */
    public native int getPageWidth(int pageNum);

    /**
     * Returns the height of the given page of the PDF. This is measured in points, but we
     * zoom-to-fit, so it doesn't matter.
     */
    public native int getPageHeight(int pageNum);

    /**
     * Renders a page to a bitmap.
     *
     * @param pageNum the page number of the page to be rendered
     * @param clipLeft the left coordinate of the clipping boundary in bitmap coordinates
     * @param clipTop the top coordinate of the clipping boundary in bitmap coordinates
     * @param clipRight the right coordinate of the clipping boundary in bitmap coordinates
     * @param clipBottom the bottom coordinate of the clipping boundary in bitmap coordinates
     * @param transform an affine transform matrix in the form of an array.
     * @see android.graphics.Matrix#getValues(float[])
     * @param renderMode the render mode
     * @param hideTextAnnots whether to hide text and highlight annotations
     * @return true if the page was rendered into the destination bitmap
     */
    public native boolean render(
            int pageNum,
            Bitmap bitmap,
            int clipLeft,
            int clipTop,
            int clipRight,
            int clipBottom,
            float[] transform,
            int renderMode,
            boolean hideTextAnnots);

    /**
     * Clones the currently loaded document using the provided file descriptor.
     * <p>You are required to detach the file descriptor as the native code will close it.
     *
     * @param destination native fd pointer
     * @return true if the cloning was successful
     */
    private native boolean cloneWithoutSecurity(int destination);

    /**
     * Clones the currently loaded document using the provided file descriptor.
     * <p>You are required to detach the file descriptor as the native code will close it.
     *
     * @param destination {@link ParcelFileDescriptor} to which the document needs to be written to.
     * @return true if the cloning was successful
     */
    public boolean cloneWithoutSecurity(ParcelFileDescriptor destination) {
        return cloneWithoutSecurity(destination.detachFd());
    }

    /**
     * Gets the text of the entire page as a string, in the order the text is
     * found in the PDF stream.
     */
    public native String getPageText(int pageNum);

    /**
     * Gets all pieces of alt-text found for the page, in the order the alt-text is found in the
     * PDF stream.
     */
    public native List<String> getPageAltText(int pageNum);

    /**
     * Searches for the given string on the page and returns the bounds of all of the matches.
     * The number of matches is {@link MatchRects#size()}.
     */
    public native MatchRects searchPageText(int pageNum, String query);

    /**
     * Get the text selection that spans between the two boundaries (inclusive of start and
     * exclusive of stop), both of which can be either exactly defined with text indexes, or
     * approximately defined with points on the page. The resulting selection will also be exactly
     * defined with both indexes and points. If the start and stop boundary are both the same point,
     * selects the word at that point.
     */
    public native PageSelection selectPageText(int pageNum, SelectionBoundary start,
            SelectionBoundary stop);

    /** Get the bounds and URLs of all the links on the given page. */
    public native LinkRects getPageLinks(int pageNum);

    /** Returns bookmarks and other goto links (within the current document) on a page */
    public native List<PdfPageGotoLinkContent> getPageGotoLinks(int pageNum);

    /** Loads a page object and retains it in memory when a page becomes visible. */
    public native void retainPage(int pageNum);

    /** Cleans up objects in memory related to a page after it is no longer visible. */
    public native void releasePage(int pageNum);

    /** Returns true if the PDF is linearized. (May give false negatives for <1KB PDFs). */
    public native boolean isPdfLinearized();

    /** Returns true if the document prefers to be scaled for printing. */
    public native boolean scaleForPrinting();

    /**
     * Returns an int representing the form type contained in the PDF, e.g. Acro vs XFA (if any).
     */
    public native int getFormType();

    /** Obtains information about the widget at point ({@code x}, {@code y}), if any. */
    public native FormWidgetInfo getFormWidgetInfo(int pageNum, int x, int y);

    /**
     * Obtains information about the widget with ({@code annotationIndex} on page {@code pageNum}),
     * if any.
     */
    public native FormWidgetInfo getFormWidgetInfo(int pageNum, int annotationIndex);

    /**
     * Obtains information about all form widgets on page ({@code pageNum}, if any.
     *
     * <p>Optionally restricts by {@code typeIds}. If {@code typeIds} is empty, all form widgets on
     * the page will be returned.
     */
    public native List<FormWidgetInfo> getFormWidgetInfos(int pageNum, int[] typeIds);

    /**
     * Executes an interactive click on the page at the given point ({@code x}, {@code y}).
     *
     * @return rectangular areas of the page bitmap that have been invalidated by this action
     */
    public native List<Rect> clickOnPage(int pageNum, int x, int y);

    /**
     * Sets the text of the widget at {@code annotationIndex}, if applicable.
     *
     * @return rectangular areas of the page bitmap that have been invalidated by this action
     */
    public native List<Rect> setFormFieldText(int pageNum, int annotIndex, String text);

    /**
     * Selects the {@code selectedIndices} and unselects all others for the widget at {@code
     * annotationIndex}, if applicable.
     *
     * @return Rectangular areas of the page bitmap that have been invalidated by this action
     */
    public native List<Rect> setFormFieldSelectedIndices(
            int pageNum, int annotIndex, int[] selectedIndices);
}
