
/* 0123456789012345678901234567890123456789012345678901234567890123456789789 */

/*
 * CDCase.java
 *
 * This file is part of cd_create.
 *
 * cd_create is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * cd_create is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with cd_create; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Copyright 2004 Abi Arroyo
 */

import java.io.*;
import java.util.*;
import java.util.logging.*;

public class CDCase {
   
   // Configurable runtime variables
   public static String tempDir;         // temporary directory
   public static int MAX_CD_SIZE;        // maximum size of CD
   public static boolean keepContinuous; // false maximizes free space CD media
   public static boolean stopOnFailure;  // what happens with errors?
   public static boolean enableLogging;  // Enable logging for debugging
   public static int logLevel;           // Amount of log info to report
   public static boolean verifyData;     // Verify data upon completition
   public static String cdPath;			  // path to CD
   public static boolean isWindows;      // Windows or UNIX?
   
   public static boolean addBigFirst;    // This variables determines what
   										        // files are added first.
                                         // - true: big files added first
                                         // - false: small files added first
   
   public static final String version = "cd_create-0.3"; // Version number
   public static final String ext     = ".edc";          // Filename extension
   public static final String logfile = "edc.log";	      // log file
   
   // Internal variables
   private LinkedList preList;           // list of files for CD Case
   private List sortedList;              // sorted preList with sizes
   private List verifyFiles;             // data to verify

   private long postProcessSize;
   
   private CD cd;                        // Current CD
   private LinkedList cd_case;           // List used to store CD objects
   										        // (after compile())
   
   private final long PERFORM = -1;
   private final long FAIL = -2;
   
   
   public static Logger logger; 
   private static FileHandler fh;
   private static ConsoleHandler ch; 
   
   public CDCase (String temp) {
   	
      // Find OS 
      String osName = System.getProperty("os.name" );
      // String dir;
      
      if (osName.toLowerCase().indexOf ("window") >= 0) {
         // dir = "C:\\temp\\" + version;
         this.isWindows = true;
      }
      else {
         // dir = "/tmp/" + version;
         this.isWindows = false;
      }
      
      logger = Logger.getLogger ("CDCase.class");
      
   	if (this.makeTemp (temp + File.separator + "logs"))
			this.init();
      
   }
   
   private boolean init () {	       

	   // http://javaboutique.internet.com/tutorials/logging_API/
	   // The Java Developers Almanac 1.4
	   // http://javaalmanac.com/egs/java.util.logging/LogFile.html
	  
	   // New APIs in Java 1.4 make logging a cinch 
	   // http://builder.com.com/5100-6370-1051768-2.html#Listing%20B
	  
	   if (fh != null)
	      logger.removeHandler (fh);
	   if (ch != null)
	      logger.removeHandler (ch);
	  
	   // disable logging by parent handlers
      logger.setUseParentHandlers(false);	
	  
	   try {
	      fh = new FileHandler(tempDir + File.separator + logfile, true);
	  	   ch = new ConsoleHandler();
	  	 
	  	   fh.setFormatter (new MyFormatter());
	  	   ch.setFormatter (new MyFormatter());

   	   switch (logLevel) {
	         case 1:
	            logger.setLevel (Level.OFF);
	            // System.out.println("Logging has been disabled.");
   	         break;
	         case 2:
	            logger.setLevel (Level.WARNING);
	            // System.out.println ("Logging has been set to lowest level.");
	            break;
	         case 3:
	            logger.setLevel (Level.INFO);
	            // System.out.println ("Logging has been set to highest level.");
	            break;
	         default:
	            logger.setLevel (Level.WARNING);
	         break;
	      }
	  	 
	  	   ch.setLevel (Level.SEVERE);
	  	 
	  	   logger.addHandler(ch);
	      logger.addHandler(fh);
	   }
	   catch (Exception err) {
	      System.err.println(version + ": error setting up log file");
	      return false;
	   }
	  
	   preList = new LinkedList();
      
      verifyFiles = new ArrayList ();
	   
	   cd_case = new LinkedList();
	   cd = null;
	   sortedList = null;
	  
	  
	   logger.log (Level.WARNING, "------------------------------------------------------------\n");
	   logger.log (Level.WARNING, "CDCase.init(): the beginning...\n");

	   this.createCD();
	   
      return true;
   }
   
