
//--------------------------------------------------------------------
// Class FileServer extends Thread
//
// Purpose  : interface with library access procedure from each 
//            client connection.
//--------------------------------------------------------------------

import java.net.*;
import java.io.*;
import java.util.*;

public class FileServer extends Thread
{
//	final static int theCacheSize = 4096;
	final static int theCacheSize = 256;
	final static int theNumOfCache = 8;
//	final static int diskBlockSize = 4096;
	final static int diskBlockSize = 256;
	final static int diskNoOfBlocks = 50;
	final static int stdMFTIndex = 0;
	final static int stdFSMIndex = 1;
	final static int stdRootIndex = 2;

	private static String theVolumeName;
	private Socket theConnection;
	private String theClient;
	private String theCommand;
	private String theFileArgs;
	private DataInputStream theInput;
	private PrintStream theOutput;
	private static FileMonitor fileMonitor;
	private static ServerCacheMgr theCacheMgr;

	private DirFile currentDir;
	private DirFile previousDir;

	final static int maxLinkCount = 7;
	private int linkCount = 0;


//--------------------------------------------------------------------
// main Method
//
// Input	: None
// Output   : None
// Purpose  : create thread to interface with each clients, setup the
//            file monitor
//--------------------------------------------------------------------

	public static void main(String[] args)
	{
		theCacheMgr = new ServerCacheMgr(theNumOfCache, theCacheSize, 
			                             diskBlockSize, diskNoOfBlocks,
										 stdMFTIndex, stdFSMIndex, stdRootIndex);
		fileMonitor = new FileMonitor();
		int thePortNumber;
		ServerSocket theServerSocket;
		Socket theConnection;
		
		// set the port to listen on
		try
		{
			thePortNumber = Integer.parseInt(args[1]);
			if (thePortNumber < 0 || thePortNumber > 65535) thePortNumber = 1997;
		}
		catch (Exception e)
		{
			thePortNumber = 1997;
		}
		
		try
		{
			theServerSocket = new ServerSocket(thePortNumber);
			System.out.println("Accepting connections on port " 
				+ theServerSocket.getLocalPort());

			// spawn the thread waiting for client connection
			while (true)
			{
				theConnection = theServerSocket.accept();
		        System.out.println("Connection ESTABLISHED with " + theConnection);
				FileServer fileServer = new FileServer(theConnection);
				fileServer.start();
			}
		}
		catch (SocketException e)
		{
			System.out.println(e);
		}
		catch (IOException e) { }
	}


//--------------------------------------------------------------------
// Contructor for FileServer
//
// Input	: socket
// Output   : None
// Purpose  : established connection with specific client
//--------------------------------------------------------------------

	public FileServer(Socket s)
	{
		this.theConnection = s;
		this.theClient = s.toString();
	}


//--------------------------------------------------------------------
// Method run for thread
//
// Input	: None
// Output   : None
// Purpose  : established stream socket connection with specific client
//--------------------------------------------------------------------

	public void run()
	{
		try
		{
			theInput = new DataInputStream(theConnection.getInputStream()); 
			theOutput = new PrintStream(theConnection.getOutputStream());
			
			// set current dir to root
			currentDir = new DirFile(theCacheMgr.readFile(stdRootIndex), new String(),
				                     theCacheMgr.getFileName(stdRootIndex));

			// add into monitor
			fileMonitor.add("pwd->" + currentDir.getAbsolutePath(), theClient);

			// listen to command from client
			while(true)
			{
				try 
				{
					theCommand = theInput.readLine();
					System.out.println("recieved from" + theClient + " = " + theCommand);
					if (theCommand.equals("null")) break;
					commandHandler();
				}
				catch (IOException e)
				{
					System.out.println("Connection CLOSED by " + theClient);

					// when client connection is closed, remove all file accessed by
					// him out from the file monitor
					fileMonitor.remove(theClient);
					break;
				}
			}
			theConnection.close();
		}
		catch (IOException e)
		{
			System.out.println(e);
		}
		catch (NullPointerException e)
		{
			System.out.println("Connection CLOSED by " + theClient);
			fileMonitor.remove(theClient);
		}

	}


//--------------------------------------------------------------------
// Method commandHandler
//
// Input	: command
// Output   : None
// Purpose  : handle all available command from client
//--------------------------------------------------------------------

	public void commandHandler()
	{
		try
		{
			if (theCommand.startsWith("read "))
			{
				try { read(); }
				catch (Exception e)
				{
					theOutput.println(e.toString());
				}
				return;
			}

			if (theCommand.startsWith("write "))
			{
				try { write(); }
				catch (Exception e)
				{
					theOutput.println(e.toString());
				}
				return;
			}

			if (theCommand.startsWith("cat "))
			{
				try { cat(); }
				catch (Exception e)
				{
					theOutput.println(e.toString());
				}
				return;
			}

			if (theCommand.startsWith("ls")) ls();
			if (theCommand.startsWith("dir")) dir();
			if (theCommand.startsWith("ln ")) ln();
			if (theCommand.startsWith("cp ")) cp();
			if (theCommand.startsWith("mv ")) mv();
			if (theCommand.startsWith("cd ")) cd();
			if (theCommand.startsWith("rm ")) rm();
			if (theCommand.startsWith("rmdir ")) rmdir();
			if (theCommand.startsWith("mkdir ")) mkdir();
			if (theCommand.startsWith("format ")) format();
			if (theCommand.startsWith("close ")) close();
			if (theCommand.startsWith("save ")) save();
			if (theCommand.equals("defrag")) defrag();

			// system command
			if (theCommand.startsWith("lcache"))
			{
				if (theCommand.length() >= 7)
				{
					theCacheMgr.printCache(Integer.parseInt
						                  (theCommand.substring(7)));
				}
				else
				{
					theCacheMgr.printAllCaches();
				}
				theOutput.println(currentDir.getAbsolutePath());
				return;
			}

			endOfTransmission();
			return;
		}
		catch (Exception evt)
		{
			try
			{
				theOutput.println(evt);
				endOfTransmission();
			}
			catch (Exception e) { }
		}
	}
		

//--------------------------------------------------------------------
// Method fileNameHandler
//
// Input	: None
// Output   : None
// Purpose  : handle the file name argument from client.  Pipe the
//            multiple files to each individual file train with full
//            path and lastModified information
//--------------------------------------------------------------------

