/*
 * VFSFile.java - A file residing on a virtual file system
 * :tabSize=4:indentSize=4:noTabs=false:
 * :folding=explicit:collapseFolds=1:
 *
 * Copyright (C) 1998, 2005 Slava Pestov
 * Portions copyright (C) 2007 Matthieu Casanova
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package org.gjt.sp.jedit.io;

//{{{ Imports
import java.awt.Color;
import java.io.*;
import java.io.Closeable;

import org.gjt.sp.jedit.*;
import org.gjt.sp.jedit.browser.VFSBrowser;
import org.gjt.sp.jedit.browser.FileCellRenderer;
import org.gjt.sp.jedit.manager.BufferManagerImpl;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.IOUtilities;
import org.gjt.sp.util.StandardUtilities;

import javax.swing.*;
//}}}

/**
 * A directory entry returned from a file listing.
 * @since jEdit 4.3pre2
 */
public class VFSFile implements Serializable
{
	//{{{ findCompletion() method
	/**
	 * Return the index of a file whose name matches the given string,
	 * in a case-insensitive manner. Exact matches are preferred,
	 * then same length with different cases, then first found.
	 * @param files The list of files
	 * @param start The start index, inclusive
	 * @param end The end index, exclusive
	 * @param str The string to match
	 * @param dirsOnly Only match directories?
	 * @since jEdit 4.3pre3
	 */
	public static int findCompletion(VFSFile[] files, int start, int end,
		String str, boolean dirsOnly)
	{
		boolean strIsAbsolute = MiscUtilities.isAbsolutePath(str);
		int strLen = str.length();
		int iPotentialMatch = -1;
		boolean potentialMatchGTStr = false;

		for(int i = start; i < end; i++)
		{
			VFSFile file = files[i];
			String matchAgainst = (strIsAbsolute
				? file.getPath() : file.getName());

			if(dirsOnly && file.getType() == FILE)
			{
				continue;
			}
			/* try exact match first */
			else if(matchAgainst.equals(str))
			{
				return i;
			}
			else if(matchAgainst.regionMatches(true,0,str,0,strLen))
			{
				// not found yet or not exact length yet and has exact length
				if(iPotentialMatch == -1
						|| (potentialMatchGTStr
							&& (matchAgainst.length() == strLen)))
				{
					iPotentialMatch = i;
					potentialMatchGTStr = matchAgainst.length() > strLen;
				}
			}
		}

		return iPotentialMatch;
	} //}}}

	//{{{ findCompletion() method
	public static String findCompletion(String path, String complete,
		VFSBrowser browser, boolean dirsOnly)
	{
		Log.log(Log.DEBUG,VFSFile.class,"findCompletion(" + path + ',' + complete
			+ ',' + dirsOnly + ')');

		if(complete.equals("~"))
			return System.getProperty("user.home");
		else if(complete.equals("-"))
			return browser.getView().getBuffer().getDirectory();
		else if(complete.equals(".."))
			return MiscUtilities.getParentOfPath(path);

		if(MiscUtilities.isAbsolutePath(complete))
		{
			if(MiscUtilities.isURL(complete))
				return complete;
			else
				path = "roots:";
		}

		VFS vfs = VFSManager.getVFSForPath(path);
		if((vfs.getCapabilities() & VFS.LOW_LATENCY_CAP) == 0)
			return null;
		Object session = vfs.createVFSSession(path,browser);
		if(session == null)
			return null;

		try
		{
			VFSFile[] files = vfs._listFiles(session,path,browser);
			int index = findCompletion(files,0,files.length,complete,dirsOnly);
			if(index != -1)
				return files[index].path;
		}
		catch(IOException e)
		{
			VFSManager.error(e,path,browser);
		}
		finally
		{
			try
			{
				vfs._endVFSSession(session,browser);
			}
			catch(IOException e)
			{
				VFSManager.error(e,path,browser);
			}
		}

		return null;
	} //}}}

	//{{{ getIcon() method
	/**
	 * Returns the icon for the file.
	 *
	 * @since jEdit 4.3pre9
	 */
	public final Icon getIcon(boolean expanded)
	{
		// I don't know if we should expose _getBuffer in BufferManager interface
		BufferManagerImpl bufferManager = (BufferManagerImpl) jEdit.getBufferManager();
		return getIcon(expanded, bufferManager._getBuffer(getSymlinkPath()).isPresent());
	} //}}}

