
//--------------------------------------------------------------------
// Class Library of file-access procedure
//
// Purpose  : handle all client interactive command, handling cache 
//            manager and talk to server.
//--------------------------------------------------------------------

import java.net.*;
import java.io.*;
import java.util.*;

public class Library
{
//	final static int theCacheSize = 4096;
	final static int theCacheSize = 256;
	final static int theNumOfCache = 4;

	private static ClientCacheMgr theCacheMgr;
	private static Socket theConnection;
	private static String theServerAddress;
	private static int thePortNumber;
	private static DataInputStream theInput;
	private static PrintStream theOutput;

	private static String theCurrentDirPath;


//--------------------------------------------------------------------
// Contructor for Library
//
// Input	: None
// Output   : None
// Purpose  : construct the caches required
//--------------------------------------------------------------------

	public Library()
	{
		theCacheMgr = new ClientCacheMgr(theNumOfCache, theCacheSize);
		theCurrentDirPath = new String ("$ ");
	}


//--------------------------------------------------------------------
// Method commandHandler
//
// Input	: command
// Output   : None
// Purpose  : handle all available command from client
//--------------------------------------------------------------------

	public void commandHandler(String command)
		throws NullPointerException, IOException
	{
		if (command.length() == 0) return;
		StringTokenizer parameter = new StringTokenizer(command);
		parameter.nextToken();
		int noOfArgs = parameter.countTokens();


		if (command.indexOf(",") >= 0)
		{
			System.out.println("Syntax error : -comma- is not allowed in command.");
			System.out.println("type -help- for list of available commands");
			return;
		}

		// command -help-
		if (command.equals("help"))
		{
			help();
			return;
		}

		// socket command
		if (command.startsWith("open ") ||
			command.equals("open"))
		{
			open(command);
			serverInteract(command);
			return;
		}

		// socket command
		if (command.equals("close"))
		{
			close();
			theCurrentDirPath = "$ ";
			return;
		}

		// commands that take no argument 
		if (command.equals("defrag"))
		{
			serverInteract(command);
			return;
		}

		// Interactive Commands that take 1 argument
		if (command.startsWith("cd ") ||
		    command.startsWith("mkdir ") ||
		    command.startsWith("rm ") ||
			command.startsWith("format "))
		{
			if (noOfArgs == 1)
			{
				serverInteract(command);
				return;
			}
		}

		// ls Commands that take 0, 1 ,2 argument
		if (command.startsWith("ls ") ||
			command.equals("ls") ||
			command.startsWith("dir ") ||
			command.equals("dir"))
		{
			if (noOfArgs <= 1)
			{
				serverInteract(command);
				return;
			}
			if ((noOfArgs == 2) && (command.indexOf(" -l ") != -1))
			{
				serverInteract(command);
				return;
			}
		}

		// Interactive Commands that take 1 ,2 argument
		if (command.startsWith("rmdir "))
		{
			if (noOfArgs == 1)
			{
				serverInteract(command);
				return;
			}
			if ((noOfArgs == 2) && (command.indexOf(" -r ") != -1))
			{
				serverInteract(command);
				return;
			}
		}

		// Interactive  Commands that take 2 arguments
		if (command.startsWith("cp ") ||
			command.startsWith("mv "))
		{
			if (noOfArgs == 2)
			{
				serverInteract(command);
				return;
			}
		}

		// cat Commands that take 1 ,2 argument
		if (command.startsWith("cat ") &&
			!command.endsWith("/"))
		{
			if (noOfArgs == 1)
			{
				cat(command);
				return;
			}
			if ((noOfArgs == 2) && 
				(command.indexOf(" > ") != -1) &&
				(command.indexOf("*") == -1))
			{
				cat(command);
				return;
			}
		}

		// ln Commands that take 2 ,3 argument
		if (command.startsWith("ln "))
		{
			if (noOfArgs == 2)
			{
				serverInteract(command);
				return;
			}
			if ((noOfArgs == 3) && (command.indexOf(" -s ") != -1))
			{
				serverInteract(command);
				return;
			}
		}

		// command -read- file from server
		if (command.startsWith("read "))
		{
			if (noOfArgs == 1)
			{
				read(command);
				return;
			}
		}

		// command -write- file to server
		if (command.startsWith("write "))
		{
			if (noOfArgs == 1)
			{
				write(command);
				return;
			}
		}


		// command -lcache- list cahe content of 
		// both server's and client's caches
		if (command.startsWith("lcache ") ||
			command.equals("lcache"))
		{
			// list specific cache id
			if (command.length() >= 7) 
			{
				theCacheMgr.printCache
					(Integer.parseInt
					(command.substring(7)));
			}
			else
			{
				theCacheMgr.printAllCaches();
			}
			
			theOutput.println(command);
			theCurrentDirPath = theInput.readLine();
			return;
		}


		// command -bye- to exit the program
		if (command.equals("bye"))
		{
			theOutput.println(command);
			cleanup();
			return;
		}

		System.out.println("Syntax error : command not available.");
		System.out.println("type -help- for list of available commands");
		return;
	}


//--------------------------------------------------------------------
// Method open
//
// Input	: <serverAddresss> <portNumber>
// Output   : None
// Purpose  : open connection with server.
//--------------------------------------------------------------------