	public String fileNameHandler(String fileArgs)
		throws RequestFailException
	{
		String filePipe = new String();
		StringTokenizer paths = new StringTokenizer(fileArgs, "/");
		String tmpString;
		int count = paths.countTokens();
		DirFile tmpDir = currentDir;

		if (fileArgs.startsWith("/"))
		{
			// return to root
			tmpDir = new DirFile(theCacheMgr.readFile(stdRootIndex), 
								 new String(),
			                     theCacheMgr.getFileName(stdRootIndex));
			fileArgs = fileArgs.substring(1);
		}
		
		if (fileArgs.indexOf("/") != -1)
		{
			int end = fileArgs.lastIndexOf("/");
			String dirArgs = fileArgs.substring(0, end);
			fileArgs = fileArgs.substring(end + 1);

			// check if directory if exists
			try
			{
				tmpDir = cd(tmpDir, dirArgs);
			}
			catch (Exception e)
			{
				return e.toString();
			}
		}

		if (fileArgs.indexOf("*") == -1)
		{
			// filePipe = <MFTIndex>;<lastModified>;<fileSize>
			int MFTIndex = tmpDir.getValue(fileArgs);
			try
			{
				int i = 0;
				while (theCacheMgr.isLink(MFTIndex))
				{
					MFTIndex = theCacheMgr.getLinkIndex(MFTIndex);
					i++;
					if (theCacheMgr.isAvailable(MFTIndex))
					{
						MFTIndex = -1;
						break;
					}
					if (i > maxLinkCount)
						throw new RequestFailException
						(maxLinkCount + " link limit exceeded.");
				}

				// constructing filePipe for cat
				long lastModified = theCacheMgr.getLastModified(MFTIndex);
				int fileSize = theCacheMgr.getSize(MFTIndex);
				filePipe = Integer.toString(MFTIndex) + ";" +
						   Long.toString(lastModified) + ";" +
						   Integer.toString(fileSize) + ";" +
						   fileArgs + " ";
			}
			catch (Exception e)
			{
				if (e.toString().indexOf("link limit exceeded") != -1)
					throw new RequestFailException
					(maxLinkCount + " link limit exceeded.");

				// file Not found
				filePipe = Integer.toString(-1) + ";" +
						   Long.toString(-1) + ";" +
						   Integer.toString(-1) + ";" +
						   fileArgs + " ";
			}
		}
		else
		{
			boolean allFiles = false;
			boolean suffix = false;
			boolean prefix = false;
			if (fileArgs.equals("*"))
				allFiles = true;
			else if (fileArgs.startsWith("*")) 
			{
				suffix = true;
				fileArgs = fileArgs.substring(1);
			}
			else if (fileArgs.endsWith("*"))
			{
				prefix = true;
				fileArgs = fileArgs.substring(0, fileArgs.length() - 1);
			}

			int noOfFiles = tmpDir.getNoOfFiles();
			String tmpName;
			for (int j = 0 ; j < noOfFiles ; ++j)
			{
				tmpName = tmpDir.getFileName(j);
				if (allFiles ||
					(prefix && tmpName.startsWith(fileArgs)) ||
					(suffix && tmpName.endsWith(fileArgs)))
				{
					// filePipe = <MFTIndex>;<lastModified>;<fileSize>
					int MFTIndex = tmpDir.getValue(tmpName);
					try
					{
						int i = 0;
						while (theCacheMgr.isLink(MFTIndex))
						{
							MFTIndex = theCacheMgr.getLinkIndex(MFTIndex);
							i++;
							if (theCacheMgr.isAvailable(MFTIndex))
							{
								MFTIndex = -1;
								break;
							}
							if (i > maxLinkCount)
								throw new RequestFailException
								(maxLinkCount + " link limit exceeded.");
						}
		
						// constructing filePipe for cat
						long lastModified = theCacheMgr.getLastModified(MFTIndex);
						int fileSize = theCacheMgr.getSize(MFTIndex);
						filePipe += Integer.toString(MFTIndex) + ";" +
								   Long.toString(lastModified) + ";" +
								   Integer.toString(fileSize) + ";" +
								   tmpName + " ";

					}
					catch (Exception e)
					{
						if (e.toString().indexOf("link limit exceeded") != -1)
							throw new RequestFailException
							(maxLinkCount + " link limit exceeded.");

						// file Not found
						filePipe += Integer.toString(-1) + ";" +
								   Long.toString(-1) + ";" +
								   Integer.toString(-1) + ";" +
								   fileArgs + " ";
					}
			
				}
			}
		}
		return filePipe;
	}

	
//--------------------------------------------------------------------
// Method read
//
// Input	: None
// Output   : None
// Purpose  : read request from client.  Request file from cacheMgr.  
//            The cacheMgr will decide to take from cache or from disk
//--------------------------------------------------------------------

	private void read () 
		throws RequestFailException
	{
		StringTokenizer lsArgs = new StringTokenizer(theCommand);
		int count = lsArgs.countTokens();
		lsArgs.nextToken();

		String fileArgs = lsArgs.nextToken();
		String multiFilePipe = fileNameHandler(fileArgs);
		StringTokenizer filePipe = new StringTokenizer(multiFilePipe);

		if (multiFilePipe.indexOf("Exception:") != -1)
		{
			theOutput.println(multiFilePipe);
			return;
		}

		DirFile relDir = currentDir;
		try
		{
			if (fileArgs.indexOf("/") != -1)
			{
				int end = fileArgs.lastIndexOf("/");
				String dirArgs = fileArgs.substring(0, end);
				fileArgs = fileArgs.substring(end + 1);
				relDir = cd(currentDir, dirArgs);
			}

			while (filePipe.hasMoreElements())
			{
				// filePipe = <MFTIndex>;<lastModified>;<fileSize>
				lsArgs = new StringTokenizer(filePipe.nextToken(), ";");
				int MFTIndex = Integer.parseInt(lsArgs.nextToken());
				long lastModified = Long.parseLong(lsArgs.nextToken());
				int fileSize = Integer.parseInt(lsArgs.nextToken());
				fileArgs = lsArgs.nextToken();

				String fileName = relDir.getAbsolutePath() + fileArgs;

				if (MFTIndex == -1)
				{
					throw new FileNotExistException(fileName);
				}
				else
				{
					if (fileMonitor.isBeingWritten(fileName))
						throw new RequestFailException
						(fileName + " is being written.");

					// send to client for confirming the validity of client cache
					theOutput.println(MFTIndex + ";" + lastModified + ";" + fileSize);
				}

				synchronized (fileMonitor)
				{ 
					fileMonitor.add("read " + fileName, theClient);
				}

				catRead(MFTIndex, lastModified, fileSize);
	
				// return fileName 
				theOutput.println(fileName);
				theCacheMgr.updateMFT();
			}
		}
		catch (Exception e) 
		{ 
			theOutput.println(e.toString());
			return;
		}

		theOutput.println("server: all files sent successfully.");
		return;
	}


//--------------------------------------------------------------------
// Method write
//
// Input	: None
// Output   : None
// Purpose  : write request from client.  Request file from cacheMgr.  
//            The cacheMgr will decide to take from cache or from disk
//--------------------------------------------------------------------

	public void write ()
		throws RequestFailException
	{
		StringTokenizer lsArgs = new StringTokenizer(theCommand);
		int count = lsArgs.countTokens();
		lsArgs.nextToken();

		String fileArgs = lsArgs.nextToken();
		String multiFilePipe = fileNameHandler(fileArgs);
		StringTokenizer filePipe = new StringTokenizer(multiFilePipe);

		if (multiFilePipe.indexOf("Exception:") != -1)
		{
			theOutput.println(multiFilePipe);
			return;
		}

		DirFile relDir = currentDir;
		try
		{
			if (fileArgs.indexOf("/") != -1)
			{
				int end = fileArgs.lastIndexOf("/");
				String dirArgs = fileArgs.substring(0, end);
				fileArgs = fileArgs.substring(end + 1);
				relDir = cd(currentDir, dirArgs);
			}

			while (filePipe.hasMoreElements())
			{
				// filePipe = <MFTIndex>;<lastModified>;<fileSize>
				lsArgs = new StringTokenizer(filePipe.nextToken(), ";");
				int MFTIndex = Integer.parseInt(lsArgs.nextToken());
				long lastModified = Long.parseLong(lsArgs.nextToken());
				int fileSize = Integer.parseInt(lsArgs.nextToken());
				fileArgs =  lsArgs.nextToken();

				String fileName;
	
				if (MFTIndex == -1)
				{
					// update current directory, check notenough space
					MFTIndex = theCacheMgr.newFile(fileArgs, "-", 0, "t", 1);
					relDir.newEntry(fileArgs, MFTIndex);
					theCacheMgr.writeFile(relDir.getValue("."), 
						relDir.getContent());
	
					fileName = relDir.getAbsolutePath() + fileArgs;
					theOutput.println(MFTIndex + ";0;0");
				}
				else
				{
					fileName = relDir.getAbsolutePath() + fileArgs;

					if (theCacheMgr.isSystem(MFTIndex))
						throw new RequestFailException
						(fileName + " is a system file.");

					if (theCacheMgr.isDirectory(MFTIndex))
						throw new RequestFailException
						(fileName + " is a directory file.");

					if (fileMonitor.isBeingWritten(fileName))
						throw new RequestFailException
						(fileName + " is being written.");

					if (fileMonitor.isBeingRead(fileName))
						throw new RequestFailException
						(fileName + " is being read.");
				
					// send to client for confirming the validity of client cache
					theOutput.println(MFTIndex + ";" + lastModified + ";" + fileSize);
				}

				// cat new file does not need to check if being read.
				synchronized (fileMonitor)
				{ 
					fileMonitor.add("write " + fileName, theClient);
				}

				catRead(MFTIndex, lastModified, fileSize);

				// return fileName 
				theOutput.println(fileName);
				theCacheMgr.updateMFT();
			}
		}
		catch (Exception e) 
		{ 
			theOutput.println(e.toString());
			return;
		}

		theOutput.println("server: all files sent successfully.");
		return;
	}


//--------------------------------------------------------------------
// Method save
//
// Input	: None
// Output   : None
// Purpose  : save request from client.  Request file to be save to 
//            disk.  The cacheMgr will update cache and save to disk.
//--------------------------------------------------------------------

