package Templates.API_Support.Filesystems_API;

import java.beans.*;
import java.io.*;
import java.util.Date;

import org.openide.ErrorManager;
import org.openide.filesystems.*;
import org.openide.filesystems.FileSystem; // override java.io.FileSystem
import org.openide.util.NbBundle;

/** A filesystem.
 *
 * @author __USER__
 */
public class __Sample__FileSystem extends AbstractFileSystem {

    private static final int REFRESH_TIME = 15000; // 15 seconds

    private File rootFile = new File(".");
    private boolean readOnly;

    public __Sample__FileSystem() {
        // Create and use implementations of filesystem functionality:
        info = new InfoImpl();
        change = new ChangeImpl();
        DefaultAttributes defattr;
        // Handle filesystem.attributes files normally:
         defattr = new DefaultAttributes(info, change, new ListImpl());
        
        /** Handle filesystem.attributes files normally + adds virtual attribute 
         "java.io.File" that is used in conversion routines FileUtil.toFile and 
         FileUtil.fromFile
         */
        //defattr = new InnerAttrs(this, info, change, new ListImpl());        
        
        // (Otherwise set attr to a special implementation, and use ListImpl for list.)
        attr = defattr;

        list = defattr;
        // See below:
        // transfer = new TransferImpl();
        setRefreshTime(REFRESH_TIME);
    }

    public __Sample__FileSystem(FileSystemCapability cap) {
        this();
        setCapability(cap);
    }

    // --------- NAMING AND PROPERTIES ---------

    // This should be something unique based on the filesystem's config:
    protected String computeSystemName(File rootFile) {
        return "__PACKAGE_AND_NAME__[" + rootFile.toString() + "]";
    }

    // As displayed to the user:
    public String getDisplayName() {
	if (!isValid())
	    return NbBundle.getMessage(__NAME__.class, "LAB_invalid_file_system",
					rootFile.toString());
	else
	    return NbBundle.getMessage(__NAME__.class, "LAB_valid_file_system",
					rootFile.toString());
    }

    // Bean getter.
    public File getRootDirectory() {
	return rootFile;
    }

    // Bean setter. Changing the root directory (or in general, the identity
    // of the root file object) should cause everything using this filesystem
    // to refresh. The system name must change and refreshRoot should be used
    // to ensure that everything is correctly updated.
    public synchronized void setRootDirectory(File r) throws PropertyVetoException, IOException {

	if (!r.exists() || r.isFile()) {
            IOException ioe = new IOException(r.toString() + " does not exist");
            ErrorManager.getDefault().annotate(ioe,
                NbBundle.getMessage(__NAME__.class, "EXC_root_dir_does_not_exist", r.toString()));
            throw ioe;
        }

	setSystemName(computeSystemName(r));
	rootFile = r;
	firePropertyChange(PROP_ROOT, null, refreshRoot());
    }

    // Bean getter.
    public boolean isReadOnly() {
	return readOnly;
    }

    // Bean setter.
    public void setReadOnly(boolean flag) {
	if (flag != readOnly) {
	    readOnly = flag;
	    firePropertyChange(PROP_READ_ONLY, new Boolean(!flag), new Boolean(flag));
	}
    }

    // ----------- SPECIAL CAPABILITIES --------------

    // This is how you can affect the classpath for execution, compilation, etc.:
    public void prepareEnvironment(FileSystem.Environment environment) {
	environment.addClassPath(rootFile.toString());
    }

    /*
    // Affect the name and icon of files on this filesystem according to their
    // "status", e.g. version-control modification-commit state:
    private class StatusImpl implements Status {
        public Image annotateIcon(Image icon, int iconType, Set files) {
            // You may first modify it, e.g. by adding a check mark to the icon
            // if that makes sense for this file or group of files.
            return icon;
        }
        public String annotateName(String name, Set files) {
            // E.g. add some sort of suffix to the name if some of the
            // files are modified but not backed up or committed somehow:
            if (theseFilesAreModified(files))
                return NbBundle.getMessage(__NAME__.class, "LBL_modified_files", name);
            else
                return name;
        }
    }
    private transient Status status;
    public Status getStatus() {
        if (status == null) {
           status = new StatusImpl();
        }
	return status;
    }
    // And use fireFileStatusChanged whenever you know something has changed.
    */

