package de.idyl.crypto.zip.impl;

import java.util.Calendar;
import java.util.Date;
import java.util.zip.ZipEntry;

/**
 * information about one zip entry that is written to an encrypted zip archive
 * or read from one
 * 
 * @author olaf@merkert.de
 */
public class ExtZipEntry extends ZipEntry {

	private CentralDirectoryEntry centralDirectoryEntry;
	
	/** empty instance with only a name */
	public ExtZipEntry(String name) {
		super(name);
	}

	/** copy all "non-compression" attributes */
	public ExtZipEntry(ExtZipEntry entry) {
		super(entry.getName());
		setCompressedSize(entry.getCompressedSize());
		setSize(entry.getSize());
		setComment(entry.getComment());
		setTime(entry.getTime());
		setMethod(entry.getMethod());
	}

	public ExtZipEntry(String name,CentralDirectoryEntry centralDirectoryEntry) {
		super(name);
		this.centralDirectoryEntry = centralDirectoryEntry;
	}
	
	public void initEncryptedEntry() {
		setCrc(0); // CRC-32 / for encrypted files it's 0 as AES/MAC checks integritiy

		this.flag |= 1; // bit0 - encrypted
		// flag |= 8; // bit3 - use data descriptor

		this.primaryCompressionMethod = 0x63;

		byte[] extraBytes = new byte[11];
		extraBytes = new byte[11];

		// extra data header ID for AES encryption is 0x9901
		extraBytes[0] = 0x01;
		extraBytes[1] = (byte)0x99;

		// data size (currently 7, but subject to possible increase in the
		// future)
		extraBytes[2] = 0x07; // data size
		extraBytes[3] = 0x00; // data size

		// Integer version number specific to the zip vendor
		extraBytes[4] = 0x02; // version number
		extraBytes[5] = 0x00; // version number

		// 2-character vendor ID
		extraBytes[6] = 0x41; // vendor id
		extraBytes[7] = 0x45; // vendor id

		// AES encryption strength - 1=128, 2=192, 3=256
		extraBytes[8] = 0x03;

		// actual compression method - 0x0000==stored (no compression) - 2 bytes
		extraBytes[9] = (byte) (getMethod() & 0xff);
		extraBytes[10] = (byte) ((getMethod() & 0xff00) >> 8);

		setExtra(extraBytes);
	}

	protected int flag;

	public int getFlag() {
		return this.flag;
	}

	public boolean isAesEncrypted() {
		return isEncrypted() && centralDirectoryEntry!=null && centralDirectoryEntry.isAesEncrypted();
	}
	
	public boolean isEncrypted() {
		return (flag & 1) > 0;
	}

	protected int offset;

	public int getOffset() {
		return offset;
	}

	public void setOffset(int offset) {
		this.offset = offset;
	}

	// 0x63 for encryption
	protected int primaryCompressionMethod;

	public int getPrimaryCompressionMethod() {
		return primaryCompressionMethod;
	}

	public void setPrimaryCompressionMethod(int primaryCompressionMethod) {
		this.primaryCompressionMethod = primaryCompressionMethod;
	}

	/**
	 * Encrypted files: Note that the value in the "compressed size" fields of
	 * the local file header and the central directory entry is the total size
	 * of all the items listed above. In other words, it is the total size of
	 * the salt value, password verification value, encrypted data, and
	 * authentication code.
	 * 
	 * @return data size only
	 */
	public long getEncryptedDataSize() {
		// authentication (10), salt (16), verification (2)
		return getCompressedSize() - 10 - 16 - 2;
	}

	public CentralDirectoryEntry getCentralDirectoryEntry() {
		return centralDirectoryEntry;
	}

	// --------------------------------------------------------------------------

	/**
	 * ZipEntry (my superclass) uses dosTime internally. On getTime() you get a
	 * java time based long value. This method provides the DOS value that is
	 * stored in the zip file.
	 */
	public long getDosTime() {
		return javaToDosTime(getTime());
	}

	public static long javaToDosTime(long javaTime) {
		Date d = new Date(javaTime);
		Calendar ca = Calendar.getInstance();
		ca.setTime(d);
		int year = ca.get(Calendar.YEAR);
		if (year < 1980) {
			return (1 << 21) | (1 << 16);
		}
		return (year - 1980) << 25 | (ca.get(Calendar.MONTH) + 1) << 21
				| ca.get(Calendar.DAY_OF_MONTH) << 16
				| ca.get(Calendar.HOUR_OF_DAY) << 11
				| ca.get(Calendar.MINUTE) << 5 | ca.get(Calendar.SECOND) >> 1;
	}

	public static long dosToJavaTime(long dosTime) {
		Calendar ca = Calendar.getInstance();
		ca.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
		ca.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
		ca.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
		ca.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
		ca.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
		ca.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
		return ca.getTime().getTime();
	}

}
