/*
* Copyright 2003-2010 Tufts University  Licensed under the
 * Educational Community 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.osedu.org/licenses/ECL-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.
 */

/*
 * FileManager.java
 *
 * Created on September 16, 2003, 9:31 AM
 *
 *  The software contained in this file is copyright 2003 by Mark J. Norton, all rights reserved.
 */

package tufts.oki.remoteFiling;
import org.apache.commons.net.ftp.*;
import tufts.oki.shared.*;
import tufts.oki.OsidManager;
import java.io.*;
import java.util.*;

/**
 *  The RemoteFilingManager provides a means to represent a remote file system logically 
 *  in memory.  Operations on the remote file system is implemented using FTP.
 *  The RemoteFileManager creates the FTP client and active session.  Since osid.filing
 *  doesn't have provisions in ByteStore or Cabinet for opening a stream, those operations
 *  are included in the RemoteFilingManager.  Remote files may be opened for read or
 *  write access.  Paired with a local file manager, file uploading and downloading
 *  is possible.
 *  <p>
 *  The remote filing manager also implements the concept of current working directory
 *  and provides a number of methods for operating on it or its entries.
 *
 *  @author  Mark Norton
 *
 */
public class RemoteFilingManager extends tufts.oki.OsidManager implements osid.filing.FilingManager {
    private static final int BUFFER_SIZE = 1024;    //  Size of buffer to use.
    //private String rootBase = null;                 //  Path to the remote file system root.
    private RemoteCabinet root = null;              //  The root cabinet for a client-session.
    private RemoteCabinet cwd = null;               //  The current working directory.
    
    /** Creates a new instance of the RemoteFileManager */
    public RemoteFilingManager() {
        super();
    }
    
    /**
     *  Create a client by connecting to the given host using the given username & password.
     *  The rootBase is initialized by grabbing the current working directory after establishing
     *  the connection.  A root cabinet named "/" is created to represent the remote root
     *  in the file system.
     *  <p>
     *  The remote server name, username, and password are all cached in the RemoteFilingManager.
     *  This is necessary to re-establish connections that time out remotely.
     *
     *  @author Mark Norton
     */     
    public void createClient(String host, String username, String password) throws osid.filing.FilingException {
        try {
            RemoteClient rc = new RemoteClient (host, username, password);
            FTPClient client = rc.getClient();
            tufts.oki.shared.Agent agent = new tufts.oki.shared.Agent(username, new tufts.oki.shared.AgentPersonType());
            this.root = new RemoteCabinet("/", agent, null,rc);
            this.cwd = this.root;           //  Set the root as current working directory.
            cwd.entries();
        }
        catch (osid.shared.SharedException ex2) {
            throw new osid.filing.FilingException (osid.filing.FilingException.OPERATION_FAILED);
        }
        catch (osid.OsidException ex3) {
            throw new osid.filing.FilingException (osid.filing.FilingException.OPERATION_FAILED);
        }
    }
     
    /**
     *  Return the root RemoteCabinet.
     */
    public RemoteCabinet getRoot () {
        return root;
    }

    /**
     *  No mention is made if the entry in question is a cabinet or not.  Since only
     *  root cabinets exist at FilingManager level, we can probably assume that the
     *  search for this entry should recurse to sub-cabinents.
     *
     *  @author Mark Norton
     *
     */
    public osid.filing.CabinetEntry getCabinetEntry(osid.shared.Id id) throws osid.filing.FilingException {

        osid.filing.CabinetEntry found = null;
        
        /*  Search the root cabinets for the entry with id.  */
        RemoteCabinet entry = root;
        osid.shared.Id entry_id = root.getId();

        /*  Check to see if the entry we are looking for is a root cabinet.  */
        try {
            if (entry_id.isEqual(id))
                found = entry;

            /*  Otherwise, check sub-cabinets to see if it's in there.  */
            else {
                try {
                    found = entry.getCabinetEntryById(id);
                }
                catch (osid.filing.FilingException ex) {
                    /*  An UNKNOWN_ID exception is expected.  Continue search. */
                }
            }
        }
        catch (osid.shared.SharedException ex) {
            /*  isEqual doesn't really throw an exception.  */
        }
        
        /*  If found is null at this point, we didn't find it.  */
        if (found == null)
            throw new osid.filing.FilingException (osid.filing.FilingException.ITEM_DOES_NOT_EXIST);
        return found;
    }
    
