/**
 * Copyright 2012 Kamran Zafar 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 * 
 */

package org.kamranzafar.jtar;

import java.io.File;

/**
 * Header
 * 
 * <pre>
 * Offset  Size     Field
 * 0       100      File name
 * 100     8        File mode
 * 108     8        Owner's numeric user ID
 * 116     8        Group's numeric user ID
 * 124     12       File size in bytes
 * 136     12       Last modification time in numeric Unix time format
 * 148     8        Checksum for header block
 * 156     1        Link indicator (file type)
 * 157     100      Name of linked file
 * </pre>
 * 
 * 
 * File Types
 * 
 * <pre>
 * Value        Meaning
 * '0'          Normal file
 * (ASCII NUL)  Normal file (now obsolete)
 * '1'          Hard link
 * '2'          Symbolic link
 * '3'          Character special
 * '4'          Block special
 * '5'          Directory
 * '6'          FIFO
 * '7'          Contigous
 * </pre>
 * 
 * 
 * 
 * Ustar header
 * 
 * <pre>
 * Offset  Size    Field
 * 257     6       UStar indicator "ustar"
 * 263     2       UStar version "00"
 * 265     32      Owner user name
 * 297     32      Owner group name
 * 329     8       Device major number
 * 337     8       Device minor number
 * 345     155     Filename prefix
 * </pre>
 */

public class TarHeader {

	/*
	 * Header
	 */
	public static final int NAMELEN = 100;
	public static final int MODELEN = 8;
	public static final int UIDLEN = 8;
	public static final int GIDLEN = 8;
	public static final int SIZELEN = 12;
	public static final int MODTIMELEN = 12;
	public static final int CHKSUMLEN = 8;
	public static final byte LF_OLDNORM = 0;

	/*
	 * File Types
	 */
	public static final byte LF_NORMAL = (byte) '0';
	public static final byte LF_LINK = (byte) '1';
	public static final byte LF_SYMLINK = (byte) '2';
	public static final byte LF_CHR = (byte) '3';
	public static final byte LF_BLK = (byte) '4';
	public static final byte LF_DIR = (byte) '5';
	public static final byte LF_FIFO = (byte) '6';
	public static final byte LF_CONTIG = (byte) '7';

	/*
	 * Ustar header
	 */

	public static final String USTAR_MAGIC = "ustar"; // POSIX

	public static final int USTAR_MAGICLEN = 8;
	public static final int USTAR_USER_NAMELEN = 32;
	public static final int USTAR_GROUP_NAMELEN = 32;
	public static final int USTAR_DEVLEN = 8;
	public static final int USTAR_FILENAME_PREFIX = 155;

	// Header values
	public StringBuffer name;
	public int mode;
	public int userId;
	public int groupId;
	public long size;
	public long modTime;
	public int checkSum;
	public byte linkFlag;
	public StringBuffer linkName;
	public StringBuffer magic; // ustar indicator and version
	public StringBuffer userName;
	public StringBuffer groupName;
	public int devMajor;
	public int devMinor;
	public StringBuffer namePrefix;

	public TarHeader() {
		this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);

		this.name = new StringBuffer();
		this.linkName = new StringBuffer();

		String user = System.getProperty("user.name", "");

		if (user.length() > 31)
			user = user.substring(0, 31);

		this.userId = 0;
		this.groupId = 0;
		this.userName = new StringBuffer(user);
		this.groupName = new StringBuffer("");
		this.namePrefix = new StringBuffer();
	}

	/**
	 * Parse an entry name from a header buffer.
	 * 
	 * @param header
	 *            The header buffer from which to parse.
	 * @param offset
	 *            The offset into the buffer from which to parse.
	 * @param length
	 *            The number of header bytes to parse.
	 * @return The header's entry name.
	 */
	public static StringBuffer parseName(byte[] header, int offset, int length) {
		StringBuffer result = new StringBuffer(length);

		int end = offset + length;
		for (int i = offset; i < end; ++i) {
			if (header[i] == 0)
				break;
			result.append((char) header[i]);
		}

		return result;
	}

	/**
	 * Determine the number of bytes in an entry name.
	 * 
	 * @param name
	 *            The header buffer from which to parse.
	 * @param offset
	 *            The offset into the buffer from which to parse.
	 * @param length
	 *            The number of header bytes to parse.
	 * @return The number of bytes in a header's entry name.
	 */
	public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
		int i;

		for (i = 0; i < length && i < name.length(); ++i) {
			buf[offset + i] = (byte) name.charAt(i);
		}

		for (; i < length; ++i) {
			buf[offset + i] = 0;
		}

		return offset + length;
	}

	/**
	 * Creates a new header for a file/directory entry.
	 * 
	 * 
	 * @param entryName
	 *            File name
	 * @param size
	 *            File size in bytes
	 * @param modTime
	 *            Last modification time in numeric Unix time format
	 * @param dir
	 *            Is directory
	 */
	public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir, int permissions) {
		String name = entryName;
		name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');

		TarHeader header = new TarHeader();
		header.linkName = new StringBuffer("");
		header.mode = permissions;

		if (name.length() > 100) {
			header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
			header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
		} else {
			header.name = new StringBuffer(name);
		}
		if (dir) {
			header.linkFlag = TarHeader.LF_DIR;
			if (header.name.charAt(header.name.length() - 1) != '/') {
				header.name.append("/");
			}
			header.size = 0;
		} else {
			header.linkFlag = TarHeader.LF_NORMAL;
			header.size = size;
		}

		header.modTime = modTime;
		header.checkSum = 0;
		header.devMajor = 0;
		header.devMinor = 0;

		return header;
	}
}