	//{{{ getIcon() method
	/**
	 * Returns the icon for the file.
	 * Implementations of File system browsers can override this method
	 *
	 * @since jEdit 4.3pre9
	 */
	public Icon getIcon(boolean expanded, boolean openBuffer)
	{
		return getDefaultIcon(expanded, openBuffer);
	} //}}}

	//{{{ getDefaultIcon() method
	/**
	 * Returns the default icon for the file.
	 *
	 * @since jEdit 4.3pre9
	 */
	public final Icon getDefaultIcon(boolean expanded, boolean openBuffer)
	{
		if(getType() == DIRECTORY)
			return expanded ? FileCellRenderer.openDirIcon : FileCellRenderer.dirIcon;
		else if(getType() == FILESYSTEM)
			return FileCellRenderer.filesystemIcon;
		else if(openBuffer)
			return FileCellRenderer.openFileIcon;
		else
			return FileCellRenderer.fileIcon;
	} //}}}

	//{{{ getDefaultIcon() method
	/**
	 * Returns the default icon of the file.
	 *
	 * @return the default icon of the file
	 * @since jEdit 4.3pre9
	 */
	public final Icon getDefaultIcon(boolean expanded)
	{
		// I don't know if we should expose _getBuffer in BufferManager interface
		BufferManagerImpl bufferManager = (BufferManagerImpl) jEdit.getBufferManager();
		return getDefaultIcon(expanded, bufferManager._getBuffer(getSymlinkPath()).isPresent());
	} //}}}

	//{{{ File types
	public static final int FILE = 0;
	public static final int DIRECTORY = 1;
	public static final int FILESYSTEM = 2;
	//}}}

	//{{{ Instance variables
	private String name;
	private String path;
	private String symlinkPath;
	private String deletePath;
	private int type;
	private long length;
	private boolean hidden;
	private boolean canRead;
	private boolean canWrite;
	//}}}

	//{{{ VFSFile constructor
	/**
	 * @since jEdit 4.3pre2
	 */
	public VFSFile()
	{
	} //}}}

	//{{{ VFSFile constructor
	public VFSFile(String name, String path, String deletePath,
		int type, long length, boolean hidden)
	{
		this.name = name;
		this.path = path;
		this.deletePath = deletePath;
		this.symlinkPath = path;
		this.type = type;
		this.length = length;
		this.hidden = hidden;
		if(path != null)
		{
			// maintain backwards compatibility
			VFS vfs = VFSManager.getVFSForPath(path);
			canRead = ((vfs.getCapabilities() & VFS.READ_CAP) != 0);
			canWrite = ((vfs.getCapabilities() & VFS.WRITE_CAP) != 0);
		}
	} //}}}

	//{{{ getVFS() method
	/**
	 * @return The originating virtual file system of this file.
	 */
	public VFS getVFS()
	{
		return VFSManager.getVFSForPath(path);
	} //}}}

	//{{{ getName() method
	public String getName()
	{
		return name;
	} //}}}

	//{{{ setName() method
	public void setName(String name)
	{
		this.name = name;
	} //}}}

	//{{{ isBinary() method
	/**
	 * Check if a file is binary file.
	 *
	 * @param session the VFS session
	 * @return <code>true</code> if the file was detected as binary
	 * @throws IOException IOException If an I/O error occurs
	 * @since jEdit 4.3pre5
	 */
	public boolean isBinary(Object session)
		throws IOException
	{
		InputStream in = getVFS()._createInputStream(session,getPath(),
			false,jEdit.getActiveView());
		if(in == null)
			throw new IOException("Unable to get a Stream for this file "+this);

		try
		{
			return MiscUtilities.isBinary(in);
		}
		finally
		{
			IOUtilities.closeQuietly((Closeable)in);
		}
	} //}}}

	//{{{ getPath() method
	public String getPath()
	{
		return path;
	} //}}}

	//{{{ setPath() method
	public void setPath(String path)
	{
		this.path = path;
	} //}}}

	//{{{ getSymlinkPath() method
	public String getSymlinkPath()
	{
		return symlinkPath;
	} //}}}

	//{{{ setSymlinkPath() method
	public void setSymlinkPath(String symlinkPath)
	{
		this.symlinkPath = symlinkPath;
	} //}}}