	public void save ()
		throws IOException, NotEnoughSpaceException
	{
		StringTokenizer fileArgs = new StringTokenizer(theCommand);
		fileArgs.nextToken();

		String fileName = fileArgs.nextToken();
		int MFTIndex = Integer.parseInt(theInput.readLine());
		catWrite(MFTIndex);
	}


//--------------------------------------------------------------------
// Method close
//
// Input	: None
// Output   : None
// Purpose  : client closes file.  
//--------------------------------------------------------------------

	public void close ()
	{
		StringTokenizer filesArgs = new StringTokenizer(theCommand);
		filesArgs.nextToken();

		String fileName = filesArgs.nextToken();

		// remove that specific access from file monitor
		synchronized (fileMonitor)
		{ 
			fileMonitor.remove(fileName, theClient);
		}

		return;
	}


//--------------------------------------------------------------------
// Method cat
//
// Input	: None
// Output   : None
// Purpose  : cat request from client.
//--------------------------------------------------------------------

	private void cat () 
		throws IOException, 
			NotEnoughSpaceException,
			FileNotExistException,
			FileAlreadyExistException,
			RequestFailException
	{
		StringTokenizer lsArgs = new StringTokenizer(theCommand);
		int count = lsArgs.countTokens();
		lsArgs.nextToken();
		boolean toWrite = false;
		if (theCommand.indexOf(" > ") != -1)
		{
			toWrite = true;
			lsArgs.nextToken();
		}

		String fileArgs = lsArgs.nextToken();
		String multiFilePipe = fileNameHandler(fileArgs);
		StringTokenizer filePipe = new StringTokenizer(multiFilePipe);

		if (multiFilePipe.indexOf("Exception:") != -1)
		{
			theOutput.println(multiFilePipe);
			return;
		}

		DirFile relDir = currentDir;
		try
		{
			if (fileArgs.indexOf("/") != -1)
			{
				int end = fileArgs.lastIndexOf("/");
				String dirArgs = fileArgs.substring(0, end);
				fileArgs = fileArgs.substring(end + 1);
				relDir = cd(currentDir, dirArgs);
			}
		}
		catch (Exception e) { }

		while (filePipe.hasMoreElements())
		{
			// filePipe = <MFTIndex>;<lastModified>;<fileSize>
			lsArgs = new StringTokenizer(filePipe.nextToken(), ";");
			int MFTIndex = Integer.parseInt(lsArgs.nextToken());
			long lastModified = Long.parseLong(lsArgs.nextToken());
			int fileSize = Integer.parseInt(lsArgs.nextToken());

			if (toWrite)
			{

				if (MFTIndex != -1)
					throw new FileAlreadyExistException(fileArgs);

				else
				{
					MFTIndex = theCacheMgr.newFile(fileArgs, "-", 0, "t", 1);
					theOutput.println
						(MFTIndex + ";" + lastModified + ";" + fileSize);

					// update current directory
					try { relDir.newEntry(fileArgs, MFTIndex); }
					catch (Exception e) { }

					// cat new file does not need to check if being read.
					synchronized (fileMonitor)
					{ 
						fileMonitor.add("cat > " + relDir.getAbsolutePath() + 
							             fileArgs, theClient);
					}

					catWrite(MFTIndex);

					synchronized (fileMonitor)
					{ 
						fileMonitor.remove("cat > " + relDir.getAbsolutePath() + 
							             fileArgs, theClient);
					}

					theCacheMgr.writeFile
						(relDir.getValue("."), relDir.getContent());
				}
				return;
			}
			else
			{

				if (MFTIndex == -1)
					throw new FileNotExistException(fileArgs);

				String fileName = relDir.getAbsolutePath() + fileArgs;

				if (fileMonitor.isBeingWritten(fileName))
					throw new RequestFailException
						(fileName + " is being written.");

				// send to client for confirming the validity of client cache
				theOutput.println(MFTIndex + ";" + lastModified + ";" + fileSize);

				catRead(MFTIndex, lastModified, fileSize);
			}

			theCacheMgr.updateMFT();
		}
		theOutput.println("all files sent successfully");
		return;
	}


//--------------------------------------------------------------------
// Method catWrite
//
// Input	: None
// Output   : None
// Purpose  : catWrite to disk.
//--------------------------------------------------------------------

	private void catWrite (int MFTIndex)
		throws IOException, NotEnoughSpaceException
	{
		// client will first sent fileSize
		int fileSize = Integer.parseInt(theInput.readLine());

		if ((fileSize - theCacheMgr.getNoOfBlocks(MFTIndex) * diskBlockSize) > 
			(theCacheMgr.getNoOfFreeBlocks() * diskBlockSize))
		{
			theOutput.println("NotEnoughSpaceException: request = " 
				+ fileSize + " bytes, available = "
				+ (theCacheMgr.getNoOfFreeBlocks() * diskBlockSize) + " bytes");
			return;
		}
		else
		{
			theOutput.println("EnoughSpace");
		}

		// client request specific block offset
		long lastModified = 
			theCacheMgr.writeFile(MFTIndex, fileSize, theInput);

		// for client to update his cache
		theOutput.println(lastModified); 
		theCacheMgr.setLastAccessed(MFTIndex);
		return;
	}


//--------------------------------------------------------------------
// Method catRead
//
// Input	: None
// Output   : None
// Purpose  : catRead from disk.
//--------------------------------------------------------------------

	private void catRead (int MFTIndex, long lastModified, int fileSize)
		throws IOException
	{
		// client request specific block offset
		String blockIndexRequest = theInput.readLine();
		while (blockIndexRequest.indexOf("end request") == -1)
		{
			int blockIndex = Integer.parseInt(blockIndexRequest);
			theCacheMgr.readFile(MFTIndex, blockIndex, lastModified, 
				                 fileSize, theOutput);
			blockIndexRequest = theInput.readLine();
		}
		
		theCacheMgr.setLastAccessed(MFTIndex);
		return;
	}


//--------------------------------------------------------------------
// Method ln
//
// Input	: None
// Output   : None
// Purpose  : ln request from client.
//--------------------------------------------------------------------

	private void ln () 
		throws RequestFailException,
		       IOException,
			   NotEnoughSpaceException,
			   FileAlreadyExistException
	{
		StringTokenizer parameter = new StringTokenizer(theCommand);

		if (theCommand.indexOf("*") != -1)
			throw new RequestFailException
				("\"*\" is a reserved character.");

		parameter.nextToken();

		boolean symbolicLink = false;

		// symbolic link request
		if (theCommand.indexOf(" -s ") != -1)
		{
			parameter.nextToken();
			symbolicLink = true;
		}

		String originalFile = parameter.nextToken();
		String newFile = parameter.nextToken(); 

		// some legal checks
		if (isExist(currentDir, newFile))
			throw new RequestFailException
			   (newFile + " is already exist.");

		if (!isExist(currentDir, originalFile))
			throw new RequestFailException
			   (originalFile + " is not exist.");

		DirFile relDir = currentDir;
		int linkIndex = 0;
		String type = new String();
		String fileArgs;
		try
		{
			relDir = cd(currentDir, originalFile);

			// link to directory
			linkIndex = relDir.getValue(".");
			type = "d";
		}
		catch (Exception e) { }

		// link to typical file
		if (!type.equals("d"))
		{
			relDir = nearestDir(relDir, originalFile);
			if (originalFile.indexOf("/") != -1)
			{
				int end = originalFile.lastIndexOf("/");
				fileArgs = originalFile.substring(end + 1);
			}
			else
			{
				relDir = currentDir;
				fileArgs = originalFile;
			}
			linkIndex = relDir.getValue(fileArgs);
		}

		int MFTIndex;
		if (symbolicLink)
		{
			// req theCacheMgr constructing newFile
			MFTIndex = theCacheMgr.newFile
				(newFile, "->" + originalFile, linkIndex, "l", 0);
		}
		else
		{
			MFTIndex = linkIndex;
			theCacheMgr.setHardLinkCount(MFTIndex,
				theCacheMgr.getHardLinkCount(MFTIndex) + 1);
		}


		// update current directory
		currentDir.newEntry(newFile, MFTIndex);
		theCacheMgr.writeFile(currentDir.getValue("."), currentDir.getContent());

		theCacheMgr.updateMFT();
		requestSuccess();
		return;
	}


//--------------------------------------------------------------------
// Method dir
//
// Input	: None
// Output   : None
// Purpose  : dir request from client.
//--------------------------------------------------------------------