    /*
    // Filesystem-specific actions, such as version-control operations.
    // The actions should typically be CookieActions looking for DataObject
    // cookies, where the object's primary file is on this type of filesystem.
    public SystemAction[] getActions() {
	return new SystemAction[] {
	    SystemAction.get(SomeAction.class),
	    null, // separator
	    SystemAction.get(SomeOtherAction.class)
	};
    }
    */

    // ----------- IMPLEMENTATIONS OF ABSTRACT FUNCTIONALITY ----------

    // Utility method only:
    private File getFile(String name) {
	return new File(rootFile, name);
    }

    // Information about files and operations on the contents which do
    // not affect the file's presence or name.
    private class InfoImpl implements Info {

	public boolean folder(String name) {
	    return getFile(name).isDirectory();
	}

	public Date lastModified(String name) {
	    return new Date(getFile(name).lastModified());
	}

	public boolean readOnly(String name) {
	    File f = getFile(name);
	    return f.exists() && !f.canWrite();
	}

	public String mimeType(String name) {
            // Unless you have some special means of determining MIME type
            // (e.g. HTTP headers), ask IDE to use its normal heuristics:
            // the MIME resolver pool and then file extensions, or if nothing
            // matches, just content/unknown.
            return null;
	}

	public long size(String name) {
	    return getFile(name).length();
	}

	public InputStream inputStream(String name) throws FileNotFoundException {
	    return new FileInputStream(getFile(name));
	}

	public OutputStream outputStream(String name) throws IOException {
	    return new FileOutputStream(getFile(name));
	}

	// AbstractFileSystem handles locking the file to the rest of the IDE.
	// This only means that you should define how the file should be locked
	// to the outside world--perhaps it does not need to be.
	public void lock(String name) throws IOException {
	    File file = getFile(name);

	    if (file.exists() && !file.canWrite()) {
                IOException ioe = new IOException("file " + file + " could not be locked");
                ErrorManager.getDefault().annotate(ioe,
                    NbBundle.getMessage(__NAME__.class, "EXC_file_could_not_be_locked",
                                        file.getName(), getDisplayName(), file.getPath()));
                throw ioe;
            }
	}

	public void unlock(String name) {
	    // Nothing special needed to unlock a file to the outside world.
	}

	public void markUnimportant(String name) {
	    // Do nothing special. Version-control systems may use this to mark
	    // certain files (e.g. *.class) as not needing to be stored in the VCS
	    // while others (source files) are by default important.
	}

    }

    // Operations that change the available files.
    private class ChangeImpl implements Change {

	public void createFolder(String name) throws IOException {
	    File f = getFile(name);
	    Object[] errorParams = new Object[] {
		f.getName(),
		getDisplayName(),
		f.getPath()
	    };

	    if (name.equals("")) {
                IOException ioe = new IOException("cannot create empty name");
                ErrorManager.getDefault().annotate(ioe, NbBundle.getMessage(__NAME__.class, "EXC_create_empty_name", errorParams));
                throw ioe;
            }

	    if (f.exists()) {
                IOException ioe = new IOException("folder " + f + " already exists");
                ErrorManager.getDefault().annotate(ioe, NbBundle.getMessage(__NAME__.class, "EXC_folder_already_exists", errorParams));
                throw ioe;
            }

	    boolean b = createRecursiveFolder(f);

	    if (!b) {
                IOException ioe = new IOException("folder " + f + " could not be created");
                ErrorManager.getDefault().annotate(ioe, NbBundle.getMessage(__NAME__.class, "EXC_folder_could_not_be_created", errorParams));
                throw ioe;
            }
	}

