/**
 * Copyright 2010 - 2017 JetBrains s.r.o.
 *
 * 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.
 */
package jetbrains.exodus.io;

import jetbrains.exodus.ExodusException;
import jetbrains.exodus.OutOfDiskSpaceException;
import jetbrains.exodus.log.LogUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;

public class FileDataWriter extends AbstractDataWriter {

    private static final Logger logger = LoggerFactory.getLogger(FileDataWriter.class);

    @NotNull
    private final File dir;
    private final FileChannel dirChannel;
    @NotNull
    private final LockingManager lockingManager;
    @Nullable
    private RandomAccessFile file;

    public FileDataWriter(final File directory) {
        file = null;
        dir = directory;
        FileChannel channel = null;
        try {
            channel = FileChannel.open(dir.toPath());
        } catch (IOException e) {
            logger.warn("Cannot open the environment directory. Log directory fsync will not be performed.", e);
        }
        dirChannel = channel;
        lockingManager = new LockingManager(dir);
    }

    @Override
    public boolean write(byte[] b, int off, int len) throws ExodusException {
        try {
            file.write(b, off, len);
        } catch (IOException ioe) {
            if (lockingManager.getUsableSpace() < len) {
                throw new OutOfDiskSpaceException(ioe);
            }
            throw new ExodusException("Can't write to file", ioe);
        }
        return true;
    }

    @Override
    public boolean lock(long timeout) {
        return lockingManager.lock(timeout);
    }

    @Override
    public boolean release() {
        return lockingManager.release();
    }

    @Override
    public String lockInfo() {
        return lockingManager.lockInfo();
    }

    @Override
    protected void syncImpl() {
        final RandomAccessFile file = this.file;
        if (file != null) {
            forceSync(file);
        }
    }

    @Override
    protected void closeImpl() {
        try {
            file.close();
            file = null;
        } catch (IOException e) {
            throw new ExodusException(e);
        }
    }

    @Override
    protected void openOrCreateBlockImpl(final long address, final long length) {
        try {
            final RandomAccessFile result = new RandomAccessFile(new File(dir, LogUtil.getLogFilename(address)), "rw");
            result.seek(length);
            if (length != result.length()) {
                result.setLength(length);
                forceSync(result);
            }
            file = result;
        } catch (IOException ioe) {
            throw new ExodusException(ioe);
        }
    }

    private void forceSync(@NotNull final RandomAccessFile file) {
        try {
            final FileChannel channel = file.getChannel();
            channel.force(false);
        } catch (ClosedChannelException e) {
            // ignore
        } catch (IOException ioe) {
            if (file.getChannel().isOpen()) {
                throw new ExodusException(ioe);
            }
        }
    }

    @Override
    public void syncDirectory() {
        if (dirChannel != null) {
            try {
                dirChannel.force(false);
            } catch (IOException e) {
                throw new ExodusException("Cannot fsync directory", e);
            }
        }
    }
}

@@ -1435,16 +1435,25 @@
 aWriter(
+@NotNull 
 final Fi
@@ -1464,24 +1464,24 @@
 irectory) %7B%0A
-
         file
@@ -1684,33 +1684,16 @@
 %22Can
-no
+'
 t open 
-the environment 
 dire
@@ -1697,16 +1697,24 @@
 irectory
+ channel
 . Log di
@@ -1732,14 +1732,11 @@
 nc w
-ill no
+on'
 t be
@@ -1942,16 +1942,183 @@
 ption %7B%0A
+        final RandomAccessFile file = this.file;%0A        if (file == null) %7B%0A            throw new ExodusException(%22Can't write, FileDataWriter is closed%22);%0A        %7D%0A
         
@@ -2377,16 +2377,8 @@
 rite
- to file
 %22, i
@@ -2921,32 +2921,203 @@
 d closeImpl() %7B%0A
+        final RandomAccessFile file = this.file;%0A        if (file == null) %7B%0A            throw new ExodusException(%22Can't close already closed FileDataWriter%22);%0A        %7D%0A
         try %7B%0A  
@@ -3144,32 +3144,37 @@
 ();%0A            
+this.
 file = null;%0A   
@@ -3242,16 +3242,46 @@
 ception(
+%22Can't close FileDataWriter%22, 
 e);%0A    
@@ -3812,24 +3812,24 @@
    %7D%0A    %7D%0A%0A
-
     private 
@@ -3828,16 +3828,23 @@
 private 
+static 
 void for

add guards failing fast on attempt to use closed FileDataWriter