	private void dir () 
		throws RequestFailException,
		       FileNotExistException
	{
		if (theCommand.equals("dir"))
		{
			theCommand = "ls -l";
		}
		else
		{
			theCommand = "ls -l" + theCommand.substring(3);
		}
		ls();
	}

//--------------------------------------------------------------------
// Method ls
//
// Input	: None
// Output   : None
// Purpose  : ls request from client.
//--------------------------------------------------------------------

	private void ls () 
		throws RequestFailException,
		       FileNotExistException
	{
		StringTokenizer parameter = new StringTokenizer(theCommand);
		int count = parameter.countTokens();
		String opt = "-s";
		String fileArgs = "*";
		parameter.nextToken();

		// refresh currentDir
		currentDir = cd(currentDir, ".");
		DirFile relDir = currentDir;

		if (theCommand.indexOf(" -l") != -1)
			opt = "-l";

		if (theCommand.indexOf(" -la") != -1)
			opt = "-la";

		if ((count == 2) && (opt.equals("-s")))
			fileArgs = parameter.nextToken();

		if (count >= 3)
		{
			parameter.nextToken();
			fileArgs = parameter.nextToken();
		}

		try 
		{
			relDir = cd(currentDir, fileArgs);

			// it is the directory, ls anything under
			theOutput.println(theCacheMgr.ls(opt, "*", relDir));
			return;
		}
		catch (Exception e) { }

		if (fileArgs.indexOf("/") != -1)
		{
			int end = fileArgs.lastIndexOf("/");
			String tmpString = fileArgs.substring(0, end + 1);
			relDir = cd(relDir, tmpString);
			fileArgs = fileArgs.substring(end + 1);
		}

		// standard ls
		theOutput.println(theCacheMgr.ls(opt, fileArgs, relDir));
		return;
	}


//--------------------------------------------------------------------
// Method cd(1)
//
// Input	: None
// Output   : None
// Purpose  : change directory by calling cd(2)
//--------------------------------------------------------------------

	private void cd()
		throws RequestFailException,
		       FileNotExistException
	{
		StringTokenizer parameter = new StringTokenizer(theCommand);
		String oldAbsolutePath = currentDir.getAbsolutePath();
		String nextDirName = parameter.nextToken();
		nextDirName = parameter.nextToken();


		DirFile tmpDir = currentDir;
		if (nextDirName.equals("-"))
		{
			currentDir = previousDir;
			nextDirName = ".";
		}

	
		// remove oldpath add newpath to monitor
		currentDir = cd(currentDir, nextDirName); 
		fileMonitor.remove(oldAbsolutePath, theClient);
		fileMonitor.add("pwd->" + currentDir.getAbsolutePath(), theClient);

		previousDir = tmpDir;

		return;
	}


//--------------------------------------------------------------------
// Method cd(2)
//
// Input	: origDir, nextDirName
// Output   : newDir
// Purpose  : change to specific directory, throws Exception otherwise
//--------------------------------------------------------------------

	private DirFile cd (DirFile relDir, String nextDirName)
		throws RequestFailException,
		       FileNotExistException
	{
		if (nextDirName.indexOf("*") != -1)
			throw new RequestFailException
				("\"*\" is a reserved character.");

		DirFile tmpDir = relDir;
		if (nextDirName.startsWith("/"))
		{
			// set current dir to root
			tmpDir = new DirFile(theCacheMgr.readFile(stdRootIndex),
									 new String(),
									 theCacheMgr.getFileName(stdRootIndex));

			if (nextDirName.equals("/")) return tmpDir;
		}

			
		StringTokenizer parameter = new StringTokenizer(nextDirName, "/");
		int count = parameter.countTokens();

		// change directory token by token
		while (count > 0)
		{
			nextDirName = new String(parameter.nextToken());

			if (!tmpDir.isExist(nextDirName))
				throw new FileNotExistException
					("directory " + tmpDir.getAbsolutePath() + nextDirName);

			int MFTIndex = tmpDir.getValue(nextDirName);

			// if link is found, switch to linkIndex
			if (theCacheMgr.isLink(MFTIndex))
			{
				nextDirName = theCacheMgr.getLinkName(MFTIndex).substring(2);
				++linkCount;
				if (linkCount > maxLinkCount)
				{
					linkCount = 0;
					throw new RequestFailException
						(maxLinkCount + " link limit exceeded.");
				}
				tmpDir = cd(tmpDir, nextDirName);
				linkCount = 0;
				return tmpDir;
			}

			// req dirContent from theCacheMgr
			String nextDirContent = theCacheMgr.cd(tmpDir, nextDirName);

			if (nextDirContent.indexOf("is not directory") != -1)
				throw new RequestFailException
					(nextDirName + " is not directory.");

			String nextAbsolutePath = new String();

			// reaching the root
			if ( (nextDirName.equals(".")) || 
				 (nextDirName.equals("..") && 
				 (tmpDir.getValue(".") == stdRootIndex)) )
			{
				nextAbsolutePath = tmpDir.getAbsolutePath();
				int endIndex = nextAbsolutePath.lastIndexOf("/");
				nextAbsolutePath = nextAbsolutePath.substring(0, endIndex);
				nextDirName = new String();
			}
			else if (nextDirName.equals(".."))
			{
				// if <..> step up
				nextAbsolutePath = tmpDir.getAbsolutePath();
				int endIndex = nextAbsolutePath.lastIndexOf("/");
				endIndex = nextAbsolutePath.lastIndexOf("/", endIndex - 1);
				nextAbsolutePath = nextAbsolutePath.substring(0, endIndex);
				nextDirName = new String();
			}
			else
			{
				nextAbsolutePath = tmpDir.getAbsolutePath();
			}

			// change to new directory
			tmpDir = new DirFile (nextDirContent, nextAbsolutePath, nextDirName);
			count--;
		}

		return tmpDir;
	}


//--------------------------------------------------------------------
// Method nearestDir 
//
// Input	: origDir, nextDirName
// Output   : newDir
// Purpose  : to change directory to the nearest possible location
//--------------------------------------------------------------------

	private DirFile nearestDir (DirFile relDir, String nextDirName)
	{
		try
		{
			StringTokenizer parameter = 
				new StringTokenizer(nextDirName, "/");
			if (nextDirName.startsWith("/"))
			{
				// set current dir to root
				relDir = new DirFile(theCacheMgr.readFile(stdRootIndex),
									 new String(),
									 theCacheMgr.getFileName(stdRootIndex));

				// if it is root just exit
				if (nextDirName.equals("/")) 
					throw new Exception(new String());
			}

			while(true)
			{
				// throws Exception is not the directory
				relDir = cd(relDir, parameter.nextToken());
			}
		}
		catch (Exception e) { }
		return relDir;
	}


//--------------------------------------------------------------------
// Method isExist
//
// Input	: directory, fileName
// Output   : flag true if file exists
// Purpose  : check the existent of file under certain directory
//--------------------------------------------------------------------

