/*
 * Class: Record
 *
 * Author: Alex Usun
 *
 * Date: 31/8/2005
 */
package com.jscape.inet.ssh.util.keyreader.openssh;

import com.jscape.inet.ssh.util.keyreader.CipherFactory;
import com.jscape.inet.ssh.util.keyreader.FormatException;
import com.jscape.inet.ssh.util.keyreader.IKeyFactory;
import com.jscape.util.Assert;

import javax.crypto.Cipher;

/**
 * OpenSSH key pair record.
 *
 * @author Alex Usun
 * @version 1.0
 * @see <a href="Record.java.html">Source code</a>
 */
public class Record {

    public static final String NO_ENCRYPTION = "none";

    private final String algorithm;
    private final String procType;
    private final String encryptionAlgorithm;
    private final byte[] iv;
    private final byte[] keyPairData;

    /**
     * Creates a new record instance.
     *
     * @param algorithm           key pair algorithm
     * @param procType            procedure type
     * @param encryptionAlgorithm encryption algorithm
     * @param iv                  IV
     * @param keyPairData         key pair data
     */
    public Record(String algorithm, String procType, String encryptionAlgorithm, byte[] iv, byte[] keyPairData) {
        Assert.isValidString(algorithm);
        Assert.notNull(procType);
        Assert.isValidString(encryptionAlgorithm);
        Assert.notNull(iv);
        Assert.notNull(keyPairData);

        this.algorithm = algorithm;
        this.procType = procType;
        this.encryptionAlgorithm = encryptionAlgorithm;
        this.iv = (byte[]) iv.clone();
        this.keyPairData = (byte[]) keyPairData.clone();
    }

    /**
     * Returns key pair algorithm name.
     *
     * @return key pair algorithm name
     */
    public String getAlgorithm() {
        return this.algorithm;
    }

    /**
     * Returns record proc type.
     *
     * @return record proc type
     */
    public String getProcType() {
        return this.procType;
    }

    /**
     * Checks if the current record is encrypted.
     *
     * @return <code>true</code> if record is encrypted; <code>false</code> otherwise
     */
    public boolean isEncrypted() {
        return !this.encryptionAlgorithm.equalsIgnoreCase(NO_ENCRYPTION);
    }

    /**
     * Returns encryption algorithm name.
     *
     * @return encryption algorithm name
     */
    public String getEncryptionAlgorithm() {
        return this.encryptionAlgorithm;
    }

    /**
     * Returns encryption IV.
     *
     * @return encryption IV
     */
    public byte[] getIv() {
        return (byte[]) this.iv.clone();
    }

    /**
     * Returns key pair data.
     *
     * @return key pair data
     */
    public byte[] getKeyPairData() {
        return (byte[]) this.keyPairData.clone();
    }

    /**
     * Decrypts current record.
     *
     * @param cipherFactory cipher factory
     * @param keyFactory    key factory
     *
     * @return decrypted record
     *
     * @throws FormatException if decryption error occurs
     */
    public Record decrypt(CipherFactory cipherFactory, IKeyFactory keyFactory)
            throws FormatException {
        Assert.notNull(cipherFactory);
        Assert.notNull(keyFactory);

        if (!isEncrypted()) {
            return this;
        }

        try {
            Cipher cipher = cipherFactory.createDecipher(this.encryptionAlgorithm, keyFactory);
            byte[] decryptedData = cipher.doFinal(this.keyPairData);

            return new Record(this.algorithm, this.procType, NO_ENCRYPTION, this.iv, decryptedData);
        } catch (Exception e) {
            throw new FormatException(e.getMessage());
        }
    }

    /**
     * Encrypt current record.
     *
     * @param encryptionAlgorithm encryption algorithm
     * @param cipherFactory       cipher factory
     * @param keyFactory          key factory
     *
     * @return encrypted record
     *
     * @throws FormatException if encryption error occurs
     */
    public Record encrypt(String encryptionAlgorithm, CipherFactory cipherFactory, IKeyFactory keyFactory)
            throws FormatException {
        Assert.isValidString(encryptionAlgorithm);
        Assert.notNull(cipherFactory);
        Assert.notNull(keyFactory);

        try {
            Cipher cipher = cipherFactory.createEncipher(encryptionAlgorithm, keyFactory);
            byte[] encryptedData = cipher.doFinal(this.keyPairData);

            return new Record(this.algorithm, this.procType, encryptionAlgorithm, this.iv, encryptedData);
        } catch (Exception e) {
            throw new FormatException(e.getMessage());
        }

    }

    /**
     * @see Object#toString()
     */
    public String toString() {
        return (this.algorithm + " key pair, " + (isEncrypted() ? "encrypted" : "plain"));
    }

}
