package org.hive2hive.core.file;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.hive2hive.core.model.Chunk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FileChunkUtil {

	private static final Logger logger = LoggerFactory.getLogger(FileChunkUtil.class);

	private FileChunkUtil() {
		// only static methods
	}

	/**
	 * Calculates the number of chunks. This depends on the file size and the chunk size
	 * 
	 * @param file the file to chunk
	 * @param chunkSize the size of an individual chunk
	 * @return the number of chunks, if the file is empty, 1 is returned. If file is not existing, 0 is
	 *         returned. In case the given chunkSize is smaller or equal to zero, 0 is returned
	 */
	public static int getNumberOfChunks(File file, int chunkSize) {
		if (file == null || !file.exists()) {
			// no chunk needed
			return 0;
		} else if (chunkSize <= 0) {
			// don't divide by 0
			return 0;
		}

		long fileSize = FileUtil.getFileSize(file);
		if (fileSize == 0) {
			// special case
			return 1;
		}

		return (int) Math.ceil((double) fileSize / Math.abs(chunkSize));
	}

	/**
	 * Returns the chunk of a given file.
	 * 
	 * @param file the file to chunk
	 * @param chunkSize the maximum size of a single chunk. If the end of the file has been reached before,
	 *            the returned chunk can be smaller.
	 * @param chunkNumber the index of the chunk, starting at 0. When giving 0, the first chunk is read. This
	 *            parameter is similar to the offset.
	 * @param chunkId the id of the chunk which should be returned
	 * @return the chunk or null if no data could be read with the given parameter
	 * @throws IOException if the file cannot be read
	 */
	public static Chunk getChunk(File file, int chunkSize, int chunkNumber, String chunkId) throws IOException {
		if (file == null || !file.exists()) {
			throw new IOException("File does not exist");
		} else if (chunkSize <= 0) {
			throw new IOException("Chunk size cannot be smaller or equal to 0");
		} else if (chunkNumber < 0) {
			throw new IOException("Chunk number cannot be smaller than 0");
		}

		if (FileUtil.getFileSize(file) == 0 && chunkNumber == 0) {
			// special case: file exists but is empty.
			// return an empty chunk
			return new Chunk(chunkId, new byte[0], 0);
		}

		int read = 0;
		long offset = chunkSize * (long) chunkNumber;
		byte[] data = new byte[chunkSize];

		// read the next chunk of the file considering the offset
		RandomAccessFile rndAccessFile = new RandomAccessFile(file, "r");
		rndAccessFile.seek(offset);
		read = rndAccessFile.read(data);
		rndAccessFile.close();

		if (read > 0) {
			// the byte-Array may contain many empty slots if last chunk. Truncate it
			data = truncateData(data, read);
			return new Chunk(chunkId, data, chunkNumber);
		} else {
			return null;
		}
	}

	/**
	 * Truncates a byte array
	 * 
	 * @param data
	 * @param read
	 * @return a shorter byte array
	 */
	private static byte[] truncateData(byte[] data, int numOfBytes) {
		// shortcut
		if (data.length == numOfBytes) {
			return data;
		} else {
			byte[] truncated = new byte[numOfBytes];
			for (int i = 0; i < truncated.length; i++) {
				truncated[i] = data[i];
			}
			return truncated;
		}
	}

	/**
	 * Reassembling of multiple file parts to a single file. Note that the file parts need to be sorted
	 * beforehand
	 * 
	 * @param fileParts the sorted file parts.
	 * @param destination the destination
	 * @throws IOException in case the files could not be read or written.
	 */
	public static void reassembly(List<File> fileParts, File destination) throws IOException {
		if (fileParts == null || fileParts.isEmpty()) {
			throw new IllegalArgumentException("File parts can't be null.");
		} else if (fileParts.isEmpty()) {
			throw new IllegalArgumentException("File parts can't be empty.");
		} else if (destination == null) {
			throw new IllegalArgumentException("Destination can't be null");
		}

		if (destination.exists()) {
			// overwrite
			if (destination.delete()) {
				logger.warn("Destination gets overwritten. destination = '{}'", destination);
			} else {
				throw new IOException(String.format("Couldn't overwrite destination. destination = '%s'", destination));
			}
		}

		//The file parts are assembled where they are located, then moved to the destination
		File assembled = new File(fileParts.get(0).getParentFile().toString().concat("_assembled"));
		logger.trace("Assemble file to {}", assembled.getPath());
		for (File filePart : fileParts) {
			// copy file parts to the new location, append
			FileUtils.writeByteArrayToFile(assembled, FileUtils.readFileToByteArray(filePart), true);

			if (!filePart.delete()) {
				logger.warn("Couldn't delete temporary file part. filePart = '{}'", filePart);
			}
		}
		
		Files.move(assembled.toPath(), destination.toPath());
	}
}

@@ -113,64 +113,8 @@
 le;%0A
-import java.nio.file.Files;%0Aimport java.nio.file.Paths;%0A
 impo
@@ -4345,16 +4345,17 @@
 %09%7D%0A%0A%09%09//
+ 
 The file
@@ -4493,17 +4493,34 @@
 le()
-.toString
+, fileParts.get(0).getName
 ().c
@@ -4906,23 +4906,29 @@
 %09%09%7D%0A
-%09%09
 %0A%09%09File
+Util
 s.move
+File
 (ass
@@ -4933,25 +4933,16 @@
 ssembled
-.toPath()
 , destin
@@ -4950,17 +4950,8 @@
 tion
-.toPath()
 );%0A%09

Remove dependency to java.nio.Files (not available on Android)