package com.box.androidsdk.content.requests;

import androidx.annotation.StringDef;
import android.text.TextUtils;
import android.util.Base64;

import com.box.androidsdk.content.BoxException;
import com.box.androidsdk.content.BoxFutureTask;
import com.box.androidsdk.content.listeners.ProgressListener;
import com.box.androidsdk.content.models.BoxComment;
import com.box.androidsdk.content.models.BoxDownload;
import com.box.androidsdk.content.models.BoxEntity;
import com.box.androidsdk.content.models.BoxEvent;
import com.box.androidsdk.content.models.BoxExpiringEmbedLinkFile;
import com.box.androidsdk.content.models.BoxFile;
import com.box.androidsdk.content.models.BoxFileVersion;
import com.box.androidsdk.content.models.BoxFolder;
import com.box.androidsdk.content.models.BoxIteratorCollaborations;
import com.box.androidsdk.content.models.BoxIteratorComments;
import com.box.androidsdk.content.models.BoxIteratorFileVersions;
import com.box.androidsdk.content.models.BoxIteratorUploadSessionParts;
import com.box.androidsdk.content.models.BoxRepresentation;
import com.box.androidsdk.content.models.BoxSession;
import com.box.androidsdk.content.models.BoxUploadSession;
import com.box.androidsdk.content.models.BoxUploadSessionPart;
import com.box.androidsdk.content.models.BoxVoid;
import com.box.androidsdk.content.utils.ProgressOutputStream;
import com.box.androidsdk.content.utils.SdkUtils;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Request class that groups all file operation requests together
 */
public class BoxRequestsFile {

    private static final String ATTRIBUTES = "attributes";

    /**
     * Request for retrieving information on a file
     */
    public static class GetFileInfo extends BoxRequestItem<BoxFile, GetFileInfo> implements BoxCacheableRequest<BoxFile> {
        private static final long serialVersionUID = 8123965031279971501L;

        /**
         * Creates a file information request with the default parameters
         *
         * @param id            id of the file to get information on
         * @param requestUrl    URL of the file information endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public GetFileInfo(String id, String requestUrl, BoxSession session) {
            super(BoxFile.class, id, requestUrl, session);
            mRequestMethod = Methods.GET;
        }

        /**
         * Sets the if-none-match header for the request.
         * The file will only be retrieved if the etag does not match the most current etag for the file.
         *
         * @param etag  etag to set in the if-none-match header.
         * @return request with the updated if-none-match header.
         */
        @Override
        public GetFileInfo setIfNoneMatchEtag(String etag) {
            return super.setIfNoneMatchEtag(etag);
        }

        /**
         * Returns the etag currently set in the if-none-match header.
         *
         * @return  etag set in the if-none-match header, or null if none set.
         */
        @Override
        public String getIfNoneMatchEtag() {
            return super.getIfNoneMatchEtag();
        }

        @Override
        public BoxFile sendForCachedResult() throws BoxException {
            return super.handleSendForCachedResult();
        }

        @Override
        public BoxFutureTask<BoxFile> toTaskForCachedResult() throws BoxException {
            return super.handleToTaskForCachedResult();
        }

    }

    /**
     * Request for retrieving information on a file with an expiring embed link
     */
    public static class GetEmbedLinkFileInfo extends BoxRequestItem<BoxExpiringEmbedLinkFile, GetEmbedLinkFileInfo> {
        private static final long serialVersionUID = 8123965031279971501L;

        /**
         * Creates a file information request with the default parameters
         *
         * @param id            id of the file to get information on
         * @param requestUrl    URL of the file information endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public GetEmbedLinkFileInfo(String id, String requestUrl, BoxSession session) {
            super(BoxExpiringEmbedLinkFile.class, id, requestUrl, session);
            mRequestMethod = Methods.GET;
            setFields(BoxExpiringEmbedLinkFile.FIELD_EMBED_LINK);
        }

        @Override
        public GetEmbedLinkFileInfo setFields(String... fields) {
            boolean hasEmbedLinkField = false;
            for (String field: fields){
                if (field.equalsIgnoreCase(BoxExpiringEmbedLinkFile.FIELD_EMBED_LINK)){
                    hasEmbedLinkField = true;
                }
            }
            if (! hasEmbedLinkField){
                String[] addedFields = new String[fields.length + 1];
                System.arraycopy(fields,0,addedFields,0,fields.length);
                addedFields[fields.length] = BoxExpiringEmbedLinkFile.FIELD_EMBED_LINK;
                return super.setFields(addedFields);
            }
            return super.setFields(fields);
        }

        /**
         * Sets the if-none-match header for the request.
         * The file will only be retrieved if the etag does not match the most current etag for the file.
         *
         * @param etag  etag to set in the if-none-match header.
         * @return request with the updated if-none-match header.
         */
        @Override
        public GetEmbedLinkFileInfo setIfNoneMatchEtag(String etag) {
            return super.setIfNoneMatchEtag(etag);
        }

        /**
         * Returns the etag currently set in the if-none-match header.
         *
         * @return  etag set in the if-none-match header, or null if none set.
         */
        @Override
        public String getIfNoneMatchEtag() {
            return super.getIfNoneMatchEtag();
        }

    }

    /**
     * Request for updating information on a file
     */
    public static class UpdateFile extends BoxRequestItemUpdate<BoxFile, UpdateFile> {

        private static final long serialVersionUID = 8123965031279971521L;

        /**
         * Creates an update file request with the default parameters
         *
         * @param id            id of the file to update information on
         * @param requestUrl    URL of the update file endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public UpdateFile(String id, String requestUrl, BoxSession session) {
            super(BoxFile.class, id, requestUrl, session);
        }

        @Override
        public UpdatedSharedFile updateSharedLink() {
            return new UpdatedSharedFile(this);
        }
    }

    public static class UpdatedSharedFile extends BoxRequestUpdateSharedItem<BoxFile, UpdatedSharedFile> {

        private static final long serialVersionUID = 8123965031279971520L;

        /**
         * Creates an update shared file request with the default parameters
         *
         * @param id            id of the file to update information on
         * @param requestUrl    URL of the update file endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public UpdatedSharedFile(String id, String requestUrl, BoxSession session) {
            super(BoxFile.class, id, requestUrl, session);
        }

        protected UpdatedSharedFile(UpdateFile r) {
            super(r);
        }

        /**
         * Sets whether or not the file can be downloaded from the shared link.
         *
         * @param canDownload   boolean representing whether or not the file can be downloaded from the shared link.
         * @return  request with the updated shared link.
         */
        @Override
        public UpdatedSharedFile setCanDownload(boolean canDownload) {
            return super.setCanDownload(canDownload);
        }

        /**
         * Returns whether or not the file can be downloaded from the shared link.
         *
         * @return  Boolean representing whether or not the file can be downloaded from the shared link, or null if not set.
         */
        @Override
        public Boolean getCanDownload() {
            return super.getCanDownload();
        }
    }

    /**
     * Request for copying a file
     */
    public static class CopyFile extends BoxRequestItemCopy<BoxFile, CopyFile> {

        private static final long serialVersionUID = 8123965031279971533L;