   // This function will load a previously saved project. The clear arguments
   // determines whether the current files are removed or left intact.
   // DONE: load (file, true);  <- close existing project, and open new one
   //       load (file, false); <- import project onto existing work
   
   // This funtion assumes that all files will remain to be the same size
   // To remove this problem, add files through CDCase.add(), instead of passing
   // files onto CD.add()
   public boolean load (String name, boolean clear) {
      if (name.length() <= 0)
         return false;
	         
      if (clear) {
         this.reset();
      }
         
 	   // Add extension if necessary
	   name = this.checkExt (name);
        
	   File f = new File (name);                      // CDCase file
	   boolean rtn = true;                            // return value
	   String line;                                   // current line
    
	   try {
	  	 
  	      BufferedReader saveFiles = new BufferedReader (new FileReader(f));
         BufferedReader actualFiles;
         String asdf;
         
         while ((line = saveFiles.readLine()) != null) {
         	
            if (line.length() <= 0) return false;
            
            actualFiles = new BufferedReader (new FileReader (new File (line)));
            
            while ((asdf = actualFiles.readLine()) != null) {
               if (!this.add (asdf) && stopOnFailure) return false;
            }
         }
      }
	   catch (FileNotFoundException e) {
	      logger.log(Level.WARNING, "CDCase.load(): File cannot be found: " + name + '\n');
	  	   return false;
	   }
	   catch (IOException e) {
	      logger.log(Level.WARNING, "CDCase.load(): Error reading file: " + name + '\n');
	      return false;
	   }  
	  
	   this.compile();
	   
	   return true;
   }
   
   // Add to preList

   // When a file is added:
   // 1. added to prelist (simple check: canRead())
   // 2. When done adding to prelist, objects passed to compile()
   public boolean add (String name) {
      if (name.length() <= 0) {
         return false;
      }
      
      File f = new File (name);
      
      if (f.canRead() && f.exists()) {
         preList.add(name);
         verifyFiles.add(name);

         return true;
      } else
         return false;
   }
   
   // Remove from preList
   public boolean remove (String name) {
      int pos = preList.indexOf (name);

	   if (pos < 0) {
	      logger.log (Level.INFO, "CDCase.remove(): cannot find file: " + name + '\n');
	   }
	   else {
	      try {
		      preList.remove (pos);
		      verifyFiles.remove(pos);
		      
		      return true;
	      }
	      catch (IndexOutOfBoundsException e) {
		      logger.log (Level.WARNING, "CDCase.remove(): error removing file: " + name + '\n');
	      }
	   }

	   return false;
   }
   
   // This function converts a preList to a CDCase
   
   // This function was created to aid the illusion of speed within the program...
   // This is where most of the processing happens, and thus would be greatly
   // aided by a progress bar.
   public boolean compile () {
      
      logger.log (Level.WARNING, "CDCase.compile(): start\n");

		if (preList.size() <= 0) {
			logger.log (Level.SEVERE, "CDCase.compile(): attempting to compile empty archive\n");
			return false;
		}
      
      
      if (CDCase.verifyData && !verify.goodSum(verifyFiles)) {
         logger.log (Level.SEVERE, "CDCase.compile(): error creating original checksums\n");
                  
         if (stopOnFailure) { System.out.println("exitting"); return false; }
                  
      }
      else {
         logger.log (Level.SEVERE, "CDCase.compile(): checksum of original data created successfully\n");
      }            
  
      if (this.sortList()) {
         // this.printSortedList();
         // return (this.createCDCase());
         this.createCDCase();
      }
      
      logger.log (Level.WARNING, "CDCase.compile(): end\n");

		return true;
   }
   
   private boolean sortList () {
   	
      logger.log (Level.WARNING, "CDCase.sortList(): start\n");
   	
   	sortedList = new ArrayList (preList.size());
   	
   	postProcessSize = 0;
   	  
   	// print top level file sizes
   	// for (int i = 0; i < preList.size(); i++)
   	//   printSizeInfo((String) preList.get (i));
   	// This file calls sizeOf_real()... this was used for verification purposes
      // now we are using md5deep
   
      // look up file sizes, and perform recursion on directories over partition size
      // eligible files are copied to sortedList from preList
      for (int i = 0; i < preList.size(); i++) {
      	if (!this.processFile ((String) preList.get (i))) {
      	   if (stopOnFailure)
      	      return false;
      	}
      }
      
      logger.log (Level.WARNING, "CDCase.sortList(): size of CDCase after processing: " + postProcessSize + '\n');
      
	   if (!keepContinuous) { Collections.sort(sortedList); } 
	   // Collections.sort(sortedList, Collections.reverseOrder());
	  
	   preList = new LinkedList ();
	  
	   logger.log (Level.WARNING, "CDCase.sortList(): done\n");
	  
	   return true;  
   }
   
