import java.io.*;
import java.security.*;
import java.util.Arrays;

// An implementation of File which has a System-Independant compareTo()
// so that the list of files is traversed identically across win/lin
class SIFile extends File
{

	public SIFile(String arg0) 
	{
		super(arg0);
		// TODO Auto-generated constructor stub
	}
	
	@Override
	public int compareTo(File file2)
	{
		return this.getName().compareTo(file2.getName()); // compare them via thier name strings compareTo()
	}
}

// Directory Digester
// Given a path to a folder it will hash each "File" inside and sum hashes in alphabetical order.
public class DirDigester
{
	
	public static byte[] DigestDirectory(SIFile dir)throws Exception
	{
		if(dir.isDirectory())
		{
			//We need to construct a list of universally sorted subfiles (SIFile) in the current directory
			String[] strFiles = dir.list(); // strings contain just the last part of the pathname...
			SIFile[] thefiles = new SIFile[strFiles.length];
			for(int i=0; i<thefiles.length; ++i)
				thefiles[i] = new SIFile(dir.getAbsolutePath() + File.separator + strFiles[i]);
			Arrays.sort(thefiles); // sort according to SI's compareTo() 
			
			SIFile curFile;
			byte[] curDigest = {
					0x00,0x00,0x00,0x00,
					0x00,0x00,0x00,0x00,
					0x00,0x00,0x00,0x00,
					0x00,0x00,0x00,0x00, };
			byte[] doubleDigestBuf = new byte[32]; // 2 md5 digests wide
			for(int i=0; i<doubleDigestBuf.length; ++i)
			{
				doubleDigestBuf[i] = 0x00;
			}
			byte[] newDigest = new byte[16];
			MessageDigest md;
			for(int i=0; i<thefiles.length; ++i)
			{
				curFile = thefiles[i];
				
				if(!curFile.exists())
				{
					throw new FileNotFoundException();
				}

				
				System.out.println("File:  " + dir.getAbsolutePath()+ File.separator + curFile.getName() );
					
				//1ST load the cur digest into the low bytes of double buffer 
				for(int j=0; j<16; ++j)
					doubleDigestBuf[j] = curDigest[j];
					
				//2ND Get the newDigest
				if(curFile.isFile())
				{
					md = MessageDigest.getInstance("MD5");
					newDigest = getDigestBytes(new FileInputStream(curFile), md, 16384);
				}
				else // if dir, recursively eval it and get its result...
				{
					newDigest = DigestDirectory(curFile);
				}
				//3RD load the newest digest into the upper bytes of the double buffer
				for(int j=16; j<32; ++j)
					doubleDigestBuf[j] = newDigest[j-16];
					
				//4TH make a digest of the two digests combined (32 bytes) inside the double buffer
				//and make it current
				md = MessageDigest.getInstance("MD5");
				md.update(doubleDigestBuf, 0, doubleDigestBuf.length);
				curDigest = md.digest();
					
				//System.out.println("current Digest \t" + bytesToHex(curDigest) + "\n");				
			}
			//System.out.println("curDigest (End) " + dir.getAbsolutePath() + "\r\n\t" + bytesToHex(curDigest) + "\n");
			
			return curDigest;
		}
		return null;
	}
	
	// http://www.avajava.com/tutorials/lessons/how-do-i-generate-an-md5-digest-for-a-file.html
	public static byte[] getDigestBytes(InputStream is, MessageDigest md, int byteArraySize)
			throws NoSuchAlgorithmException, IOException 
	{

		md.reset();
		byte[] bytes = new byte[byteArraySize];
		int numBytes;
		while ((numBytes = is.read(bytes)) != -1) 
		{
			md.update(bytes, 0, numBytes);
		}
		return md.digest();
	}
	
	// http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java
	final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
	public static String bytesToHex(byte[] bytes) 
	{
	    char[] hexChars = new char[bytes.length * 2];
	    for ( int j = 0; j < bytes.length; j++ ) {
	        int v = bytes[j] & 0xFF;
	        hexChars[j * 2] = hexArray[v >>> 4];
	        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
	    }
	    return new String(hexChars);
	}
	
	public static void main(String[] args) throws Exception
	{
		String arg;
		if(args.length < 1) 
			arg = ".";
		else
			arg = args[0];
		System.out.println("Input Dir: " + arg);
		byte[] finaldigest = DigestDirectory(new SIFile(arg)); 
		System.out.println("Final Digest: " + bytesToHex(finaldigest) + "\n");
				
	}	
	
	
	
}