        /**
         * Creates a copy file request with the default parameters
         * @param id    id of the file to copy
         * @param parentId  id of the new parent folder
         * @param requestUrl    URL of the copy file endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public CopyFile(String id, String parentId, String requestUrl, BoxSession session) {
            super(BoxFile.class, id, parentId, requestUrl, session);
        }
    }

    /**
     * Request for deleting a file
     */
    public static class DeleteFile extends BoxRequestItemDelete<DeleteFile> {

        private static final long serialVersionUID = 8123965031279971593L;

        /**
         * Creates a delete file request with the default parameters
         *
         * @param id            id of the file to delete
         * @param requestUrl    URL of the delete file endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public DeleteFile(String id, String requestUrl, BoxSession session) {
            super(id, requestUrl, session);
        }

        @Override
        protected void onSendCompleted(BoxResponse<BoxVoid> response) throws BoxException {
            super.onSendCompleted(response);
            super.handleUpdateCache(response);
        }
    }

    /**
     * Request for retrieving information on a trashed file
     */
    public static class GetTrashedFile extends BoxRequestItem<BoxFile, GetTrashedFile> implements BoxCacheableRequest<BoxFile> {

        private static final long serialVersionUID = 8123965031279971543L;

        /**
         * Creates a request to get a trashed file with the default parameters
         *
         * @param id            id of the file in the trash
         * @param requestUrl    URL of the trashed file endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public GetTrashedFile(String id, String requestUrl, BoxSession session) {
            super(BoxFile.class, id, requestUrl, session);
            mRequestMethod = Methods.GET;
        }

        /**
         * Sets the if-none-match header for the request.
         * The trashed file will only be retrieved if the etag does not match the most current etag for the file.
         *
         * @param etag  etag to set in the if-none-match header.
         * @return request with the updated if-none-match header.
         */
        @Override
        public GetTrashedFile setIfNoneMatchEtag(String etag) {
            return super.setIfNoneMatchEtag(etag);
        }

        /**
         * Returns the etag currently set in the if-none-match header.
         *
         * @return  etag set in the if-none-match header, or null if none set.
         */
        @Override
        public String getIfNoneMatchEtag() {
            return super.getIfNoneMatchEtag();
        }

        @Override
        public BoxFile sendForCachedResult() throws BoxException {
            return super.handleSendForCachedResult();
        }

        @Override
        public BoxFutureTask<BoxFile> toTaskForCachedResult() throws BoxException {
            return super.handleToTaskForCachedResult();
        }
    }

    /**
     * Request for permanently deleting a trashed file
     */
    public static class DeleteTrashedFile extends BoxRequestItemDelete<DeleteTrashedFile> {

        private static final long serialVersionUID = 8123965031279971590L;


        /**
         * Creates a delete trashed file request with the default parameters
         *
         * @param id            id of the trashed file to permanently delete
         * @param requestUrl    URL of the delete trashed file endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public DeleteTrashedFile(String id, String requestUrl, BoxSession session) {
            super(id, requestUrl, session);
        }
    }

    /**
     * Request for restoring a trashed file
     */
    public static class RestoreTrashedFile extends BoxRequestItemRestoreTrashed<BoxFile, RestoreTrashedFile> {

        private static final long serialVersionUID = 8123965031279971535L;

        /**
         * Creates a restore trashed file request with the default parameters
         *
         * @param id            id of the trashed file to restore
         * @param requestUrl    URL of the restore trashed file endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public RestoreTrashedFile(String id, String requestUrl, BoxSession session) {
            super(BoxFile.class, id, requestUrl, session);
        }
    }

    /**
     * Request for getting comments on a file
     */
    public static class GetFileComments extends BoxRequestItem<BoxIteratorComments, GetFileComments> implements BoxCacheableRequest<BoxIteratorComments> {

        private static final long serialVersionUID = 8123965031279971525L;
        private static final String QUERY_LIMIT = "limit";
        private static final String QUERY_OFFSET = "offset";


        /**
         * Creates a get file comments request with the default parameters
         *
         * @param id    id of the file to get comments of
         * @param requestUrl    URL of the file comments endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public GetFileComments(String id, String requestUrl, BoxSession session) {
            super(BoxIteratorComments.class, id, requestUrl, session);
            mRequestMethod = Methods.GET;
            setFields(BoxComment.ALL_FIELDS);
        }

        @Override
        public BoxIteratorComments sendForCachedResult() throws BoxException {
            return super.handleSendForCachedResult();
        }

        @Override
        public BoxFutureTask<BoxIteratorComments> toTaskForCachedResult() throws BoxException {
            return super.handleToTaskForCachedResult();
        }

        /**
         * Set the response size limit
         * @param limit - number of entries to limit the response
         */
        public void setLimit(int limit) {
            mQueryMap.put(QUERY_LIMIT, Integer.toString(limit));
        }

        /**
         * Set the query comment offset
         * @param offset - offset count
         */
        public void setOffset(int offset) {
            mQueryMap.put(QUERY_OFFSET, Integer.toString(offset));
        }
    }

    /**
     * Request for adding a comment to a file
     */
    public static class AddCommentToFile extends BoxRequestCommentAdd<BoxComment, AddCommentToFile> {
        private static final long serialVersionUID = 8123965031279971514L;

        /**
         * Creates an add comment to file request with the default parameters
         *
         * @param fileId    id of the file to add a comment to
         * @param message   message of the new comment
         * @param requestUrl    URL of the add comment endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public AddCommentToFile(String fileId, String message, String requestUrl, BoxSession session) {
            super(BoxComment.class, requestUrl, session);
            setItemId(fileId);
            setItemType(BoxFile.TYPE);
            setMessage(message);
        }
    }

    /**
     * Request for adding a comment with tags to mention users.
     * The server will notify mentioned users of the comment.
     *
     * Tagged users must be collaborators of the parent folder.
     * Format for adding a tag @[userid:username], E.g. "Hello @[12345:Jane Doe]" will create a comment
     * 'Hello Jane Doe', and notify Jane that she has been mentioned.
     *
     */
    public static class AddTaggedCommentToFile extends BoxRequestCommentAdd<BoxComment, AddTaggedCommentToFile> {

        /**
         * Creates a file request to add a tagged comment
         *
         * @param fileId    id of the file to add a comment to
         * @param taggedMessage   message of the new comment
         * @param requestUrl    URL of the add comment endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public AddTaggedCommentToFile(String fileId, String taggedMessage, String requestUrl, BoxSession session) {
            super(BoxComment.class, requestUrl, session);
            setItemId(fileId);
            setItemType(BoxFile.TYPE);
            setTaggedMessage(taggedMessage);
        }




    }
    /**
     * Request for getting versions of a file
     */
    public static class GetFileVersions extends BoxRequestItem<BoxIteratorFileVersions, GetFileVersions> implements BoxCacheableRequest<BoxIteratorFileVersions> {

        private static final long serialVersionUID = 8123965031279971530L;

