/*
 *  Freeplane - mind map editor
 *  Copyright (C) 2008 Dimitry Polivaev
 *
 *  This file author is Dimitry Polivaev
 *
 *  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 org.freeplane.features.encrypt;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import org.freeplane.core.util.LogUtils;
import org.freeplane.features.map.IEncrypter;

/**
 * @author Dimitry Polivaev
 * 29.12.2008
 */
public class DesEncrypter implements IEncrypter {
	private static final int SALT_LENGTH = 8;
	private static final String SALT_PRESENT_INDICATOR = " ";

	/**
	 * @throws IOException
	 */
	public static byte[] fromBase64(final String base64String) {
		return Base64Coding.decode64(base64String);
	}

	/**
	 */
	public static String toBase64(final byte[] byteBuffer) {
		return Base64Coding.encode64(byteBuffer);
	}

	private Cipher dcipher;
	private Cipher ecipher;
	int iterationCount = 19;
	final private String mAlgorithm;
	byte[] mSalt = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3,
	        (byte) 0x03 };
	private char[] passPhrase;

	public DesEncrypter(final StringBuilder pPassPhrase, final String pAlgorithm) {
		passPhrase = new char[pPassPhrase.length()];
		pPassPhrase.getChars(0, passPhrase.length, passPhrase, 0);
		mAlgorithm = pAlgorithm;
	}

	public String decrypt(String str) {
		if (str == null) {
			return null;
		}
		try {
			byte[] salt = null;
			final int indexOfSaltIndicator = str.indexOf(DesEncrypter.SALT_PRESENT_INDICATOR);
			if (indexOfSaltIndicator >= 0) {
				final String saltString = str.substring(0, indexOfSaltIndicator);
				str = str.substring(indexOfSaltIndicator + 1);
				salt = DesEncrypter.fromBase64(saltString);
			}
			final byte[] dec = DesEncrypter.fromBase64(str);
			init(salt);
			if (dcipher == null) {
				return null;
			}
			final byte[] utf8 = dcipher.doFinal(dec);
			return new String(utf8, "UTF8");
		}
		catch (final javax.crypto.BadPaddingException e) {
		}
		catch (final IllegalBlockSizeException e) {
		}
		catch (final UnsupportedEncodingException e) {
		}
		catch (final IllegalArgumentException e) {
		}
		return null;
	}

	public String encrypt(final String str) {
		try {
			initWithNewSalt();
			if(ecipher == null)
				return null;
			final byte[] utf8 = str.getBytes("UTF8");
			final byte[] enc = ecipher.doFinal(utf8);
			return DesEncrypter.toBase64(mSalt) + DesEncrypter.SALT_PRESENT_INDICATOR + DesEncrypter.toBase64(enc);
		}
		catch (final javax.crypto.BadPaddingException e) {
		}
		catch (final IllegalBlockSizeException e) {
		}
		catch (final UnsupportedEncodingException e) {
		}
		return null;
	}

	public void initWithNewSalt() {
	    final byte[] newSalt = new byte[DesEncrypter.SALT_LENGTH];
	    for (int i = 0; i < newSalt.length; i++) {
	    	newSalt[i] = (byte) (Math.random() * 256l - 128l);
	    }
	    init(newSalt);
    }

	/**
	 */
	private void init(final byte[] salt) {
		if (ecipher != null && mSalt != null && !Arrays.equals(mSalt, salt)) {
			ecipher = null;
			dcipher = null;
		}
		if (salt != null) {
			mSalt = salt;
		}
		if (ecipher == null) {
			try {
				SecretKey key;
				try{
					KeySpec keySpec = new PBEKeySpec(passPhrase, mSalt, iterationCount);
					key = SecretKeyFactory.getInstance(mAlgorithm).generateSecret(keySpec);
				}
				catch (final java.security.spec.InvalidKeySpecException e) {
					try {
	                    passPhrase = URLEncoder.encode(new String(passPhrase),  "UTF-8").toCharArray();
                    }
                    catch (UnsupportedEncodingException e1) {
                    	throw e;
                    }
					KeySpec keySpec = new PBEKeySpec(passPhrase, mSalt, iterationCount);
					key = SecretKeyFactory.getInstance(mAlgorithm).generateSecret(keySpec);
				}
				ecipher = Cipher.getInstance(mAlgorithm);
				dcipher = Cipher.getInstance(mAlgorithm);
				final AlgorithmParameterSpec paramSpec = new PBEParameterSpec(mSalt, iterationCount);
				ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
				dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
			}
			catch (final java.security.InvalidAlgorithmParameterException e) {
				LogUtils.severe(e);
			}
			catch (final java.security.spec.InvalidKeySpecException e) {
				LogUtils.severe(e);
			}
			catch (final javax.crypto.NoSuchPaddingException e) {
				LogUtils.severe(e);
			}
			catch (final java.security.NoSuchAlgorithmException e) {
				LogUtils.severe(e);
			}
			catch (final java.security.InvalidKeyException e) {
				LogUtils.severe(e);
			}
		}
	}
}
