package freenet.support.io;

import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import freenet.support.Logger;
import freenet.support.api.LockableRandomAccessBuffer;
import freenet.support.api.LockableRandomAccessBufferFactory;

public class DiskSpaceCheckingRandomAccessBufferFactory implements LockableRandomAccessBufferFactory, 
    DiskSpaceChecker, FileRandomAccessBufferFactory {

    private final LockableRandomAccessBufferFactory underlying;
    private final File dir;
    private volatile long minDiskSpace;
    
    /** LOCKING: We synchronize throughout the whole operation to prevent fragmentation and to have
     * an accurate free space estimate. FIXME ideally this would be per-filesystem. It might be 
     * possible to get that information from Java (1.7) via java.nio.file. */
    private static final Lock lock = new ReentrantLock(true);
    
    public DiskSpaceCheckingRandomAccessBufferFactory(LockableRandomAccessBufferFactory underlying, 
            File dir, long minDiskSpace) {
        this.underlying = underlying;
        this.dir = dir;
        this.minDiskSpace = minDiskSpace;
    }
    
    public void setMinDiskSpace(long min) {
        if(min < 0) throw new IllegalArgumentException();
        this.minDiskSpace = min;
    }
    
    @Override
    public LockableRandomAccessBuffer makeRAF(long size) throws IOException {
        lock.lock();
        try {
            if(dir.getUsableSpace() > size + minDiskSpace)
                return underlying.makeRAF(size);
            else
                throw new InsufficientDiskSpaceException();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public synchronized LockableRandomAccessBuffer makeRAF(byte[] initialContents, int offset, int size, boolean readOnly)
            throws IOException {
        lock.lock();
        try {
            if(dir.getUsableSpace() > size + minDiskSpace)
                return underlying.makeRAF(initialContents, offset, size, readOnly);
            else
                throw new InsufficientDiskSpaceException();
        } finally {
            lock.unlock();
        }
    }
    
    public String toString() {
        return super.toString()+":"+underlying.toString();
    }
    
    /** Create a new RAF for a specified file, which must exist but be 0 bytes long. Will delete 
     * the file if an RAF cannot be created.
     * @throws InsufficientDiskSpaceException If there is not enough disk space.
     * @throws IOException If some other disk I/O error occurs. */
    public PooledFileRandomAccessBuffer createNewRAF(File file, long size, Random random) throws IOException {
        lock.lock();
        PooledFileRandomAccessBuffer ret = null;
        try {
            if(!file.exists()) throw new IOException("File does not exist");
            if(file.length() != 0) throw new IOException("File is wrong length");
            // FIXME ideally we would have separate locks for each filesystem ...
            if(dir.getUsableSpace() > size + minDiskSpace) {
                ret = new PooledFileRandomAccessBuffer(file, false, size, random, -1, true);
                return ret;
            } else {
                throw new InsufficientDiskSpaceException();
            }
        } finally {
            if(ret == null) file.delete();
            lock.unlock();
        }
    }

    @Override
    public boolean checkDiskSpace(File file, int toWrite, int bufferSize) {
        if(!FileUtil.isParent(dir, file)) {
            Logger.error(this, "Not checking disk space because "+file+" is not child of "+dir);
            return true;
        }
        lock.lock();
        try {
            if(dir.getUsableSpace() - (toWrite + bufferSize) < minDiskSpace)
                return false;
            else
                return true;
        } finally {
            lock.unlock();
        }
    }

}