        /**
         * Creates a get file versions request with the default parameters
         *
         * @param id    id of the file to get versions of
         * @param requestUrl    URL of the file versions endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public GetFileVersions(String id, String requestUrl, BoxSession session) {
            super(BoxIteratorFileVersions.class, id, requestUrl, session);
            mRequestMethod = Methods.GET;
            // this call will by default set all fields as we need the deleted_at to know which are actual versions.
            setFields(BoxFileVersion.ALL_FIELDS);
        }

        @Override
        public BoxIteratorFileVersions sendForCachedResult() throws BoxException {
            return super.handleSendForCachedResult();
        }

        @Override
        public BoxFutureTask<BoxIteratorFileVersions> toTaskForCachedResult() throws BoxException {
            return super.handleToTaskForCachedResult();
        }
    }

    /**
     * Request for promoting an old version to the top of the version stack for a file
     */
    public static class PromoteFileVersion extends BoxRequestItem<BoxFileVersion, PromoteFileVersion> {

        private static final long serialVersionUID = 8123965031279971527L;

        /**
         * Creates a promote file version request with the default parameters
         *
         * @param id    id of the file to promote a version of
         * @param versionId id of the version to promote to the top
         * @param requestUrl    URL of the promote file version endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public PromoteFileVersion(String id, String versionId, String requestUrl, BoxSession session) {
            super(BoxFileVersion.class, id, requestUrl, session);
            mRequestMethod = Methods.POST;
            setVersionId(versionId);
        }

        /**
         * Sets the id of the version to promote.
         *
         * @param versionId id of the version to promote.
         * @return  request with the updated version id.
         */
        public PromoteFileVersion setVersionId(String versionId) {
            mBodyMap.put(BoxFileVersion.FIELD_TYPE, BoxFileVersion.TYPE);
            mBodyMap.put(BoxFolder.FIELD_ID, versionId);
            return this;
        }

        @Override
        protected void onSendCompleted(BoxResponse<BoxFileVersion> response) throws BoxException {
            super.onSendCompleted(response);
            super.handleUpdateCache(response);
        }
    }

    /**
     * Request for deleting an old version of a file
     */
    public static class DeleteFileVersion extends BoxRequest<BoxVoid, DeleteFileVersion> {

        private static final long serialVersionUID = 8123965031279971575L;

        private final String mVersionId;

        /**
         * Creates a delete old file version request with the default parameters
         *
         * @param versionId    id of the version to delete
         * @param requestUrl    URL of the delete file version endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public DeleteFileVersion(String versionId, String requestUrl, BoxSession session) {
            super(BoxVoid.class, requestUrl, session);
            mRequestMethod = Methods.DELETE;
            mVersionId = versionId;
        }

        /**
         * Returns the id of the file version to delete.
         *
         * @return  id of the file version to delete.
         */
        public String getVersionId() {
            return mVersionId;
        }

        @Override
        protected void onSendCompleted(BoxResponse<BoxVoid> response) throws BoxException {
            super.onSendCompleted(response);
            super.handleUpdateCache(response);
        }
    }

    /**
     * Request for uploading a new file
     */
    public static class UploadFile extends BoxRequestUpload<BoxFile, UploadFile> {
        private static final long serialVersionUID = 8123965031279971502L;

        String mDestinationFolderId;

        /**
         * Creates an upload file from input stream request with the default parameters
         *
         * @param inputStream   input stream of the file
         * @param fileName  name of the new file
         * @param destinationFolderId   id of the parent folder for the new file
         * @param requestUrl    URL of the upload file endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public UploadFile(InputStream inputStream, String fileName, String destinationFolderId, String requestUrl, BoxSession session) {
            super(BoxFile.class, inputStream, requestUrl, session);
            mRequestUrlString = requestUrl;
            mRequestMethod = Methods.POST;
            mFileName = fileName;
            mStream = inputStream;
            mDestinationFolderId = destinationFolderId;
        }

        /**
         * Creates an upload file from file request with the default parameters
         *
         * @param file  file to upload
         * @param destinationFolderId   id of the parent folder for the new file
         * @param requestUrl    URL of the upload file endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public UploadFile(File file, String destinationFolderId, String requestUrl, BoxSession session) {
            super(BoxFile.class, null, requestUrl, session);
            mRequestUrlString = requestUrl;
            mRequestMethod = Methods.POST;
            mDestinationFolderId = destinationFolderId;
            mFileName = file.getName();
            mFile = file;
            mUploadSize = file.length();
            mModifiedDate = new Date(file.lastModified());
        }


        @Override
        protected BoxRequestMultipart createMultipartRequest() throws IOException, BoxException {
            BoxRequestMultipart request = super.createMultipartRequest();
            request.putField("parent_id", mDestinationFolderId);
            return request;
        }

        /**
         * Returns the name of the file to upload.
         *
         * @return  name of the file to upload.
         */
        public String getFileName() {
            return mFileName;
        }

        /**
         * Sets the name of the file to upload.
         *
         * @param mFileName name of the file to upload.
         * @return  request with the updated file to upload.
         */
        public UploadFile setFileName(String mFileName) {
            this.mFileName = mFileName;
            return this;
        }

        /**
         * Returns the destination folder id for the uploaded file.
         *
         * @return  id of the destination folder for the uploaded file.
         */
        public String getDestinationFolderId() {
            return mDestinationFolderId;
        }
    }


    /**
     * Request for getting the collaborations of a file
     */
    public static class GetCollaborations extends BoxRequestItem<BoxIteratorCollaborations, BoxRequestsFile.GetCollaborations> implements BoxCacheableRequest<BoxIteratorCollaborations> {
        private static final long serialVersionUID = 8123965031219971519L;

        /**
         * Creates a request that gets the collaborations of a file with the default parameters
         *
         * @param id         id of the file to get collaborations on
         * @param requestUrl URL of the file collaborations endpoint
         * @param session    the authenticated session that will be used to make the request with
         */
        public GetCollaborations(String id, String requestUrl, BoxSession session) {
            super(BoxIteratorCollaborations.class, id, requestUrl, session);
            mRequestMethod = Methods.GET;
        }

        @Override
        public BoxIteratorCollaborations sendForCachedResult() throws BoxException {
            return super.handleSendForCachedResult();
        }

        @Override
        public BoxFutureTask<BoxIteratorCollaborations> toTaskForCachedResult() throws BoxException {
            return super.handleToTaskForCachedResult();
        }
    }

    /**
     * Request for uploading a new version of a file
     */
    public static class UploadNewVersion extends BoxRequestUpload<BoxFile, UploadNewVersion> {
        private static String NEW_NAME_JSON_TEMPLATE = "{\"name\": \"%s\"}";

        /**
         * Creates an upload new file version request with the default parameters
         *
         * @param fileInputStream   input stream of the new file version
         * @param requestUrl    URL of the upload new version endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public UploadNewVersion(InputStream fileInputStream, String requestUrl, BoxSession session) {
            super(BoxFile.class, fileInputStream, requestUrl, session);
        }

        /**
         * Sets the if-match header for the request.
         * The new version will only be uploaded if the specified etag matches the most current etag for the file.
         *
         * @param etag  etag to set in the if-match header.
         * @return  request with the updated if-match header.
         */
        @Override
        public UploadNewVersion setIfMatchEtag(String etag) {
            return super.setIfMatchEtag(etag);
        }

        /**
         * Returns the etag currently set in the if-match header.
         *
         * @return  etag set in the if-match header, or null if none set.
         */
        @Override
        public String getIfMatchEtag() {
            return super.getIfMatchEtag();
        }