	private boolean isExist (DirFile relDir, String nextDirName)
	{
		if (nextDirName.indexOf("/") == -1)
			return relDir.isExist(nextDirName);

		try
		{
			relDir = cd(relDir, nextDirName);
			return true;
		}
		catch (Exception e) { }

		int end = nextDirName.lastIndexOf("/");
		String fileArgs = nextDirName.substring(end + 1);
		relDir = nearestDir(relDir, nextDirName);
		return relDir.isExist(fileArgs);
	}


//--------------------------------------------------------------------
// Method isDirectory
//
// Input	: directory, fileName
// Output   : flag true if file is of type directory
// Purpose  : check if the type of file is directory
//--------------------------------------------------------------------

	private boolean isDirectory (DirFile relDir, String nextDirName)
	{
		try
		{
			cd(relDir, nextDirName);
			return true;
		}
		catch (Exception e) { }
		return false;
	}


//--------------------------------------------------------------------
// Method cp
//
// Input	: None
// Output   : None
// Purpose  : cp request from client.
//--------------------------------------------------------------------


	private void cp ()
		throws RequestFailException, 
			   FileNotExistException,
			   FileAlreadyExistException,
			   NotEnoughSpaceException,
			   IOException
	{
		StringTokenizer parameter = new StringTokenizer(theCommand);
		String opt = new String();
		parameter.nextToken();

		String srcName = parameter.nextToken();
		String dstName = parameter.nextToken();
		DirFile srcDir = currentDir;
		DirFile dstDir = currentDir;

		// dstName with wildCard(*) not allowed
		if (dstName.indexOf("*") != -1)
		{
			throw new RequestFailException
				("command " + theCommand + " is not supported.");
		}

		// srcName without wildcard(*) must be exist
		if (srcName.indexOf("*") == -1)
		{
			if (!isExist(currentDir, srcName))
				throw new RequestFailException
					(srcName + " is not exist.");
		}
		else
		{
			// srcName with wildcard(*) 
			// the dstName must be directory
			if (!isDirectory(currentDir, dstName))
				throw new RequestFailException
					(dstName + " must be directory.");
		}

		// no redirection and wildcard(*) used
		if ((srcName.indexOf("/") == -1) &&
			(srcName.indexOf("*") == -1) &&
			(dstName.indexOf("/") == -1) )
		{
			// req theCacheMgr to cpSingleFile 
			theCacheMgr.cpSingleFile(srcName, srcDir,
				dstName, dstDir);
			currentDir = new DirFile 
				(theCacheMgr.readFile(currentDir.getValue(".")),
				currentDir.getParentPath(),
				currentDir.getDirName());

			requestSuccess();
			return;
		}

		// redirection is used on dstName
		if ((srcName.indexOf("/") == -1) &&
			(srcName.indexOf("*") == -1) &&
			(dstName.indexOf("/") != -1) )
		{
			int end = dstName.lastIndexOf("/");
			String tmpString = dstName.substring(0, end + 1);
			dstDir = cd(currentDir, tmpString); 
			dstName = (dstName.endsWith("/")) ? 
	                  srcName : dstName.substring(end + 1);

			if (dstDir.isExist(dstName))
				throw new RequestFailException
					(dstDir.getAbsolutePath() + dstName + " is already exist.");


			// req theCacheMgr to cpSingleFile 
			theCacheMgr.cpSingleFile(srcName, srcDir,
				dstName, dstDir);

			currentDir = new DirFile 
				(theCacheMgr.readFile(currentDir.getValue(".")),
				currentDir.getParentPath(),
				currentDir.getDirName());
			requestSuccess();
			return;
		}

		// redirection is used on srcName
		if ((srcName.indexOf("/") != -1) &&
			(srcName.indexOf("*") == -1) &&
			(dstName.indexOf("/") == -1) )
		{
			int end = srcName.lastIndexOf("/");
			String tmpString = srcName.substring(0, end + 1);
			srcDir = cd(currentDir, tmpString);
			srcName = srcName.substring(end + 1);

			// req theCacheMgr to cpSingleFile 
			theCacheMgr.cpSingleFile(srcName, srcDir,
				dstName, dstDir);

			currentDir = new DirFile 
				(theCacheMgr.readFile(currentDir.getValue(".")),
				currentDir.getParentPath(),
				currentDir.getDirName());
			requestSuccess();
			return;
		}

		// redirection is used on both srcName and dstName
		if ((srcName.indexOf("/") != -1) &&
			(srcName.indexOf("*") == -1) &&
			(dstName.indexOf("/") != -1) )
		{
			int end = srcName.lastIndexOf("/");
			String tmpString = srcName.substring(0, end + 1);
			srcDir = cd(currentDir, tmpString);
			srcName = srcName.substring(end + 1);
			if (isDirectory(currentDir, dstName))
			{
				dstDir = cd(currentDir, dstName);
				dstName = srcName;
			}
			else
			{
				end = dstName.lastIndexOf("/");
				tmpString = dstName.substring(0, end + 1);
				dstDir = cd(currentDir, tmpString);
				dstName = dstName.substring(end + 1);
			}

			// req theCacheMgr to cpSingleFile 
			theCacheMgr.cpSingleFile(srcName, srcDir,
				dstName, dstDir);

			currentDir = new DirFile 
				(theCacheMgr.readFile(currentDir.getValue(".")),
				currentDir.getParentPath(),
				currentDir.getDirName());
			requestSuccess();
			return;
		}

		// srcName use wildcard(*)
		if (srcName.indexOf("*") != -1)
		{
			dstDir = cd(currentDir, dstName);
			if (srcName.indexOf("/") != -1)
			{
				int end = srcName.lastIndexOf("/");
				String tmpString = srcName.substring(0, end + 1);
				srcDir = cd(currentDir, tmpString);
				srcName = srcName.substring(end + 1);
			}
			int MFTIndex;
			if (srcName.equals("*"))
			{
				// command : cp <>/<>/* <>/<>
				for (int i = 2 ; i < srcDir.getNoOfFiles() ; i++)
				{
					srcName = srcDir.getFileName(i);
					dstName = srcName;
					MFTIndex = srcDir.getMFTIndexAt(i);

					if (!theCacheMgr.isSystem(MFTIndex))
						theCacheMgr.cpSingleFile(srcName, srcDir,
							dstName, dstDir);
				}
				currentDir = new DirFile 
					(theCacheMgr.readFile(currentDir.getValue(".")),
					currentDir.getParentPath(),
					currentDir.getDirName());
				// done
				requestSuccess();
				return;
			}
			else if (srcName.startsWith("*"))
			{
				// command : cp <>/<>/*xxx <>/<>
				String suffix = srcName.substring(1); 
				for (int i = 2 ; i < srcDir.getNoOfFiles() ; i++)
				{
					srcName = srcDir.getFileName(i);
					dstName = srcName;
					MFTIndex = srcDir.getMFTIndexAt(i);
					if (!theCacheMgr.isSystem(MFTIndex) &&
						 srcName.endsWith(suffix))
						theCacheMgr.cpSingleFile(srcName, srcDir,
							dstName, dstDir);
				}
				currentDir = new DirFile 
					(theCacheMgr.readFile(currentDir.getValue(".")),
					currentDir.getParentPath(),
					currentDir.getDirName());
				requestSuccess();
				return;
			}
			else if (srcName.endsWith("*"))
			{
				// command : cp <>/<>/xxx* <>/<>
				String prefix = srcName.substring
					(0, srcName.length() - 1); 
				for (int i = 2 ; i < srcDir.getNoOfFiles() ; i++)
				{
					srcName = srcDir.getFileName(i);
					dstName = srcName;
					MFTIndex = srcDir.getMFTIndexAt(i);
					if (!theCacheMgr.isSystem(MFTIndex) &&
						 srcName.startsWith(prefix))
						theCacheMgr.cpSingleFile(srcName, srcDir,
							dstName, dstDir);
				}
				currentDir = new DirFile 
					(theCacheMgr.readFile(currentDir.getValue(".")),
					currentDir.getParentPath(),
					currentDir.getDirName());
				requestSuccess();
				return;
			}
		}
		throw new RequestFailException("command " + 
			theCommand + " is not supported.");
	}


//--------------------------------------------------------------------
// Method defrag
//
// Input	: None
// Output   : None
// Purpose  : Defragmentation.
//--------------------------------------------------------------------

