package backupAndRecovery;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dom4j.*;

/**
 * Container storing information about backup.
 * 
 * @author Marcel Duris
 */
public class BackupMetaContainer {

    public static final String ROOT = "backup";
    private final String FN = "fileName";
    private final String FS = "fileSize";
    private final String BN = "backupName";
    private final String HS = "hash";
    private final String BT = "backupTime";
    private final String MT = "modificiationTime";
    private final String DC = "dataCount";
    private final String CC = "checksumCount";
    private final String UN = "userName";
    private String filename;
    private String bcpname;
    private String user;
    private long size;
    private String hash;
    private int datapcs;
    private int csums;
    private long modified;
    private long backed;

    /**
     * Constructor loads information about backup from xml document.
     *
     * @param doc xml document
     * @throws IllegalArgumentException when document is malformed
     * @see Document
     */
    public BackupMetaContainer(Document doc) throws IllegalArgumentException {
        this(doc.getRootElement());
    }

    /**
     * Constructor loads information about backup from xml element.
     *
     * @param root root element of xml document containing metadata
     * @throws IllegalArgumentException if document is malformed
     */
    public BackupMetaContainer(Element root) throws IllegalArgumentException {
        try {
            filename = root.elementTextTrim(FN);
            bcpname = root.elementTextTrim(BN);
            size = Long.parseLong(root.element(FS).getTextTrim());
            hash = root.elementTextTrim(HS);
            datapcs = Integer.parseInt(root.element(DC).getTextTrim());
            csums = Integer.parseInt(root.element(CC).getTextTrim());
            modified = Long.parseLong(root.element(MT).getTextTrim());
            backed = Long.parseLong(root.element(BT).getTextTrim());
            user = root.elementText(UN);
        } catch (NullPointerException ex) {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Computes metadata to given file.
     *
     * @param file File to which we want to compute metadata
     * @param name name of backup to be created
     * @param dc number of framgents of original file
     * @param cc number of checksums to be calculated
     */
    public BackupMetaContainer(File file, String name, int dc, int cc) {
        filename = file.getName();
        bcpname = name;
        size = file.length();
        hash = getFileHash(file);
        datapcs = dc;
        csums = cc;
        modified = file.lastModified();
        backed = new Date().getTime();
        user = System.getProperty("user.name");
    }

    /**
     * Returns string representation of xml corresponding to current backup
     * meta.
     *
     * @return string of xml document
     */
    public String getXmlString() {
        return getXmlDocument().asXML();
    }

    /**
     * Returns Xml document of current metadata.
     *
     * @return xml Document
     * @see Document
     */
    public Document getXmlDocument() {

        Document doc = DocumentHelper.createDocument();
        Element root = doc.addElement(ROOT);
        root.addElement(FN).addText(filename);
        root.addElement(BN).addText(bcpname);
        root.addElement(FS).addText(Long.toString(size));
        root.addElement(HS).addText(hash);
        root.addElement(DC).addText(Integer.toString(datapcs));
        root.addElement(CC).addText(Integer.toString(csums));
        root.addElement(MT).addText(Long.toString(modified));
        root.addElement(BT).addText(Long.toString(backed));
        root.addElement(UN).addText(user);

        return doc;

    }

    /**
     * Returns name of file to which this backup belongs.
     *
     * @return String name of file
     */
    public String getFilename() {
        return filename;
    }

    /**
     * Returns name of this backup.
     *
     * @return String name of backup
     */
    public String getBackupName() {
        return bcpname;
    }

    /**
     * Returns name of user which created this backup or under which application
     * was running during process of backing up.
     *
     * @return username
     */
    public String getUserName() {
        return user;
    }

    /**
     * Time when backup was created.
     *
     * @return Date object containing exact time of backup
     */
    public Date getBackupTime() {
        return new Date(backed);
    }

    /**
     * Returns time of last modification of file to which this backup belongs
     *
     * @return Date object representing time of last modification
     */
    public Date getModificationTime() {
        return new Date(modified);
    }

    /**
     * Returns number of fragments of original file created for this backup.
     * 
     * @return number of fragments
     */
    public int getFragmentCount() {
        return datapcs;
    }

    /**
     * Returns numbers of checksums calculated for this backup.
     *
     * @return number of checksums
     */
    public int getChecksumCount() {
        return csums;
    }

    /**
     * Returns size of original file in bytes.
     *
     * @return size of file
     */
    public long getSize() {
        return size;
    }

    /**
     * Returns MD5 hash of file.
     *
     * @param path absolute path to file
     * @return
     */
    private String getFileHash(File file) {
        String digest = null;

        InputStream in = null;
        try {
            MessageDigest d = MessageDigest.getInstance("sha1");
            in = new DigestInputStream(new BufferedInputStream(new FileInputStream(file)), d);
            int r;
            byte[] buff = new byte[1024];//zda sa rychlejsie
            while ((r = in.read(buff)) >= 0) {
                //d.update(buff, 0, r); //netreba, ak je DigestInputStream
                }
            digest = new BigInteger(1, d.digest()).toString(16);

        } catch (IOException ex) {
            Logger.getLogger(Backup.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(Backup.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                in.close();
            } catch (IOException ex) {
                Logger.getLogger(Backup.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        return digest;
    }

/* == Object methods ======================================================== */

    @Override
    public int hashCode() {
        return getXmlString().hashCode();
    }

    @Override
    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (getClass().isInstance(obj)) {
            return ((BackupMetaContainer) obj).getXmlString().compareTo(getXmlString()) == 0;
        }
        return false;
    }

    /**
     * Returns Xml of this metadata in string format.
     *
     * @return string of xml
     */
    @Override
    public String toString() {
        return getXmlString();
    }
}