	private void open(String command)
	{
		StringTokenizer parameter = new StringTokenizer(command);
		if (parameter.countTokens() > 3)
		{
			System.out.println(" Syntax used  : open <serverAddress> <portNumber>");
			return;
		}

		parameter.nextToken();
		// default "localhost"
		try
		{
			theServerAddress = parameter.nextToken();
		}
		catch (Exception e)
		{
			theServerAddress = "localhost";
		}
		
		// default portNumber = 1997
		try
		{
			thePortNumber = Integer.parseInt(parameter.nextToken());
			if (thePortNumber < 0 || thePortNumber > 65535)
			{
				System.out.println(" Syntax used  : open <serverAddress> <portNumber>");
				System.out.println(" <portNumber> : 0..65535");
				return;
			}
		}
		catch (Exception e)
		{
			thePortNumber = 1997;
		}

		try
		{
			cleanup();
			theConnection = new Socket(theServerAddress, thePortNumber);
			System.out.println("Connection established with " + theServerAddress);
			help();
			theInput = new DataInputStream(theConnection.getInputStream()); 
			theOutput = new PrintStream(theConnection.getOutputStream()); 
		}
		catch (IOException e) { System.out.println(e); }

		return;
	}


//--------------------------------------------------------------------
// Method close
//
// Input	: None
// Output   : None
// Purpose  : close connection with server.
//--------------------------------------------------------------------

	private void close()
	{
		try
		{
			theConnection.close();
		}
		catch (IOException e) { System.out.println(e); }
		catch (NullPointerException e)
		{ 
			System.out.println("error : Close an invalid connection.");
		}
	}


//--------------------------------------------------------------------
// Method read
//
// Input	: None
// Output   : None
// Purpose  : open file(s) for reading.  Request file from cacheMgr.  
//            The cacheMgr will decide to take from cache or from server
//--------------------------------------------------------------------

