import java.io.*;		// input/output classes
import java.util.zip.CRC32;	// CRC checksumming
import java.util.Properties;	// Hashtable
import java.util.Enumeration;	// Enumeration for the hashtable
import java.net.*;		// internet sockets

class CRCRepair {

	private String CRCPropertiesFile;			// contains the CRC values, filename, and mirror
	private String File2Test;				// the file to check
	private Properties failedCRCTest = new Properties();	// hashtable of failed sections
	private String mirror;					// URL of the file (HTTP only)
	private String totalsize;				// total size of the File2Test

	CRCRepair(String crcfile){				// Constructor
		CRCPropertiesFile = crcfile;
	}

	public static void main(String[] Args){			// main
		if(Args.length==0){				// no arguments supplied
			System.out.println("Usage:");
			System.out.println("java crcrepair [filename]\tChecks the file whose CRC values are stated in [filename]");
			System.out.println("java crcrepair --make [file] [filename]\tCreates a valid profile of [file] in [filename] holding CRC values");
			System.exit(0);
		}else if(Args.length==1){			// one argument supplied
			CRCRepair crcr = new CRCRepair(Args[0]);
			crcr.examine();
		}else if(Args[0].compareTo("--make")==0){	// "make" arguement supplied
			CRCRepair crcr = new CRCRepair(Args[2]);
			crcr.make(Args[1]);
		}
	}

	public void examine(){		// examine a file
		try{
			BufferedReader props = new BufferedReader(new FileReader(CRCPropertiesFile));	// the Filename, Mirror, and CRC values
			File2Test = props.readLine();							// read the Filename and parse it
			File2Test = File2Test.substring(File2Test.indexOf(":")+1).trim();
			FileInputStream testfile = new FileInputStream(File2Test);			// open the file
			long index = 0;									// position is at the beginning of the file
			long filelen = (new File(File2Test)).length();					// set the size of the file (long)
			totalsize = Long.toString(filelen);						// set the size of the file (String)

			mirror = props.readLine();							// set the mirror of the file

			String line = props.readLine();
			while(line != null){								// for each line in the profile,
				CRC32 checksum = new CRC32();								// make a CRC checking object
				long checksumresult = Long.parseLong(line.substring(line.indexOf(":")+1).trim());	// get the expected checksum result
				int size = Integer.parseInt(line.substring(line.indexOf("[")+1, line.indexOf("]")));	// get the number of bytes in the CRC
				byte[] input = new byte[size];								// create a byte buffer
				testfile.read(input);									// read into the byte buffer
				checksum.update(input);									// add the bytes to the CRC checker
				System.out.println((checksum.getValue() == checksumresult)?"Passed CRC":"Failed CRC!");	// show the result of the check
				if(checksum.getValue() != checksumresult){						// compare the result to the expected result
					failedCRCTest.setProperty(Long.toString(index), Integer.toString(size));	// it failed the test -- add an entry to the hashtable
					System.out.println("Failure noted: starting index " + index + ", size " + size + " bytes");	// notify the user which range
				}
				line = props.readLine();								// read in another line
				index += size;										// move the index
			}
			fixit();											// attempt to fix any checksum failures
			System.out.println("Press return to exit.");							// all done
			System.in.read();
		}catch(Exception e){		// catch and print any exceptions
			e.printStackTrace();
		}
	}

	public void fixit() throws Exception {

		Enumeration keys = failedCRCTest.propertyNames();			// list all the hashtable elements
		while(keys.hasMoreElements()){						// while there are elements
			String key = (String)keys.nextElement();				// get the next one
			download(key, failedCRCTest.getProperty(key), totalsize);		// attempt to download the portion that failed
		}
	}

