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

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;

/**
 * VanDyke 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[] publicKeyData;
    private final byte[] privateKeyData;
    private final byte[] mac;

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

        this.algorithm = algorithm;
        this.encryptionAlgorithm = encryptionAlgorithm;
        this.comments = new HashMap(comments);
        this.publicKeyData = (byte[]) publicKeyData.clone();
        this.privateKeyData = (byte[]) privateKeyData.clone();
        this.mac = (byte[]) mac.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 public key data.
     *
     * @return public key data
     */
    public byte[] getPublicKeyData() {
        return (byte[]) this.publicKeyData.clone();
    }

    /**
     * Returns private key data.
     *
     * @return private key data
     */
    public byte[] getPrivateKeyData() {
        return (byte[]) this.privateKeyData.clone();
    }


    /**
     * Returns MAC value.
     *
     * @return MAC value
     */
    public byte[] getMac() {
        return (byte[]) mac.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.privateKeyData);

            return new Record(this.algorithm, NO_ENCRYPTION, this.comments, this.publicKeyData, decryptedData, this.mac); // todo reciompute mac
        } 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.privateKeyData);

            return new Record(this.algorithm, encryptionAlgorithm, this.comments, this.publicKeyData, encryptedData, this.mac); // todo reciompute mac
        } catch (Exception e) {
            throw new FormatException(e.getMessage());
        }
    }

}
