/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  See the NOTICE file distributed with this work for additional
 *  information regarding copyright ownership.
 */

package org.seaborne.delta.server.local.patchstores.filestore;

import java.io.IOException ;
import java.io.OutputStream;
import java.nio.file.Files ;
import java.nio.file.Path;

import org.apache.jena.atlas.RuntimeIOException;
import org.apache.jena.atlas.io.IO;
import org.seaborne.delta.lib.IOX;
import org.seaborne.delta.lib.IOX.IOConsumer;

/**
 * Record of a file in a {@link FileStore}.
 * <p>
 * It provides the path to the file, a path to an associated temporary file used to perform
 * atomic writes to the file.
 * <p>
 * {@code FileEntry} are "write once".
 * <p>
 * The write sequence is
 * <pre>
 *    FileStore fileStore = ...
 *    FileEntry entry = fileStore.allocateFilename();
 *    OutputStream out = entry.openForWrite();
 *    ... write contents ...
 *    entry.completeWrite();
 * </pre>
 * and made convenient with:
 * <pre>
 * fileStore.writeNewFile(IOConsumer<OutoutStream>)
 * </pre>
 */
public class FileEntry {
    public final long version;
    public final Path datafile;
    private final Path tmpfile;

    // Only for openForWrite / completeWrite
    private OutputStream out = null ;
    private boolean haveWritten = false;

    /*package*/ FileEntry(long index, Path datafile, Path tmpfile) {
        this.version = index;
        this.datafile = datafile;
        this.tmpfile = tmpfile;
    }

    /** Atomically write the file */
    public void write(IOConsumer<OutputStream> action) {
        if ( haveWritten )
            throw new RuntimeIOException("FileEntry has already been written: "+datafile);
        IOX.safeWrite(datafile, tmpfile, action);
        haveWritten = true;
    }

    /**
     * Initiate the write process. The {@code Outstream} returned is to a
     * temporary file (same filing system) that is moved into place in
     * {@link #completeWrite}, making the writing of the file atomic.
     * <p>
     * Note that {@code Outstream.close} is idempotent - it is safe for the application to
     * close the {@code Outstream}.
     */
    public OutputStream openForWrite( ) {
        if ( haveWritten )
            throw new RuntimeIOException("FileEntry has already been written: "+datafile);
        try {
            return out = Files.newOutputStream(tmpfile) ;
        } catch (IOException ex) { throw IOX.exception(ex); }
    }

    /**
     * Complete the write process: closes the OutputStream allocated by
     * {@link #openForWrite} thenn
     *
     * <p>
     * Note that {@code Outstream.close} is idempotent - it is safe
     * for the application to close the {@code Outstream}.
     * <p>
     * The application must flush any buffered output prior to calling {@code completeWrite}.
     */
    public void completeWrite() {
        IO.close(out);
        IOX.move(tmpfile, datafile);
        haveWritten = true;
        out = null ;
    }

    public String getDatafileName() {
        return datafile.toString();
    }

    @Override
    public String toString() {
        // Dirctory is quite long.
        return String.format("FileEntry[%d, %s, %s]", version,
                             datafile.getFileName(), tmpfile.getFileName(), datafile.getParent());
    }
}