   // This function processes the preList.
   // It ensures that it meets the minimum requirements (readable, max size)
   private boolean processFile (String filename) {
      
      edc_file ok;                               // a special edc file type structure
      File testFile = new File (filename);       // can be file or directory
      String[] contents = testFile.list();       // contents of file
      String curFile;                            // full path to current file
      File sizeCheck;                            // structure used to check file size
      long curSize = 0;                          // current size of structure
      boolean rtn = true;

      logger.log (Level.INFO, "CDCase.processFile(): start: " + filename + '\n');
      
      // what happens when you add only a file?
      // ie, filename is specified, but contents is null
      curSize = this.sizeOf(testFile);
      if ((curSize >= 0) && (curSize < MAX_CD_SIZE)) {
         ok = new edc_file (filename, curSize);
         sortedList.add (ok);
         postProcessSize += curSize;
         logger.log (Level.INFO, "CDCase.processFile(): successfully processed: " + filename + " " + curSize + '\n');
	      rtn = true;
      }
      // what about directories that don't fit? .. how do we find 
      else if (curSize == PERFORM) {

         // process subfiles if it is a directory
         for (int i = 0; i < contents.length; i++) {
            curFile = filename + File.separator + contents[i];
	         sizeCheck = new File (curFile);
	     
	         // This try/catch statement fixes an error when adding a directory
	         // and one of the files found within it is unreadable.
	     
            try {
	            curSize = this.sizeOf(sizeCheck);
	         }
	         catch (Exception err) {
	            logger.log (Level.SEVERE, "CDCase.processFile(): exception caught processing subdirectories within: " + filename + '\n');
	            curSize = FAIL;
	            rtn = false;
	         }
	    
	         // rc = -1 means that it was a directory that was more than MAX_CD_SIZE
            if (curSize == PERFORM)
               this.processFile (curFile);	    
            else if ((curSize >= 0) && (curSize < MAX_CD_SIZE)) {
	            ok = new edc_file (curFile, curSize);
	            sortedList.add (ok);
	            postProcessSize += curSize;
	            logger.log (Level.INFO, "CDCase.processFile(): successfully processed: " + curFile + " " + curSize + '\n');

	            rtn = true;
	         }
	         else {
               // file is too big or it cannot be read
               
               // TODO: In the future we will need to learn to split and join large files
               logger.log (Level.SEVERE, "CDCase.processFile(): failure: rc = " + curSize + '\n');
	            logger.log (Level.SEVERE, "CDCase.processFile(): error processing file: " + curFile + '\n');
	            rtn = false;
	         }
         }
      }

      logger.log (Level.INFO, "CDCase.processFile: end: " + filename + '\n');

      return rtn;   	
   }
      