        /**
         * Set the file name of the new uploaded version.
         */
        public void setFileName(String newFileName) {
            mFileName = newFileName;
        }

        @Override
        protected BoxRequestMultipart createMultipartRequest() throws IOException, BoxException {
            BoxRequestMultipart request = super.createMultipartRequest();
            if (!TextUtils.isEmpty(mFileName)) {
                String value = String.format(Locale.ENGLISH, NEW_NAME_JSON_TEMPLATE, mFileName);
                request.putField(ATTRIBUTES, value);
            }
            return request;
        }
    }

    /**
     * Prepare request to Download User Avatar
     */
    public static class DownloadAvatar extends BoxRequestDownload<BoxDownload, DownloadFile> {

        public static final String LARGE = "large";
        public static final String SMALL = "small";
        public static final String PROFILE = "profile";
        private static final String QUERY_AVATAR_TYPE = "pic_type";

        @StringDef({LARGE, SMALL, PROFILE})
        @Retention(RetentionPolicy.SOURCE)
        public @interface AvatarType {}

        /**
         * Creates a download file to file request with the default parameters
         *
         * @param id The Id of the file to download
         * @param target The target file to download to
         * @param requestUrl URL of the download file endpoint
         * @param session The authenticated session that will be used to make the request with
         */
        public DownloadAvatar(String id, final File target, String requestUrl, BoxSession session) {
            super(id, BoxDownload.class, target, requestUrl, session);
        }

        /**
         * Set the avatar type to be downloaded
         *
         * @param avatarType The type of avatar requested
         * @return DownloadAvatar
         */
        public DownloadAvatar setAvatarType(@AvatarType String avatarType){
            mQueryMap.put(QUERY_AVATAR_TYPE, avatarType);
            return this;
        }
    }

    /**
     * Request for downloading a file
     */
    public static class DownloadFile extends BoxRequestDownload<BoxDownload, DownloadFile> {

        private static final long serialVersionUID = 8123965031279971588L;

        /**
         * Creates a download file to output stream request with the default parameters
         *
         * @param id The id of the file to download
         * @param outputStream The output stream to download the file to
         * @param requestUrl URL of the download file endpoint
         * @param session The authenticated session that will be used to make the request with
         */
        public DownloadFile(String id, final OutputStream outputStream, String requestUrl, BoxSession session) {
            super(id, BoxDownload.class, outputStream, requestUrl, session);
        }

        /**
         * Creates a download file to output stream request with the default parameters
         *
         * @param outputStream The output stream to download the file to
         * @param requestUrl URL of the download file endpoint
         * @param session The authenticated session that will be used to make the request with
         * @deprecated Please use the DownloadFile constructor that takes in an id as this method may be removed in future releases
         */
        @Deprecated
        public DownloadFile(final OutputStream outputStream, String requestUrl, BoxSession session) {
            super(BoxDownload.class, outputStream, requestUrl, session);
        }

        /**
         * Creates a download file to file request with the default parameters
         *
         * @param id The Id of the file to download
         * @param target The target file to download to
         * @param requestUrl URL of the download file endpoint
         * @param session The authenticated session that will be used to make the request with
         */
        public DownloadFile(String id, final File target, String requestUrl, BoxSession session) {
            super(id, BoxDownload.class, target, requestUrl, session);
        }

        /**
         * Creates a download file to file request with the default parameters
         *
         * @param target Target file to download to
         * @param requestUrl URL of the download file endpoint
         * @param session The authenticated session that will be used to make the request with
         * @deprecated Please use the DownloadFile constructor that takes in an id as this method may be removed in future releases
         */
        @Deprecated
        public DownloadFile(final File target, String requestUrl, BoxSession session) {
            super(BoxDownload.class, target, requestUrl, session);
        }
    }

    /**
     * Request for downloading a thumbnail
     */
    public static class DownloadThumbnail extends BoxRequestDownload<BoxDownload, DownloadThumbnail> {

        private static final long serialVersionUID = 8123965031279971587L;

        private static final String FIELD_MIN_WIDTH = "min_width";
        private static final String FIELD_MIN_HEIGHT = "min_height";
        private static final String FIELD_MAX_WIDTH = "max_width";
        private static final String FIELD_MAX_HEIGHT = "max_height";

        public static int SIZE_32 = 32;
        public static int SIZE_64 = 64;
        public static int SIZE_94 = 94;
        public static int SIZE_128 = 128;
        public static int SIZE_160 = 160;
        public static int SIZE_256 = 256;
        public static int SIZE_320 = 320;

        public enum Format {
            JPG(".jpg"),
            PNG(".png");

            private final String mExt;

            Format(String ext) {
                mExt = ext;
            }

            @Override
            public String toString() {
                return mExt;
            }
        }

        protected Format mFormat = null;

        /**
         * Creates a download thumbnail to output stream request with the default parameters
         *
         * @param id The id of the file to download the thumbnail for
         * @param outputStream The output stream to download the thumbnail to
         * @param requestUrl URL of the download thumbnail endpoint
         * @param session The authenticated session that will be used to make the request with
         */
        public DownloadThumbnail(String id, final OutputStream outputStream, String requestUrl, BoxSession session) {
            super(id, BoxDownload.class, outputStream, requestUrl, session);
        }

        /**
         * Creates a download thumbnail to output stream request with the default parameters
         *
         * @param outputStream The output stream to download the thumbnail to
         * @param requestUrl URL of the download thumbnail endpoint
         * @param session The authenticated session that will be used to make the request with
         * @deprecated Please use the DownloadFile constructor that takes in an id as this method may be removed in future releases
         */
        @Deprecated
        public DownloadThumbnail(final OutputStream outputStream, String requestUrl, BoxSession session) {
            super(BoxDownload.class, outputStream, requestUrl, session);
        }

        /**
         * Creates a download thumbnail to file request with the default parameters
         *
         * @param id The id of the file to download a thumbnail for
         * @param target The target file to download thumbnail to
         * @param requestUrl URL of the download thumbnail endpoint
         * @param session The authenticated session that will be used to make the request with
         */
        public DownloadThumbnail(String id, final File target, String requestUrl, BoxSession session) {
            super(id, BoxDownload.class, target, requestUrl, session);
        }

        /**
         * Creates a download thumbnail to file request with the default parameters
         *
         * @param target The target file to download thumbnail to
         * @param requestUrl URL of the download thumbnail endpoint
         * @param session The authenticated session that will be used to make the request with
         * @deprecated Please use the DownloadFile constructor that takes in an id as this method may be removed in future releases
         */
        @Deprecated
        public DownloadThumbnail(final File target, String requestUrl, BoxSession session) {
            super(BoxDownload.class, target, requestUrl, session);
        }

        /**
         * Gets the minimum width for the thumbnail in the request
         *
         * @return the minimum width of the thumbnail
         */
        public Integer getMinWidth() {
            return mQueryMap.containsKey(FIELD_MIN_WIDTH) ?
                Integer.parseInt(mQueryMap.get(FIELD_MIN_WIDTH)) :
                null;
        }

        /**
         * Sets the minimum width for the thumbnail in the request.
         *
         * @param width int for the minimum width.
         * @return  request with the updated minimum width.
         */
        public DownloadThumbnail setMinWidth(int width){
            mQueryMap.put(FIELD_MIN_WIDTH, Integer.toString(width));
            return this;
        }

