package main;

import java.io.File;
import java.io.IOException;
import schedule.ScheduleConfigManager;
import schedule.Scheduler;
import network.RepositoryPoller;
import backupAndRecovery.Backup;
import backupAndRecovery.BackupMetaContainer;
import backupAndRecovery.Delete;
import network.ManagementServer;
import network.ReliableNetworkListener;
import backupAndRecovery.Recovery;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.RollingFileAppender;

/**
 * Singleton adapter for managing services. Provides access to functions of
 * aplication in library like manner.
 * 
 * @author Marcel Duris
 */
public class AppManager {

    /*
     * List of variables representing system components.
     */
    private ConfigurationReader cfg;
    private ManagementServer managementServer;
    private ReliableNetworkListener reliableServer;
    //private Backup bcp;
    private Scheduler scheduler;
    private static Logger logger;
    private static AppManager manager = null;

    /**
     * Private constructor for singleton. Initiates components of system like
     * UDP and TCP servers, configuration and schedule.
     * 
     * @throws Exception if TCP socket could not be started.
     */
    private AppManager() throws Exception {
        lowInit();

        Logger.getLogger("qbackup").info("Starting");
        cfg = ConfigurationReader.getCfg();
        reliableServer = ReliableNetworkListener.getFactory();
        managementServer = new ManagementServer();

        try {
            scheduler = Scheduler.getScheduler();
            ScheduleConfigManager.getScheduleConfigManager().parseFile(scheduler);
            scheduler.reschedule();
        } catch (Exception ex) {
            Logger.getLogger("qbackup").warn("Unable to load scheduler. Configuration probably malformed.");
        }
    }

    /**
     * Returns AppManager if it has been already initialized or null otherwise.
     *
     * @return instance AppManager or null
     */
    public static AppManager getManager() {
        return manager;
    }

    /**
     * Starts application servers only if they are not already running. Performs
     * initialization of application.
     * 
     * @throws Exception if error starting servers.
     */
    public static void init() throws Exception {
        if (manager == null) {
            manager = new AppManager();
        } else {
            throw new IllegalStateException("Manager probably running.");
        }
    }

    /**
     * Performs low level initialization, so more complex task can be performed.
     */
    public static void lowInit() {
        if (logger == null) {
            PatternLayout layout = new PatternLayout("%-5p%d{ISO8601} [%t]: %m%n");
            logger = Logger.getLogger("qbackup");
            logger.setLevel(Level.INFO);
            logger.addAppender(new ConsoleAppender(layout));
            try {
                RollingFileAppender app = new RollingFileAppender(layout, "qbackup.log");
                app.setMaxBackupIndex(2);
                logger.addAppender(app);
            } catch (IOException ex) {
            }
        }

        ConfigurationReader.readConfiguration();
    }

    /**
     * Stops application servers and disposes application manager.
     */
    public static void stop() {
        manager.closeServers();
        manager = null;
        Logger.getLogger("qbackup").info("Stopped");
    }

    public static void backupFile(String fileName) {
        int n = manager.cfg.getFragmentCount();
        int m = manager.cfg.getChecksumCount();
        backupFile(fileName, n, m);
    }

    /**
     * Starts process of file backup.
     *
     * @param fileName name of file to be backed up
     * @param n desired number of file fragments
     * @param m desired number of file checksums
     */
    public static void backupFile(String fileName, int n, int m) {
        File file = new File(fileName);
        if (!file.exists() || !file.canRead()) {
            throw new IllegalArgumentException("Cannot access file.");
        }
        try {
            new Backup().createBackup(fileName, n, m);
        } catch (Exception ex) {
            Logger.getLogger("qbackup").error("Unable to create backup. Reason: " + ex.getMessage());
        }
    }

    /**
     * Begins recovery of file identified by metafile and saves it into 
     * specified filename.
     *
     * @param meta metafile containing identification of backup
     * @param dest File into which recovered file should be saved
     */
    public static void recover(BackupMetaContainer meta, File dest) {
        new Recovery().recover(meta, dest);
    }

    /**
     * Performs search for filename that was previously backed up. Network hosts
     * storing fragments of any backup related to that file respond with meta
     * information of that backups.
     * 
     * @param filename name of file that was backed up
     * @return BackupMetaContainer[] array holding meta info of found backups.
     */
    public static BackupMetaContainer[] searchBackups(String filename) {

        Logger.getLogger("qbackup").info("Searching backup of file: " + filename);

        RepositoryPoller poller = new RepositoryPoller();
        BackupMetaContainer[] metas = poller.pollForFilename(filename);
        return metas;
    }

    /**
     * Given meta information of backup this method deletes backup represented
     * by it from distributed repository.
     * 
     * @param meta information about any backup to be deleted
     */
    public static void delete(BackupMetaContainer meta) {
        Delete.delete(meta);
    }

    /**
     * Given meta information of backup this method deletes backup represented
     * by it from distributed repository.
     *
     * This method also provides certain way to ensure, that all related
     * fragments of backup have been deleted.
     *
     * @param meta information about any backup to be deleted
     * @param repeat true if additional measures to ensure backup erasure are to
    //     * be taken, false otherwise
     */
    public static void delete(BackupMetaContainer meta, boolean repeat) {
        Delete.delete(meta, repeat);
    }

    /**
     * Stops application servers i.e Client factory, ManagementServer and scheduler.
     */
    private void closeServers() {
        try {
            managementServer.close();
            reliableServer.close();
            scheduler.clear();
        } catch (IOException ex) {
            Logger.getLogger("qbackup").warn("Error closing servers.");
            //Logger.getLogger(AppManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