   // Performance enhanced size_of() function.
   // Returns:
   //    -1: If the given file is larger than MAX_CD_SIZE
   //     N: The size of the file
   private long sizeOf (File someFile) {

      long sum = 0;                                    // the size of a given path
      final long err = -999999999;
      long temp;
	  
      logger.log (Level.INFO, "CDCase.sizeOf(): computing size of: " + someFile.toString() + '\n');
      
      // file is unreadable
      if (!someFile.canRead()) {
	      // this situation fixes sticky bit in UNIX
	      // TODO: what does this do to unreadable directories in windows?!
	      if ((sum = someFile.length()) >= 0)
	         return sum;
	      else {
			   logger.log (Level.SEVERE, "CDCase.sizeOf(): cannot read file: " + someFile.toString() + '\n');
            return err;
		   }
      }
      // return length of file
      else if (!someFile.isDirectory()) {
             
         try { 	
            // this if statement handles symbolic links in UNIX
            if ((!(someFile.getCanonicalPath()).equals(someFile.getAbsolutePath())) && (!isWindows)) {
               // logger.log (Level.SEVERE, "CDCase.sizeOf(): found symbolic link: " + someFile.toString() + '\n');
	            String real_f = (someFile.getCanonicalFile()).toString();
               String link_f = (someFile.getAbsoluteFile()).toString();

	            int link_i = link_f.lastIndexOf("/");
	            int real_i = real_f.lastIndexOf("/");

               // a problem arises with double indirect links...
               // the function will not compute actual size of 1st level symbolic
               // link... this is because the real_f will point to the final destination, and not the
               // intermediary level link...
               
               // use JNI here
		   
               sum = real_f.length() - (++real_i);
            }
	         else 
	            sum = someFile.length();
	      }
	      catch (IOException e) {
	         logger.log (Level.SEVERE, "CDCase.sizeOf(): exception caught: " + e + '\n');
	         sum = err;
         }
	      finally {
	         if (sum > this.MAX_CD_SIZE) {
	            logger.log (Level.SEVERE, "CDCase.sizeOf(): file is too large: " + someFile.toString() + '\n');
	            sum = err;
	         }
	      }

	      return sum;
      }
      else {
        
         // At this point we now know we are dealing with a directory
	      String[] filesInDir = someFile.list();     // all of the files in a given path
         File sizeCheck;                            // object used to check the size of files
        
         // account for size of the directory
         // in UNIX, we will use a C code to call the lstat() function
         // if (!isWindows) {
         //    sum += this.sizeOfJNI();
         //    System.out.println("CDCase.sizeOf() -> directory JNI()");
         // }

        /* check for empty directory    
        if (filesInDir == null) {
	        logger.log (Level.WARNING, "CDCase.sizeOf(): cannot read directory: " + someFile.toString() + '\n');
	        return err;
        }
        */

        logger.log(Level.INFO, "CDCase.sizeOf(): looking inside: " + someFile.toString() + '\n');
		    
        for (int i = 0; i < filesInDir.length; i++) {
           String subfile = someFile.toString() + File.separator + filesInDir[i];	
	        sizeCheck = new File (subfile);
		        
           // what happens when you can't read a directorie's file?!
	        // how do you properly handle that error?
	        logger.log (Level.INFO, "CDCase.sizeOf():    + subfile: " + subfile + '\n');

           // ... with the I/O Exception ..
	        temp = this.sizeOf (sizeCheck);
	        if ((temp >= 0) && sizeCheck.canRead()) {
              if ((sum + temp) < MAX_CD_SIZE)
	              sum += temp;
	           else 
	              return PERFORM;
           }
	        else if (temp == PERFORM) {
	           return PERFORM;
           }
	        else {
              logger.log (Level.SEVERE, "CDCase.sizeOf(): cannot compute size: " + sizeCheck.toString() + '\n');
				  if (this.stopOnFailure)
   	           return err;
           }
        } // end for

      } // end else
	  
      logger.log (Level.INFO, "CDCase.sizeOf(): result: " + sum + " " +  someFile.toString() + '\n');
      
      return sum;

   }
   

   private boolean createCDCase () {

      logger.log (Level.WARNING, "CDCase.createCDCase(): start\n");
      
	   do {
	  	 
	  	   if (addBigFirst) {
	         this.addBigFiles();
	         this.addSmallFiles();
	  	   }
	  	 
	  	   else {
	  	      this.addSmallFiles();
	  	 	   this.addBigFiles();
	  	   }
	  	 
	   } while (sortedList.size() != 0);
	  
	   logger.log (Level.WARNING, "CDCase.createCDCase(): CDCase end\n");
	  
	   // how do we determine there is an error here...?
	  
	   return false;
   }
   
   private boolean addBigFiles () {
      edc_file f;
      boolean rtn;
      for (int i = sortedList.size()-1; i >= 0; i--) {
         f = (edc_file) sortedList.get(i);
         
         if ((rtn = this.add (f.getName(), f.getSize(), -1))) {
			sortedList.remove(i);
			logger.log(Level.INFO, "CDCase.addBigFiles(): successfully added: " + f.getName() + '\n');
         }
         else {
			logger.log (Level.INFO, "CDCase.addBigFiles(): failed to add file: " + f.getName() + '\n');
			return false;
         }
      }
      
	  return true;
   }
   
   private boolean addSmallFiles() {
	  edc_file f;
	  for (int i = 0; i < sortedList.size(); i++) {
	     f = (edc_file) sortedList.get(i);
	     
	     if (!this.add (f.getName(), f.getSize(), -1)) {
		    logger.log (Level.WARNING, "CDCase.addSmallFiles(): failed to add file: " + f.getName() + '\n');
		    return false;
		 }
		else {
		   sortedList.remove(i);
		   logger.log(Level.WARNING, "CDCase.addSmallFiles(): successfully added: " + f.getName() + '\n');
		}
	  }
	  
	  return true;  	
   }
      
