/*
 * Copyright 2015 Marvin Ramin
 *
 * 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.mtramin.rxfingerprint;

import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.support.annotation.Nullable;

import com.mtramin.rxfingerprint.data.FingerprintDecryptionResult;
import com.mtramin.rxfingerprint.data.FingerprintEncryptionResult;
import com.mtramin.rxfingerprint.data.FingerprintResult;

import javax.crypto.Cipher;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;

/**
 * Decrypts data with fingerprint authentication. Initializes a {@link Cipher} for decryption which
 * can only be used with fingerprint authentication and uses it once authentication was successful
 * to encrypt the given data.
 * <p/>
 * The date handed in must be previously encrypted by a {@link AesEncryptionObservable}.
 */
@SuppressLint("NewApi") // SDK check happens in {@link FingerprintObservable#subscribe}
class AesDecryptionObservable extends FingerprintObservable<FingerprintDecryptionResult> {

	private final AesCipherProvider cipherProvider;
	private final String encryptedString;
	private final EncodingProvider encodingProvider;

	/**
	 * Creates a new AesEncryptionObservable that will listen to fingerprint authentication
	 * to encrypt the given data.
	 *
	 * @param context   context to use
	 * @param keyName   keyName to use for the decryption
	 * @param encrypted data to encrypt  @return Observable {@link FingerprintEncryptionResult}
	 * @return Observable result of the decryption
	 */
	static Observable<FingerprintDecryptionResult> create(Context context, String keyName, String encrypted) {
		try {
			return Observable.create(new AesDecryptionObservable(new FingerprintApiWrapper(context),
					new AesCipherProvider(context, keyName),
					encrypted,
					new Base64Provider()));
		} catch (Exception e) {
			return Observable.error(e);
		}
	}

	private AesDecryptionObservable(FingerprintApiWrapper fingerprintApiWrapper,
									AesCipherProvider cipherProvider,
									String encrypted,
									EncodingProvider encodingProvider) {
		super(fingerprintApiWrapper);
		this.cipherProvider = cipherProvider;
		encryptedString = encrypted;
		this.encodingProvider = encodingProvider;
	}

	@Nullable
	@Override
	protected CryptoObject initCryptoObject(ObservableEmitter<FingerprintDecryptionResult> subscriber) {
		try {
			CryptoData cryptoData = CryptoData.fromString(encodingProvider, encryptedString);
			Cipher cipher = cipherProvider.getCipherForDecryption(cryptoData.getIv());
			return new CryptoObject(cipher);
		} catch (Exception e) {
			subscriber.onError(e);
			return null;
		}
	}

	@Override
	protected void onAuthenticationSucceeded(ObservableEmitter<FingerprintDecryptionResult> emitter, AuthenticationResult result) {
		try {
			CryptoData cryptoData = CryptoData.fromString(encodingProvider, encryptedString);
			Cipher cipher = result.getCryptoObject().getCipher();
			byte[] bytes = cipher.doFinal(cryptoData.getMessage());

			emitter.onNext(new FingerprintDecryptionResult(FingerprintResult.AUTHENTICATED, null, ConversionUtils.toChars(bytes)));
			emitter.onComplete();
		} catch (Exception e) {
			emitter.onError(cipherProvider.mapCipherFinalOperationException(e));
		}

	}

	@Override
	protected void onAuthenticationHelp(ObservableEmitter<FingerprintDecryptionResult> emitter, int helpMessageId, String helpString) {
		emitter.onNext(new FingerprintDecryptionResult(FingerprintResult.HELP, helpString, null));
	}

	@Override
	protected void onAuthenticationFailed(ObservableEmitter<FingerprintDecryptionResult> emitter) {
		emitter.onNext(new FingerprintDecryptionResult(FingerprintResult.FAILED, null, null));
	}
}