	private void defrag ()
		throws RequestFailException
	{
		if (fileMonitor.getNoOfRecords() > 1)
			throw new RequestFailException
				("harddisk is being accessed.");

		theCacheMgr.defrag();
		try { currentDir = cd(currentDir, "."); }
		catch (Exception e) { }
		theCacheMgr.defrag();
		requestSuccess();
		return;
	}


//--------------------------------------------------------------------
// Method mv
//
// Input	: None
// Output   : None
// Purpose  : mv request from client.
//--------------------------------------------------------------------

	private void mv ()
		throws RequestFailException, 
			   FileNotExistException,
			   FileAlreadyExistException,
			   NotEnoughSpaceException,
			   IOException
	{
		StringTokenizer parameter = new StringTokenizer(theCommand);
		String opt = new String();
		parameter.nextToken();

		String srcName = parameter.nextToken();
		String dstName = parameter.nextToken();
		DirFile srcDir = currentDir;
		DirFile dstDir = currentDir;

		// dstName must not have wildcard(*)
		if (dstName.indexOf("*") != -1)
		{
			throw new RequestFailException
				("command " + theCommand + " is not supported.");
		}

		if (srcName.indexOf("*") == -1)
		{
			// wildcard(*) is not used, srcName must be exist
			if (!isExist(currentDir, srcName))
				throw new RequestFailException
					(srcName + " is not exist.");
		}
		else
		{
			// wildcard(*) is used, the dstName must be directory
			if (!isDirectory(currentDir, dstName))
				throw new RequestFailException
					(dstName + " must be directory.");
		}

		// no redirect and wildcard(*) used
		if ((srcName.indexOf("/") == -1) &&
			(srcName.indexOf("*") == -1) &&
			(dstName.indexOf("/") == -1) )
		{
			// check if any user accessed the file
			if (fileMonitor.isBeingAccessed
				(currentDir.getAbsolutePath() + srcName))
				throw new RequestFailException
					(srcDir.getAbsolutePath()
		                + srcName
						+ " is being accessed.");

			// request theCacheMgr to mvSingleFile
			if (!isRelated(srcDir.getValue(srcName), dstDir))
				theCacheMgr.mvSingleFile(srcName, srcDir,
					dstName, dstDir);

			currentDir = new DirFile 
				(theCacheMgr.readFile(currentDir.getValue(".")),
				currentDir.getParentPath(),
				currentDir.getDirName());

			requestSuccess();
			return;
		}

		// redirection is used on the dstName
		if ((srcName.indexOf("/") == -1) &&
			(srcName.indexOf("*") == -1) &&
			(dstName.indexOf("/") != -1) )
		{
			// check if any user accessed the file
			if (fileMonitor.isBeingAccessed
				(currentDir.getAbsolutePath() + srcName))
				throw new RequestFailException
					(srcDir.getAbsolutePath()
		                + srcName
						+ " is being accessed.");
			int end = dstName.lastIndexOf("/");
			String tmpString = dstName.substring(0, end + 1);
			dstDir = cd(currentDir, tmpString);
			dstName = dstName.endsWith("/") ? srcName : dstName.substring(end + 1);

			// request theCacheMgr to mvSingleFile
			if (!isRelated(srcDir.getValue(srcName), dstDir))
				theCacheMgr.mvSingleFile(srcName, srcDir,
					dstName, dstDir);

			currentDir = new DirFile 
				(theCacheMgr.readFile(currentDir.getValue(".")),
				currentDir.getParentPath(),
				currentDir.getDirName());
			requestSuccess();
			return;
		}

		// redirection is used on the srcName
		if ((srcName.indexOf("/") != -1) &&
			(srcName.indexOf("*") == -1) &&
			(dstName.indexOf("/") == -1) )
		{
			int end = srcName.lastIndexOf("/");
			String tmpString = srcName.substring(0, end + 1);
			srcDir = cd(currentDir, tmpString);
			srcName = srcName.substring(end + 1);

			// check if any user accessed the file
			if (fileMonitor.isBeingAccessed
				(currentDir.getAbsolutePath() + srcName))
				throw new RequestFailException
					(srcDir.getAbsolutePath()
		                + srcName
						+ " is being accessed.");

			// request theCacheMgr to mvSingleFile
			if (!isRelated(srcDir.getValue(srcName), dstDir))
				theCacheMgr.mvSingleFile(srcName, srcDir,
					dstName, dstDir);

			currentDir = new DirFile 
				(theCacheMgr.readFile(currentDir.getValue(".")),
				currentDir.getParentPath(),
				currentDir.getDirName());
			requestSuccess();
			return;
		}

		// both srcName and dstName use redirection
		if ((srcName.indexOf("/") != -1) &&
			(srcName.indexOf("*") == -1) &&
			(dstName.indexOf("/") != -1) )
		{
			int end = srcName.lastIndexOf("/");
			String tmpString = srcName.substring(0, end + 1);
			srcDir = cd(currentDir, tmpString);
			srcName = srcName.substring(end + 1);

			// check if any user accessed the file
			if (fileMonitor.isBeingAccessed
				(currentDir.getAbsolutePath() + srcName))
				throw new RequestFailException
					(srcDir.getAbsolutePath()
		                + srcName
						+ " is being accessed.");

			if (isDirectory(currentDir, dstName))
			{
				dstDir = cd(currentDir, dstName);
				dstName = srcName;
			}
			else
			{
				end = dstName.lastIndexOf("/");
				tmpString = dstName.substring(0, end + 1);
				dstDir = cd(currentDir, tmpString);
				dstName = dstName.endsWith("/") ? srcName : dstName.substring(end + 1);
			}

			// request theCacheMgr to mvSingleFile
			if (!isRelated(srcDir.getValue(srcName), dstDir))
				theCacheMgr.mvSingleFile(srcName, srcDir, dstName, dstDir);

			currentDir = new DirFile 
				(theCacheMgr.readFile(currentDir.getValue(".")),
				currentDir.getParentPath(),
				currentDir.getDirName());

			requestSuccess();
			return;
		}

		// srcName uses wildcard(*)
		if (srcName.indexOf("*") != -1)
		{
			dstDir = cd(currentDir, dstName);
			if (srcName.indexOf("/") != -1)
			{
				int end = srcName.lastIndexOf("/");
				String tmpString = srcName.substring(0, end + 1);
				srcDir = cd(currentDir, tmpString);
				srcName = srcName.substring(end + 1);
			}
			int MFTIndex;
			if (srcName.equals("*"))
			{
				// command : mv <>/<>/* <>/<>
				for (int i = 2 ; i < srcDir.getNoOfFiles() ; )
				{
					srcName = srcDir.getFileName(i);
					dstName = srcName;
					MFTIndex = srcDir.getMFTIndexAt(i);
					if (fileMonitor.isBeingAccessed
						(srcDir.getAbsolutePath() + srcName))
						throw new RequestFailException
							(srcDir.getAbsolutePath()
								+ srcName
								+ " is being accessed.");
					if (!theCacheMgr.isSystem(MFTIndex) &&
						!isRelated(MFTIndex, dstDir))
						theCacheMgr.mvSingleFile(srcName, srcDir,
							dstName, dstDir);
					else i++;
				}

				currentDir = new DirFile 
					(theCacheMgr.readFile(currentDir.getValue(".")),
					currentDir.getParentPath(),
					currentDir.getDirName());
				requestSuccess();
				return;
			}
			else if (srcName.startsWith("*"))
			{
				// command : mv <>/<>/*xxx <>/<>
				String suffix = srcName.substring(1); 
				for (int i = 2 ; i < srcDir.getNoOfFiles() ; )
				{
					srcName = srcDir.getFileName(i);
					dstName = srcName;
					MFTIndex = srcDir.getMFTIndexAt(i);
					if (fileMonitor.isBeingAccessed
						(srcDir.getAbsolutePath() + srcName) &&
						 srcName.endsWith(suffix))
						throw new RequestFailException
							(srcDir.getAbsolutePath()
								+ srcName
								+ " is being accessed.");
					if (!theCacheMgr.isSystem(MFTIndex) &&
						 srcName.endsWith(suffix) &&
						!isRelated(MFTIndex, dstDir))
						theCacheMgr.mvSingleFile(srcName, srcDir,
							dstName, dstDir);
					else i++;
				}

				currentDir = new DirFile 
					(theCacheMgr.readFile(currentDir.getValue(".")),
					currentDir.getParentPath(),
					currentDir.getDirName());
				requestSuccess();
				return;
			}
			else if (srcName.endsWith("*"))
			{
				// command : mv <>/<>/xxx* <>/<>
				String prefix = srcName.substring
					(0, srcName.length() - 1); 
				for (int i = 2 ; i < srcDir.getNoOfFiles() ; )
				{
					srcName = srcDir.getFileName(i);
					dstName = srcName;
					MFTIndex = srcDir.getMFTIndexAt(i);
					if (fileMonitor.isBeingAccessed
						(srcDir.getAbsolutePath() + srcName) &&
						srcName.startsWith(prefix))
						throw new RequestFailException
							(srcDir.getAbsolutePath()
								+ srcName
								+ " is being accessed.");
					if (!theCacheMgr.isSystem(MFTIndex) &&
						 srcName.startsWith(prefix) &&
						!isRelated(MFTIndex, dstDir))
						theCacheMgr.mvSingleFile(srcName, srcDir,
							dstName, dstDir);
					else i++;
				}

				currentDir = new DirFile 
					(theCacheMgr.readFile(currentDir.getValue(".")),
					currentDir.getParentPath(),
					currentDir.getDirName());
				requestSuccess();
				return;
			}
		}

		throw new RequestFailException("command " + 
			theCommand + " is not supported.");
	}


//--------------------------------------------------------------------
// Method isRelated
//
// Input	: ParentIndex, childDir 
// Output   : flag true if it is related
// Purpose  : check that the directory are related
//--------------------------------------------------------------------