	public void download(String start, String size, String total) throws Exception {
		System.out.println("Downloading the section starting at " + start + " with a length of " + size + " bytes");	// notify of download
		Socket http = new Socket(mirror.substring(mirror.indexOf(":")+1, mirror.indexOf(",")).trim(), 80);		// parse the site
		PrintWriter pw = new PrintWriter(http.getOutputStream(), true);							// make an outstream
		InputStream is = http.getInputStream();										// get an instream
		pw.println("GET " + mirror.substring(mirror.indexOf(",")+1).trim() + " HTTP/1.1");				// send the HTTP/1.1 request
		pw.println("Host: " + mirror.substring(mirror.indexOf(":")+1, mirror.indexOf(",")).trim());
		pw.println("Range: bytes=" + start + "-" + (Integer.parseInt(start) + Integer.parseInt(size)));			// with the correct range
		pw.println("Connection: close");
		pw.println();
		RandomAccessFile raf = new RandomAccessFile(File2Test, "rw");							// open the corrupted file with the ability to seek throughout
		raf.seek(Integer.parseInt(start));										// seek to the beginning of the corrupted data
		String line = "";
		while(line.endsWith("\r\n\r\n")==false){									// read through the HTTP/1.1 headers
			line += (char)is.read();
		}
		System.out.print("0%");												// here we go
		byte[] buf = new byte[6144];											// create a byte buffer
		int inp = is.read(buf);												// read to it
		int count = 0;													// keep track of % done
		while(inp != -1){												// while there's more
			for(int i=0; i<inp; i++){											// for each byte
				raf.writeByte(buf[i]);											// write it to the file
			}
			inp = is.read(buf);											// read into the buffer again
			count+=inp;												// keep track of the % done
			System.out.print((char)8); System.out.print((char)8); System.out.print((char)8);			// 3 backspace characters
			System.out.print(count*100/Integer.parseInt(size) + "%");						// display % done
		}
		System.out.print((char)8); System.out.print((char)8); System.out.print((char)8);				// 3 backspace characters
		System.out.println("Done!");											// display that you're done with section
		raf.close();													// close the file
	}



	public void make(String filename){
		File2Test = filename;						// the file you want to test
		try{
			int K = 1024;						// kilobytes
			int M = 1024*K;						// megabytes
			int G = 1024*M;						// gigabytes

			FileWriter fw = new FileWriter(CRCPropertiesFile);	// open the file you're going to write the CRC values to
			FileInputStream fis = new FileInputStream(File2Test);	// open the file you want to test
			long totalsize = (new File(File2Test)).length();	// find the total size of the file you want to test
			int size;						// choose the CRC block size
			if(totalsize < 1*K){						// if under 1K, only one block
				size = (int)totalsize;
			}else if(totalsize < 64*K){					// if under 64K, use 4K blocks
				size = 4*K;
			}else if(totalsize < 128*K){					// etc etc etc
				size = 8*K;
			}else if(totalsize < 256*K){
				size = 16*K;
			}else if(totalsize < 512*K){
				size = 32*K;
			}else if(totalsize < 1*M){
				size = 64*K;
			}else if(totalsize < 4*M){
				size = 128*K;
			}else if(totalsize < 16*M){
				size = 256*K;
			}else if(totalsize < 32*M){
				size = 512*K;
			}else if(totalsize < 64*M){
				size = 1*M;
			}else if(totalsize < 256*M){
				size = 2*M;
			}else if(totalsize < 512*M){
				size = 4*M;
			}else if(totalsize < 1*G){
				size = 8*M;
			}else{								// of huge file, use 32 meg blocks
				size = 32*M;
			}
			fw.write("Filename: " + File2Test + "\r\n"); fw.flush();	// write the filename to the CRC file
			fw.write("Mirror: \r\n"); fw.flush();				// write a mirror the file CRC file
			
			byte[] input = new byte[size];					// create byte buffer
			int len = fis.read(input);					// read to it
			while(len != 0 && len != -1){					// while still in file
				CRC32 checksum = new CRC32();					// make CRC checker
				checksum.update(input, 0, len);					// add bytes to the checker
				fw.write("CRC [" + len + "]: " + checksum.getValue() + "\r\n"); fw.flush();	// write the block size and value to file
				len = fis.read(input);						// read from file again
			}
		}catch(Exception e){		// catch any exceptions
			e.printStackTrace();
		}
	}
}