   // Adds a file to a CDCase
   //    * If size is negative, then ignore the field
   //    * If num is negative, then ignore the field
   
   // By adding the size field, we are able to add files in stage 1 and stage 2 using
   // the same method, and not sacrificing: space , consistency, or performance.
   public boolean add (String name, long size, int num) {
   
      CD temp;
	   boolean rtn = false;
	  
   	// TODO: this should be removed if store() is being called first
   	// What the hell does this mean??? 
      if (name.length() <= 0) {
   	   return false;
   	}

      // TODO: what happens will all of the different combinations of performing additions to this function?
      // add (name, -1, 5);                 <- specify CD but not size
      // add (name, 1000000, 3);
      // add (name, 1000000, -1);
      // add (name, -1, -1);
      // Which one do we optimize for?
      
      // This combination for different combinations of input
      if (size > 0) {
      	// specify size but not CD
         rtn = cd.addFile (name, size);
      }
      else if (num < 0) {
      	// specify CD
      	// TODO WHAT IS 
         rtn = cd.addFile(name);
      }
         
     // TODO: Test adding onto a specified CD (untested code)
	  if ((num >= 0) && (num <= cd_case.size()+1)) {
	     int pos;
	  	  cd_case.add(cd);
	  	  pos = cd_case.size();
	     temp = (CD) cd_case.remove (num);
	     rtn = temp.addFile(name);
	     cd_case.add(num, temp);
	     cd  = (CD) cd_case.remove (pos);
	  }
	  else if (!rtn) {
	  	
	     // This check is needed here because a file can go by unchecked
	  	  // if it is added from stage 2.
	  	
	  	  logger.log (Level.WARNING, "CDCase.add(): Attempting to correct failure: " + name + " " + cd.length + '\n');
	  	 
	  	  // Does the file fit on a CD?
	     if ((cd.length > 0) && (cd.length < MAX_CD_SIZE)) {
	  	 	
	  	     // Performance enhancement: we can access the size of the last attempted addition
	  	 	  // instead of calling addFile() again
	  	 	
	  	 	  // Attempt to add to other CDs inside of CDCase
	  	     if (!keepContinuous) {	 
	  	       
			     logger.log (Level.WARNING, "CDCase.add(): Attempting to add file to all CDs in CDCase: " + name + '\n');
	  	        	       
	           for (int i = 0; i < cd_case.size(); i++) {
	              // TODO: By creating an array of CD sizes, we would only need to loop
	              // instead of having to call LinkedList.remove() for every CD.
	              // Moreover, if we sort the cd_case by decreasing size, we are bound to find a spot quicker
	              temp = (CD) cd_case.remove(i);
		           if (   ((cd.length + temp.cdSize) < MAX_CD_SIZE)   &&   (rtn = temp.addFileNoCheck (name, cd.length))   ) {
			           cd_case.add (i, temp);
			           logger.log (Level.WARNING, "CDCase.add(): added to CD: " + i + '\n');
			           break;
		           }
		      
		           cd_case.add(i, temp);
	           }
	  	     }
	  	    
	  	     // If we cannot add it to any of the cd_case in the CDCase above, create a new CD
	  	     if (!rtn) {
  	  	        this.createCD();
	  	        rtn = cd.addFile(name);
	  	     }
	  	    
	  	  }
	  	  else if (cd.length == -1) {
	  	 	
	  	     logger.log (Level.INFO, "CDCase.add(): files does not fit on a CD... recursing directory: " + name + '\n');
	  	     File testDir = new File (name);
	  	    
	  	     if (testDir.isDirectory()) {
	  	        String[] filesInDir = testDir.list();
	  	    	
	  	        for (int i = 0; i < filesInDir.length; i++) {
	  	    	     logger.log (Level.INFO, "CDCase.add(): checking subdirectory: " + name + File.separator + filesInDir[i] + '\n');
	  	    		
	  	    	     if (!this.add(name + File.separator + filesInDir[i], -1, -1)) {
	  	    		     logger.log (Level.SEVERE, "CDCase.add(): failed to add file: " + name + File.separator + filesInDir[i] + '\n');
	  	    			  return false;
	  	    		  }
	  	    	  }
	  	    	
	  	    	  // If none of the files fail then set variable for proper return value
	  	        rtn = true;	
	  	    
	  	     }
	        else {
	           // We get here when a file is over the size of the media
	           // how we chop up the file?
	           logger.log (Level.SEVERE, "CDCase.add(): Fill cannot be added because of its size: " + name + '\n');
	           rtn = false;
	        }
	  	 }
		 else { // if (cd.length == -2) {
		    logger.log(Level.INFO, "CDCase.add(): Unable to read file: " + name + '\n');
		    rtn = false;
		 }
	  }
   	  
	  return rtn;
   }