        /**
         * Gets the maximum width for the thumbnail in the request
         *
         * @return the maximum width of the thumbnail
         */
        public Integer getMaxWidth() {
            return mQueryMap.containsKey(FIELD_MAX_WIDTH) ?
                Integer.parseInt(mQueryMap.get(FIELD_MAX_WIDTH)) :
                null;
        }

        /**
         * Sets the maximum width for the thumbnail in the request.
         *
         * @param width int for the maximum width.
         * @return  request with the updated maximum width.
         */
        public DownloadThumbnail setMaxWidth(int width){
            mQueryMap.put(FIELD_MAX_WIDTH, Integer.toString(width));
            return this;
        }

        /**
         * Gets the minimum height for the thumbnail in the request
         *
         * @return the minimum height of the thumbnail
         */
        public Integer getMinHeight() {
            return mQueryMap.containsKey(FIELD_MIN_HEIGHT) ?
                    Integer.parseInt(mQueryMap.get(FIELD_MIN_HEIGHT)) :
                    null;
        }

        /**
         * Sets the minimum height for the thumbnail in the request.
         *
         * @param height int for the minimum height.
         * @return  request with the updated minimum height.
         */
        public DownloadThumbnail setMinHeight(int height){
            mQueryMap.put(FIELD_MIN_HEIGHT, Integer.toString(height));
            return this;
        }

        /**
         * Gets the maximum height for the thumbnail in the request
         *
         * @return the maximum height of the thumbnail
         */
        public Integer getMaxHeight() {
            return mQueryMap.containsKey(FIELD_MAX_HEIGHT) ?
                    Integer.parseInt(mQueryMap.get(FIELD_MAX_HEIGHT)) :
                    null;
        }

        /**
         * Sets the maximum height for the thumbnail in the request.
         *
         * @param height int for the maximum height.
         * @return  request with the updated maximum height.
         */
        public DownloadThumbnail setMaxHeight(int height){
            mQueryMap.put(FIELD_MAX_HEIGHT, Integer.toString(height));
            return this;
        }

        /**
         * Sets both the minimum height and minimum width for the thumbnail in the request.
         *
         * @param size int for the minimum height and minimum width.
         * @return  request with the updated minimum size.
         */
        public DownloadThumbnail setMinSize(int size){
            setMinWidth(size);
            setMinHeight(size);
            return this;
        }

        /**
         * Sets the file format of the thumbnail that will be downloaded. This overrides the default
         * behavior of returning the best file format for the requested thumbnail size.
         *
         * @param format Format of the thumbnail to return
         * @return The updated DownloadThumbnail request
         */
        public DownloadThumbnail setFormat(Format format) {
            mFormat = format;
            return this;
        }

        /**
         * Gets the file format of the thumbnail that will be downloaded
         *
         * @return The file format of the thumbnail
         */
        public Format getFormat() {
            return mFormat;
        }

        @Override
        protected URL buildUrl() throws MalformedURLException, UnsupportedEncodingException {
            // The url construction must be overriden as we need to dynamically include the thumbnail format
            String queryString = createQuery(mQueryMap);
            String urlString = String.format(Locale.ENGLISH, "%s%s", mRequestUrlString, getThumbnailExtension());
            URL requestUrl = TextUtils.isEmpty(queryString) ? new URL(urlString) : new URL(String.format(Locale.ENGLISH, "%s?%s", urlString,
                    queryString));
            return requestUrl;
        }

        /**
         * Gets the recommended thumbnail extension for the set thumbnail size.
         * Defaults to JPG if no sizing values have been set
         *
         * @return default thumbnail extension
         */
        protected String getThumbnailExtension() {
            // If format has been set it overrides all default settings
            if (mFormat != null) {
                return mFormat.toString();
            }

            Integer thumbSize = getMinWidth() != null ? getMinWidth() :
                getMinHeight() != null ? getMinHeight() :
                getMaxWidth() != null ? getMaxWidth() :
                getMaxHeight() != null ? getMaxHeight() :
                null;

            if (thumbSize == null) {
                return Format.JPG.toString();
            }

            int size = thumbSize.intValue();
            return size <= SIZE_32 ? Format.PNG.toString() :
                size <= SIZE_64 ? Format.PNG.toString() :
                size <= SIZE_94 ? Format.JPG.toString() :
                size <= SIZE_128 ? Format.PNG.toString() :
                size <= SIZE_160 ? Format.JPG.toString() :
                size <= SIZE_256 ? Format.PNG.toString() :
                Format.JPG.toString();
        }

    }

    /**
     * Request for adding a file to a collection
     */
    public static class AddFileToCollection extends BoxRequestCollectionUpdate<BoxFile, AddFileToCollection> {

        private static final long serialVersionUID = 8123965031279971537L;

        /**
         * Creates an add file to collection request with the default parameters
         *
         * @param id    id of the file to add to the collection
         * @param collectionId  id of the collection to add the file to
         * @param requestUrl    URL of the add to collection endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public AddFileToCollection(String id, String collectionId, String requestUrl, BoxSession session) {
            super(BoxFile.class, id, requestUrl, session);
            setCollectionId(collectionId);
        }

        /**
         * Sets the collection id in the request to add the file to.
         *
         * @param id    id of the collection to add the file to.
         * @return  request with the updated collection id.
         */
        @Override
        public AddFileToCollection setCollectionId(String id) {
            return super.setCollectionId(id);
        }
    }

    /**
     * Request for removing a file from a collection
     */
    public static class DeleteFileFromCollection extends BoxRequestCollectionUpdate<BoxFile, DeleteFileFromCollection> {

        private static final long serialVersionUID = 8123965031279971538L;

        /**
         * Creates a delete file from collection request with the default parameters
         *
         * @param id    id of the file to delete from collection
         * @param requestUrl    URL of the delete from collection endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public DeleteFileFromCollection(String id, String requestUrl, BoxSession session) {
            super(BoxFile.class, id, requestUrl, session);
            setCollectionId(null);
        }
    }

    /**
     * Request to notify the server when a file is previewed.
     * This request allows the server to maintain a recents list.
     */
    public static class FilePreviewed extends BoxRequest<BoxVoid, FilePreviewed> {

        private static final String TYPE_ITEM_PREVIEW = "PREVIEW";
        private static final String TYPE_FILE = "file";

        private String mFileId;

        /**
         * Constructs an event request with the default parameters.
         *
         * @param fileId     Id of the file previewed
         * @param requestUrl URL of the event stream endpoint.
         * @param session    the authenticated session that will be used to make the request with.
         */
        public FilePreviewed(String fileId, String requestUrl, BoxSession session) {
            super(BoxVoid.class, requestUrl, session);
            mFileId = fileId;
            mRequestMethod = Methods.POST;
            mBodyMap.put(BoxEvent.FIELD_TYPE, BoxEvent.TYPE);
            mBodyMap.put(BoxEvent.FIELD_EVENT_TYPE, TYPE_ITEM_PREVIEW);
            JsonObject objSource = new JsonObject();
            objSource.add(BoxEntity.FIELD_TYPE, TYPE_FILE);
            objSource.add(BoxEntity.FIELD_ID, fileId);
            mBodyMap.put(BoxEvent.FIELD_SOURCE, BoxEntity.createEntityFromJson(objSource));
        }

