/*******************************************************************************
 * Copyright (c) MOBAC developers
 * 
 * This program 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.
 * 
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package mobac.utilities.tar;

import java.io.File;
import java.io.UnsupportedEncodingException;

public class TarHeader {

	// private static Logger log = Logger.getLogger(TarHeader.class);

	private File baseFilePath;

	private int fileNameLength;
	private final char[] fileName = new char[100];
	private final char[] fileMode = new char[8];
	private final char[] fileOwnerUserID = new char[8];
	private final char[] fileOwnerGroupID = new char[8];
	private final char[] fileSize = new char[12];
	private final char[] lastModificationTime = new char[12];
	private final char[] linkIndicator = new char[1];
	private final char[] nameOfLinkedFile = new char[100];
	private static final char[] padding = new char[255];

	public TarHeader() {
	}

	public TarHeader(File theFile, File theBaseFilePath) {
		this();
		baseFilePath = theBaseFilePath;

		this.setFileName(theFile, baseFilePath);
		this.setFileMode();
		this.setFileOwnerUserID();
		this.setFileOwnerGroupID();
		this.setFileSize(theFile);
		this.setLastModificationTime(theFile);
		this.setLinkIndicator(theFile);
	}

	public TarHeader(String fileName, int fileSize, boolean isDirectory) {
		this();
		this.setFileName(fileName);
		this.setFileMode();
		this.setFileOwnerUserID();
		this.setFileOwnerGroupID();
		this.setFileSize(fileSize);
		this.setLastModificationTime(System.currentTimeMillis());
		this.setLinkIndicator(isDirectory);
	}

	public void read(byte[] buffer) {
		String fn = new String(buffer, 0, 512);
		fn.getChars(0, 100, fileName, 0);
		fileNameLength = fn.indexOf((char) 0);
		fn.getChars(100, 108, fileMode, 0);
		fn.getChars(108, 116, fileOwnerUserID, 0);
		fn.getChars(116, 124, fileOwnerGroupID, 0);
		fn.getChars(124, 136, fileSize, 0);
		fn.getChars(136, 148, lastModificationTime, 0);
		// fn.getChars(148, 156, checksum, 0); we ignore the checksum
		fn.getChars(156, 157, linkIndicator, 0);
		fn.getChars(157, 257, nameOfLinkedFile, 0);
	}

	// S E T - Methods
	public void setFileName(File theFile, File theBaseFilePath) {

		String filePath = theFile.getAbsolutePath();
		String basePath = theBaseFilePath.getAbsolutePath();
		if (!filePath.startsWith(basePath))
			throw new RuntimeException("File \"" + filePath + "\" is outside of archive base path \"" + basePath
					+ "\"!");

		String tarFileName = filePath.substring(basePath.length(), filePath.length());

		tarFileName = tarFileName.replace('\\', '/');

		if (tarFileName.startsWith("/"))
			tarFileName = tarFileName.substring(1, tarFileName.length());

		if (theFile.isDirectory())
			tarFileName = tarFileName + "/";
		setFileName(tarFileName);
	}

	public void setFileName(String newFileName) {
		int newFileNameLength = newFileName.length();
		if (newFileNameLength > fileName.length)
			throw new RuntimeException("File name length exceeds limit of 100 characters: " + newFileNameLength
					+ "chars:" + newFileName);

		char[] theFileName = newFileName.toCharArray();
		System.arraycopy(theFileName, 0, fileName, 0, newFileNameLength);
		fileNameLength = newFileNameLength;

		for (int i = newFileNameLength; i < fileName.length; i++) {
			fileName[i] = 0;
		}
	}

	public void setFileMode() {
		"   777 ".getChars(0, 7, fileMode, 0);
	}

	public void setFileOwnerUserID() {
		"     0  ".getChars(0, 7, fileOwnerUserID, 0);
	}

	public void setFileOwnerGroupID() {
		"     0  ".getChars(0, 7, fileOwnerGroupID, 0);
	}

	public void setFileSize(File theFile) {
		long fileSizeLong = 0;
		if (!theFile.isDirectory()) {
			fileSizeLong = theFile.length();
		}
		setFileSize(fileSizeLong);
	}

	public void setFileSize(long fileSize) {
		char[] fileSizeCharArray = Long.toString(fileSize, 8).toCharArray();

		int offset = 11 - fileSizeCharArray.length;

		for (int i = 0; i < 12; i++) {
			if (i < offset) {
				this.fileSize[i] = ' ';
			} else if (i == 11) {
				this.fileSize[i] = ' ';
			} else {
				this.fileSize[i] = fileSizeCharArray[i - offset];
			}
		}
	}

	public void setLastModificationTime(File theFile) {

		setLastModificationTime(theFile.lastModified());
	}

	public void setLastModificationTime(long lastModifiedTime) {
		lastModifiedTime /= 1000;

		char[] fileLastModifiedTimeCharArray = Long.toString(lastModifiedTime, 8).toCharArray();

		for (int i = 0; i < fileLastModifiedTimeCharArray.length; i++) {
			lastModificationTime[i] = fileLastModifiedTimeCharArray[i];
		}

		if (fileLastModifiedTimeCharArray.length < 12) {
			for (int i = fileLastModifiedTimeCharArray.length; i < 12; i++) {
				lastModificationTime[i] = ' ';
			}
		}
	}

	public void setLinkIndicator(File theFile) {
		setLinkIndicator(theFile.isDirectory());
	}

	public void setLinkIndicator(boolean isDirectory) {
		if (isDirectory) {
			linkIndicator[0] = '5';
		} else {
			linkIndicator[0] = '0';
		}
	}

	// G E T - Methods
	public String getFileName() {
		return new String(fileName, 0, fileNameLength);
	}

	public int getFileNameLength() {
		return fileNameLength;
	}

	public char[] getFileMode() {
		return fileMode;
	}

	public char[] getFileOwnerUserID() {
		return fileOwnerUserID;
	}

	public char[] getFileOwnerGroupID() {
		return fileOwnerGroupID;
	}

	public char[] getFileSize() {
		return fileSize;
	}

	public int getFileSizeInt() {
		return Integer.parseInt(new String(fileSize).trim(), 8);
	}

	public char[] getLastModificationTime() {
		return lastModificationTime;
	}

	public char[] getLinkIndicator() {
		return linkIndicator;
	}

	public char[] getNameOfLinkedFile() {
		return nameOfLinkedFile;
	}

	public char[] getPadding() {
		return padding;
	}

	/**
	 * <p>
	 * Checksum field content:<br>
	 * Header checksum, stored as an octal number in ASCII. To compute the checksum, set the checksum field to all
	 * spaces, then sum all bytes in the header using unsigned arithmetic. This field should be stored as six octal
	 * digits followed by a null and a space character. Note that many early implementations of tar used signed
	 * arithmetic for the checksum field, which can cause inter- operability problems when transferring archives between
	 * systems. Modern robust readers compute the checksum both ways and accept the header if either computation
	 * matches.<br>
	 * <a href="http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&manpath=FreeBSD+8-current" >definition source</a>
	 * </p>
	 * 
	 * @param header
	 *            array containing a tar header at offset 0 (512 bytes of size) with prepared checksum field (filled
	 *            with spaces)
	 */
	public void correctCheckSum(byte[] header) {
		// Compute the checksum
		// theoretical max = 512 bytes * 255 = 130560 = o377000
		int checksum = 0;
		for (int i = 0; i < 512; i++) {
			// compute the checksum with unsigned arithmetic
			checksum = checksum + (header[i] & 0xFF);
		}
		String s = Integer.toOctalString(checksum);
		while (s.length() < 6)
			s = '0' + s;
		byte[] checksumBin = (s).getBytes();
		System.arraycopy(checksumBin, 0, header, 148, 6);
		header[154] = 0;
	}

	public byte[] getBytes() {

		StringBuffer sb = new StringBuffer(512);

		sb.append(fileName);
		sb.append(fileMode);
		sb.append(fileOwnerUserID);
		sb.append(fileOwnerGroupID);
		sb.append(fileSize);
		sb.append(lastModificationTime);
		sb.append("        "); // empty/prepared checksum
		sb.append(linkIndicator);
		sb.append(nameOfLinkedFile);
		sb.append(padding);

		byte[] result;
		try {
			result = sb.toString().getBytes("US-ASCII");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e); // should never happen
		}
		if (result.length != 512)
			throw new RuntimeException("Invalid tar header size: " + result.length);
		correctCheckSum(result);

		return result;
	}
}
