/*
* Copyright 2010 Srikanth Reddy Lingala  
* 
* 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 com.zip4j.crypto;

import com.zip4j.crypto.engine.ZipCryptoEngine;

import java.util.Random;

import com.zip4j.exception.ZipException;
import com.zip4j.util.InternalZipConstants;

public class StandardEncrypter implements IEncrypter {
	
	private ZipCryptoEngine zipCryptoEngine;
	private byte[] headerBytes;
	
	public StandardEncrypter(char[] password, int crc) throws ZipException {
		if (password == null || password.length <= 0) {
			throw new ZipException("input password is null or empty in standard encrpyter constructor");
		}
		
		this.zipCryptoEngine = new ZipCryptoEngine();
		
		this.headerBytes = new byte[InternalZipConstants.STD_DEC_HDR_SIZE];
		init(password, crc);
	}
	
	private void init(char[] password, int crc) throws ZipException {
		if (password == null || password.length <= 0) {
			throw new ZipException("input password is null or empty, cannot initialize standard encrypter");
		}
		zipCryptoEngine.initKeys(password);
		headerBytes = generateRandomBytes(InternalZipConstants.STD_DEC_HDR_SIZE);
		// Initialize again since the generated bytes were encrypted.
		zipCryptoEngine.initKeys(password);
		
		headerBytes[InternalZipConstants.STD_DEC_HDR_SIZE - 1] = (byte)((crc >>> 24));
		headerBytes[InternalZipConstants.STD_DEC_HDR_SIZE - 2] = (byte)((crc >>> 16));
		
		if (headerBytes.length < InternalZipConstants.STD_DEC_HDR_SIZE) {
			throw new ZipException("invalid header bytes generated, cannot perform standard encryption");
		}
		
		encryptData(headerBytes);
	}
	
	public int encryptData(byte[] buff) throws ZipException {
		if (buff == null) {
			throw new NullPointerException();
		}
		return encryptData(buff, 0, buff.length);
	}
	
	public int encryptData(byte[] buff, int start, int len) throws ZipException {
		
		if (len < 0) {
			throw new ZipException("invalid length specified to decrpyt data");
		}
		
		try {
			for (int i = start; i <  start + len; i++) {
				buff[i] = encryptByte(buff[i]);
			}
			return len;
		} catch (Exception e) {
			throw new ZipException(e);
		}
	}
	
	protected byte encryptByte(byte val) {
		byte temp_val = (byte) (val ^ zipCryptoEngine.decryptByte() & 0xff);
		zipCryptoEngine.updateKeys(val);
		return temp_val;
	}
	
	protected byte[] generateRandomBytes(int size) throws ZipException {
		
		if (size <= 0) {
			throw new ZipException("size is either 0 or less than 0, cannot generate header for standard encryptor");
		}
		
		byte[] buff = new byte[size];
		
		Random rand = new Random();
		
		for (int i = 0; i < buff.length; i ++) {
			// Encrypted to get less predictability for poorly implemented
			// rand functions.
			buff[i] = encryptByte((byte) rand.nextInt(256));
		}
		
//		buff[0] = (byte)87;
//		buff[1] = (byte)176;
//		buff[2] = (byte)-49;
//		buff[3] = (byte)-43;
//		buff[4] = (byte)93;
//		buff[5] = (byte)-204;
//		buff[6] = (byte)-105;
//		buff[7] = (byte)213;
//		buff[8] = (byte)-80;
//		buff[9] = (byte)-8;
//		buff[10] = (byte)21;
//		buff[11] = (byte)242;
		
//		for( int j=0; j<2; j++ ) {
//			Random rand = new Random();
//			int i = rand.nextInt();
//			buff[0+j*4] = (byte)(i>>24);
//			buff[1+j*4] = (byte)(i>>16);
//			buff[2+j*4] = (byte)(i>>8);
//			buff[3+j*4] = (byte)i;
//		}
		return buff;
	}

	public byte[] getHeaderBytes() {
		return headerBytes;
	}

}
