/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.pdfbox.pdmodel.encryption;

/**
 * This class represents the access permissions to a document.
 * These permissions are specified in the PDF format specifications, they include:
 * <ul>
 * <li>print the document</li>
 * <li>modify the content of the document</li>
 * <li>copy or extract content of the document</li>
 * <li>add or modify annotations</li>
 * <li>fill in interactive form fields</li>
 * <li>extract text and graphics for accessibility to visually impaired people</li>
 * <li>assemble the document</li>
 * <li>print in degraded quality</li>
 * </ul>
 *
 * This class can be used to protect a document by assigning access permissions to recipients.
 * In this case, it must be used with a specific ProtectionPolicy.
 *
 *
 * When a document is decrypted, it has a currentAccessPermission property which is the access permissions
 * granted to the user who decrypted the document.
 *
 * @see ProtectionPolicy
 * @see org.apache.pdfbox.pdmodel.PDDocument#getCurrentAccessPermission()
 *
 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
 * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
 *
 */

public class AccessPermission
{

    private static final int DEFAULT_PERMISSIONS = 0xFFFFFFFF ^ 3;//bits 0 & 1 need to be zero
    private static final int PRINT_BIT = 3;
    private static final int MODIFICATION_BIT = 4;
    private static final int EXTRACT_BIT = 5;
    private static final int MODIFY_ANNOTATIONS_BIT = 6;
    private static final int FILL_IN_FORM_BIT = 9;
    private static final int EXTRACT_FOR_ACCESSIBILITY_BIT = 10;
    private static final int ASSEMBLE_DOCUMENT_BIT = 11;
    private static final int DEGRADED_PRINT_BIT = 12;

    private int bytes = DEFAULT_PERMISSIONS;

    private boolean readOnly = false;

    /**
     * Create a new access permission object.
     * By default, all permissions are granted.
     */
    public AccessPermission()
    {
        bytes = DEFAULT_PERMISSIONS;
    }

    /**
     * Create a new access permission object from a byte array.
     * Bytes are ordered most significant byte first.
     *
     * @param b the bytes as defined in PDF specs
     */

    public AccessPermission(byte[] b)
    {
        bytes = 0;
        bytes |= b[0] & 0xFF;
        bytes <<= 8;
        bytes |= b[1] & 0xFF;
        bytes <<= 8;
        bytes |= b[2] & 0xFF;
        bytes <<= 8;
        bytes |= b[3] & 0xFF;
    }

    /**
     * Creates a new access permission object from a single integer.
     *
     * @param permissions The permission bits.
     */
    public AccessPermission( int permissions )
    {
        bytes = permissions;
    }

    private boolean isPermissionBitOn( int bit )
    {
        return (bytes & (1 << (bit-1))) != 0;
    }

    private boolean setPermissionBit( int bit, boolean value )
    {
        int permissions = bytes;
        if( value )
        {
            permissions = permissions | (1 << (bit-1));
        }
        else
        {
            permissions = permissions & (0xFFFFFFFF ^ (1 << (bit-1)));
        }
        bytes = permissions;

        return (bytes & (1 << (bit-1))) != 0;
    }




    /**
     * This will tell if the access permission corresponds to owner
     * access permission (no restriction).
     *
     * @return true if the access permission does not restrict the use of the document
     */
    public boolean isOwnerPermission()
    {
        return (this.canAssembleDocument()
                && this.canExtractContent()
                && this.canExtractForAccessibility()
                && this.canFillInForm()
                && this.canModify()
                && this.canModifyAnnotations()
                && this.canPrint()
                && this.canPrintDegraded()
                );
    }

    /**
     * returns an access permission object for a document owner.
     *
     * @return A standard owner access permission set.
     */

    public static AccessPermission getOwnerAccessPermission()
    {
        AccessPermission ret = new AccessPermission();
        ret.setCanAssembleDocument(true);
        ret.setCanExtractContent(true);
        ret.setCanExtractForAccessibility(true);
        ret.setCanFillInForm(true);
        ret.setCanModify(true);
        ret.setCanModifyAnnotations(true);
        ret.setCanPrint(true);
        ret.setCanPrintDegraded(true);
        return ret;
    }