        /**
         * Retrieve the FileId for this request
         * @return the string that represents this file on Box.
         */
        public String getFileId() {
            return mFileId;
        }

        @Override
        protected void onSendCompleted(BoxResponse<BoxVoid> response) throws BoxException {
            super.onSendCompleted(response);
            super.handleUpdateCache(response);
        }
    }

    public static class DownloadRepresentation extends BoxRequestDownload<BoxDownload, DownloadRepresentation> {

        protected BoxRepresentation mRepresentation;

        /**
         * The page number to retrieve in case the representation is paged.
         * If page is required and not provided, retrieve page 1 as default
         */
        protected int mRequestPage = 1;

        public DownloadRepresentation(String id, final File target, BoxRepresentation representation, BoxSession session) {
            super(id, BoxDownload.class, target, representation.getContent().getUrl(), session);
            mRepresentation = representation;
        }

        @Override
        protected URL buildUrl() throws MalformedURLException, UnsupportedEncodingException {
            String assetPathReplacement = "";
            if(isPaged()) {
                // Paged is usually used for image representation only, but using the representation type as extension so it is more flexible
                assetPathReplacement = String.format(Locale.ENGLISH, "%d.%s", mRequestPage, mRepresentation.getRepresentationType());
            }
            return new URL(mRequestUrlString.replace(BoxRepresentation.BoxRepContent.ASSET_PATH_STRING,
                                                     assetPathReplacement));
        }

        /**
         * Returns whether this request supports specifying a page
         * @return whether this representation is paged
         */
        public boolean isPaged() {
            return mRepresentation.getProperties().isPaged();
        }

        /**
         * Define which representation page to request (if supported on this representation)
         * Please refer to {@link #isPaged()} to check whether this request supports multiple pages
         * @param page the representation page # to be downloaded
         */
        public void setRequestedPage(int page) {
            mRequestPage = page;
        }
    }
    /**
     * Request for creating a chunked upload session for a new file
     */
    public static class CreateUploadSession extends BoxRequest<BoxUploadSession, CreateUploadSession> {
        private static final long serialVersionUID = 8145675031279971502L;

        private String mDestinationFolderId;
        private String mFileName;
        private long mFileSize;
        private InputStream mFileInputStream;

        /**
         * Creates an upload file session  with the default parameters
         *
         * @param file file to upload
         * @param destinationFolderId   id of the parent folder for the new file
         * @param requestUrl    URL of the upload file endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public CreateUploadSession(File file, String destinationFolderId, String requestUrl, BoxSession session)
                throws FileNotFoundException {
            super(BoxUploadSession.class, requestUrl, session);
            mRequestUrlString = requestUrl;
            mRequestMethod = Methods.POST;
            mFileName = file.getName();
            mFileSize = file.length();
            mFileInputStream = new FileInputStream(file);
            mDestinationFolderId = destinationFolderId;
            mBodyMap.put("folder_id", destinationFolderId);
            mBodyMap.put("file_size", mFileSize);
            mBodyMap.put("file_name", mFileName);
        }

        /**
         * Creates an upload file session  with the default parameters
         * @param is the inputStream from where to read the file data
         * @param fileName the new file name
         * @param fileSize the file size
         * @param destinationFolderId id of the parent folder for the new file
         * @param requestUrl URL of the upload file endpoint
         * @param session the authenticated session that will be used to make the request with
         */
        public CreateUploadSession(InputStream is, String fileName, long fileSize, String destinationFolderId, String requestUrl, BoxSession session) {
            super(BoxUploadSession.class, requestUrl, session);
            mRequestUrlString = requestUrl;
            mRequestMethod = Methods.POST;
            mFileName = fileName;
            mFileSize = fileSize;
            mFileInputStream = is;
            mDestinationFolderId = destinationFolderId;
            mBodyMap.put("folder_id", destinationFolderId);
            mBodyMap.put("file_size", mFileSize);
            mBodyMap.put("file_name", mFileName);
        }

        @Override
        protected void onSendCompleted(BoxResponse<BoxUploadSession> response) throws BoxException {
            if (response.isSuccess()) {
                BoxUploadSession uploadSession = response.getResult();

                try {
                  computeSha1s(mFileInputStream, uploadSession, mFileSize);
                } catch (NoSuchAlgorithmException e) {
                    throw new BoxException("Can't compute sha1 for file",e);
                } catch (IOException e) {
                    throw new BoxException("Can't compute sha1 for file",e);
                }
            }
            super.onSendCompleted(response);
        }


        //Pre-compute sha1s for sending in future calls and save them to BoxUploadSession
        static void computeSha1s(InputStream fileInputStream, BoxUploadSession uploadSession,
                                 long fileSize)
                throws NoSuchAlgorithmException, IOException {
            int totalParts = uploadSession.getTotalParts();
            List<String> partSha1s = new ArrayList<>(totalParts);

            byte[] partBuffer = new byte[SdkUtils.BUFFER_SIZE];
            int bytesOfChunkToRead;
            MessageDigest mdFile = MessageDigest.getInstance("SHA-1"); //Store sha1 over entire file
            MessageDigest mdPart = MessageDigest.getInstance("SHA-1");

            for (int i = 0; i < totalParts; i++) {
                bytesOfChunkToRead = BoxUploadSession.getChunkSize(uploadSession, i, fileSize);
                while (bytesOfChunkToRead > 0) {
                    int readTo = Math.min(bytesOfChunkToRead, partBuffer.length);
                    int bytesRead = fileInputStream.read(partBuffer, 0, readTo);
                    if (bytesRead != -1) {
                        bytesOfChunkToRead -= bytesRead;
                        mdPart.update(partBuffer, 0, readTo);
                        mdFile.update(partBuffer, 0, readTo);
                    } else if (bytesRead == -1){
                        // should be unnecessary, but added as a precaution if somehow bytesOfChunkToRead did not decrement
                        break;
                    }
                }
                partSha1s.add(Base64.encodeToString(mdPart.digest(), Base64.DEFAULT));
                mdPart.reset();
            }
            uploadSession.setPartsSha1(partSha1s);
            uploadSession.setSha1(Base64.encodeToString(mdFile.digest(), Base64.DEFAULT));
        }

        /**
         * Set file name of the file to upload
         * @param mFileName
         */
        public void setFileName(String mFileName) {
            this.mFileName = mFileName;
            mBodyMap.put("file_name", mFileName);
        }

        /**
         * Returns the name of the file to upload.
         *
         * @return  name of the file to upload.
         */
        public String getFileName() {
            return mFileName;
        }

        /**
         * Returns the size of the file to upload
         * @return file size in bytes
         */
        public long getFileSize() {
            return mFileSize;
        }

        /**
         * Returns the destination folder id for the uploaded file.
         *
         * @return  id of the destination folder for the uploaded file.
         */
        public String getDestinationFolderId() {
            return mDestinationFolderId;
        }

    }

