/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.content.filestore;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.text.MessageFormat;

import org.alfresco.api.AlfrescoPublicApi;    
import org.alfresco.repo.content.AbstractContentReader;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Provides direct access to a local file.
 * <p>
 * This class does not provide remote access to the file.
 * 
 * @author Derek Hulley
 */
@AlfrescoPublicApi
public class FileContentReader extends AbstractContentReader
    implements org.alfresco.service.cmr.repository.FileContentReader
{
    /**
     * message key for missing content.  Parameters are
     * <ul>
     *    <li>{@link org.alfresco.service.cmr.repository.NodeRef NodeRef}</li>
     *    <li>{@link ContentReader ContentReader}</li>
     * </ul>
     */
    public static final String MSG_MISSING_CONTENT = "content.content_missing";
    
    private static final Log logger = LogFactory.getLog(FileContentReader.class);
    
    private File file;
    private boolean allowRandomAccess;
    
    /**
     * Checks the existing reader provided and replaces it with a reader onto some
     * fake content if required.  If the existing reader is invalid, an debug message
     * will be logged under this classname category.
     * <p>
     * It is a convenience method that clients can use to cheaply get a reader that
     * is valid, regardless of whether the initial reader is valid.
     * 
     * @param existingReader a potentially invalid reader or null
     * @param msgTemplate the template message that will used to format the final <i>fake</i> content
     * @param args arguments to put into the <i>fake</i> content
     * @return Returns a the existing reader or a new reader onto some generated text content
     */
    public static ContentReader getSafeContentReader(ContentReader existingReader, String msgTemplate, Object ... args)
    {
        ContentReader reader = existingReader;
        if (existingReader == null || !existingReader.exists())
        {
            // the content was never written to the node or the underlying content is missing
            String fakeContent = MessageFormat.format(msgTemplate, args);
            
            // log it
            if (logger.isDebugEnabled())
            {
                logger.debug(fakeContent);
            }
            
            // fake the content
            File tempFile = TempFileProvider.createTempFile("getSafeContentReader_", ".txt");
            ContentWriter writer = new FileContentWriter(tempFile);
            writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
            writer.setEncoding("UTF-8");
            writer.putContent(fakeContent);
            // grab the reader from the temp writer
            reader = writer.getReader();
        }
        // done
        if (logger.isDebugEnabled())
        {
            logger.debug("Created safe content reader: \n" +
                    "   existing reader: " + existingReader + "\n" +
                    "   safe reader: " + reader);
        }
        return reader;
    }
    
    /**
     * Constructor that builds a URL based on the absolute path of the file.
     * 
     * @param file the file for reading.  This will most likely be directly
     *      related to the content URL.
     */
    public FileContentReader(File file)
    {
        this(
                file,
                FileContentStore.STORE_PROTOCOL + ContentStore.PROTOCOL_DELIMITER + file.getAbsolutePath());
    }
    
    /**
     * Constructor that explicitely sets the URL that the reader represents.
     * 
     * @param file the file for reading.  This will most likely be directly
     *      related to the content URL.
     * @param url the relative url that the reader represents
     */
    public FileContentReader(File file, String url)
    {
        super(url);
        
        this.file = file;
        allowRandomAccess = true;
    }
    
    /* package */ void setAllowRandomAccess(boolean allow)
    {
        this.allowRandomAccess = allow;
    }
    
    /**
     * @return Returns the file that this reader accesses
     */
    public File getFile()
    {
        return file;
    }

    /**
     * @return Whether the file exists or not
     */
    public boolean exists()
    {
        return file.exists();
    }

    /**
     * @see File#length()
     */
    public long getSize()
    {
        if (!exists())
        {
            return 0L;
        }
        else
        {
            return file.length();
        }
    }
    
    /**
     * @see File#lastModified()
     */
    public long getLastModified()
    {
        if (!exists())
        {
            return 0L;
        }
        else
        {
            return file.lastModified();
        }
    }

    /**
     * The URL of the write is known from the start and this method contract states
     * that no consideration needs to be taken w.r.t. the stream state.
     */
    @Override
    protected ContentReader createReader() throws ContentIOException
    {
        FileContentReader reader = new FileContentReader(this.file, getContentUrl());
        reader.setAllowRandomAccess(this.allowRandomAccess);
        return reader;
    }
    
    @Override
    protected ReadableByteChannel getDirectReadableChannel() throws ContentIOException
    {
        try
        {
            // the file must exist
            if (!file.exists())
            {
                throw new IOException("File does not exist: " + file);
            }
            // create the channel
            ReadableByteChannel channel = null;
            if (allowRandomAccess)
            {
                RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");  // won't create it
                channel = randomAccessFile.getChannel();
            }
            else
            {
                InputStream is = new FileInputStream(file);
                channel = Channels.newChannel(is);
            }
            // done
            if (logger.isDebugEnabled())
            {
                logger.debug("Opened read channel to file: \n" +
                        "   file: " + file + "\n" +
                        "   random-access: " + allowRandomAccess);
            }
            return channel;
        }
        catch (Throwable e)
        {
            throw new ContentIOException("Failed to open file channel: " + this, e);
        }
    }

    /**
     * @return Returns false as this is a reader
     * @deprecated Since 5.1.  This method has no value: a file reader can never write (DH: 2015/02/17)
     */
    @Deprecated
    public boolean canWrite()
    {
        return false;   // we only allow reading
    }
}