    /**
     * This returns an integer representing the access permissions.
     * This integer can be used for public key encryption. This format
     * is not documented in the PDF specifications but is necessary for compatibility
     * with Adobe Acrobat and Adobe Reader.
     *
     * @return the integer representing access permissions
     */

    public int getPermissionBytesForPublicKey()
    {
        setPermissionBit(1, true);
        setPermissionBit(7, false);
        setPermissionBit(8, false);
        for(int i=13; i<=32; i++)
        {
            setPermissionBit(i, false);
        }
        return bytes;
    }

    /**
     * The returns an integer representing the access permissions.
     * This integer can be used for standard PDF encryption as specified
     * in the PDF specifications.
     *
     * @return the integer representing the access permissions
     */
    public int getPermissionBytes()
    {
        return bytes;
    }

    /**
     * This will tell if the user can print.
     *
     * @return true If supplied with the user password they are allowed to print.
     */
    public boolean canPrint()
    {
        return isPermissionBitOn( PRINT_BIT );
    }

    /**
     * Set if the user can print.
     * <p>
     * This method will have no effect if the object is in read only mode.
     *
     * @param allowPrinting A boolean determining if the user can print.
     */
    public void setCanPrint( boolean allowPrinting )
    {
        if(!readOnly)
        {
            setPermissionBit( PRINT_BIT, allowPrinting );
        }
    }

    /**
     * This will tell if the user can modify contents of the document.
     *
     * @return true If supplied with the user password they are allowed to modify the document
     */
    public boolean canModify()
    {
        return isPermissionBitOn( MODIFICATION_BIT );
    }

    /**
     * Set if the user can modify the document.
     * <p>
     * This method will have no effect if the object is in read only mode.
     *
     * @param allowModifications A boolean determining if the user can modify the document.
     */
    public void setCanModify( boolean allowModifications )
    {
        if(!readOnly)
        {
            setPermissionBit( MODIFICATION_BIT, allowModifications );
        }
    }

    /**
     * This will tell if the user can extract text and images from the PDF document.
     *
     * @return true If supplied with the user password they are allowed to extract content
     *              from the PDF document
     */
    public boolean canExtractContent()
    {
        return isPermissionBitOn( EXTRACT_BIT );
    }

    /**
     * Set if the user can extract content from the document.
     * <p>
     * This method will have no effect if the object is in read only mode.
     *
     * @param allowExtraction A boolean determining if the user can extract content
     *                        from the document.
     */
    public void setCanExtractContent( boolean allowExtraction )
    {
        if(!readOnly)
        {
            setPermissionBit( EXTRACT_BIT, allowExtraction );
        }
    }

    /**
     * This will tell if the user can add or modify text annotations and fill in interactive forms
     * fields and, if {@link #canModify() canModify()} returns true, create or modify interactive
     * form fields (including signature fields). Note that if
     * {@link #canFillInForm() canFillInForm()} returns true, it is still possible to fill in
     * interactive forms (including signature fields) even if this method here returns false.
     *
     * @return true If supplied with the user password they are allowed to modify annotations.
     */
    public boolean canModifyAnnotations()
    {
        return isPermissionBitOn( MODIFY_ANNOTATIONS_BIT );
    }

    /**
     * Set if the user can add or modify text annotations and fill in interactive forms fields and,
     * if {@link #canModify() canModify()} returns true, create or modify interactive form fields
     * (including signature fields). Note that if {@link #canFillInForm() canFillInForm()} returns
     * true, it is still possible to fill in interactive forms (including signature fields) even the
     * parameter here is false.
     * <p>
     * This method will have no effect if the object is in read only mode.
     *
     * @param allowAnnotationModification A boolean determining the new setting.
     */
    public void setCanModifyAnnotations( boolean allowAnnotationModification )
    {
        if(!readOnly)
        {
            setPermissionBit( MODIFY_ANNOTATIONS_BIT, allowAnnotationModification );
        }
    }

