package main;

import java.io.File;
import backupAndRecovery.BackupMetaContainer;
import org.apache.log4j.Logger;

/**
 * This class parses commnand line arguments passed to application and performs
 * apropriate action. Purpose of this class is to replace GUI when needed.
 * 
 * @author Marcel Duris
 */
public class CommandInterface {

    /**
     * Parameter requesting launch in command line mode.
     */
    public final String CNOGUI = "--nogui";
    /**
     * Parameter requesting new backup, following parameters specifies path to
     * file to be backed up.
     */
    public final String CBCKUP = "--backup";
    /**
     * Parameter requesting recovery of backup. Depending on used option either
     * latest backup of given file name or backup with specified name is
     * recovered.
     */
    public final String CRCVR = "--recover";
    /**
     * Use this paratemer to request deletion of backup. Oldest backup or backup
     * with specified name can be deleted.
     */
    public final String CDEL = "--delete";
    /**
     * Given name of file this command argument searches distributed repository
     * for backups of that file. List of found backups is displayed.
     */
    public final String CLIST = "--list";
    /**
     * Starts application server including scheduler, network command listener
     * and reliable transport listener.
     */
    public final String CSRV = "--server";
    /**
     * When run with this argument, application prints basic guide on how to
     * use command line interface.
     */
    public final String CUSG = "--usage";
    /**
     * When this option is used along with recovery or deletion request,
     * following parameters is considered to be backup name and not the
     * filename.
     */
    public final String OPT_BACKUP_NAME = "--byBackupName";
    /**
     * String containing basic help or manual on how to ues CLI.
     */
    public final String HELP = "usage: QBackup-dist.jar " +
            " [".concat(CNOGUI) +
            " [".concat(CBCKUP) + " PATH-TO-FILE [NUMBER-OF-FRAGMENTS NUMBER-OF-CHECKSUMS]]\n" +
            " [".concat(CRCVR) + " [".concat(OPT_BACKUP_NAME) + "] NAME DESTINATION]\n" +
            " [".concat(CDEL) + " [".concat(OPT_BACKUP_NAME) + "] NAME]\n" +
            " [".concat(CLIST) + " [FILENAME]]\n" +
            " [".concat(CSRV) + "]]\n" +
            " [".concat(CUSG) + "]";

    /**
     * Basic constructor initiates required components to run more complex
     * commands. Only configuration file is read.
     */
    public CommandInterface() {
        AppManager.lowInit();
    }

