/*
 * Class: Record
 *
 * Author: Alex Usun
 *
 * Date: 1/9/2005
 */
package com.jscape.inet.ssh.util.keyreader.sshcom;

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;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * SSH.COM key pair record.
 *
 * @author Alex Usun
 */
@SuppressWarnings("unchecked")
public class Record {

    public static final String NO_ENCRYPTION = "none";

    private final String algorithm;
    private final String encryptionAlgorithm;
    private final Map comments;
    private final byte[] keyPairData;

    /**
     * Creates a new record instance.
     *
     * @param algorithm           key pair algorithm
     * @param encryptionAlgorithm encryption algorithm
     * @param comments            comments
     * @param keyPairData         key pair data
     */
    public Record(String algorithm, String encryptionAlgorithm, Map comments, byte[] keyPairData) {
        Assert.notNull(algorithm);
        Assert.notNull(encryptionAlgorithm);
        Assert.notNull(comments);
        Assert.notNull(keyPairData);

        this.algorithm = algorithm;
        this.encryptionAlgorithm = encryptionAlgorithm;
        this.comments = new HashMap(comments);
        this.keyPairData = (byte[]) keyPairData.clone();
    }

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

    /**
     * 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 record comment.
     *
     * @param name comment name
     *
     * @return comment value
     */
    public String getComment(String name) {
        return (String) this.comments.get(name);
    }

    /**
     * Returns comments names iterator.
     *
     * @return comments names iterator
     */
    public Iterator comments() {
        return this.comments.keySet().iterator();
    }

    /**
     * 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 com.jscape.inet.ssh.util.keyreader.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, NO_ENCRYPTION, this.comments, 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, encryptionAlgorithm, this.comments, encryptedData);
        } catch (Exception e) {
            throw new FormatException(e.getMessage());
        }

    }

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

}