	private void read (String command)
		throws IOException, NullPointerException
	{
		// send command to server
		theOutput.println(command);

		StringTokenizer parameter = new StringTokenizer(command);
		parameter.nextToken();

		String fileName = parameter.nextToken();

		while (true)
		{
			String dataPackage = new String();

			// server clarifies the name
			// return with <MFTIndex>;<lastModified>;<fileSize>
			String filePipe = theInput.readLine();
			if (filePipe.indexOf("all files sent successfully.") != -1)
			{
				System.out.println(filePipe);
				break;
			}

			// in case some exception is thrown
			if (filePipe.indexOf("Exception:") != -1)
			{
				System.out.println(filePipe);
				return;
			}

			StringTokenizer lsArgs = new StringTokenizer(filePipe, ";");
			int MFTIndex = Integer.parseInt(lsArgs.nextToken());
			long lastModified = Long.parseLong(lsArgs.nextToken());
			int fileSize = Integer.parseInt(lsArgs.nextToken());

//			if (MFTIndex == -1)
//			{
//				System.out.println("FileNotExistException: " + 
//					fileName + " is not exist.");
//				return;
//			}

			// request file from cacheMgr
			dataPackage = theCacheMgr.readFile
				         (MFTIndex, lastModified,
						   fileSize, theInput, theOutput);

			// server return with absolute fileName
			fileName = theInput.readLine();				
			new Display("read " + fileName, dataPackage, theInput, theOutput);			
		}
		return;
	}


//--------------------------------------------------------------------
// Method write
//
// Input	: None
// Output   : None
// Purpose  : open file(s) for writing.  Request file from cacheMgr.  
//            The cacheMgr will decide to take from cache or from server
//--------------------------------------------------------------------

	private void write(String command)
		throws IOException
	{
		// send command to server
		theOutput.println(command);

		StringTokenizer parameter = new StringTokenizer(command);
		parameter.nextToken();

		String fileName = parameter.nextToken();

		while (true)
		{
			String dataPackage = new String();

			// server clarifies the name
			// return with <MFTIndex>;<lastModified>;<fileSize>
			String filePipe = theInput.readLine();
			if (filePipe.indexOf("all files sent successfully.") != -1)
			{
				System.out.println(filePipe);
				break;
			}

			// in case some exception is thrown
			if (filePipe.indexOf("Exception:") != -1)
			{
				System.out.println(filePipe);
				return;
			}

			StringTokenizer lsArgs = new StringTokenizer(filePipe, ";");
			int MFTIndex = Integer.parseInt(lsArgs.nextToken());
			long lastModified = Long.parseLong(lsArgs.nextToken());
			int fileSize = Integer.parseInt(lsArgs.nextToken());

			// request file from cacheMgr
			dataPackage = theCacheMgr.readFile
				         (MFTIndex, lastModified,
						   fileSize, theInput, theOutput);

			// server return with absolute fileName
			fileName = theInput.readLine();				
			new Display("write " + fileName, MFTIndex, 
				dataPackage, theInput, theOutput, theCacheMgr);			
		}
		return;
	}


//--------------------------------------------------------------------
// Method cleanup
//
// Input	: None
// Output   : None
// Purpose  : clean-up by closing connection with server.
//--------------------------------------------------------------------

	private void cleanup()
	{
		try
		{
			theConnection.close();
		}
		catch (IOException e) { }
		catch (NullPointerException e) { }
	}


//--------------------------------------------------------------------
// Method serverInteract
// eg. mkdir, rmdir, cd, pwd, lfsm, lmft, format
//
// Input	: command
// Output   : None
// Purpose  : change directory or present working directory
//--------------------------------------------------------------------

	private void serverInteract(String command)
		throws IOException, NullPointerException
	{
		// send command to server
		theOutput.println(command);

		// reply from server
		while(true)
		{
			String tmpString = theInput.readLine();

			// end of transmission
			if (tmpString.equals("<end of transmission>"))
			{
				theCurrentDirPath = theInput.readLine();
				break;
			}

			// result from server
			System.out.println(tmpString);
		}
		return;
	}


//--------------------------------------------------------------------
// Method cat
//
// Input	: command
// Output   : None
// Purpose  : cat file
//--------------------------------------------------------------------