   // Remove file from CDCase.
   // If CD is more than or equal to zero, remove file from the specified CD number (int num) only.
	
   public boolean remove (String name, int num) {
      
      // check for empty archive
	  if ((cd.size() <= 0) && (cd_case.size() <= 0)) {
	     logger.log(Level.INFO, "CDCase.remove(): Attempting to remove file from empty Archive" + '\n');
	     return false;
	  }
	     	
     CD temp;
     boolean rtn = false;
     
     cd_case.add (cd); 
      
     // TODO: test this code (removing file from specified a CD) 
     //Attempt to remove file from specified archive
     if ((num >= 0) && (num <= cd_case.size())) {
        temp = (CD) cd_case.remove (num);
   	  rtn = temp.removeFile(name);
   	  this.remove (name);
   	  cd_case.add(num, temp);
      } 
      // Attempt to remove file from every CD in the Archive
   	else {
   	   for (int i = 0; i < cd_case.size(); i++) {
   	  	   temp = (CD) cd_case.remove(i);
   	  	   if (temp.removeFile(name)) {
   	  		   this.remove (name);
   	  		   rtn = true;
   	  		}
   	  		cd_case.add(i, temp);
   	  	}	  	
      }
   	  
   	// TODO: Check to ensure that if a file is removed from the current working CD it is done properly
	   cd = (CD) cd_case.remove(cd_case.size()-1);
	  
      return (rtn);
   }
   
   // this function can only be called after sortList(),
   // but not after createCDCase
   // temporary function
   public void printSortedList () {
      long totalsize = 0;
   	  
   	if (sortedList == null)
   	   return;
   	     
   	logger.log (Level.WARNING, "[ >>>>> ] Sorted Pre List [ <<<<< ]\n");
   	     
   	edc_file ok;
      
      for (int i = 0; i < sortedList.size(); i++) {
         ok = (edc_file) sortedList.get(i);
         logger.log(Level.WARNING, "CDCase.printSortedList(): " + ok.toString() + '\n');
         totalsize += ok.getSize();
      }
      
      logger.log (Level.WARNING, "[ >>>>> ] Size of sorted preList: " + totalsize + '\n');
      
      /*
      this.printSizeInfo("C:\\");
      this.printSizeInfo("D:\\");
      */
      
      logger.log (Level.WARNING, "[ >>>>> ] End Pre List [ <<<<< ]\n");
   }
   
   public void printPreList() {
   	System.out.println();
      for (int i = 0; i < preList.size(); i++) {
         System.out.println((preList.get(i)).toString());
      }
      System.out.println();
   }
   
   // This is a temporary function used for debugging purposes.
   public void printCDCase () {
      int i;
	   CD temp;
	   long totalsize = 0;
	
      logger.log (Level.WARNING, "CDCase.printCDCase(): start\n");
      
	   for (i = 0; i < cd_case.size(); i++) {
         temp = (CD) cd_case.get(i);
         logger.log (Level.WARNING, "[>>>>>>] [START] CD " + i + ": " + temp.cdSize + " bytes\n");
         System.out.print("[>>>>>>] [START] CD " + i + ": " + temp.cdSize + " bytes\n");
         temp.printCD(true);
         logger.log (Level.WARNING, "[>>>>>>] [END] CD " + i + ": " + temp.cdSize + " bytes\n");
         System.out.print("[>>>>>>] [END] CD " + i + ": " + temp.cdSize + " bytes\n");
         totalsize += temp.cdSize;
      }
      
	   logger.log (Level.WARNING, "[>>>>>>] [START] CD " + i + " size: " + cd.cdSize + " bytes\n");
      System.out.print("[>>>>>>] [START] CD " + i + " size: " + cd.cdSize + " bytes\n");
	   cd.printCD(true);
	   totalsize += cd.cdSize;
	   logger.log (Level.WARNING, "[>>>>>>] [END] CD " + i + " size: " + cd.cdSize + " bytes\n");
      System.out.print("[>>>>>>] [END] CD " + i + " size: " + cd.cdSize + " bytes\n");
	  
	   logger.log (Level.WARNING, "[TOTAL BYTES] " + totalsize + " [TOTAL CDs] " + (++i) + '\n');
	   System.out.print("\n[TOTAL BYTES] " + totalsize + " [TOTAL CDs] " + i + '\n');
	  
	   logger.log (Level.WARNING, "CDCase.printCDCase(): end\n");
   }
   
