/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.commons.vfs2.provider.ram;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.FileType;
import org.apache.commons.vfs2.RandomAccessContent;
import org.apache.commons.vfs2.provider.AbstractFileName;
import org.apache.commons.vfs2.provider.AbstractFileObject;
import org.apache.commons.vfs2.util.FileObjectUtils;
import org.apache.commons.vfs2.util.RandomAccessMode;

/**
 * A RAM File contains a single RAM FileData instance, it provides methods to access the data by implementing FileObject
 * interface.
 */
public class RamFileObject extends AbstractFileObject<RamFileSystem> {

    /**
     * RAM File Object Data.
     */
    private RamFileData data;

    /**
     * @param name The name of the file.
     * @param fs The FileSystem.
     */
    protected RamFileObject(final AbstractFileName name, final RamFileSystem fs) {
        super(name, fs);
        this.getAbstractFileSystem().attach(this);
    }

    private void save() throws FileSystemException {
        this.getAbstractFileSystem().save(this);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetType()
     */
    @Override
    protected FileType doGetType() throws Exception {
        return data.getType();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doListChildren()
     */
    @Override
    protected String[] doListChildren() throws Exception {
        return this.getAbstractFileSystem().listChildren(this.getName());
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetContentSize()
     */
    @Override
    protected long doGetContentSize() throws Exception {
        return this.size();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetInputStream()
     */
    @Override
    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
        // VFS-210: ram allows to gather an input stream even from a directory. So we need to check the type anyway.
        if (!getType().hasContent()) {
            throw new FileSystemException("vfs.provider/read-not-file.error", getName());
        }

        return new ByteArrayInputStream(this.data.getContent());
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetOutputStream(boolean)
     */
    @Override
    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
        if (!bAppend) {
            this.data.setContent(ArrayUtils.EMPTY_BYTE_ARRAY);
        }
        return new RamFileOutputStream(this);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doDelete()
     */
    @Override
    protected void doDelete() throws Exception {

        if (this.isContentOpen()) {
            throw new FileSystemException(this.getName() + " cannot be deleted while the file is openg");
        }
        getAbstractFileSystem().delete(this);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetLastModifiedTime()
     */
    @Override
    protected long doGetLastModifiedTime() throws Exception {
        return data.getLastModified();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doSetLastModifiedTime(long)
     */
    /** @since 2.0 */
    @Override
    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
        data.setLastModified(modtime);
        return true;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doCreateFolder()
     */
    @Override
    protected void doCreateFolder() throws Exception {
        this.injectType(FileType.FOLDER);
        this.save();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doRename(org.apache.commons.vfs2.FileObject)
     */
    @Override
    protected void doRename(final FileObject newFile) throws Exception {
        final RamFileObject newRamFileObject = (RamFileObject) FileObjectUtils.getAbstractFileObject(newFile);
        getAbstractFileSystem().rename(this, newRamFileObject);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetRandomAccessContent(
     * org.apache.commons.vfs2.util.RandomAccessMode)
     */
    @Override
    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
        return new RamFileRandomAccessContent(this, mode);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doAttach()
     */
    @Override
    protected void doAttach() throws Exception {
        this.getAbstractFileSystem().attach(this);
    }

    /**
     * @return Returns the data.
     */
    RamFileData getData() {
        return data;
    }

    /**
     * @param data The data to set.
     */
    void setData(final RamFileData data) {
        this.data = data;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#injectType(org.apache.commons.vfs2.FileType)
     */
    @Override
    protected void injectType(final FileType fileType) {
        this.data.setType(fileType);
        super.injectType(fileType);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.vfs2.provider.AbstractFileObject#endOutput()
     */
    @Override
    protected void endOutput() throws Exception {
        super.endOutput();
        this.save();
    }

    /**
     * @return Returns the size of the {@link RamFileData}.
     */
    int size() {
        return data == null ? 0 : data.size();
    }

    /**
     * @param newSize The new buffer size.
     * @throws IOException if the new size exceeds the limit
     */
    synchronized void resize(final long newSize) throws IOException {
        final RamFileSystem afs = getAbstractFileSystem();
        final FileSystemOptions afsOptions = afs.getFileSystemOptions();
        if (afsOptions != null) {
            final long maxSize = RamFileSystemConfigBuilder.getInstance().getLongMaxSize(afsOptions);
            if (afs.size() + newSize - this.size() > maxSize) {
                throw new IOException("FileSystem capacity (" + maxSize + ") exceeded.");
            }
        }
        this.data.resize(newSize);
    }

}