	private boolean createRecursiveFolder(File f) {
	    if (f.exists()) return true;
	    if (!f.isAbsolute())
		f = f.getAbsoluteFile();
	    String par = f.getParent();
	    if (par == null) return false;
	    if (!createRecursiveFolder(new File(par))) return false;
	    f.mkdir();
	    return f.exists();
	}

	public void createData(String name) throws IOException {
	    File f = getFile(name);

	    if (!f.createNewFile()) {
                IOException ioe = new IOException("file " + f + " could not be created");
                ErrorManager.getDefault().annotate(ioe,
                    NbBundle.getMessage(__NAME__.class, "EXC_file_could_not_be_created",
                                        f.getName(), getDisplayName(), f.getPath()));
                throw ioe;
            }
	}

	public void rename(String oldName, String newName) throws IOException {
	    File of = getFile(oldName);
	    File nf = getFile(newName);

	    if (!of.renameTo(nf)) {
                IOException ioe = new IOException("file " + of + " could not be renamed to " + nf);
                ErrorManager.getDefault().annotate(ioe,
                    NbBundle.getMessage(__NAME__.class, "EXC_file_could_not_be_renamed",
                                        new Object[] {of.getName(), nf.getName(), getDisplayName(),
                                                      of.getPath(), nf.getPath()}));
                throw ioe;
            }
	}

	public void delete(String name) throws IOException {
	    File file = getFile(name);

	    if (file.exists() && !deleteFile(file)) {
                IOException ioe = new IOException("file " + file + " could not be deleted");
                ErrorManager.getDefault().annotate(ioe,
                    NbBundle.getMessage(__NAME__.class, "EXC_file_could_not_be_deleted",
                                        file.getName(), getDisplayName(), file.getPath()));
                throw ioe;
            }
	}

	private boolean deleteFile(File file) {
	    if (file.isDirectory()) {
		File[] arr = file.listFiles();
		for (int i = 0; i < arr.length; i++)
		    if (!deleteFile(arr[i]))
			return false;
	    }
	    return file.delete();
	}

    }

    // Operation which provides the directory structure.
    private class ListImpl implements List {

	public String[] children(String name) {
	    File f = getFile(name);
	    if (f.isDirectory()) {
		return f.list();
	    } else {
		return null;
	    }
	}

    }

    /** This class adds new virtual attribute "java.io.File".
     * Because of the fact that FileObjects of __Sample__FileSystem are convertible
     * to java.io.File by means of attributes. */
    /*private static class InnerAttrs extends DefaultAttributes {       
        //static final long serialVersionUID = 1257351369229921993L;
        __Sample__FileSystem sfs;
        public InnerAttrs(__Sample__FileSystem sfs, AbstractFileSystem.Info info,
        AbstractFileSystem.Change change,AbstractFileSystem.List list ) {           
            super(info, change, list);
            this.sfs = sfs;
        }
        public Object readAttribute(String name, String attrName) {
            if (attrName.equals("java.io.File"))  // NOI18N
                return sfs.getFile(name);
            
            return super.readAttribute(name, attrName);
        }        
    }*/

    /*
    // Optional special implementations of copy and (cross-directory) move.
    private class TransferImpl implements Transfer {
	
	public boolean copy(String name, Transfer target, String targetName) throws IOException {
	    // Only permit special implementation within single FS
	    // (or you could implement it across filesystems if you wished):
	    if (target != this) return false;
	    // Specially copy the file in an efficient way, e.g. implement
	    // a copy-on-write algorithm.
	    return true;
	}
	
	public boolean move(String name, Transfer target, String targetName) throws IOException {
	    // Only permit special implementation within single FS
	    // (or you could implement it across filesystems if you wished):
	    if (target != this) return false;
	    // Specially move the file, e.g. retain rename information even
	    // across directories in a version-control system.
	    return true;
	}
	
    }
    */

}