   public void printSizeInfo (String loc) {
      logger.log (Level.WARNING, "CDCase.printSizeInfo(): [>>>>>] Real size of " + loc + "  " + utility.sizeOf(loc) + "\n\n");
   }

   // This function collects the name of CDs and writes them to a project file
   // Projects can only be saved after they have been compiled.
   public boolean save (String saveName) throws IOException {
      
      // Check for unspecified SaveName
      if (saveName.length() <= 0)
         return false;
         
	  // Does the current CD have any files?
	  if (cd != null) {
	     cd_case.add(cd);
	  }
	  // Check for empty archive.
	  else if (cd_case.size() <= 0) {
	     logger.log (Level.INFO, "CDCase.save(): Cannot save an empty archive.\n");
	     return false;
	  }
     
     this.compile(); 
                         
	  // Add extension if necessary
	  saveName = this.checkExt (saveName);
	
      // create INDEX file (a listing of all of the files in a CD)
	  CD temp;                                // CD Holder
	  File f = new File (saveName);           // index file
	  BufferedWriter saveFile;                // Writing object
	  boolean rtn = true;                     // return value

      if (f.exists()) {
         logger.log (Level.SEVERE, "CDCase.save(): Filename already exists: " + saveName + '\n');
         return false;
      }
       
      f.createNewFile();
      saveFile = new BufferedWriter(new FileWriter (f));

      String str;
      for (int i = 0; i < cd_case.size(); i++) {
         temp = (CD) cd_case.get(i);
         if (temp.saveCD()) {
            logger.log(Level.INFO, "CDCase.save(): CD successfully save: " + temp.cdName + '\n');
            str = tempDir + File.separator + temp.cdName;
            saveFile.write(str, 0, str.length());
            saveFile.newLine();
         }
         else {
            logger.log(Level.WARNING, "CDCase.save(): error saving CD: " + temp.cdName + '\n');
            rtn = false;
         }      
      }
    
      cd = (CD) cd_case.removeLast();
      saveFile.close();
      
      return (rtn);
   }
   
   // Adds the extension (ext) to the given filename if it is not already present.
   private String checkExt (String name) {
   
      int pos = name.lastIndexOf(".");
      if (pos > 0) {
         String test = name.substring(pos, name.length());
         if (test.compareTo (ext) != 0)
            name = name.substring(0, pos) + ext;
      }
      else
         name = name + ext;
      
      return name;
   }
   
   // Returns path of a given file
   private String getPath (String name) {
   
	  int pos = name.lastIndexOf(File.separator);
	  if (pos > 0) {
		 return (name.substring(0, pos+1));
	  }
	  else
		 return ("." + File.separator);
   }  

   // This function searches all of the cd_case in the Archive for the given regular expression.
   public LinkedList[] search (String expr) {
   	
      // check for empty archive
	  if ((cd.size() <= 0) && (cd_case.size() <= 0)) {
	     logger.log(Level.INFO, "CDCase.search(): attempting to search empty CDCase\n");
	     return null;
	  }
	  
	  CD temp;
	  cd_case.add (cd);
      LinkedList[] results = new LinkedList[cd_case.size()];
      
	  for (int i = 0; i < cd_case.size(); i++) {
		  temp = (CD) cd_case.get(i);
		  // logger.log(Level.INFO, "CDCase.search(): searching CD" + i + " for: " + expr + '\n');
		  results[i] = temp.search(expr);
	  }
	  
	  cd = (CD) cd_case.removeLast();
      
      return results;
   }
   
   // Create a new CD object, saving the old object if it has any data (that is not null).
   public void createCD () {
   	
	  logger.log(Level.INFO, "CDCase.createCD(): A new CD is being created." + '\n');
   	  
	  if (cd != null) {
		 cd_case.add (cd);
	  }

	  cd = new CD();
   }
   