    /**
     * Request for uploading a session part
     */
    public static class UploadSessionPart extends BoxRequest<BoxUploadSessionPart, UploadSessionPart> {
        private static final long serialVersionUID = 8245675031279971502L;
        static final String DIGEST_HEADER = "digest";
        private static final String CONTENT_RANGE_HEADER = "content-range";
        static final String DIGEST_HEADER_PREFIX_SHA = "sha=";

        private final int mPartNumber;
        private File mFile;
        private InputStream mInputStream;
        private boolean mIsAlreadyPositioned = false;
        private int mCurrentChunkSize;
        private long mFileSize;
        private final BoxUploadSession mUploadSession;

        /**
         * Creates an Object to upload one specific multiput part of a file
         * @param file Creates an upload file session part  with the default parameters
         * @param uploadSession The uploadSession object
         * @param partNumber the part number from the file
         * @param session The BoxSession object
         */
        public UploadSessionPart(File file, BoxUploadSession uploadSession, int partNumber, BoxSession session) throws IOException {
            super(BoxUploadSessionPart.class, uploadSession.getEndpoints().getUploadPartEndpoint(), session);
            mRequestUrlString = uploadSession.getEndpoints().getUploadPartEndpoint();
            mRequestMethod = Methods.PUT;
            mPartNumber = partNumber;
            mUploadSession = uploadSession;
            mFile = file;
            mCurrentChunkSize = BoxUploadSession.getChunkSize(uploadSession, partNumber, file.length());
            mFileSize = file.length();
            mContentType = ContentTypes.APPLICATION_OCTET_STREAM;
        }

        /**
         * Creates an Object to upload one specific multiput part of a file
         * @param is the InputStream from where this object will read the file
         * @param fileSize the InputStream length to be read
         * @param uploadSession the Upload session object
         * @param partNumber the part number from the file/InputStream
         * @param session The BoxSession object
         * @throws IOException
         */
        public UploadSessionPart(InputStream is, long fileSize, BoxUploadSession uploadSession, int partNumber, BoxSession session) throws IOException {
            super(BoxUploadSessionPart.class, uploadSession.getEndpoints().getUploadPartEndpoint(), session);
            mRequestUrlString = uploadSession.getEndpoints().getUploadPartEndpoint();
            mRequestMethod = Methods.PUT;
            mPartNumber = partNumber;
            mUploadSession = uploadSession;
            mInputStream = is;
            mCurrentChunkSize = BoxUploadSession.getChunkSize(uploadSession, partNumber, fileSize);
            mFileSize = fileSize;
            mContentType = ContentTypes.APPLICATION_OCTET_STREAM;
        }

        protected InputStream getInputStream() throws FileNotFoundException{
            if (mInputStream != null){
                return mInputStream;
            }
            return new FileInputStream(mFile);
        }

        @Override
        protected void createHeaderMap() {
            super.createHeaderMap();
            long offset = ((long)mPartNumber) * mUploadSession.getPartSize();
            //Content-Range: bytes offset-part/totalSize

            long lastByte = offset + mCurrentChunkSize - 1;
            mHeaderMap.put(CONTENT_RANGE_HEADER,
                    "bytes " + offset + "-" + lastByte + "/" + mFileSize);
            mHeaderMap.put(DIGEST_HEADER, DIGEST_HEADER_PREFIX_SHA + mUploadSession.getFieldPartsSha1().get(mPartNumber));
        }

        @Override
        protected void setBody(BoxHttpRequest request) throws IOException  {
            InputStream inputStream = getInputStream();
            //skip previous parts
            if (!mIsAlreadyPositioned) {
                long skipTo = ((long)mPartNumber) * mUploadSession.getPartSize();
                inputStream.skip(skipTo);
            }

            //Write bytes to URLConnection of the request
            URLConnection urlConnection = request.getUrlConnection();
            urlConnection.setDoOutput(true);
            OutputStream output = urlConnection.getOutputStream();
            if(mListener != null) {
                output = new ProgressOutputStream(output, mListener, getPartSize());
            }
            byte[] byteBuf = new byte[SdkUtils.BUFFER_SIZE];
            long totalBytesRead = 0;
            int bytesRead = 0;

            try {
                do {
                    if (Thread.currentThread().isInterrupted()){
                        throw new InterruptedException();
                    }
                    if (totalBytesRead + byteBuf.length > mCurrentChunkSize){
                        bytesRead = inputStream.read(byteBuf, 0, (int)(totalBytesRead + byteBuf.length - mCurrentChunkSize));
                    } else {
                        bytesRead = inputStream.read(byteBuf, 0, byteBuf.length);
                    }
                    if (bytesRead != -1) {
                        output.write(byteBuf, 0, bytesRead);
                        totalBytesRead += bytesRead;
                    }
                } while (bytesRead != -1 && totalBytesRead < mCurrentChunkSize);
            } catch (InterruptedException ie) {
                throw new IOException(ie);
            } finally {
                output.close();
                if (mFile != null || !mIsAlreadyPositioned){
                    inputStream.close();
                }
            }
        }

        /**
         * Sets the progress listener for the upload request.
         *
         * @param listener  progress listener for the request.
         * @return  request with the updated progress listener.
         */
        public UploadSessionPart setProgressListener(ProgressListener listener){
            mListener = listener;
            return this;
        }

        /**
         * Set to true only if the file or input stream used in the constructor starts at
         * the beginning of this part. Default is false, in which case the parameter is assumed
         * to represent the entire file.
         * @param alreadyPositioned true to reuse the input stream.
         * @return request which will not try to skip to a new place in the position.
         */
        public UploadSessionPart setAlreadyPositioned(boolean alreadyPositioned) {
            return this;
        }

        /**
         * Returns the multiput part size to be uploaded
         * @return the part size
         */
        public long getPartSize() {
            return mCurrentChunkSize;
        }
    }

    /**
     * Request for committing an upload session after all parts have been uploaded, creating the new file or the version.
     */
    public static class CommitUploadSession extends BoxRequest<BoxFile, CommitUploadSession> {
        private static final long serialVersionUID = 8245675031279972519L;
        private final String mIfMatch;
        private final String mIfNoneMatch;
        private final BoxUploadSession mUploadSession;
        private final String mSha1;
        private static final String IF_MATCH = "If-Match";
        private static final String IF_NONE_MATCH = "If-Non-Match";

        /**
         * @param uploadedParts the list of uploaded parts to be committed.
         * @param attributes    optional key value pairs of attributes from the file instance.
         * @param ifMatch       optional ifMatch header that ensures that your app only alters
         *                      files/folders on Box if you have the current version.
         * @param ifNoneMatch   optional ifNoneMatch header that ensures that you don't retrieve unnecessary
         *                      data if the most current version of file is on-hand.
         * @param uploadSession
         */
        public CommitUploadSession(List<BoxUploadSessionPart> uploadedParts, Map<String, String> attributes,
                                   String ifMatch, String ifNoneMatch, BoxUploadSession uploadSession, BoxSession session) {
            super(BoxFile.class, uploadSession.getEndpoints().getCommitEndpoint(), session);
            mRequestMethod = Methods.POST;
            mIfMatch = ifMatch;
            mIfNoneMatch = ifNoneMatch;
            mUploadSession = uploadSession;
            mSha1 = uploadSession.getSha1();
            mContentType = ContentTypes.JSON;
            setCommitBody(uploadedParts, attributes);
            setRequestHandler(new MultiputResponseHandler(this));
        }