	private void cat(String command)
		throws IOException, NullPointerException
	{
		// send command to server
		theOutput.println(command);

		StringTokenizer parameter = new StringTokenizer(command);
		parameter.nextToken();
		boolean toWrite = false;
		if (command.indexOf(" > ") != -1)
		{
			toWrite = true;
			parameter.nextToken();
		}

		String fileName = parameter.nextToken();

		while (true)
		{
			String dataPackage = new String();

			// server clarifies the name
			// return with <MFTIndex>;<lastModified>;<fileSize>
			String filePipe = theInput.readLine();
			if (filePipe.indexOf("all files sent successfully") != -1) return;

			// in case some exception is thrown
			if (filePipe.indexOf("Exception:") != -1)
			{
				System.out.println(filePipe);
				return;
			}

			StringTokenizer lsArgs = new StringTokenizer(filePipe, ";");
			int MFTIndex = Integer.parseInt(lsArgs.nextToken());
			long lastModified = Long.parseLong(lsArgs.nextToken());
			int fileSize = Integer.parseInt(lsArgs.nextToken());

			if (toWrite && (MFTIndex == -1))
			{
				System.out.println("FileAlreadyExistException: " + 
					fileName + " is already exist.");
				return;
			}
			if (!toWrite && (MFTIndex == -1))
			{
				System.out.println("FileNotExistException: " + 
					fileName + " is not exist.");
				return;
			}

			if (toWrite)
			{
				BufferedReader input 
						= new BufferedReader(new InputStreamReader(System.in));
				try
				{
					while (true)
					{
						String line = input.readLine();
						if (line.equals(".")) break;
						dataPackage += line + "\n";
					}
				}
				catch (Exception e) { }

				// send fileSize for server preparation
				theOutput.println(dataPackage.length());

				String confirm = theInput.readLine();
				if (confirm.indexOf("Exception:") != -1)
				{
					System.out.println(confirm);
					return;
				}

				// write file to cacheMgr
				theCacheMgr.writeFile (MFTIndex, dataPackage,
						theInput, theOutput);

				break;
			}
			else
			{
				// request file from cacheMgr
				dataPackage = theCacheMgr.readFile
					         (MFTIndex, lastModified,
							   fileSize, theInput, theOutput);
				System.out.println(dataPackage);
			}
		}
		return;
	}



//--------------------------------------------------------------------
// Method getCurrentDirPath
//
// Input	: None
// Output   : None
// Purpose  : getCurrentDirPath
//--------------------------------------------------------------------

	public String getCurrentDirPath()
	{
		if (theCurrentDirPath.equals("$ ")) 
			return theCurrentDirPath;
		else 
			return theCurrentDirPath + ">";
	}

//--------------------------------------------------------------------
// Method help
//
// Input	: None
// Output   : None
// Purpose  : list all available commands.
//--------------------------------------------------------------------

	private void help()
	{
		System.out.println("list of available commands");
		System.out.println("==========================");
		System.out.println("Open Connection           : open <serverAddress> <portNumber>");
		System.out.println("Close Connection          : close");
		System.out.println("Read File                 : read <fileArgs>");
		System.out.println("Write File                : write <fileArgs>");
		System.out.println("Remove File               : rm <fileArgs>");
		System.out.println("Remove Directory          : rmdir <-r> <dirArgs>");
		System.out.println("Change Directory          : cd <dirArgs>");
		System.out.println("List Directory            : ls <-l> <fileArgs>");
		System.out.println("Copy File                 : cp <srcFile> <dstFile>");
		System.out.println("Cat File                  : cat <\">\"> <fileArgs>");
		System.out.println("Rename File               : mv <srcFile> <dstFile>");
		System.out.println("Link (symbolic/hardlink)  : ln <-s> <srcFile> <dstFile>");
		System.out.println("Defragmentation           : defrag");
		System.out.println("List Cache Content        : lcache");
		System.out.println("==========================");
		System.out.println("Help Menu    	          : help");
		System.out.println("Exit                      : bye");
		return;
	}
}

//--------------------------------------------------------------------
// End Class Library
//--------------------------------------------------------------------