/**
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package cloudExplorer;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;

public class CLI {

    Delete delete;
    String[] object_array = null;
    String getOperationCount;
    Get get;
    String name = null;
    String arg1 = null;
    String operation = null;
    String destination = null;
    String delete_file = null;
    String get_file = null;
    String Home = System.getProperty("user.home");
    String s3_config_file = Home + File.separator + "s3.config";
    BucketClass bucketObject = new BucketClass();
    Acl objectacl = new Acl();
    File put_file;
    String build_file_location;
    Put put;
    String build_name = null;
    String[] saved_s3_configs = null;
    String secret_key = null;
    String access_key = null;
    String endpoint = null;
    String bucket = null;
    double[] x;
    double[] y;
    double[] x_latency;
    double[] y_latency;
    double[] x_iops;
    double[] y_iops;
    String temp_file = (Home + File.separator + "object.tmp");
    String throughput_log = Home + File.separator + "throughput_results.csv";
    String latency_log = Home + File.separator + "latency_results.csv";
    String ops_log = Home + File.separator + "ops_results.csv";
    int threadcount;
    String getValue;
    Boolean performance_operation = true;
    public static Boolean mixed_mode = false;
    String win = "\\";
    String lin = "/";
    BucketMigrationCLI migrate;
    Runnable syncengine;
    ExecutorService executor = Executors.newFixedThreadPool((int) 5);
    String region = null;

    void messageParser(String message) {
        System.out.print(message);
    }

    void mainmenu() {

        for (int i = 0; i != 20; i++) {
            messageParser("\n");
        }
        messageParser("\n------------------------------------------------");
        messageParser("\n           Cloud Explorer CLI: " + operation);
        messageParser("\n------------------------------------------------");

    }

    void deleteAll(String bucket) {
        System.out.print("\n\nDeleting all files in bucket: " + bucket);
        reloadObjects();
        for (int y = 1; y != object_array.length; y++) {
            if (object_array[y] != null) {
                arg1 = object_array[y];
                deleteFromS3(arg1);
            }
        }
        System.out.print("\n\nDelete everything operation complete.\n\n");
    }

    void createFolder(String folder
    ) {
        try {
            if (folder != null) {
                FileWriter frr = new FileWriter(temp_file, true);
                BufferedWriter bfrr = new BufferedWriter(frr);
                bfrr.write("This is a place holder file to simulate a folder for Cloud Explorer only. This file can be deleted.");
                bfrr.close();
                File temp = new File(temp_file);
                if (temp.exists()) {
                    System.out.print("\n\nAttempting to create folder: " + folder);
                    put = new Put(temp.getAbsolutePath().toString(), access_key, secret_key, bucket, endpoint, folder + File.separator, false, false, false);
                    Put.terminal = true;
                    put.run();
                    System.out.print("\nCreate folder operation complete\n\n");
                }

            }
        } catch (Exception createFolder) {
        }
    }

    void addS3Account(String access_key, String secret_key, String endpoint, String port, String name) {
        Credentials cred = new Credentials();
        cred.writeConfig(access_key, secret_key, endpoint, port, name);
        System.out.print("\nAdded Account:" + name + "\n\n");

    }

    void removeS3Account(String name) {
        try {
            StringBuffer config = new StringBuffer();
            FileReader fr = new FileReader(s3_config_file);
            BufferedReader bfr = new BufferedReader(fr);
            String read = null;
            while ((read = bfr.readLine()) != null) {
                if (read != null) {
                    if (read.length() > 1 && read != null) {
                        if (!read.contains(name)) {
                            config.append("\n" + read);
                        }
                    }
                }
            }

            bfr.close();

            FileWriter frr = new FileWriter(s3_config_file);
            BufferedWriter bfrr = new BufferedWriter(frr);
            read = null;
            bfrr.write("\n" + config.toString());
            bfrr.close();

            System.out.print("\nRemoved account: " + name + "\n\n");
        } catch (Exception ex) {
            System.out.print("\nError: " + ex.getMessage());
        }

    }

    void loadS3credentials() {
        try {
            for (String what : saved_s3_configs) {
                if (what == null) {
                    messageParser("\nError: an S3 config was null");
                    System.exit(-1);
                }
            }

            access_key = saved_s3_configs[0];
            secret_key = saved_s3_configs[1];
            endpoint = saved_s3_configs[2] + ":" + saved_s3_configs[3];
        } catch (Exception loadS3Credentials) {
        }
    }

    String loadConfig(String what
    ) {
        String data = null;

        try {
            FileReader fr = new FileReader(what);
            BufferedReader bfr = new BufferedReader(fr);
            String read = null;

            while ((read = bfr.readLine()) != null) {
                if (read.contains("@")) {
                    data = data + read;
                    break;
                }
            }
        } catch (Exception loadConfig) {
        }
        String remove_null = data.replace("null", "");
        String remove_symbol = remove_null.replace("@", " ");
        return remove_symbol;
    }

    void loadEnvars() {
        access_key = System.getenv("ACCESS_KEY");
        secret_key = System.getenv("SECRET_KEY");
        endpoint = System.getenv("ENDPOINT");
    }

    void start(String arg0, final String arg1, final String arg2, final String arg3, final String arg4, final String arg5
    ) {
        operation = arg0;
        Put.terminal = true;
        Get.terminal = true;

        if ((System.getenv("ACCESS_KEY") == null) || System.getenv("SECRET_KEY") == null || System.getenv("ENDPOINT") == null) {
            File s3config = new File(s3_config_file);
            if (s3config.exists()) {
                saved_s3_configs = loadConfig(this.s3_config_file).toString().split(" ");
                loadS3credentials();
            } else {
                messageParser("\nError: S3 config file not found.\n\n");
                System.exit(-1);
            }
        } else {
            loadEnvars();
        }

        if (operation.contains("listbuckets") || operation.contains("makebucket") || operation.contains("rmbucket") || operation.contains("ls") || operation.contains("createfolder") || operation.contains("deleteall") || operation.contains("remove-account") || operation.contains("add-account")) {

            mainmenu();
            bucket = arg1;
            region = arg2;

            if (operation.contains("add-account")) {
                if (arg1 != null && arg2 != null && arg3 != null && arg4 != null && arg5 != null) {
                    addS3Account(arg1, arg2, arg3, arg4, arg5);
                } else {
                    System.out.print("\nError: Missing arguments.\n\n");
                    System.exit(-1);
                }
            }

            if (operation.contains("remove-account")) {
                if (arg1 != null) {
                    removeS3Account(arg1);
                } else {
                    System.out.print("\nError: Missing arguments.\n\n");
                    System.exit(-1);
                }
            }
            if (operation.contains("ls")) {
                ls(0);
            }
            if (operation.contains("deleteall")) {
                deleteAll(arg1);
            }
            if (operation.contains("createfolder")) {
                createFolder(arg2);
            }
            if (operation.contains("listbuckets")) {
                listBuckets();
            }

            if (operation.contains("makebucket")) {
                if (region == null) {
                    System.out.print("\nError: No region specified!\n\n");
                    System.exit(-1);
                } else {
                    makeBucket();
                }
            }
            if (operation.contains("rmbucket")) {
                rmBucket();
            }

        } else {
            if (arg2 == null) {
                System.out.print("\n\n\nError, no bucket specified.\n\n\n");
                System.exit(-1);
            } else {
                bucket = arg2;
            }

            put_file = new File(arg1);
            get_file = arg1;

            destination = arg1;
            mainmenu();

            try {

                new Thread(new Runnable() {
                    public void run() {
                        if (operation.contains("downloadtest") || operation.contains("uploadtest")) {
                            if (operation.contains("downloadtest")) {
                                performance_operation = false;
                            } else {
                                performance_operation = true;
                            }

                            bucket = arg1;
                            getValue = arg2;
                            threadcount = Integer.parseInt(arg3);
                            getOperationCount = arg4;
                            if (arg5 != null) {
                                if (arg5.contains("mixed")) {
                                    mixed_mode = true;
                                }
                            }
                            performance();
                        }

                        if (operation.contains("syncfroms3") || operation.contains("synctos3")) {

                            File check_destination = new File(destination);
                            if (check_destination.exists()) {

                                if (operation.contains("syncfroms3")) {
                                    syncFromS3(arg3);
                                }

                                if (operation.contains("synctos3")) {
                                    syncToS3(arg3);
                                }
                            } else {
                                System.out.print("\n\n\nError: origin/destination directory not found.\n\n\n");
                                System.exit(-1);
                            }
                        }
                        if (operation.contains("delete")) {
                            deleteFromS3(arg1);
                        }

                        if (operation.contains("put")) {
                            if (put_file.exists()) {
                                putTOs3(put_file);
                            } else {
                                messageParser("\nError: " + put_file.toString() + " does not exist");
                            }
                        }
                        if (operation.contains("migrate")) {
                            bucket = arg1;
                            migrateBucket(arg1, arg2, "migrate", null);
                            System.exit(0);
                        }
                        if (operation.contains("snapshot")) {
                            bucket = arg1;
                            if (arg3 != null) {
                                migrateBucket(arg1, arg2, "snapshot", arg3);
                            } else {
                                migrateBucket(arg1, arg2, "snapshot", null);
                            }

                            System.exit(0);
                        }

                        if (operation.contains("search")) {
                            bucket = arg1;
                            get_file = arg2;
                            ls(1);
                        }

                        if (operation.contains("get")) {
                            destination = arg3;
                            getFromS3();
                        }

                    }

                }).start();

            } catch (Exception Start) {
            }

        }
    }

    void syncToS3(String folder) {
        if (folder != null) {
            System.out.print("\n\nStarting sync from: " + destination + " to bucket: " + bucket + " in folder: " + folder + " \n");
        } else {
            System.out.print("\n\nStarting sync from: " + destination + " to bucket: " + bucket + "\n");
        }

        File dir = new File(destination);

        reloadObjects();
        String[] extensions = new String[]{" "};
        List<File> files = (List<File>) FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
        for (File file_found : files) {
            String clean_object_name[] = destination.split(Pattern.quote(File.separator));
            String object = file_found.getAbsolutePath().toString();
            object = object.replace(destination, "");
            object = clean_object_name[clean_object_name.length - 1] + object;
            if (folder != null) {
                object = folder + File.separator + object;
            }

            syncengine = new SyncEngine(object, file_found.getAbsolutePath(), file_found, object, bucket, access_key, secret_key, endpoint, false, false, false, true, null);
            executor.execute(syncengine);
            System.gc();
        }
        executor.shutdown();
        while (!executor.isTerminated()) {
        }

        System.out.print(
                "\nSync operation complete.\n\n\n");
    }

    void reloadObjects() {
        try {
            if (bucket != null) {
                String objectlist = bucketObject.listBucketContents(access_key, secret_key, bucket, endpoint);
                object_array = objectlist.split("@@");
            } else {
                System.out.print("\n\n\nError, no bucket specified.\n\n\n");
            }
        } catch (Exception reloadObjects) {
            System.out.print("\n\n\nError with gathering object list:\n\n" + reloadObjects.getMessage() + "\n\n");
        }
    }

    void syncFromS3(String folder) {
        try {
            if (folder != null) {
                System.out.print("\n\nStarting sync from Folder: " + folder + " on bucket: " + bucket + " to destination: " + destination + ".\n");
            } else {
                System.out.print("\n\nStarting sync from bucket: " + bucket + " to destination: " + destination + ".\n");
            }
            reloadObjects();
            File[] foo = new File[object_array.length];
            for (int i = 1; i != object_array.length; i++) {
                if (object_array[i] != null) {
                    int found = 0;
                    foo[i] = new File(destination + File.separator + object_array[i]);
                    if (folder != null) {
                        if (object_array[i].contains(folder)) {
                            syncengine = new SyncEngine(object_array[i], null, null, object_array[i], bucket, access_key, secret_key, endpoint, null, null, null, false, destination);
                        }
                    } else {
                        syncengine = new SyncEngine(object_array[i], null, null, object_array[i], bucket, access_key, secret_key, endpoint, null, null, null, false, destination);

                    }
                    executor.execute(syncengine);
                    System.gc();
                }
            }
            executor.shutdown();
            while (!executor.isTerminated()) {
            }
            System.out.print("\nSync operation finished running");
        } catch (Exception sync) {
        }

    }

    void ls(int op
    ) {
        try {
            int found = 0;

            if (op == 0) {
                System.out.print("\n\nLoading files for Bucket: " + bucket + "........\n\n");
            } else {
                System.out.print("\n\nSearching for \"" + get_file + "\" in bucket \"" + bucket + "\"........\n\n");
            }
            String objectlist = bucketObject.listBucketContents(access_key, secret_key, bucket, endpoint);
            object_array = objectlist.split("@@");

            for (String obj : object_array) {
                if (obj.contains("null")) {
                } else {
                    if (op == 0) {
                        System.out.print("\n" + obj);
                        found++;
                    } else {
                        if (obj.contains(get_file)) {
                            System.out.print("\n" + obj);
                            found++;
                        }
                    }
                }
            }

            System.out.print("\n\n\nBucket listing operation complete. Found: " + found + " file(s).\n\n\n");

        } catch (Exception ls) {
            System.out.print("\n\nAn Error has occured while listing the files or the bucket does not exist.\n\n\n");
            System.exit(-1);
        }
    }

    String convertObject(String what, String operation
    ) {

        if (what.contains("/")) {
            what = what.replace("/", File.separator);
        }

        if (what.contains("\\")) {
            what = what.replace("\\", File.separator);
        }

        int count = 0;
        int slash_counter = 0;
        String out_file = null;
        int another_counter = 0;

        for (int y = 0; y != what.length(); y++) {
            if (what.substring(y, y + 1).contains(File.separator)) {
                slash_counter++;
                another_counter = y;
            }
        }

        for (int y = 0; y != what.length(); y++) {
            if (y == another_counter) {
                if (operation.contains("download")) {
                    if (what.contains(File.separator)) {
                        out_file = (what.substring(y, what.length()));
                    } else {
                        out_file = (what);
                    }
                } else {
                    out_file = (what.substring(y + 1, what.length()));
                }
            }
        }
        return out_file;
    }

    void getFromS3() {
        try {
            NewJFrame.perf = true;
            System.out.print("\n\nDownloading " + get_file + "........");
            String new_object_name = convertObject(get_file, "download");
            if (destination == null) {
                destination = Home + File.separator + new_object_name;
            } else {
                destination = destination + File.separator + new_object_name;
            }
            get = new Get(get_file, access_key, secret_key, bucket, endpoint, destination, null);
            Get.debug = true;
            get.run();
            File check = new File(Home + File.separator + new_object_name);
            if (check.exists()) {
                System.out.print("\n\nGET operation Complete. File Saved to: " + destination + ". \n\n\n");
            } else {
                System.out.print("\n\nError: GET did not complete successfully.\n\n\n");
            }
        } catch (Exception send) {
            System.out.print("\n\nAn Error has occured while downloading the file.");
            System.exit(-1);
        }
    }

    void listBuckets() {
        try {
            System.out.print("\n\n\nListing Buckets:");
            BucketClass.terminal = true;
            String bucketlist = bucketObject.listBuckets(access_key, secret_key, endpoint);
            System.out.print("\n" + bucketlist.replace(" ", "\n"));
            System.out.print("\n\n\nBucket listing operation Complete\n\n\n");
        } catch (Exception listBuckets) {
            System.out.print("\n\n\nAn error has occurred while listing buckets.\n\n\n");
        }
    }

    void makeBucket() {
        try {
            BucketClass.terminal = true;
            System.out.print("\n\n\nCreating Bucket \"" + bucket + "\".......");
            bucketObject.makeBucket(access_key, secret_key, bucket, endpoint, region);
            System.out.printf("\n\n\nBucket creation operaton complete.\n\n\n");
        } catch (Exception makeBucket) {
            System.out.print("\n\n\nAn error has occurred with making a bucket.\n\n\n");
        }
    }

    void migrateBucket(String bucket, String destination_bucket, String operation, String existing_snapshot_folder) {

        if ((System.getenv("MIGRATE_ACCESS_KEY") == null) || System.getenv("MIGRATE_SECRET_KEY") == null || System.getenv("MIGRATE_ENDPOINT") == null || destination_bucket == null) {
            System.out.print("\nError: Missing a complete set of S3 Credentials in environment variables.\n\n");
        } else {
            System.out.print("\nStarting to " + operation + " " + bucket + " to " + destination_bucket + "\n\n");
            reloadObjects();
            if (operation.contains("migrate")) {
                migrate = new BucketMigrationCLI(access_key, secret_key, bucket, endpoint, object_array, false, null, false, false);
            } else {
                if (existing_snapshot_folder != null) {
                    migrate = new BucketMigrationCLI(access_key, secret_key, bucket, endpoint, object_array, true, existing_snapshot_folder, true, false);
                } else {
                    migrate = new BucketMigrationCLI(access_key, secret_key, bucket, endpoint, object_array, true, null, false, false);
                }
            }
            migrate.new_access_key = System.getenv("MIGRATE_ACCESS_KEY");
            migrate.new_secret_key = System.getenv("MIGRATE_SECRET_KEY");
            migrate.new_endpoint = System.getenv("MIGRATE_ENDPOINT");
            migrate.new_bucket = destination_bucket;
            migrate.run();
        }
    }

    void rmBucket() {
        try {
            BucketClass.terminal = true;
            System.out.print("\n\n\nDeleting Bucket \"" + bucket + "\".......");
            bucketObject.deleteBucket(access_key, secret_key, bucket, endpoint);
            System.out.print("\n\n\nBucket Delete operation Complete\n\n\n");

        } catch (Exception deleteBucket) {
            System.out.print("\n\n\nAn error has occurred with deleting a bucket.");
        }
    }

    void deleteFromS3(String what
    ) {
        try {
            delete_file = what;
            System.out.print("\n\nDeleting: " + delete_file + "........");
            delete = new Delete(delete_file, access_key, secret_key, bucket, endpoint, null);
            Delete.debug = true;
            delete.startc(delete_file, access_key, secret_key, bucket, endpoint, null);
            System.out.print("\n\nDELETE operation Complete\n\n\n");
        } catch (Exception send) {
            System.out.print("\n\nAn Error has occured while deleting the file");
            System.exit(-1);
        }
    }

    void putTOs3(File dir
    ) {
        try {
            NewJFrame.perf = true;
            System.out.print("\n\nUploading: " + put_file.getAbsolutePath().toString() + "........");
            put = new Put(put_file.getAbsolutePath().toString(), access_key, secret_key, bucket, endpoint, put_file.getName(), false, false, false);
            Put.debug = true;
            put.startc(put_file.getAbsolutePath().toString(), access_key, secret_key, bucket, endpoint, put_file.getName(), false, false, false);
            System.out.print("\n\nPUT operation Complete\n\n\n");
        } catch (Exception send) {
            System.out.print("\n\nAn Error has occured while uploading the file");
            System.exit(-1);
        }
    }

    public void performance_logger(double time, double rate, String what) {
        try {
            FileWriter frr = new FileWriter(what, true);
            BufferedWriter bfrr = new BufferedWriter(frr);
            bfrr.write("\n" + time + "," + rate);
            bfrr.close();
        } catch (Exception perf_logger) {
        }
    }

    void performance() {
        System.out.print("\n\nStarting performance test.....\n\n\n");
        NewJFrame.perf = true;
        File tempFile = new File(temp_file);
        File throughputfile = new File(throughput_log);
        File opsfile = new File(ops_log);
        File latencyfile = new File(latency_log);

        int op_count = Integer.parseInt(getOperationCount);
        int file_size = Integer.parseInt(getValue);
        float num_threads = threadcount;

        if (tempFile.exists()) {
            tempFile.delete();
        }

        if (throughputfile.exists()) {
            throughputfile.delete();
        }
        if (latencyfile.exists()) {
            latencyfile.delete();
        }

        if (opsfile.exists()) {
            opsfile.delete();
        }

        if (file_size > 0 && num_threads > 0 && op_count > 0) {

            try {
                FileOutputStream s = new FileOutputStream(temp_file);
                byte[] buf = new byte[file_size * 1024];
                s.write(buf);
                s.flush();
                s.close();
            } catch (Exception add) {
            }

            try {
                String upload = tempFile.getAbsolutePath();

                if (!performance_operation || mixed_mode) {
                    put = new Put(upload, access_key, secret_key, bucket, endpoint, "performance_test_data", false, false, false);
                    put.startc(upload, access_key, secret_key, bucket, endpoint, "performance_test_data", false, false, false);
                }

                x = new double[op_count];
                y = new double[op_count];
                x_latency = new double[op_count];
                y_latency = new double[op_count];
                x_iops = new double[op_count];
                y_iops = new double[op_count];

                int counter = 0;
                int display_counter = 0;

                for (int z = 0; z != op_count; z++) {

                    if (mixed_mode) {
                        if (performance_operation) {
                            performance_operation = false;
                        } else {
                            performance_operation = true;
                        }
                    }

                    long t1 = System.currentTimeMillis();

                    for (int i = 0; i != num_threads; i++) {

                        if (performance_operation) {
                            put = new Put(upload, access_key, secret_key, bucket, endpoint, "performance_test_data_" + i + "_" + z, false, false, false);
                            put.startc(upload, access_key, secret_key, bucket, endpoint, "performance_test_data_" + i + "_" + z, false, false, false);
                        } else {
                            get = new Get("performance_test_data", access_key, secret_key, bucket, endpoint, temp_file + i, null);
                            get.startc("performance_test_data", access_key, secret_key, bucket, endpoint, temp_file + i, null);
                        }
                    }

                    double t2 = System.currentTimeMillis();
                    double diff = t2 - t1;
                    double total_time = diff / 1000;

                    double rate = (num_threads * file_size / total_time / 1024);
                    rate = Math.round(rate * 100);
                    rate = rate / 100;

                    double iops = (num_threads / total_time);
                    iops = Math.round(iops * 100);
                    iops = iops / 100;

                    if (performance_operation) {
                        System.out.print("\nPUT Operation: " + z + ". Time: " + total_time + " seconds." + " Average speed with " + num_threads + " thread(s) is: " + rate + " MB/s. OPS/s: " + iops);
                    } else {
                        System.out.print("\nGET Operation: " + z + ". Time: " + total_time + " seconds." + " Average speed with " + num_threads + " thread(s) is: " + rate + " MB/s. OPS/s: " + iops);
                    }
                    if (!mixed_mode) {
                        performance_logger(counter, rate, throughput_log);
                        performance_logger(counter, iops, ops_log);
                        performance_logger(counter, total_time, latency_log);
                    }

                    if (counter == 100) {
                        counter = 0;
                        x = new double[op_count];
                        y = new double[op_count];
                        x_latency = new double[op_count];
                        y_latency = new double[op_count];
                        x_iops = new double[op_count];
                        y_iops = new double[op_count];
                    }
                    y[counter] = (Double) rate;
                    x[counter] = counter;
                    y_latency[counter] = total_time;
                    x_latency[counter] = counter;
                    y_iops[counter] = iops;
                    x_iops[counter] = counter;
                    counter++;
                    display_counter++;
                }

            } catch (Exception ex) {
            }

            if (!mixed_mode) {
                System.out.print("\n\n\nResults saved in CSV format to: " + "\n" + throughput_log + "\n" + latency_log + "\n" + ops_log + "\n\n\n");
            }

            NewJFrame.perf = false;

        } else {
            System.out.print("\nError: Thread and Count values must be greater than 0. Object Size value must be 1024 or greater.");
        }
    }
}