   // Return the number of cd_case
   public int cd_count () {
      return (cd_case.size());
   }
   
   // Remove the given CD
   public boolean removeCD (int num) {
      if ((num >= 0) && (num <= cd_case.size())) {
      	 if (num == cd_case.size())
      	    cd = (CD) cd_case.removeLast();
	     else 
	        cd_case.remove (num);
	        
	     return true;
      }   	
      
      return false;
   }
   
   // Delete all of the contents of this CD
   public boolean clear (int num) {
	if ((num >= 0) && (num <= cd_case.size())) {
	   if (num == cd_case.size())
		  cd = new CD();
	   else {
		  cd_case.remove (num);
		  CD temp = new CD();
		  cd_case.add (num, temp);
	   }
	        
	   return true;
	}   	
      
	return false;   	
   }
   
   public void reset() {
      init();
   }
   
   // This function creates a temporary directory   
   public boolean makeTemp (String name) {
 
	  File f = new File (name);
         
	  if (f.exists()) {
		 if (!f.isDirectory() || !f.canWrite()) {
			logger.log (Level.SEVERE, "CDCase.makeTemp(): invalid temporary directory: " + name + '\n');
		 }
		 else {
			tempDir = name;
			// logger.log (Level.INFO, "CDCase.makeTemp(): temporary directory verified: " + tempDir + '\n');
			return true;
		 }
	  }
	  else {
		 if (!f.mkdirs()) {
			logger.log (Level.SEVERE, "Archive.makeTemp(): unable to create temporary directory: " + name + '\n');
		 }
		 else {
			tempDir = name;
			// logger.log (Level.INFO, "CDCase.makeTemp(): temporary created: " + tempDir + '\n');
			return true;
		 }
	  }
      
	  return false;
   }
   
   public void close () {
      
      logger.log (Level.WARNING, "CDCase.close(): [*****] END [*****]\n");
   	
   	fh.close();
      ch.close();
   
   }
   
   public boolean burn (int num) {
      
      boolean rtn = true;
   	CD temp;
   	
   	logger.log (Level.WARNING, "CDCase.burn(): start\n");
   	  
   	// TODO: test this code
      if ((num >= 0) && (num <= cd_case.size())) {
		   cd_case.add(cd);
		   temp = (CD) cd_case.get (num);
		   rtn = temp.createISO();
		
		   if (rtn) {
		      rtn = temp.burnISO();
		   
		      if (rtn && verifyData) {
		         if (verify.mediaSum(verifyFiles)) {
                  logger.log (Level.WARNING, "CDCase.burn(): checksum media created\n");
               }
               else {
                  logger.log (Level.SEVERE, "CDCase.burn(): error creating media checksum\n");
               }
            }
         }
        
		   cd = (CD) cd_case.removeLast();
		
		   return rtn;
      }

	   cd_case.add (cd);
      
	   for (int i = 0; i < cd_case.size(); i++) {
	  	   logger.log (Level.INFO, "CDCase.burn(): burning cd " + (i+1) + " of " + cd_case.size() + '\n');
	  	   
		   // create ISO
		   temp = (CD) cd_case.get(i);
		   rtn = temp.createISO();
		 
		   // burn ISO
		   if (rtn) {
		      
				logger.log (Level.SEVERE, "CDCase.burn(): ISO created successfully\n");
		      rtn = temp.burnISO();
		      
		      if (rtn) {

					logger.log (Level.SEVERE, "CDCase.burn(): data burned successfully\n");

					if (verifyData) {
		            if (verify.mediaSum(verifyFiles)) {
                     logger.log (Level.WARNING, "CDCase.burn(): checksum media created\n");
                  }
                  else {
                     logger.log (Level.SEVERE, "CDCase.burn(): error creating media checksum\n");
                  }
					}
            } 
            if (!rtn) {
					logger.log (Level.SEVERE, "CDCase.burn(): error burning ISO\n");
					if (stopOnFailure) 
					   return false;
				}
		   }
		   else {
				logger.log (Level.SEVERE, "CDCase.burn(): error creating ISO\n");
				if (stopOnFailure)
				   return false;
			}
	   }
	   
	   cd = (CD) cd_case.removeLast();
      logger.log (Level.WARNING, "CDCase.burn(): end\n");
          
	   return rtn;
   }
}
