/*
* Copyright (c) 2015 Cossack Labs Limited
*
* 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.cossacklabs.themis;

/**
 * Themis secure message
 */
public class SecureMessage {
	
	static {
		System.loadLibrary("themis_jni");
	}
	
	PrivateKey privateKey;
	PublicKey peerPublicKey;
	
	/**
	 * Creates new SecureMessage with specified PrivateKey
	 * @param privateKey your own PrivateKey
	 * @throws NullArgumentException when privateKey is null
	 */
	public SecureMessage(PrivateKey privateKey) {

		if (null == privateKey) {
			throw new NullArgumentException("Private key was not provided");
		}
		
		this.privateKey = privateKey;
	}

	/**
	 * Creates new SecureMessage with default peer PublicKey (can be used only for signature verification)
	 * @param peerPublicKey peer PublicKey
	 * @throws NullArgumentException when peerPublicKey is null
	 */
	public SecureMessage(PublicKey peerPublicKey) {

		if (null == peerPublicKey) {
			throw new NullArgumentException("Peer public key was not provided");
		}
		
		this.peerPublicKey = peerPublicKey;
	}
	
	/**
	 * Creates new SecureMessage with specified PrivateKey and default peer PublicKey
	 * @param privateKey your own PrivateKey
	 * @param peerPublicKey peer PublicKey
	 * @throws NullArgumentException when privateKey or peerPublicKey is null
	 */
	public SecureMessage(PrivateKey privateKey, PublicKey peerPublicKey) {

		if (null == privateKey) {
			throw new NullArgumentException("Private key was not provided");
		}
		
		if (null == peerPublicKey) {
			throw new NullArgumentException("Peer public key was not provided");
		}
		
		this.privateKey = privateKey;
		this.peerPublicKey = peerPublicKey;
	}

	static final int SECURE_MESSAGE_ENCRYPT = 1;
	static final int SECURE_MESSAGE_DECRYPT = 2;
	static final int SECURE_MESSAGE_SIGN    = 3;
	static final int SECURE_MESSAGE_VERIFY  = 4;
	static native byte[] process(byte[] privateKey, byte[] publicKey, byte[] message, int action);

	/**
	 * Wraps message for peer
	 * @param message message to wrap
	 * @param peerPublicKey receiver's PublicKey
	 * @return wrapped message
	 * @throws NullArgumentException when message or peerPublicKey is null
	 * @throws SecureMessageWrapException when cannot wrap message
	 */
	public byte[] wrap(byte[] message, PublicKey peerPublicKey) throws SecureMessageWrapException {

		if (null == peerPublicKey) {
			throw new NullArgumentException("Peer public key was not provided");
		}
		
		if (null == message) {
			throw new NullArgumentException("No message was provided");
		}
		
		byte[] wrappedMessage = process(this.privateKey.toByteArray(), peerPublicKey.toByteArray(), message, SECURE_MESSAGE_ENCRYPT);
		
		if (null == wrappedMessage) {
			throw new SecureMessageWrapException();
		}
		
		return wrappedMessage;
	}
	
	/**
	 * Wraps message for default peer
	 * @param message message to wrap
	 * @return wrapped message
	 * @throws NullArgumentException when message or default peer PublicKey is null
	 * @throws SecureMessageWrapException when cannot wrap message
	 */
	public byte[] wrap(byte[] message) throws SecureMessageWrapException {
		return wrap(message, this.peerPublicKey);
	}
	
	/**
	 * Unwraps message from peer
	 * @param message wrapped message
	 * @param peerPublicKey sender's PublicKey
	 * @return unwrapped message
	 * @throws NullArgumentException when message or peerPublicKey is null
	 * @throws SecureMessageWrapException when cannot unwrap message
	 */
	public byte[] unwrap(byte[] message, PublicKey peerPublicKey) throws SecureMessageWrapException {

		if (null == peerPublicKey) {
			throw new NullArgumentException("Peer public key was not provided");
		}
		
		if (null == message) {
			throw new NullArgumentException("No message was provided");
		}
		
		byte[] unwrappedMessage = process(this.privateKey.toByteArray(), peerPublicKey.toByteArray(), message, SECURE_MESSAGE_DECRYPT);
		
		if (null == unwrappedMessage) {
			throw new SecureMessageWrapException();
		}
		
		return unwrappedMessage;
	}
	
	/**
	 * Unwraps message from default peer
	 * @param message wrapped message
	 * @return unwrapped message
	 * @throws NullArgumentException when message or default peer PublicKey is null
	 * @throws SecureMessageWrapException when cannot unwrap message
	 */
	public byte[] unwrap(byte[] message) throws SecureMessageWrapException {
		return unwrap(message, this.peerPublicKey);
	}

	/**
	 * Signs message
	 * @param message message to sign
	 * @return signed message
	 * @throws NullArgumentException when message or default peer PublicKey is null
	 * @throws SecureMessageWrapException when cannot wrap message
	 */
	public byte[] sign(byte[] message) throws SecureMessageWrapException {

		if (null == privateKey) {
			throw new NullArgumentException("Private key was not provided");
		}

		if (null == message) {
			throw new NullArgumentException("No message was provided");
		}

		byte[] signedMessage = process(this.privateKey.toByteArray(), null, message, SECURE_MESSAGE_SIGN);
		
		if (null == signedMessage) {
			throw new SecureMessageWrapException();
		}
		
		return signedMessage;
	}

	/**
	 * Verifies signed message from peer
	 * @param message signed message
	 * @param peerPublicKey sender's PublicKey
	 * @return verified message
	 * @throws NullArgumentException when message or peerPublicKey is null
	 * @throws SecureMessageWrapException when cannot verify message
	 */
	public byte[] verify(byte[] message, PublicKey peerPublicKey) throws SecureMessageWrapException {

		if (null == peerPublicKey) {
			throw new NullArgumentException("Peer public key was not provided");
		}

		if (null == message) {
			throw new NullArgumentException("No message was provided");
		}

		byte[] verifiedMessage = process(null, peerPublicKey.toByteArray(), message, SECURE_MESSAGE_VERIFY);

		if (null == verifiedMessage) {
			throw new SecureMessageWrapException();
		}

		return verifiedMessage;
	}

	/**
	 * Verifies message from default peer
	 * @param message signed message
	 * @return verified message
	 * @throws NullArgumentException when message or default peer PublicKey is null
	 * @throws SecureMessageWrapException when cannot verify message
	 */
	public byte[] verify(byte[] message) throws SecureMessageWrapException {
		return verify(message, this.peerPublicKey);
	}
}
