/*******************************************************************************
 *  Copyright 2017-2018 Centre for Development of Advanced Computing(C-DAC), Pune
 *  
 * 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 cdac.medinfo.sdk.dcm30_04.services.basicapplicationlevelconfidentialityprofile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomStream;
import cdac.medinfo.sdk.dcm30_04.dicomstreams.DicomBufferStream;
import cdac.medinfo.sdk.dcm30_04.dicomstreams.DicomStream;

class DecryptAgent 
{
	int iBlockSize = 128;

	/*
	 * This method decrypts the Data Elements present in the Dicom Stream with the enumProfileAlgorithm and the objKey Speciifed.
	 */
	IDicomStream decrypt(DicomStream objDicomStream, EnumProfileAlgorithm enumProfileAlgorithm, byte[] objKey, byte[] objIV) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException
	{
		/*
		 * Created Block Size of 128 bytes to read the Data.
		 */
		byte[] blockData = new byte[iBlockSize];
		ByteArrayOutputStream objByteArrayOutputStream = new ByteArrayOutputStream();
		Cipher rsaCipher = null;
		SecretKeySpec objSecretKeySpec = null;
		IvParameterSpec  objIVSpec = null;

		if(objIV != null && objIV.length !=0)
			objIVSpec = new IvParameterSpec(objIV); 

		/*
		 * Initializes the Cipher bases on the Profile Algorithm specified
		 */
		if(enumProfileAlgorithm == EnumProfileAlgorithm.DES)
		{
			objSecretKeySpec = new SecretKeySpec(objKey, EnumProfileAlgorithm.DES.getValue());
			rsaCipher = Cipher.getInstance(EnumProfileAlgorithm.DES.getValue());
		}
		else if(enumProfileAlgorithm == EnumProfileAlgorithm.AES)
		{
			objSecretKeySpec = new SecretKeySpec(objKey, EnumProfileAlgorithm.AES.getValue());
			rsaCipher = Cipher.getInstance(EnumProfileAlgorithm.AES.getValue());
		}
		else if(enumProfileAlgorithm == EnumProfileAlgorithm.TRIPPLE_DES)
		{
			objSecretKeySpec = new SecretKeySpec(objKey, EnumProfileAlgorithm.TRIPPLE_DES.getValue());
			rsaCipher = Cipher.getInstance(EnumProfileAlgorithm.TRIPPLE_DES.getValue());
		}

		rsaCipher.init(Cipher.DECRYPT_MODE, objSecretKeySpec, objIVSpec);

		DicomBufferStream objDicomStreamDecrypted = new DicomBufferStream();
		objDicomStreamDecrypted.setEndianNess(objDicomStream.getEndianNess());
		objDicomStreamDecrypted.setOutputStream(objByteArrayOutputStream);

		while(objDicomStream.available() != 0 && objDicomStream.available() >=128)
		{
			byte[] arrEncryptedData = new byte[blockData.length];
			objDicomStream.readBuffer(arrEncryptedData);
			byte[] arrOriginalData = rsaCipher.update(arrEncryptedData);
			objDicomStreamDecrypted.writeBuffer(arrOriginalData, 0, arrOriginalData.length);
		}
		/*
		 * if Data available on the stream is less than the Block size (128 is used here)
		 * then read only the data available on the stream and then decrypt and then
		 * serialize it to the byte output stream.
		 * If this case is not taken then garbage data is also written to the file.
		 */
		if(objDicomStream.available() <128)
		{
			byte[] arrEncryptedData = new byte[objDicomStream.available()];
			objDicomStream.readBuffer(arrEncryptedData);
			byte[] arrOriginalData = rsaCipher.doFinal(arrEncryptedData);
			objDicomStreamDecrypted.writeBuffer(arrOriginalData, 0, arrOriginalData.length);
		}

		/*
		 * Set Byte Array Output Stream to the Dicom Buffer Stream and return this.
		 */
		return objDicomStreamDecrypted;
	}
}
