package edu.oregonstate.cope.eclipse.listeners;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.codec.binary.Base64OutputStream;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;

import edu.oregonstate.cope.clientRecorder.ClientRecorder;
import edu.oregonstate.cope.eclipse.COPEPlugin;

public class ResourceListener implements IResourceChangeListener {
	
	ClientRecorder recorder = COPEPlugin.getDefault().getClientRecorder();
	
	private Map<String, Long> lastSavedVersion;
	
	public ResourceListener() {
		lastSavedVersion = new HashMap<String, Long>();
	}

	@Override
	public void resourceChanged(IResourceChangeEvent event) {
		IResourceDelta delta = event.getDelta();
		recordChangedDelta(delta);
	}

	private void recordChangedDelta(IResourceDelta delta) {
		if (delta == null)
			return;
		
		IResource affectedResource = delta.getResource();
		if (affectedResource.getType() == IResource.FILE) {
			if (isProjectIgnored(affectedResource))
				return;
			IFile affectedFile = (IFile) affectedResource;
			String filePath = affectedFile.getFullPath().toPortableString();
			if (isClassFile(affectedFile))
				return;
			if (delta.getKind() == IResourceDelta.REMOVED) {
				recorder.recordResourceDelete(filePath);
				return;
			}
			if (delta.getKind() == IResourceDelta.ADDED) {
				recorder.recordResourceAdd(filePath, getFileContentents(affectedFile));
				return;
			}
			
			if (isSavedAction() || isRefactoringInProgress())
				recordFileSave(delta, filePath);
			else
				recordFileRefresh(affectedFile);
			return;
		}
		
		IResourceDelta[] affectedChildren = delta.getAffectedChildren();
		for (IResourceDelta child : affectedChildren) {
			recordChangedDelta(child);
		}
	}

	private void recordFileSave(IResourceDelta delta, String filePath) {
		long modificationStamp = delta.getResource().getModificationStamp();
		recorder.recordFileSave(filePath, modificationStamp);
		lastSavedVersion.put(filePath, modificationStamp);
	}

	public boolean isProjectIgnored(IResource affectedResource) {
		IProject project = affectedResource.getProject();
		if (project == null)
			return true;
		return COPEPlugin.getDefault().getIgnoreProjectsList().contains(project.getName());
	}

	private void recordFileRefresh(IFile affectedFile) {
		String filePath = affectedFile.getFullPath().toPortableString();
		long modificationStamp = affectedFile.getModificationStamp();
		
		Long lastVersion = lastSavedVersion.get(filePath);
		if (lastVersion != null && modificationStamp == lastVersion)
			return;
		
		String contents = getFileContentents(affectedFile);
		recorder.recordRefresh(contents, filePath, modificationStamp);
	}

	@SuppressWarnings("resource")
	private String getFileContentents(IFile affectedFile) {
		InputStream inputStream;
		try {
			inputStream = affectedFile.getContents();
			return getBinaryFileContents(inputStream);
		} catch (CoreException | IOException e) {
			COPEPlugin.getDefault().getLogger().error(this, "Could not get contents of file", e);
		}
		return "";
	}

	/**
	 * I return a base64 encoding of the file contents.
	 * @param inputStream the InputStream I have to read from.
	 * @return the base64 string containing the encoded file contents.
	 * @throws IOException if I cannot read from the InputStream.
	 */
	private String getBinaryFileContents(InputStream inputStream) throws IOException {
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		Base64OutputStream base64OutputStream = new Base64OutputStream(byteArrayOutputStream, true, 0, null);
		do {
			byte[] b = new byte[1024];
			int read = inputStream.read(b, 0, 1024);
			if (read == -1)
				break;
			base64OutputStream.write(b);
		} while (true);
		byte[] byteArray = byteArrayOutputStream.toByteArray();
		base64OutputStream.close();
		byteArrayOutputStream.close();
		return new String(byteArray);
	}

	private boolean isClassFile(IFile affectedFile) {
		String fileExtension = affectedFile.getFileExtension();
		if (fileExtension.equals("class"))
			return true;
		return false;
	}

	private boolean isSavedAction() {
		return CommandExecutionListener.isSaveInProgress();
	}

	private boolean isRefactoringInProgress() {
		return RefactoringExecutionListener.isRefactoringInProgress();
	}
}

@@ -128,32 +128,61 @@
 io.InputStream;%0A
+import java.io.OutputStream;%0A
 import java.util
@@ -3961,317 +3961,460 @@
 ;%0A%09%09
-do %7B%0A%09%09%09byte%5B%5D b = new byte%5B1024%5D;%0A%09%09%09int read = inputStream.read(b, 0, 1024);%0A%09%09%09if (read == -1)%0A%09%09%09%09break;%0A%09%09%09base64OutputStream.write(b);%0A%09%09%7D while (true);%0A%09%09byte%5B%5D byteArray = byteArrayOutputStream.toByteArray();%0A%09%09base64OutputStream.close();%0A%09%09byteArrayOutputStream.close();%0A%09%09return new String(byteArray
+readFromTo(inputStream, base64OutputStream);%0A%09%09byte%5B%5D byteArray = byteArrayOutputStream.toByteArray();%0A%09%09base64OutputStream.close();%0A%09%09byteArrayOutputStream.close();%0A%09%09return new String(byteArray);%0A%09%7D%0A%0A%09private void readFromTo(InputStream inputStream, OutputStream outputStream) throws IOException %7B%0A%09%09do %7B%0A%09%09%09byte%5B%5D b = new byte%5B1024%5D;%0A%09%09%09int read = inputStream.read(b, 0, 1024);%0A%09%09%09if (read == -1)%0A%09%09%09%09break;%0A%09%09%09outputStream.write(b);%0A%09%09%7D while (true
 );%0A%09

Extracted the reading part into a method