/*
    Copyright (c) 2005-2024 Leisenfels GmbH. All rights reserved.
    Use is subject to license terms.
*/

package com.lf.vfslib.test.gdrive;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.lf.vfslib.VFSLib;
import com.lf.vfslib.core.VFSLibConstants;
import com.lf.vfslib.core.VFSLibSettings;
import com.lf.vfslib.gdrive.GDriveFileObject;
import com.lf.vfslib.gdrive.GDriveFileProvider;
import com.lf.vfslib.gdrive.GDriveFileSystemConfigBuilder;
import org.apache.commons.vfs2.*;
import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
import org.apache.commons.vfs2.util.RandomAccessMode;

import java.awt.*;
import java.io.*;
import java.util.Date;


/**
 * Example class showing the use of the Google&reg; Drive provider.
 * <p>
 *
 * @author Axel Schwolow
 * @since 1.6
 */
public class ExampleGoogleDrive {


    /**
     * Constructor method.
     * <p>
     *
     * @param appname    The identifier for application
     * @param accesstype "online" for web applications and "offline" for installed applications
     * @param tokensdir  The directory to store the tokens
     * @param usetrash   Deleted files are moved to trash
     * @throws org.apache.commons.vfs2.FileSystemException Error indication
     * @since 1.6
     */
    public ExampleGoogleDrive(String appname, String accesstype, String tokensdir, boolean usetrash)
            throws FileSystemException {

        System.out.println("Configuring VFSLib Google Drive provider:");
        System.out.println("    appname            " + (appname != null ? appname : "(using default)"));
        System.out.println("    accesstype         " + (accesstype != null ? accesstype : "(using default)"));
        System.out.println("    tokensdir          " + (tokensdir != null ? tokensdir : "(using in-memory)"));
        System.out.println("    usetrash           " + usetrash);

        VFSLibSettings settings = VFSLibSettings.getSharedInstance();

        if (appname == null) appname = "VFSLib/" + settings.getDeployProps().getProperty("VERSION");
        if (accesstype == null) accesstype = "offline"; // Installed application

        // Setup the main VFSLib instance
        VFSLib vfslib = new VFSLib();

        // We use the default file system manager here
        DefaultFileSystemManager fsmanager = (DefaultFileSystemManager) VFS.getManager();

        // Add Google Drive provider to VFS, OAuth 2.0 client secret provided by Google is loaded from 'credentials.json'
        String clientsecret = null;
        try {
            InputStream in = DriveQuickstart.class.getResourceAsStream(DriveQuickstart.CREDENTIALS_FILE_PATH);
            BufferedInputStream bis = new BufferedInputStream(in);
            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            for (int result = bis.read(); result != -1; result = bis.read()) {
                buf.write((byte) result);
            }
            clientsecret = buf.toString("UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (clientsecret == null) {
            System.err.println("Sorry, could not load the Google drive OAuth 2.0 client secret JSON, exiting");
            return;
        }

        File tokensdirfile = null;
        if (tokensdir != null) {
            tokensdirfile = new File(tokensdir);
            if (!tokensdirfile.exists() || !tokensdirfile.isDirectory()) {
                System.err.println("Sorry, invalid tokens directory specified, exiting");
                return;
            }
        }

        GDriveFileProvider provider = vfslib.addProviderGoogleDrive(fsmanager, clientsecret, appname, accesstype, tokensdirfile);
        if (provider == null) {
            System.err.println("Sorry, the Google Drive provider could not be activated, exiting");
            return;
        }

        String scheme = vfslib.getSchemeGoogleDrive();
        System.out.println("\nAdded Google Drive scheme \"" + scheme + '\"');

        // Perform authorization to let application access a Google Drive account
        GoogleAuthorizationCodeFlow authflow = provider.getAuthFlow();

        // Open connection to Google Drive and authorize via browser
        String token = null;
        try {
            LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
            Credential credential = new AuthorizationCodeInstalledApp(authflow, receiver).authorize("user");
            token = credential.getAccessToken();  // Store persistently to be reused
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (token == null) {
            System.err.println("Sorry, could not get the Google Drive access token, exiting");
            return;
        }
        System.out.println("\nThe Google Drive access token is: " + token.replaceFirst("^.{5}", "*****"));
        System.out.println("You can use this token for further testing, please specify -tokensdir to store permanently\n");

        // Pass access token over to VFSLib to access the Google Drive account
        FileSystemOptions options = new FileSystemOptions();
        GDriveFileSystemConfigBuilder builder = new GDriveFileSystemConfigBuilder();

        // Setup proper account name, used as user name for Google Drive URLs
        //    gdrive://[displayname]@drive.google.com
        String username = "johndoe";
        builder.setAccountDisplayName(options, username);
        builder.setAccessToken(options, token);
        builder.setUseTrash(options, usetrash);  // Allow deleted entries be restored
        builder.setConnectTimeoutSeconds(options, 30);
        builder.setReadTimeoutSeconds(options, 600);

        // List the Google Drive root folder
        String uri = scheme + "://" + username + "@drive.google.com";
        System.out.println("Listing child entries of " + uri + ":");
        try {
            FileObject fileobj = fsmanager.resolveFile(uri, options);
            if (fileobj.getType().equals(FileType.FOLDER)) {
                FileObject[] children = fileobj.getChildren();
                for (FileObject next : children) {
                    if (next.getType().equals(FileType.FOLDER)) {
                        System.out.println("    DIR:  " + next);
                    } else System.out.println("    FILE: " + next);
                }
            } else System.out.println("    Entry " + uri + " is not a folder");
        } catch (Exception e) {
            e.printStackTrace();
        }

        // The following code modifies files in the Google Drive file system.
        // Please uncomment the lines and adjust values for your needs.

        // Create a Google Drive folder in root
        String tempfolder = scheme + "://" + username + "@drive.google.com/0123456789abcdefghijklmnopqrstuvwxyz";
        System.out.println("Creating temporary folder " + tempfolder + ":");
        boolean success = false;
        try {
            FileObject fileobj = fsmanager.resolveFile(tempfolder, options);
            if (!fileobj.exists()) {
                fileobj.createFolder();
                success = fileobj.exists();
                System.out.println(success ? "    Successful" : "    Failed");
            } else {
                System.out.println("    Entry " + tempfolder + " does already exist, exiting");
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!success) {
            System.err.println("Sorry, could not create folder, exiting");
            return;
        }

        // Rename the temporary folder
        String tempfolderrenamed = scheme + "://" + username + "@drive.google.com/0123456789abcdefghijklmnopqrstuvwxyz_renamed";
        System.out.println("Renaming temporary folder " + tempfolder + " to " + tempfolderrenamed + ":");
        success = false;
        try {
            FileObject fileobj = fsmanager.resolveFile(tempfolder, options);
            FileObject fileobjrenamed = fsmanager.resolveFile(tempfolderrenamed, options);
            fileobj.moveTo(fileobjrenamed);
            success = fileobjrenamed.exists();
            System.out.println(success ? "    Successful" : "    Failed");
            if (success) tempfolder = tempfolderrenamed;
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!success) {
            System.err.println("Sorry, could not rename folder, exiting");
            return;
        }

        // Modify a Google Drive folder timestamp
        Date newdate = new Date(new Date().getTime() - (86400 * 1000)); // Yesterday
        System.out.println("Change last modified date of directory " + tempfolder + " to " + newdate + ":");
        success = false;
        try {
            FileObject fileobj = fsmanager.resolveFile(tempfolder, options);
            fileobj.getContent().setLastModifiedTime(newdate.getTime());
            Date filedate = new Date(fileobj.getContent().getLastModifiedTime());
            success = (filedate.equals(newdate));
            System.out.println(success ? "    Successful" : "    Failed");
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!success) {
            System.err.println("Sorry, could not set last modified date, exiting");
            return;
        }

        String content = "Hello world!";
        String encoding = "ISO-8859-1";

        // Upload a file to Google Drive. Get file type, last modified, and size.
        String tempfile = tempfolder + "/readme.txt";
        System.out.println("Uploading temporary file " + tempfile + ":");
        success = false;
        try {
            FileObject fileobj = fsmanager.resolveFile(tempfile, options);
            fileobj.getContent().setAttribute(VFSLibConstants.ATTR_CONTENT_LENGTH, content.getBytes(encoding).length);
            OutputStream ostream = fileobj.getContent().getOutputStream();
            ostream.write(content.getBytes(encoding));
            ostream.flush();
            ostream.close();

            success = fileobj.exists();
            System.out.println(success ? "    Successful" : "    Failed");
            System.out.println("    TYPE:  " + fileobj.getType().getName());
            System.out.println("    MOD:   " + new Date(fileobj.getContent().getLastModifiedTime()));  // Currently not supported for folders
            System.out.println("    SIZE:  " + fileobj.getContent().getSize());
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!success) {
            System.err.println("Sorry, could not upload file, exiting");
            return;
        }

        // List the temporary Google Drive subfolder
        System.out.println("Listing child entries of " + tempfolder + ":");
        try {
            FileObject fileobj = fsmanager.resolveFile(tempfolder, options);
            if (fileobj.getType().equals(FileType.FOLDER)) {
                FileObject[] children = fileobj.getChildren();
                for (FileObject next : children) {
                    if (next.getType().equals(FileType.FOLDER)) {
                        System.out.println("    FOLDER: " + next);
                    } else System.out.println("    FILE:   " + next);
                }
            } else {
                System.out.println("    Entry " + tempfolder + " is not a folder, exiting");
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Rename a Google Drive file
        String tempfilerenamed = tempfolder + "/README";  // README.TXT not possible currently
        System.out.println("Renaming temporary file " + tempfile + " to " + tempfilerenamed + ":");
        success = false;
        try {
            FileObject fileobj = fsmanager.resolveFile(tempfile, options);
            FileObject fileobjrenamed = fsmanager.resolveFile(tempfilerenamed, options);
            fileobj.moveTo(fileobjrenamed);
            success = fileobjrenamed.exists();
            System.out.println(success ? "    Successful" : "    Failed");
            if (success) tempfile = tempfilerenamed;
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!success) {
            System.err.println("Sorry, could not rename file, exiting");
            return;
        }

        // Modify a Google Drive file timestamp
        newdate = new Date(new Date().getTime() - (86400 * 1000)); // Yesterday
        System.out.println("Change last modified date of temporary file " + tempfile + " to " + newdate + ":");
        success = false;
        try {
            FileObject fileobj = fsmanager.resolveFile(tempfile, options);
            fileobj.getContent().setLastModifiedTime(newdate.getTime());
            Date filedate = new Date(fileobj.getContent().getLastModifiedTime());
            success = (filedate.equals(newdate));
            System.out.println(success ? "    Successful" : "    Failed");
            if (success) tempfile = tempfilerenamed;
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!success) {
            System.err.println("Sorry, could not set last modified date, exiting");
            return;
        }

        // Download a file from Google Drive
        System.out.println("Downloading temporary file " + tempfile + ":");
        success = false;
        try {
            FileObject fileobj = fsmanager.resolveFile(tempfile, options);
            ByteArrayOutputStream bostream = new ByteArrayOutputStream();
            InputStream istream = fileobj.getContent().getInputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = istream.read(buffer)) != -1) {
                bostream.write(buffer, 0, len);
            }
            istream.close();
            bostream.flush();
            bostream.close();
            String loaded = bostream.toString(encoding);

            success = loaded.equals(content);
            System.out.println(success ? "    Successful (content=" + loaded + ")" : "    Failed");
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!success) {
            System.err.println("Sorry, could not download file, exiting");
            return;
        }

        // Read contents of a file from Google Drive randomly
        System.out.println("Random read temporary file " + tempfile + ":");
        success = false;
        try {
            FileObject fileobj = fsmanager.resolveFile(tempfile, options);
            // This feature is not part of the interface, cast file object
            RandomAccessContent randomAccessContent = ((GDriveFileObject) fileobj).getRandomAccessContent(RandomAccessMode.READ);
            randomAccessContent.seek(6); // "world!"
            ByteArrayOutputStream bostream = new ByteArrayOutputStream();
            InputStream istream = randomAccessContent.getInputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = istream.read(buffer)) != -1) {
                bostream.write(buffer, 0, len);
            }
            istream.close();
            bostream.flush();
            bostream.close();
            String loaded = bostream.toString(encoding);

            success = loaded.equals("world!");
            System.out.println(success ? "    Successful (content=" + loaded + ")" : "    Failed");
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!success) {
            System.err.println("Sorry, could not download file, exiting");
            return;
        }

        // Remove a Google Drive file and folder
        System.out.println("Removing temporary entries:");
        success = false;
        try {
            // Remove file
            FileObject fileobj = fsmanager.resolveFile(tempfile, options);
            System.out.print("    " + tempfile);
            success = fileobj.delete();
            System.out.println(success ? "  Successful" : "    Failed");

            // Remove folder
            fileobj = fsmanager.resolveFile(tempfolder, options);
            System.out.print("    " + tempfolder);
            fileobj.deleteAll(); // Including children
            success = !fileobj.exists();
            System.out.println(success ? "  Successful" : "    Failed");
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!success) {
            System.err.println("Sorry, could not remove file/folder, exiting");
        }
    }

    /**
     * Functionality for testing and debugging.
     * <p>
     * Supported arguments:
     * <code>
     * -appname [value]        The identifier for your application
     * -accesstype [value]     "online" for web applications and "offline" for installed applications
     * -tokensdir [value]      The directory to store the tokens
     * -usetrash               Deleted files are moved to trash
     * </code>
     * <p>
     *
     * @param args Array of strings with console arguments
     * @since 1.6
     */
    public static void main(String[] args) {

        String appname = null, accesstype = null, tokensdir = null;
        boolean usetrash = false;

        try {
            if (GraphicsEnvironment.isHeadless()) {
                System.err.println("\nSorry, this example requires a graphical environment, exiting");
                System.exit(1);
            }

            // Disable annoying VFS log messages like:
            // 20.09.2013 13:48:31 org.apache.commons.vfs2.VfsLog info
            // INFO: Using "C:\DOCUME~1\User1\LOCALS~1\Temp\vfs_cache" as temporary files store.
            System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
            System.out.println();

            // Parse arguments
            for (int i = 0; i < args.length; i++) {
                if (args[i].equals("-appname") && (i + 1) < args.length) appname = args[++i];
                else if (args[i].equals("-accesstype") && (i + 1) < args.length) accesstype = args[++i];
                else if (args[i].equals("-tokensdir") && (i + 1) < args.length) tokensdir = args[++i];
                else if (args[i].equals("-usetrash")) usetrash = true;
            }

            new ExampleGoogleDrive(appname, accesstype, tokensdir, usetrash);
            System.exit(0);
        } catch (Exception exc) {
            try {
                Thread.sleep(1000);
            } catch (Exception ignored) {
            }
            exc.printStackTrace();
        }
        System.exit(1);
    }
}
