package net.sf.jabref.logic.exporter;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.EnumSet;
import java.util.Set;

import net.sf.jabref.logic.util.io.FileBasedLock;
import net.sf.jabref.logic.util.io.FileUtil;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Class used to handle safe storage to disk.
 * <p>
 * Usage: create a SaveSession giving the file to save to, the encoding, and whether to make a backup. The SaveSession
 * will provide a Writer to store to, which actually goes to a temporary file. The Writer keeps track of whether all
 * characters could be saved, and if not, which characters were not encodable.
 * <p>
 * After saving is finished, the client should close the Writer. If the save should be put into effect, call commit(),
 * otherwise call cancel(). When canceling, the temporary file is simply deleted and the target file remains unchanged.
 * When committing, the temporary file is copied to the target file after making a backup if requested and if the target
 * file already existed, and finally the temporary file is deleted.
 * <p>
 * If committing fails, the temporary file will not be deleted.
 */
public class FileSaveSession extends SaveSession {

    private static final Log LOGGER = LogFactory.getLog(FileSaveSession.class);

    // Filenames.
    private static final String BACKUP_EXTENSION = ".bak";
    private static final String TEMP_PREFIX = "jabref";
    private static final String TEMP_SUFFIX = "save.bib";
    private final Path temporaryFile;


    public FileSaveSession(Charset encoding, boolean backup) throws SaveException {
        this(encoding, backup, createTemporaryFile());
    }

    public FileSaveSession(Charset encoding, boolean backup, Path temporaryFile) throws SaveException {
        super(encoding, backup, getWriterForFile(encoding, temporaryFile));
        this.temporaryFile = temporaryFile;
    }

    private static VerifyingWriter getWriterForFile(Charset encoding, Path file) throws SaveException {
        try {
            return new VerifyingWriter(Files.newOutputStream(file), encoding);
        } catch (IOException e) {
            throw new SaveException(e);
        }
    }

    private static Path createTemporaryFile() throws SaveException {
        try {
            return Files.createTempFile(FileSaveSession.TEMP_PREFIX, FileSaveSession.TEMP_SUFFIX);
        } catch (IOException e) {
            throw new SaveException(e);
        }
    }

    @Override
    public void commit(Path file) throws SaveException {
        if (file == null) {
            return;
        }
        if (backup && Files.exists(file)) {
            Path backupFile = FileUtil.addExtension(file, BACKUP_EXTENSION);
            FileUtil.copyFile(file, backupFile, true);
        }
        try {
            // Always use a lock file
            try {
                if (FileBasedLock.createLockFile(file)) {
                    // Oops, the lock file already existed. Try to wait it out:
                    if (!FileBasedLock.waitForFileLock(file)) {
                        throw SaveException.FILE_LOCKED;
                    }
                }
            } catch (IOException ex) {
                LOGGER.error("Error when creating lock file.", ex);
            }

            // Try to save file permissions to restore them later (by default: allow everything)
            Set<PosixFilePermission> oldFilePermissions = EnumSet.allOf(PosixFilePermission.class);
            if (FileUtil.isPosixCompilant && Files.exists(file)) {
                try {
                    oldFilePermissions = Files.getPosixFilePermissions(file);
                } catch (IOException exception) {
                    LOGGER.warn("Error getting file permissions.", exception);
                }
            }

            FileUtil.copyFile(temporaryFile, file, true);

            // Restore file permissions
            if (FileUtil.isPosixCompilant) {
                try {
                    Files.setPosixFilePermissions(file, oldFilePermissions);
                } catch (IOException exception) {
                    throw new SaveException(exception);
                }
            }
        } finally {
            FileBasedLock.deleteLockFile(file);
        }
        try {
            Files.deleteIfExists(temporaryFile);
        } catch (IOException e) {
            LOGGER.warn("Cannot delete temporary file", e);
        }
    }

    @Override
    public void cancel() {
        try {
            Files.deleteIfExists(temporaryFile);
        } catch (IOException e) {
            LOGGER.warn("Cannot delete temporary file", e);
        }
    }
}