	private boolean isRelated (int ParentIndex, DirFile childDir)
	{
		if (!theCacheMgr.isDirectory(ParentIndex))
			return false;

		DirFile tmpDir = childDir;
		while (tmpDir.getValue(".") != tmpDir.getValue(".."))
		{
			if (tmpDir.getValue(".") == ParentIndex)
				return true;
			try { tmpDir = cd (tmpDir, ".."); }
			catch (Exception e) { }
		}
		return false;
	}


//--------------------------------------------------------------------
// Method rm
//
// Input	: None
// Output   : None
// Purpose  : rm request from client.
//--------------------------------------------------------------------

	private void rm ()
		throws RequestFailException, 
			   FileNotExistException,
			   NotEnoughSpaceException,
			   IOException
	{
		StringTokenizer parameter = new StringTokenizer(theCommand);
		String delFileName = new String();
		String opt = new String();
		parameter.nextToken();

		delFileName = parameter.nextToken();

		// if wildcard(*) is not used
		// delDir must be exist and NOT of type directory
		if (delFileName.indexOf("*") == -1)
		{
			if (!isExist(currentDir, delFileName))
				throw new RequestFailException
					(delFileName + " is not exist.");
			if (isDirectory(currentDir, delFileName))
				throw new RequestFailException
					(delFileName + " is directory. (use rmdir)");
		}

		// if wildcard(*) and redirect(/) is not used
		// delFileName is on the currentDir
		if ((delFileName.indexOf("/") == -1) &&
			(delFileName.indexOf("*") == -1))
		{
			if (fileMonitor.isBeingAccessed(currentDir.getAbsolutePath() + delFileName))
				throw new RequestFailException
					(currentDir.getAbsolutePath()
		                + delFileName
						+ " is being accessed.");
			currentDir = theCacheMgr.rmSingleFile(opt, delFileName, currentDir);
			theCacheMgr.writeFile(currentDir.getValue("."), 
				currentDir.getContent());
			requestSuccess();
			return;
		}

		DirFile relDir = currentDir;
		if (delFileName.indexOf("/") != -1)
		{
			int end = delFileName.lastIndexOf("/");
			String tmpString = delFileName.substring(0, end + 1);
			relDir = cd(currentDir, tmpString);
			delFileName = delFileName.substring(end + 1);
		}

		if (delFileName.indexOf("*") == -1)
		{
			// remove delFileName
			relDir = theCacheMgr.rmSingleFile(opt, delFileName, relDir);
		}
		else
		{
			int MFTIndex;
			if (delFileName.equals("*"))
			{
				// command : rm <>/<>/*
				for (int i = 2 ; i < relDir.getNoOfFiles() ; )
				{
					delFileName = relDir.getFileName(i);
					MFTIndex = relDir.getMFTIndexAt(i);
					if (!theCacheMgr.isDirectory(MFTIndex) &&
						!theCacheMgr.isSystem(MFTIndex))
					{
						if (fileMonitor.isBeingAccessed
							(relDir.getAbsolutePath() + delFileName))
							throw new RequestFailException
								(relDir.getAbsolutePath()
									+ delFileName
									+ " is being accessed.");

						relDir = theCacheMgr.rmSingleFile(opt, delFileName, relDir);
					}
					else i++;

				}
			}
			else if (delFileName.startsWith("*"))
			{
				// command : rm <>/<>/*xxx
				String suffix = delFileName.substring(1); 
				for (int i = 2 ; i < relDir.getNoOfFiles() ; )
				{
					delFileName = relDir.getFileName(i);
					MFTIndex = relDir.getMFTIndexAt(i);
					if (!theCacheMgr.isDirectory(MFTIndex) &&
						!theCacheMgr.isSystem(MFTIndex) &&
						 delFileName.endsWith(suffix))
					{
						if (fileMonitor.isBeingAccessed
							(relDir.getAbsolutePath() + delFileName))
							throw new RequestFailException
								(relDir.getAbsolutePath()
									+ delFileName
									+ " is being accessed.");
						relDir = theCacheMgr.rmSingleFile(opt, delFileName, relDir);
					}
					else i++;
				}

			}
			else if (delFileName.endsWith("*"))
			{
				// command : rm <>/<>/xxx*
				String prefix = delFileName.substring(0, delFileName.length() - 1); 
				for (int i = 2 ; i < relDir.getNoOfFiles() ; )
				{
					delFileName = relDir.getFileName(i);
					MFTIndex = relDir.getMFTIndexAt(i);
					if (!theCacheMgr.isDirectory(MFTIndex) &&
						!theCacheMgr.isSystem(MFTIndex) &&
						 delFileName.startsWith(prefix))
					{
						if (fileMonitor.isBeingAccessed
							(relDir.getAbsolutePath() + delFileName))
							throw new RequestFailException
								(relDir.getAbsolutePath()
									+ delFileName
									+ " is being accessed.");
						relDir = theCacheMgr.rmSingleFile(opt, delFileName, relDir);
					}
					else i++;
				}
			}
			else
			{
				throw new RequestFailException
					("command " + theCommand + " is not supported.");
			}
		}

		theCacheMgr.writeFile(relDir.getValue("."), relDir.getContent());
		requestSuccess();
		return;
	}


//--------------------------------------------------------------------
// Method rmdir
//
// Input	: None
// Output   : None
// Purpose  : rmdir request from client.
//--------------------------------------------------------------------