	//{{{ getDeletePath() method
	public String getDeletePath()
	{
		return deletePath;
	} //}}}

	//{{{ setDeletePath() method
	public void setDeletePath(String deletePath)
	{
		this.deletePath = deletePath;
	} //}}}

	//{{{ getType() method
	public int getType()
	{
		return type;
	} //}}}

	//{{{ setType() method
	public void setType(int type)
	{
		this.type = type;
	} //}}}

	//{{{ getLength() method
	public long getLength()
	{
		return length;
	} //}}}

	//{{{ setLength() method
	public void setLength(long length)
	{
		this.length = length;
	} //}}}

	//{{{ isHidden() method
	public boolean isHidden()
	{
		return hidden;
	} //}}}

	//{{{ setHidden() method
	public void setHidden(boolean hidden)
	{
		this.hidden = hidden;
	} //}}}

	//{{{ isReadable() method
	public boolean isReadable()
	{
		return canRead;
	} //}}}

	//{{{ setReadable() method
	public void setReadable(boolean canRead)
	{
		this.canRead = canRead;
	} //}}}

	//{{{ isWriteable() method
	public boolean isWriteable()
	{
		return canWrite;
	} //}}}

	//{{{ setWriteable() method
	public void setWriteable(boolean canWrite)
	{
		this.canWrite = canWrite;
	} //}}}

	protected boolean colorCalculated;
	protected Color color;

	//{{{ getExtendedAttribute() method
	/**
	 * Returns the value of an extended attribute. Note that this
	 * returns formatted strings (eg, "10 Mb" for a file size of
	 * 1048576 bytes). If you need access to the raw data, access
	 * fields and methods of this class.
	 * @param name The extended attribute name
	 * @since jEdit 4.2pre1
	 */
	public String getExtendedAttribute(String name)
	{
		if(name.equals(VFS.EA_TYPE))
		{
			switch(getType())
			{
			case FILE:
				return jEdit.getProperty("vfs.browser.type.file");
			case DIRECTORY:
				return jEdit.getProperty("vfs.browser.type.directory");
			case FILESYSTEM:
				return jEdit.getProperty("vfs.browser.type.filesystem");
			default:
				throw new IllegalArgumentException();
			}
		}
		else if(name.equals(VFS.EA_STATUS))
		{
			if(isReadable())
			{
				if(isWriteable())
					return jEdit.getProperty("vfs.browser.status.rw");
				else
					return jEdit.getProperty("vfs.browser.status.ro");
			}
			else
			{
				if(isWriteable())
					return jEdit.getProperty("vfs.browser.status.append");
				else
					return jEdit.getProperty("vfs.browser.status.no");
			}
		}
		else if(name.equals(VFS.EA_SIZE))
		{
			if(getType() != FILE)
				return null;
			else
				return StandardUtilities.formatFileSize(getLength());
		}
		else
			return null;
	} //}}}

	//{{{ getColor() method
	/**
	 * Returns the color that will be used to display the file.
	 *
	 * @return the color of the file
	 */
	public Color getColor()
	{
		if(!colorCalculated)
		{
			colorCalculated = true;
			color = VFS.getDefaultColorFor(name);
		}

		return color;
	} //}}}

	//{{{ toString() method
	public String toString()
	{
		return name;
	} //}}}

	//{{{ fetchedAttrs() method
	/**
	 * Returns true if the attributes are already fetched.
	 *
	 * @see #fetchAttrs()
	 * @return <code>true</code> if the attributes are already fetched
	 */
	protected boolean fetchedAttrs()
	{
		return fetchedAttrs;
	} //}}}

	//{{{ fetchAttrs() method
	/**
	 * Fetch some attributes of the file.
	 * Some attributes are not fetched during
	 * file initialization because it takes time.
	 * They are fetched here.
	 * VFS implementation should overwrite this
	 */
	protected void fetchAttrs()
	{
		fetchedAttrs = true;
	} //}}}

	/** This is true if the attributes are already fetched. */
	private boolean fetchedAttrs;
	
	/**
	 * return file modified date, this function simply return 0,
	 * VFS implementation should overwrite this to return the 
	 * real modified data 
	 */
	public long getModified()
	{
		return 0;
	}
}