    /**
     * Given list of arguments passed on command line, this function determines
     * correct action.
     *
     * @param args command line arguments
     */
    public void run(String[] args) {
        if (args.length < 2 || args[0].compareTo(CNOGUI) != 0 || args[0].equals(CUSG)) {
            printHelp();
            return;
        }

        if (args[1].compareTo(CBCKUP) == 0) {
            createBackup(args);
        } else if (args[1].compareTo(CRCVR) == 0) {
            recoverBackup(args);
        } else if (args[1].compareTo(CDEL) == 0) {
            deleteBackup(args);
        } else if (args[1].compareTo(CLIST) == 0) {
            listBackup(args);
        } else if (args[1].compareTo(CSRV) == 0) {
            try {
                AppManager.init();
            } catch (Exception ex) {
                AppManager.stop();
                System.out.println("Could not start server.");
                System.out.println(ex.getMessage());
                //Logger.getLogger(CommandInterface.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            printHelp();
        }
    }

    /**
     * Prints basic manual on standard output.
     */
    private void printHelp() {
        System.out.println(HELP);
    }

    /**
     * Action called when new backup is requested. Takes all entered command
     * line arguments.
     *
     * @param args all command line arguments entered
     */
    private void createBackup(String[] args) {
        int n, m;

        Logger.getLogger("qbackup").info("Command invoked: Create backup");
        if (args.length == 5) {
            try {
                n = Integer.parseInt(args[3]);
                m = Integer.parseInt(args[4]);
            } catch (NumberFormatException ex) {
                ConfigurationReader.readConfiguration();
                n = ConfigurationReader.getCfg().getFragmentCount();
                m = ConfigurationReader.getCfg().getChecksumCount();
            }
        } else {
            n = ConfigurationReader.getCfg().getFragmentCount();
            m = ConfigurationReader.getCfg().getChecksumCount();
        }
        AppManager.backupFile(args[2], n, m);
    }

    /**
     * This method recovers backup. If no other options are specified, latest
     * backup is recovered. If OPT_BACKUP_NAME is entered, backup with specified
     * name is found and recovered if possible.
     * 
     * @param args all command line arguments entered
     */
    private void recoverBackup(String[] args) {
        Logger.getLogger("qbackup").info("Command invoked: Recover file");
        String name;
        String backupName = null;
        File dest;
        boolean byBackupName = false;
        if (args[2].equals(OPT_BACKUP_NAME)) {
            name = "";
            dest = new File(args[4]);
            backupName = args[3];
            byBackupName = true;
        } else {
            name = args[2];
            dest = new File(args[3]);
        }

        BackupMetaContainer[] metas = AppManager.searchBackups(name);

        if (metas.length == 0) {
            Logger.getLogger("qbackup").info("No backups found");
            return;
        }

        BackupMetaContainer meta;
        if (!byBackupName) {
            meta = findLast(metas);
        } else {
            meta = findName(metas, backupName);
        }

        if (meta == null) {
            Logger.getLogger("qbackup").info("No matching backups found");
            return;
        }
        AppManager.recover(meta, dest);
    }

    /**
     * This method will try to find and delete backup. If no other options are
     * entered. Oldest backup is deleted. If OPT_BACKUP_NAME is entered, backup
     * with specified name is found and deleted, if possible.
     * 
     * @param args all command line arguments entered
     */
    private void deleteBackup(String[] args) {
        Logger.getLogger("qbackup").info("Command invoked: Delete backup");

        String filename;
        String backupName = "";
        BackupMetaContainer meta;
        boolean usingBackupName = false;

        if (args[2].equals(OPT_BACKUP_NAME)) {
            usingBackupName = true;
        }

        if (usingBackupName) {
            filename = "";
            backupName = args[3];
        } else {
            filename = args[2];
        }

        BackupMetaContainer[] metas = AppManager.searchBackups(filename);
        if (metas == null || metas.length == 0) {
            Logger.getLogger("qbackup").info("No matching backups found");
            return;
        }

        if (usingBackupName) {
            meta = findName(metas, backupName);
        } else {
            meta = findFirst(metas);
        }

        AppManager.delete(meta);

    }

    /**
     * In array of meta information about backups, oldest backup is found and
     * returned.
     *
     * @param metas array of meta information containers
     * @return meta information belonging to oldest backup
     */
    private BackupMetaContainer findFirst(BackupMetaContainer[] metas) {
        BackupMetaContainer first = metas[0];
        for (int i = 1; i < metas.length; i++) {
            if (metas[i].getBackupTime().before(first.getBackupTime())) {
                first = metas[i];
            }
        }
        return first;
    }

    /**
     * Array of backup meta information is searched for latest backup which is 
     * then returned.
     * 
     * @param metas array of meta information containers
     * @return most current backup meta information
     */
    private BackupMetaContainer findLast(BackupMetaContainer[] metas) {
        BackupMetaContainer last = metas[0];
        for (int i = 1; i < metas.length; i++) {
            if (metas[i].getBackupTime().after(last.getBackupTime())) {
                last = metas[i];
            }
        }
        return last;
    }

    /**
     * Array of backup meta information is searched and backup with specified
     * name is returned.
     *
     * @param metas array of meta infromation containers
     * @param name name of backup
     * @return backup with specified name or null if not found
     */
    private BackupMetaContainer findName(BackupMetaContainer[] metas, String name) {
        for (BackupMetaContainer meta : metas) {
            if (meta.getBackupName().equals(name)) {
                return meta;
            }
        }
        return null;
    }

    /**
     * This method searches distributed repository for given file name. List of
     * found matching backups is then printed on standard output.
     * 
     * @param args all command line arguments entered.
     */
    private void listBackup(String[] args) {
        Logger.getLogger("qbackup").info("Command invoked: List backups");
        String name;
        if (args.length < 3) {
            name = "";
        } else {
            name = args[2];
        }
        BackupMetaContainer[] metas = AppManager.searchBackups(name);
        System.out.println();
        System.out.println("Backup Name     Filename    Last modification   Filesize");
        System.out.println();
        for (BackupMetaContainer meta : metas) {
            System.out.println(meta.getBackupName() + " " +
                    meta.getFilename() + " " +
                    meta.getModificationTime() + " " +
                    meta.getSize() + "");
        }
        System.out.println();
    }
}