    /**
     *  The documentation indicates that the cabinet being deleted must be empty and
     *  that the owner must have permissions to do this.  Permissions are not implemented
     *  at this time.
     *  <br>
     *  The cabinet entry being deleted is assumed to be a root cabinet.  To delete
     *  sub-cabinets, see Cabinet.remove();  If the entry is not found, nothing happens.
     *
     *  @author Mark Norton
     *
     */
    public void delete(osid.shared.Id cabinetEntryId) throws osid.filing.FilingException {
        throw new osid.filing.FilingException (osid.filing.FilingException.UNIMPLEMENTED);
    }
    
    /**
     *  Return an iterator over all root cabinets.  Since a remote filing system can only
     *  have a single root, a temp vector is created and the single root added to it.
     *
     *  @author Mark Norton
     *
     *  return A CabinetEntryIterator which lists all root cabinets.
     */
    public osid.filing.CabinetEntryIterator listRoots() throws osid.filing.FilingException {
        Vector vect = new Vector(10);
        vect.add (root);
        osid.filing.CabinetEntryIterator it = (osid.filing.CabinetEntryIterator) new RemoteCabinetEntryIterator(vect);
        return it;
    }

    /*  Remote File System Operations  */
    /*  -----------------------------  */
    
    /**
     *  Get the current working directory as a RemoteCabinet object.
     *
     *  @author Mark Norton
     */
    public RemoteCabinet getWorkingDirectory () {
        return this.cwd;
    }

    /**
     *  Set the working directory to the Cabinet indicated.
     *
     *  @author Mark Norton
     */
  //These method's should be either in RemoteClient or RemoteClient should be passed to them
  //Commented them, NEED TO REDESIGN- Anoop
    public void setWorkingDirectory (RemoteCabinet cabinet) throws osid.filing.FilingException {
        throw new osid.filing.FilingException(osid.filing.FilingException.UNSUPPORTED_OPERATION );
        /**
        cwd = cabinet;
        
        //  Do a remote Change Working Directory command.
        try {
            FTPClient client = RemoteClient.getClient();
            String current = client.printWorkingDirectory();
            client.changeWorkingDirectory(cabinet.getFullName());
        }
        catch (java.io.IOException ex) {
            throw new osid.filing.FilingException (osid.filing.FilingException.IO_ERROR);
        }
         */
    }
    
    /**
     *  Set the working directory to the Cabinet name indicated.  Supports changing
     *  directories to a sub-directory in the curent working directory.  Also supports
     *  a popd type command by passing "..", which causes the parent of the current
     *  directory to become active.
     *
     *  @author Mark Norton
     */
    public void setWorkingDirectory (String cabName) throws osid.filing.FilingException {
          throw new osid.filing.FilingException(osid.filing.FilingException.UNSUPPORTED_OPERATION );
          /**
        osid.filing.CabinetEntry entry = cwd.getCabinetEntryByName(cabName);
        
        if (!(entry instanceof RemoteCabinet)) {
            throw new osid.filing.FilingException (osid.filing.FilingException.NOT_A_CABINET);
        }
        
        //  Check the popd case where name is given as "..".
        if (cabName.compareTo("..") == 0) {
            entry = cwd.getParent();
        }
        //  Check for the local cabinet case.
        else {
            entry = cwd.getCabinetEntryByName(cabName);
        }
        
        //  Change the working directory remotely.
        try {
            FTPClient client = RemoteClient.getClient();
            String current = client.printWorkingDirectory();
            client.changeWorkingDirectory(current+ "/" + entry.getDisplayName());
        }
        catch (java.io.IOException ex) {
            throw new osid.filing.FilingException (osid.filing.FilingException.IO_ERROR);
        }
        
        cwd = (RemoteCabinet) entry;
        cwd.entries();      //  Make sure it is initialized
           */
    }
    
    /**
     *  List the contents of the current working directory by returning a 
     *  CabinetEntryIterator for entries in this directory.  Note that the entries
     *  in this iterator are RemoteCabinetEntry's.
     *
     *  @author Mark Norton
     */
    public osid.filing.CabinetEntryIterator list () throws osid.filing.FilingException {
        return cwd.entries();
    }
        
}