	private void rmdir ()
		throws RequestFailException, 
			   FileNotExistException,
			   NotEnoughSpaceException,
			   IOException
	{
		StringTokenizer parameter = new StringTokenizer(theCommand);
		String delDirName = new String();
		String opt = new String();

		parameter.nextToken();
		if (theCommand.indexOf(" -r ") != -1)
		{
			opt = "-r";
			parameter.nextToken();
		}

		delDirName = parameter.nextToken();

		// if wildcard(*) is not used
		// delDir must be exist and of type directory
		if (delDirName.indexOf("*") == -1)
		{
			if (!isExist(currentDir, delDirName))
				throw new RequestFailException
					(delDirName + " is not exist.");
			if (!isDirectory(currentDir, delDirName))
				throw new RequestFailException
					(delDirName + " is not directory.");
		}

		// if wildcard(*) and redirect(/) is not used
		// delDir is on the currentDir
		if ((delDirName.indexOf("/") == -1) &&
			(delDirName.indexOf("*") == -1))
		{
			if (fileMonitor.isBeingAccessed(currentDir.getAbsolutePath() + delDirName))
				throw new RequestFailException
					(currentDir.getAbsolutePath()
		                + delDirName
						+ " is being accessed.");
			currentDir = theCacheMgr.rmSingleFile(opt, delDirName, currentDir);
			theCacheMgr.writeFile(currentDir.getValue("."), 
				currentDir.getContent());
			requestSuccess();
			return;
		}


		DirFile relDir = currentDir;

		if (delDirName.equals(".") || delDirName.equals(".."))
			throw new RequestFailException
				("standard directory can not be removed.");

		try 
		{
			// the cd() will throws exception if (relDir, delDirName)
			// is not of type directory
			relDir = cd(relDir, delDirName);
			delDirName = new String();
		}
		catch (Exception e)
		{ }

		if (delDirName.indexOf("/") != -1)
		{
			int end = delDirName.lastIndexOf("/");
			String tmpString = delDirName.substring(0, end + 1);
			relDir = cd(relDir, tmpString);
			delDirName = delDirName.substring(end + 1);
		}

		if (delDirName.length() == 0)
		{
			relDir = theCacheMgr.rmdir(opt, delDirName, relDir);

			// remove relDir and update
			delDirName = relDir.getDirName();

			// go up one step
			relDir = cd(relDir, "..");

			// remove delDirName
			theCacheMgr.rmSingleFile(opt, delDirName, relDir);
		}
		else if (delDirName.indexOf("*") == -1)
		{
			// remove delFileName
			theCacheMgr.rmSingleFile(opt, delDirName, relDir);
		}
		else
		{
			int MFTIndex;
			if (delDirName.equals("*"))
			{
				// command : rmdir <>/<>/*
				for (int i = 2 ; i < relDir.getNoOfFiles() ; )
				{
					delDirName = relDir.getFileName(i);
					MFTIndex = relDir.getMFTIndexAt(i);
					if (theCacheMgr.isDirectory(MFTIndex))
					{
						if (fileMonitor.isBeingAccessed
							(relDir.getAbsolutePath() + delDirName))
							throw new RequestFailException
								(relDir.getAbsolutePath()
									+ delDirName
									+ " is being accessed.");

						relDir = theCacheMgr.rmSingleFile(opt, delDirName, relDir);
					}
					else i++;

				}
			}
			else if (delDirName.startsWith("*"))
			{
				// command : rmdir <>/<>/*xxx
				String suffix = delDirName.substring(1); 
				for (int i = 2 ; i < relDir.getNoOfFiles() ; )
				{
					delDirName = relDir.getFileName(i);
					MFTIndex = relDir.getMFTIndexAt(i);
					if (theCacheMgr.isDirectory(MFTIndex) &&
						 delDirName.endsWith(suffix))
					{
						if (fileMonitor.isBeingAccessed
							(relDir.getAbsolutePath() + delDirName))
							throw new RequestFailException
								(relDir.getAbsolutePath()
									+ delDirName
									+ " is being accessed.");

						relDir = theCacheMgr.rmSingleFile(opt, delDirName, relDir);
					}
					else i++;
				}

			}
			else if (delDirName.endsWith("*"))
			{
				// command : rmdir <>/<>/xxx*
				String prefix = delDirName.substring(0, delDirName.length() - 1); 
				for (int i = 2 ; i < relDir.getNoOfFiles() ; )
				{
					delDirName = relDir.getFileName(i);
					MFTIndex = relDir.getMFTIndexAt(i);
					if (theCacheMgr.isDirectory(MFTIndex) &&
						 delDirName.startsWith(prefix))
					{
						if (fileMonitor.isBeingAccessed
							(relDir.getAbsolutePath() + delDirName))
							throw new RequestFailException
								(relDir.getAbsolutePath()
									+ delDirName
									+ " is being accessed.");

						relDir = theCacheMgr.rmSingleFile(opt, delDirName, relDir);
					}
					else i++;
				}
			}
			else
			{
				throw new RequestFailException
					("command " + theCommand + " is not supported.");
			}
		}

		theCacheMgr.writeFile(relDir.getValue("."), relDir.getContent());
		requestSuccess();
		return;
	}


//--------------------------------------------------------------------
// Method mkdir
//
// Input	: None
// Output   : None
// Purpose  : mkdir request from client.
//--------------------------------------------------------------------

	private void mkdir ()
		throws RequestFailException, 
			   NotEnoughSpaceException,
			   IOException,
			   FileAlreadyExistException
	{
		StringTokenizer parameter = new StringTokenizer(theCommand);
		String newDirName = parameter.nextToken();
		newDirName = parameter.nextToken();

		if (newDirName.indexOf("*") != -1)
			throw new RequestFailException
				("\"*\" is a reserved character.");

		if (newDirName.indexOf("/") != -1)
			throw new RequestFailException
				("\"/\" is not allowed.");

		if (currentDir.isExist(newDirName))
			throw new RequestFailException
			   (newDirName + " is already exist.");

		int newIndex = theCacheMgr.mkdir(newDirName, currentDir);

		// update current directory
		currentDir.newEntry(newDirName, newIndex);
		theCacheMgr.writeFile(currentDir.getValue("."), currentDir.getContent());

		
		theCacheMgr.updateMFT();
		requestSuccess();
		return;
	}


//--------------------------------------------------------------------
// Method format
//
// Input	: None
// Output   : None
// Purpose  : format request from client.  
//--------------------------------------------------------------------

	private void format ()
		throws RequestFailException,
		       IOException,
			   NotEnoughSpaceException
	{
		if (fileMonitor.getNoOfRecords() > 1)
			throw new RequestFailException
				("harddisk is being accessed.");

		StringTokenizer parameter = new StringTokenizer(theCommand);
		String oldAbsolutePath = currentDir.getAbsolutePath();
		String newVolumeName = parameter.nextToken();
		newVolumeName = parameter.nextToken();

		if (newVolumeName.indexOf("*") != -1)
			throw new RequestFailException
				("\"*\" is a reserved character.");

		synchronized (theCacheMgr)
		{
			theOutput.println(theCacheMgr.format
				(diskNoOfBlocks, newVolumeName, theOutput));
		}

		theVolumeName = new String(newVolumeName);
		currentDir = new DirFile(theCacheMgr.readFile(stdRootIndex), 
								 new String(),
			                     theCacheMgr.getFileName(stdRootIndex));

		// reset monitor and add newpath
		fileMonitor.reset();
		fileMonitor.add("pwd->" + currentDir.getAbsolutePath(), theClient);

		requestSuccess();
		return;
	}


//--------------------------------------------------------------------
// Method requestSuccess
//
// Input	: None
// Output   : None
// Purpose  : standard requestSuccess protocol  
//--------------------------------------------------------------------

	public void requestSuccess()
	{
		theOutput.println("server: " + theCommand + " Done! successfully.");
		return;
	}


//--------------------------------------------------------------------
// Method endOfTransmission
//
// Input	: None
// Output   : None
// Purpose  : standard endOfTransmission protocol  
//--------------------------------------------------------------------

	public void endOfTransmission()
	{
		theOutput.println("<end of transmission>");
		theOutput.println(currentDir.getAbsolutePath());
		return;
	}

}

//--------------------------------------------------------------------
// End Class FileServer
//--------------------------------------------------------------------
