/*******************************************************************************
 * Copyright (c) 2005, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 * 	Martin Oberhuber (Wind River) - [170317] add symbolic link support to API
 *******************************************************************************/
package org.eclipse.core.filesystem.provider;

import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.internal.filesystem.local.LocalFileNativesManager;

/**
 * This class should be used by file system providers in their implementation
 * of API methods that return {@link IFileInfo} objects.
 *
 * @since org.eclipse.core.filesystem 1.0
 * @noextend This class is not intended to be subclassed by clients.
 */
public class FileInfo implements IFileInfo {
	/**
	 * Internal attribute indicating if the file is a directory
	 */
	private static final int ATTRIBUTE_DIRECTORY = 1 << 0;

	/**
	 * Internal attribute indicating if the file exists.
	 */
	private static final int ATTRIBUTE_EXISTS = 1 << 16;

	/**
	 * Bit field of file attributes. Initialized to specify a writable resource.
	 */
	private int attributes = EFS.ATTRIBUTE_OWNER_WRITE | EFS.ATTRIBUTE_OWNER_READ;

	private int errorCode = NONE;

	/**
	 * The last modified time.
	 */
	private long lastModified = EFS.NONE;

	/**
	 * The file length.
	 */
	private long length = EFS.NONE;

	/**
	 * The file name.
	 */
	private String name = ""; //$NON-NLS-1$

	/**
	 * The target file name if this is a symbolic link
	 */
	private String linkTarget = null;

	/**
	 * Creates a new file information object with default values.
	 */
	public FileInfo() {
		super();
	}

	/**
	 * Creates a new file information object. All values except the file name
	 * will have default values.
	 *
	 * @param name The name of this file
	 */
	public FileInfo(String name) {
		super();
		this.name = name;
	}

	/**
	 * Convenience method to clear a masked region of the attributes bit field.
	 *
	 * @param mask The mask to be cleared
	 */
	private void clear(int mask) {
		attributes &= ~mask;
	}

	@Override
	public Object clone() {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			// We know this object is cloneable.
			return null;
		}
	}

	/**
	 * @since 1.5
	 */
	@Override
	public int compareTo(IFileInfo o) {
		return name.compareTo(o.getName());
	}

	@Override
	public boolean exists() {
		return getAttribute(ATTRIBUTE_EXISTS);
	}

	/**
	 * @since 1.4
	 */
	@Override
	public int getError() {
		return errorCode;
	}

	@Override
	public boolean getAttribute(int attribute) {
		if (attribute == EFS.ATTRIBUTE_READ_ONLY && isAttributeSuported(EFS.ATTRIBUTE_OWNER_WRITE))
			return (!isSet(EFS.ATTRIBUTE_OWNER_WRITE)) || isSet(EFS.ATTRIBUTE_IMMUTABLE);
		else if (attribute == EFS.ATTRIBUTE_EXECUTABLE && isAttributeSuported(EFS.ATTRIBUTE_OWNER_EXECUTE))
			return isSet(EFS.ATTRIBUTE_OWNER_EXECUTE);
		else
			return isSet(attribute);
	}

	@Override
	public String getStringAttribute(int attribute) {
		if (attribute == EFS.ATTRIBUTE_LINK_TARGET)
			return this.linkTarget;
		return null;
	}

	@Override
	public long getLastModified() {
		return lastModified;
	}

	@Override
	public long getLength() {
		return length;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public boolean isDirectory() {
		return isSet(ATTRIBUTE_DIRECTORY);
	}

	private boolean isSet(long mask) {
		return (attributes & mask) != 0;
	}

	private void set(int mask) {
		attributes |= mask;
	}

	@Override
	public void setAttribute(int attribute, boolean value) {
		if (attribute == EFS.ATTRIBUTE_READ_ONLY && isAttributeSuported(EFS.ATTRIBUTE_OWNER_WRITE)) {
			if (value) {
				clear(EFS.ATTRIBUTE_OWNER_WRITE | EFS.ATTRIBUTE_OTHER_WRITE | EFS.ATTRIBUTE_GROUP_WRITE);
				set(EFS.ATTRIBUTE_IMMUTABLE);
			} else {
				set(EFS.ATTRIBUTE_OWNER_WRITE | EFS.ATTRIBUTE_OWNER_READ);
				clear(EFS.ATTRIBUTE_IMMUTABLE);
			}
		} else if (attribute == EFS.ATTRIBUTE_EXECUTABLE && isAttributeSuported(EFS.ATTRIBUTE_OWNER_EXECUTE)) {
			if (value)
				set(EFS.ATTRIBUTE_OWNER_EXECUTE);
			else
				clear(EFS.ATTRIBUTE_OWNER_EXECUTE | EFS.ATTRIBUTE_GROUP_EXECUTE | EFS.ATTRIBUTE_OTHER_EXECUTE);
		} else {
			if (value)
				set(attribute);
			else
				clear(attribute);
		}
	}

	private static boolean isAttributeSuported(int value) {
		return (LocalFileNativesManager.getSupportedAttributes() & value) != 0;
	}

	/**
	 * Sets whether this is a file or directory.
	 *
	 * @param value <code>true</code> if this is a directory, and <code>false</code>
	 * if this is a file.
	 */
	public void setDirectory(boolean value) {
		if (value)
			set(ATTRIBUTE_DIRECTORY);
		else
			clear(ATTRIBUTE_DIRECTORY);
	}

	/**
	 * Sets whether this file or directory exists.
	 *
	 * @param value <code>true</code> if this file exists, and <code>false</code>
	 * otherwise.
	 */
	public void setExists(boolean value) {
		if (value)
			set(ATTRIBUTE_EXISTS);
		else
			clear(ATTRIBUTE_EXISTS);
	}

	/**
	 * Sets the error code indicating whether an I/O error was encountered when accessing the file.
	 *
	 * @param errorCode {@link IFileInfo#IO_ERROR} if this file has an I/O error,
	 * and {@link IFileInfo#NONE} otherwise.
	 * @since 1.4
	 */
	public void setError(int errorCode) {
		this.errorCode = errorCode;
	}

	@Override
	public void setLastModified(long value) {
		lastModified = value;
	}

	/**
	 * Sets the length of this file. A value of {@link EFS#NONE}
	 * indicates the file does not exist, is a directory, or the length could not be computed.
	 *
	 * @param value the length of this file, or {@link EFS#NONE}
	 */
	public void setLength(long value) {
		this.length = value;
	}

	/**
	 * Sets the name of this file.
	 *
	 * @param name The file name
	 */
	public void setName(String name) {
		if (name == null)
			throw new IllegalArgumentException();
		this.name = name;
	}

	/**
	 * Sets or clears a String attribute, e.g. symbolic link target.
	 *
	 * @param attribute The kind of attribute to set. Currently only
	 * {@link EFS#ATTRIBUTE_LINK_TARGET} is supported.
	 * @param value The String attribute, or <code>null</code> to clear
	 * the attribute
	 * @since org.eclipse.core.filesystem 1.1
	 */
	public void setStringAttribute(int attribute, String value) {
		if (attribute == EFS.ATTRIBUTE_LINK_TARGET)
			this.linkTarget = value;
	}

	/**
	 * For debugging purposes only.
	 */
	@Override
	public String toString() {
		return name;
	}
}