/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package main;

import backupAndRecovery.*;
import io.Partlist;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;

/**
 * This clas is administrator of local repository. Local repository is folder
 * located on local host used to store backup fragments and checksums.
 * @author Marcel Duris
 */
public class RepositoryAdministrator {

    /**
     * Returns current size of repository.
     *
     * @return size of repository in bytes
     */
    public static long getRepositoryUsed() {
        return getFolderSize(ConfigurationReader.getCfg().getFolder());
    }

    /**
     * Returns number of backup currently stored on localhost.
     * 
     * @return number of backups
     */
    public static int getBackupCount() {
        File repository = ConfigurationReader.getCfg().getFolder();
        return repository.list(new FilenameFilter() {

            public boolean accept(File dir, String name) {
                return name != null && name.endsWith(".xml");
            }
        }).length;
    }

    /**
     * Registers backup metadata. If localhost is requested to store some of
     * related backup files, it accepts. If backup is not registered, requests
     * to store files are rejected by servers.
     * 
     * @param meta
     * @see network.FanInClient
     */
    public static void addBackup(BackupMetaContainer meta) {
        Logger.getLogger("qbackup.repository").info("New backup requested: " + meta.getBackupName());
        cleanRepository();
        File repoFolder = ConfigurationReader.getCfg().getFolder();
        String path = repoFolder.getAbsolutePath();

        File metafile = new File(path + "/" + meta.getBackupName() + ".xml");

        OutputStream out = null;
        try {
            out = new BufferedOutputStream(new FileOutputStream(metafile));
            out.write(meta.getXmlString().getBytes());
        } catch (IOException ex) {
            Logger.getLogger("qbackup.repository").error("Error registering backup.");
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                Logger.getLogger("qbackup.repository").error("Error registering backup.");
            }
        }
    }

    public static void cleanRepository() {
        File folder = ConfigurationReader.getCfg().getFolder();
        BackupMetaContainer[] metas = listBackups("");//Lists any stored backups
        Date now = new Date();
        for (BackupMetaContainer meta : metas) {
            File file = new File(folder, meta.getBackupName() + ".xml");

            if (new Date(file.lastModified() + 1 * 60 * 60 * 1000).after(now)) {
                //metadata file has been created hour ago, backup process may still be in progress
                continue;
            }

            String[] list = listFragments(meta);
            if (list.length == 0) {
                //Delete metafiles with no backup files stored
                file.delete();
                Logger.getLogger("qbackup.repository").info("Backup unregistered: " + meta.getBackupName());
            }
        }
    }

    /**
     * Creates or overwrites file in repository. Returns outputstream which
     * writes int specified filename.
     *
     * @param filename name of file
     * @return outputstream writing into filename
     */
    public static OutputStream getOutputStream(String filename) {
        File repoFolder = ConfigurationReader.getCfg().getFolder();
        String path = repoFolder.getAbsolutePath();
        OutputStream result = null;
        //System.out.println(new File(path + "/" + filename));
        try {
            result = new BufferedOutputStream(new FileOutputStream(path + "/" + filename));
        } catch (FileNotFoundException ex) {
            Logger.getLogger("qbackup.repository").error("");
        }

        return result;
    }

    /**
     * Reads requested file stored in repository.
     *
     * @param filename name of file in repositoty to read
     * @return inputstream reading specified file
     * @throws FileNotFoundException if file could not be read
     */
    public static InputStream getInputStream(String filename) throws FileNotFoundException {
        File repoFolder = ConfigurationReader.getCfg().getFolder();
        File file = new File(repoFolder, filename);
        InputStream in = new BufferedInputStream(new FileInputStream(file));
        return in;
    }

    /**
     * Checks whether file is registered.
     *
     * @param bcpName name of backup
     * @return true if backup is registered
     */
    public static boolean isRegistered(String bcpName) {
        File repoFolder = ConfigurationReader.getCfg().getFolder();
        final String filtername = bcpName + ".xml";

        File[] list = repoFolder.listFiles(new FilenameFilter() {

            public boolean accept(File dir, String name) {
                return name.compareToIgnoreCase(filtername) == 0;
            }
        });

        return list.length == 1;
    }

    /**
     * Given backup metadata file, list of fragments stored in local repository
     * are returned.
     *
     * @param meta metadata of backup
     * @return String array of locally stored files
     */
    public static String[] listFragments(BackupMetaContainer meta) {
        final String bcpname = meta.getBackupName();

        return ConfigurationReader.getCfg().getFolder().list(new FilenameFilter() {

            public boolean accept(File dir, String name) {
                return name.startsWith(bcpname) && !name.endsWith(".xml");
            }
        });
    }

    /**
     * This method searches local repository for backups of given file. If no
     * backup of file with that name is localy stored, empty array is returned.
     * Otherwise list of backup metadata container is returned as array.
     * 
     * @param filename name of file to look for
     * @return array of backup metadata containers
     */
    public static BackupMetaContainer[] listBackups(String filename) {
        File repository = ConfigurationReader.getCfg().getFolder();
        String[] metafiles = repository.list(new FilenameFilter() {

            public boolean accept(File dir, String name) {
                return name.endsWith(".xml");
            }
        });

        ArrayList<BackupMetaContainer> metas = new ArrayList<BackupMetaContainer>();
        for (int i = 0; i < metafiles.length; i++) {
            String metaXml = readFile(metafiles[i]);
            Document doc;
            try {
                doc = DocumentHelper.parseText(metaXml);
                BackupMetaContainer meta = new BackupMetaContainer(doc);
                if (meta.getFilename().indexOf(filename) >= 0) {
                    metas.add(meta);
                }
            } catch (DocumentException ex) {
                Logger.getLogger("qbackup.repository").warn("Backup metadata malformed.");
            } catch (IllegalArgumentException ex) {
            }
        }

        return metas.toArray(new BackupMetaContainer[metas.size()]);
    }

    /**
     * Returns list of inputstreams used to read existing parts of backup
     * identified by backup metadata. Metadata contains information on how
     * many data and checksum files should exist in ideal case.
     *
     * For every fragment and data checksum one entry in list exists, however
     * if file is missing, entry is equal to null.
     *
     * @param meta information about backup
     * @return list of inputstreams
     */
    public static ArrayList<InputStream> getInputStreamsForBackup(BackupMetaContainer meta) {
        File folder = ConfigurationReader.getCfg().getFolder();
        ArrayList<InputStream> ins = new ArrayList<InputStream>();
        Partlist pl = new Partlist(meta.getBackupName(), meta.getFragmentCount(), meta.getChecksumCount());
        for (String partName : pl) {
            File file = new File(folder, partName);
            if (file.exists()) {
                try {
                    ins.add(new BufferedInputStream(new FileInputStream(file)));
                } catch (FileNotFoundException ex) {
                    ins.add(null);
                }
            } else {
                ins.add(null);
            }
        }
        return ins;
    }

    /**
     * Returns list of outputstreams used to write missing parts of backup
     * identified by backup metadata. Metadata contains information on how
     * many data and checksum files should exist in ideal case.
     *
     * For every fragment one entry in list exists. If file already exists
     * this entry is equal to null, otherwise entry represents valid output
     * stream
     * 
     * @param meta information about backup
     * @return list of outputstreams
     * @see #getOutputStreamsForBackup(BackupMetaContainer meta, boolean overChecksums)
     */
    public static ArrayList<OutputStream> getOutputStreamsForBackup(BackupMetaContainer meta) {
        return getOutputStreamsForBackup(meta, false);
    }

    /**
     * Same as getOutputStreamsForBackup(BackupMetaContainer meta) exept
     * it allows for iteration over data checksums when overChecksums parameter
     * is set to true.
     *
     * @param meta information about backup
     * @param overChecksums true if data checksums are to be listed
     * @return list of outputstreams
     * @see #getOutputStreamsForBackup(BackupMetaContainer meta)
     */
    public static ArrayList<OutputStream> getOutputStreamsForBackup(BackupMetaContainer meta, boolean overChecksums) {
        File folder = ConfigurationReader.getCfg().getFolder();
        ArrayList<OutputStream> outs = new ArrayList<OutputStream>();
        Partlist pl = new Partlist(meta.getBackupName(), meta.getFragmentCount(), meta.getChecksumCount());
        Iterator<String> iterator = pl.iterator();
        for (int i = 0; i < pl.getDataCount(); i++) {
            String partName = iterator.next();
            File file = new File(folder, partName);
            if (file.exists()) {
                outs.add(null);
            } else {
                try {
                    outs.add(new BufferedOutputStream(new FileOutputStream(file)));
                } catch (FileNotFoundException ex) {
                    outs.add(null);
                }
            }
        }
        return outs;
    }

    /**
     * Deletes backup from local repository.
     * 
     * @param meta backup metadata of backup to be deleted
     */
    public static void deleteBackup(BackupMetaContainer meta) {
        Partlist pl = new Partlist(meta.getBackupName(), meta.getFragmentCount(), meta.getChecksumCount());
        File repository = ConfigurationReader.getCfg().getFolder();

        for (String filename : pl) {
            File part = new File(repository, filename);
            if (part.exists()) {
                part.delete();
            }
        }

        File metaFile = new File(repository, meta.getBackupName() + ".xml");
        if (metaFile.exists()) {
            metaFile.delete();
        }
    }

    /**
     * Reads file into string.
     *
     * @param name file in repo to read
     * @return contents of file
     */
    private static String readFile(String name) {
        InputStream in = null;
        String result = "";
        try {
            String repoPath = ConfigurationReader.getCfg().getFolder().getAbsolutePath();
            in = new BufferedInputStream(new FileInputStream(repoPath + "/" + name));
            byte[] buff = new byte[3000];
            int r;
            while ((r = in.read(buff)) > 0) {
                result += new String(buff);
            }

        } catch (IOException ex) {
            Logger.getLogger("qbackup.repository").error("Error registering backup.");
        } finally {
            try {
                in.close();
            } catch (IOException ex) {
                Logger.getLogger("qbackup.repository").error("Error registering backup.");
            }
        }
        return result.trim();
    }

    /**
     * Auxiliary function to get size of repository folder. Function sums size
     * of files in folder and recursivelly counts size of folders.
     * 
     * @param folder File
     * @return size of folder in bytes
     */
    private static long getFolderSize(File folder) {

        long sum = 0;
        if (folder.isDirectory()) {

            File[] files = folder.listFiles();
            for (File file : files) {
                if (file.isDirectory()) {
                    sum += getFolderSize(file);
                } else {
                    sum += file.length();
                }
            }

        } else {
            return folder.length();
        }

        return sum;
    }
}