        @Override
        protected void createHeaderMap() {
            super.createHeaderMap();
            mHeaderMap.put(UploadSessionPart.DIGEST_HEADER, UploadSessionPart.DIGEST_HEADER_PREFIX_SHA + mSha1);
            if (!TextUtils.isEmpty(mIfMatch)) {
                mHeaderMap.put(IF_MATCH, mIfMatch);
            }
            if (!TextUtils.isEmpty(IF_NONE_MATCH)) {
                mHeaderMap.put(IF_NONE_MATCH, mIfNoneMatch);
            }
        }

        /*
         * Creates the JSON body for the commit request.
         */
        private void setCommitBody(List<BoxUploadSessionPart> parts, Map<String, String> attributes) {

            JsonArray array = new JsonArray();
            for (BoxUploadSessionPart part : parts) {
                JsonObject partObj = new JsonObject();
                partObj.add(BoxUploadSessionPart.FIELD_PART_ID, part.getPartId());
                partObj.add(BoxUploadSessionPart.FIELD_OFFSET, part.getOffset());
                partObj.add(BoxUploadSessionPart.FIELD_SIZE, part.getSize());
                array.add(partObj);
            }
            mBodyMap.put("parts", array);

            if (attributes != null) {
                JsonObject attrObj = new JsonObject();
                for (String key : attributes.keySet()) {
                    attrObj.add(key, attributes.get(key));
                }
                mBodyMap.put(ATTRIBUTES, attrObj);
            }

        }

        public BoxUploadSession getUploadSession() {
            return mUploadSession;
        }
    }

        /**
         * Request for listing all parts that have been successfully uploaded
         */
        public static class ListUploadSessionParts extends BoxRequest<BoxIteratorUploadSessionParts,
                ListUploadSessionParts> {
            private static final long serialVersionUID = 8245675031255572519L;
            private static final String QUERY_LIMIT = "limit";
            private static final String QUERY_OFFSET = "offset";

            private final BoxUploadSession mUploadSession;

            public ListUploadSessionParts(BoxUploadSession uploadSession, BoxSession session) {
                super(BoxIteratorUploadSessionParts.class, uploadSession.getEndpoints().getListPartsEndpoint(), session);
                mRequestMethod = Methods.GET;
                mUploadSession = uploadSession;
            }

            /**
             * Set the response size limit
             * @param limit - number of entries to limit the response
             */
            public void setLimit(int limit) {
                mQueryMap.put(QUERY_LIMIT, Integer.toString(limit));
            }

            /**
             * Set the query comment offset
             * @param offset - offset count
             */
            public void setOffset(int offset) {
                mQueryMap.put(QUERY_OFFSET, Integer.toString(offset));
            }

            public BoxUploadSession getUploadSession() {
                return mUploadSession;
            }
        }




    /**
     * Request for aborting an upload session
     */
    public static class AbortUploadSession extends BoxRequest<BoxVoid, AbortUploadSession> {

        private static final long serialVersionUID = 8123965031279972343L;
        private final BoxUploadSession mUploadSession;

        /**
         * Creates an abort session request request with the default parameters
         *
         * @param session       the authenticated session that will be used to make the request with
         */
        public AbortUploadSession(BoxUploadSession uploadSession, BoxSession session) {
            super(BoxVoid.class, uploadSession.getEndpoints().getAbortEndpoint(), session);
            mRequestMethod = Methods.DELETE;
            mUploadSession = uploadSession;
        }

        public BoxUploadSession getUploadSession() {
            return mUploadSession;
        }
    }


    /**
     * Request for fetching upload session info
     */
    public static class GetUploadSession extends BoxRequest<BoxUploadSession, GetUploadSession> {

        private static final long serialVersionUID = 8124575031279972343L;

        private String mId;

        /**
         * Creates a file information request with the default parameters
         *
         * @param id            id of the file to get information on
         * @param requestUrl    URL of the file information endpoint
         * @param session       the authenticated session that will be used to make the request with
         */
        public GetUploadSession(String id, String requestUrl, BoxSession session) {
            super(BoxUploadSession.class, requestUrl, session);
            mRequestMethod = Methods.GET;
            mId = id;
        }

        public String getId() {
            return mId;
        }
    }

    /**
     * Request for creating a chunked upload session for new version of a file
     */
    public static class CreateNewVersionUploadSession extends BoxRequest<BoxUploadSession, CreateNewVersionUploadSession> {
        private static final long serialVersionUID = 8182475031279971502L;

        private String      mFileName;
        private long        mFileSize;
        private InputStream mInputStream;

        /**
         * Creates an upload session to upload a new version of the file.
         * The file name will be set from the file object passed.
         *
         * @param file file to upload
         * @param requestUrl    URL of the upload file endpoint
         * @param session   the authenticated session that will be used to make the request with
         */
        public CreateNewVersionUploadSession(File file,  String requestUrl, BoxSession session)
                throws FileNotFoundException {
            super(BoxUploadSession.class, requestUrl, session);
            mRequestUrlString = requestUrl;
            mRequestMethod = Methods.POST;
            mFileName = file.getName();
            mFileSize = file.length();
            mInputStream = new FileInputStream(file);
            mBodyMap.put("file_size", mFileSize);
            mBodyMap.put("file_name", mFileName);
        }

        /**
         *
         * @param is the input stream to be used to read the file data
         * @param fileName the file name
         * @param fileSize the file size
         * @param requestUrl    URL of the upload file endpoint
         * @param session   the authenticated session that will be used to make the request with
         * @throws FileNotFoundException
         */
        public CreateNewVersionUploadSession(InputStream is, String fileName, long fileSize,  String requestUrl, BoxSession session)
                throws FileNotFoundException {
            super(BoxUploadSession.class, requestUrl, session);
            mRequestUrlString = requestUrl;
            mRequestMethod = Methods.POST;
            mFileName = fileName;
            mFileSize = fileSize;
            mInputStream = is;
            mBodyMap.put("file_size", mFileSize);
            mBodyMap.put("file_name", mFileName);
        }

        @Override
        protected void onSendCompleted(BoxResponse<BoxUploadSession> response) throws BoxException {
            if (response.isSuccess()) {
                BoxUploadSession uploadSession = response.getResult();

                try {
                    CreateUploadSession.computeSha1s(mInputStream, uploadSession, mFileSize);
                } catch (NoSuchAlgorithmException e) {
                    throw new BoxException("Can't compute sha1 for file", e);
                } catch (IOException e) {
                    throw new BoxException("Can't compute sha1 for file", e);
                }
            }
            super.onSendCompleted(response);
        }

        /**
         * Set file name of the file to upload
         * @param mFileName
         */
        public void setFileName(String mFileName) {
            this.mFileName = mFileName;
            mBodyMap.put("file_name", mFileName);
        }

        /**
         * Returns the name of the file to upload.
         *
         * @return  name of the file to upload.
         */
        public String getFileName() {
            return mFileName;
        }

        /**
         * Returns the size of the file to upload
         * @return file size in bytes
         */
        public long getFileSize() {
            return mFileSize;
        }


    }

}