    /**
     * This will tell if the user can fill in interactive form fields (including signature fields)
     * even if {@link #canModifyAnnotations() canModifyAnnotations()} returns false.
     *
     * @return true If supplied with the user password they are allowed to fill in form fields.
     */
    public boolean canFillInForm()
    {
        return isPermissionBitOn( FILL_IN_FORM_BIT );
    }

    /**
     * Set if the user can fill in interactive form fields (including signature fields) even if
     * {@link #canModifyAnnotations() canModifyAnnotations()} returns false. Therefore, if you want
     * to prevent a user from filling in interactive form fields, you need to call
     * {@link #setCanModifyAnnotations(boolean) setCanModifyAnnotations(false)} as well.
     *<p>
     * This method will have no effect if the object is in read only mode.
     *
     * @param allowFillingInForm A boolean determining if the user can fill in interactive forms.
     */
    public void setCanFillInForm( boolean allowFillingInForm )
    {
        if(!readOnly)
        {
            setPermissionBit( FILL_IN_FORM_BIT, allowFillingInForm );
        }
    }

    /**
     * This will tell if the user can extract text and images from the PDF document
     * for accessibility purposes.
     *
     * @return true If supplied with the user password they are allowed to extract content
     *              from the PDF document
     */
    public boolean canExtractForAccessibility()
    {
        return isPermissionBitOn( EXTRACT_FOR_ACCESSIBILITY_BIT );
    }

    /**
     * Set if the user can extract content from the document for accessibility purposes.
     * <p>
     * This method will have no effect if the object is in read only mode.
     *
     * @param allowExtraction A boolean determining if the user can extract content
     *                        from the document.
     */
    public void setCanExtractForAccessibility( boolean allowExtraction )
    {
        if(!readOnly)
        {
            setPermissionBit( EXTRACT_FOR_ACCESSIBILITY_BIT, allowExtraction );
        }
    }

    /**
     * This will tell if the user can insert/rotate/delete pages.
     *
     * @return true If supplied with the user password they are allowed to extract content
     *              from the PDF document
     */
    public boolean canAssembleDocument()
    {
        return isPermissionBitOn( ASSEMBLE_DOCUMENT_BIT );
    }

    /**
     * Set if the user can insert/rotate/delete pages.
     * <p>
     * This method will have no effect if the object is in read only mode.
     *
     * @param allowAssembly A boolean determining if the user can assemble the document.
     */
    public void setCanAssembleDocument( boolean allowAssembly )
    {
        if(!readOnly)
        {
            setPermissionBit( ASSEMBLE_DOCUMENT_BIT, allowAssembly );
        }
    }

    /**
     * This will tell if the user can print the document in a degraded format.
     *
     * @return true If supplied with the user password they are allowed to print the
     *              document in a degraded format.
     */
    public boolean canPrintDegraded()
    {
        return isPermissionBitOn( DEGRADED_PRINT_BIT );
    }

    /**
     * Set if the user can print the document in a degraded format.
     * <p>
     * This method will have no effect if the object is in read only mode.
     *
     * @param canPrintDegraded A boolean determining if the user can print the
     *        document in a degraded format.
     */
    public void setCanPrintDegraded( boolean canPrintDegraded )
    {
        if(!readOnly)
        {
            setPermissionBit( DEGRADED_PRINT_BIT, canPrintDegraded );
        }
    }

    /**
     * Locks the access permission read only (ie, the setters will have no effects).
     * After that, the object cannot be unlocked.
     * This method is used for the currentAccessPermssion of a document to avoid
     * users to change access permission.
     */
    public void setReadOnly()
    {
        readOnly = true;
    }

    /**
     * This will tell if the object has been set as read only.
     *
     * @return true if the object is in read only mode.
     */

    public boolean isReadOnly()
    {
        return readOnly;
    }
    
    /**
     * Indicates if any revision 3 access permission is set or not.
     * 
     * @return true if any revision 3 access permission is set
     */
    protected boolean hasAnyRevision3PermissionSet()
    {
        if (canFillInForm())
        {
            return true;
        }
        if (canExtractForAccessibility())
        {
            return true;
        }
        if (canAssembleDocument())
        {
            return true;
        }
        if (canPrintDegraded())
        {
            return true;
        }
        return false;
    }
}
