package com.jscape.inet.ssh.protocol.v2.marshaling.algorithms;

import com.jscape.util.crypto.CryptoServicesProvider;

import java.security.Provider;

/**
 * JCE-based encryption algorithms.
 *
 * @author Alex Usun
 */
public class JceEncryptions {
    
    public static final Entry NONE = new Entry("none", "", null, 8, 1) {
    
        @Override
        public JceEncryptionFactory update(JceEncryptionFactory factory, String provider) {
            return factory.set(NONE);
        }
    
        @Override
        public JceEncryptionFactory update(JceEncryptionFactory factory, Provider provider) {
            return factory.set(NONE);
        }
    };

    public static final Entry THREE_DES_CBC = new Entry("3des-cbc", "DESede/CBC/NoPadding", CryptoServicesProvider.instance(), 8, 24);
    public static final Entry THREE_DES_CTR = new Entry("3des-ctr", "DESede/CTR/NoPadding", CryptoServicesProvider.instance(), 8, 24);

    public static final Entry BLOWFISH_CBC = new Entry("blowfish-cbc", "Blowfish/CBC/NoPadding", CryptoServicesProvider.instance(), 8, 16);
    public static final Entry BLOWFISH_CTR = new Entry("blowfish-ctr", "Blowfish/CTR/NoPadding", CryptoServicesProvider.instance(), 8, 16);

    public static final Entry TWOFISH128_CBC = new Entry("twofish128-cbc", "Twofish/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 16);
    public static final Entry TWOFISH128_CTR = new Entry("twofish128-ctr", "Twofish/CTR/NoPadding", CryptoServicesProvider.instance(), 16, 16);

    public static final Entry TWOFISH192_CBC = new Entry("twofish192-cbc", "Twofish/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 24);
    public static final Entry TWOFISH192_CTR = new Entry("twofish192-ctr", "Twofish/CTR/NoPadding", CryptoServicesProvider.instance(), 16, 24);

    public static final Entry TWOFISH_CBC = new Entry("twofish-cbc", "Twofish/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 32);
    public static final Entry TWOFISH256_CBC = new Entry("twofish256-cbc", "Twofish/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 32);
    public static final Entry TWOFISH256_CTR = new Entry("twofish256-ctr", "Twofish/CTR/NoPadding", CryptoServicesProvider.instance(), 16, 32);

    public static final Entry AES128_CBC = new Entry("aes128-cbc", "AES/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 16);
    public static final Entry AES128_CTR = new Entry("aes128-ctr", "AES/CTR/NoPadding", CryptoServicesProvider.instance(), 16, 16);

    public static final Entry AES192_CBC = new Entry("aes192-cbc", "AES/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 24);
    public static final Entry AES192_CTR = new Entry("aes192-ctr", "AES/CTR/NoPadding", CryptoServicesProvider.instance(), 16, 24);

    public static final Entry AES256_CBC = new Entry("aes256-cbc", "AES/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 32);
    public static final Entry AES256_CTR = new Entry("aes256-ctr", "AES/CTR/NoPadding", CryptoServicesProvider.instance(), 16, 32);

    public static final Entry SERPENT128_CBC = new Entry("serpent128-cbc", "SERPENT/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 16);
    public static final Entry SERPENT128_CTR = new Entry("serpent128-ctr", "SERPENT/CTR/NoPadding", CryptoServicesProvider.instance(), 16, 16);

    public static final Entry SERPENT192_CBC = new Entry("serpent192-cbc", "SERPENT/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 24);
    public static final Entry SERPENT192_CTR = new Entry("serpent192-ctr", "SERPENT/CTR/NoPadding", CryptoServicesProvider.instance(), 16, 24);

    public static final Entry SERPENT256_CBC = new Entry("serpent256-cbc", "SERPENT/CBC/NoPadding", CryptoServicesProvider.instance(), 16, 32);
    public static final Entry SERPENT256_CTR = new Entry("serpent256-ctr", "SERPENT/CTR/NoPadding", CryptoServicesProvider.instance(), 16, 32);

    public static final Entry IDEA_CBC = new Entry("idea-cbc", "IDEA/CBC/NoPadding", CryptoServicesProvider.instance(), 8, 16);
    public static final Entry IDEA_CTR = new Entry("idea-ctr", "IDEA/CTR/NoPadding", CryptoServicesProvider.instance(), 8, 16);

    public static final Entry CAST_CBC = new Entry("cast128-cbc", "CAST5/CBC/NoPadding", CryptoServicesProvider.instance(), 8, 16);
    public static final Entry CAST_CTR = new Entry("cast128-ctr", "CAST5/CTR/NoPadding", CryptoServicesProvider.instance(), 8, 16);

    public static final Entry ARCFOUR = new Entry("arcfour", "ARC4", CryptoServicesProvider.instance(), 0, 16);
    public static final Entry ARCFOUR128 = new Entry("arcfour128", "ARC4", CryptoServicesProvider.instance(), 0, 16);
    public static final Entry ARCFOUR256 = new Entry("arcfour256", "ARC4", CryptoServicesProvider.instance(), 0, 32);

    public static final Entry[] ALL = {
            THREE_DES_CBC,
            THREE_DES_CTR,
            BLOWFISH_CBC,
            BLOWFISH_CTR,
            TWOFISH128_CBC,
            TWOFISH128_CTR,
            TWOFISH192_CBC,
            TWOFISH192_CTR,
            TWOFISH_CBC,
            TWOFISH256_CBC,
            TWOFISH256_CTR,
            AES128_CBC,
            AES128_CTR,
            AES192_CBC,
            AES192_CTR,
            AES256_CBC,
            AES256_CTR,
            SERPENT128_CBC,
            SERPENT128_CTR,
            SERPENT192_CBC,
            SERPENT192_CTR,
            SERPENT256_CBC,
            SERPENT256_CTR,
            IDEA_CBC,
            IDEA_CTR,
            CAST_CBC,
            CAST_CTR,
            ARCFOUR,
            ARCFOUR128,
            ARCFOUR256
    };

    public static final Entry[] FIPS = {
            THREE_DES_CBC,
            THREE_DES_CTR,
            AES128_CBC,
            AES192_CBC,
            AES256_CBC,
            AES128_CTR,
            AES192_CTR,
            AES256_CTR
    };

    public static JceEncryptionFactory init(JceEncryptionFactory factory, Entry... entries) {
        return init(factory, CryptoServicesProvider.instance(), entries);
    }

    public static JceEncryptionFactory init(JceEncryptionFactory factory, String provider, Entry... entries) {
        for (Entry entry : entries) {
            entry.update(factory, provider);
        }

        return factory;
    }

    public static JceEncryptionFactory init(JceEncryptionFactory factory, Provider provider, Entry... entries) {
        for (Entry entry : entries) {
            entry.update(factory, provider);
        }

        return factory;
    }


    /**
     * Setups required encryption algorithms.
     *
     * @param factory a factory
     *
     * @return a factory
     */
    public static JceEncryptionFactory initRequired(JceEncryptionFactory factory) {
        return initRequired(factory, CryptoServicesProvider.instance());
    }

    public static JceEncryptionFactory initRequired(JceEncryptionFactory factory, String provider) {
        return init(factory, provider, THREE_DES_CBC);
    }

    public static JceEncryptionFactory initRequired(JceEncryptionFactory factory, Provider provider) {
        return init(factory, provider, THREE_DES_CBC);
    }

    /**
     * Setups recommended encryption algorithms.
     *
     * @param factory a factory
     *
     * @return a factory
     */
    public static JceEncryptionFactory initRecommended(JceEncryptionFactory factory) {
        return initRecommended(factory, CryptoServicesProvider.instance());
    }

    public static JceEncryptionFactory initRecommended(JceEncryptionFactory factory, String provider) {
        return init(factory, provider, AES128_CBC, AES128_CTR, AES192_CTR, AES256_CTR, THREE_DES_CTR);
    }

    public static JceEncryptionFactory initRecommended(JceEncryptionFactory factory, Provider provider) {
        return init(factory, provider, AES128_CBC, AES128_CTR, AES192_CTR, AES256_CTR, THREE_DES_CTR);
    }

    /**
     * Setups optional encryption algorithms.
     *
     * @param factory a factory
     *
     * @return a factory
     */
    public static JceEncryptionFactory initOptional(JceEncryptionFactory factory) {
        return initOptional(factory, CryptoServicesProvider.instance());
    }

    public static JceEncryptionFactory initOptional(JceEncryptionFactory factory, String provider) {
        return init(factory,
                    provider,
                    BLOWFISH_CBC,
                    BLOWFISH_CTR,
                    TWOFISH_CBC,
                    TWOFISH256_CBC,
                    TWOFISH256_CTR,
                    TWOFISH192_CBC,
                    TWOFISH192_CTR,
                    TWOFISH128_CBC,
                    TWOFISH128_CTR,
                    AES256_CBC,
                    AES192_CBC,
                    SERPENT256_CBC,
                    SERPENT256_CTR,
                    SERPENT192_CBC,
                    SERPENT192_CTR,
                    SERPENT128_CBC,
                    SERPENT128_CTR,
                    IDEA_CBC,
                    IDEA_CTR,
                    CAST_CBC,
                    CAST_CTR,
                    ARCFOUR256,
                    ARCFOUR128,
                    ARCFOUR);
    }

    public static JceEncryptionFactory initOptional(JceEncryptionFactory factory, Provider provider) {
        return init(factory,
                    provider,
                    BLOWFISH_CBC,
                    BLOWFISH_CTR,
                    TWOFISH_CBC,
                    TWOFISH256_CBC,
                    TWOFISH256_CTR,
                    TWOFISH192_CBC,
                    TWOFISH192_CTR,
                    TWOFISH128_CBC,
                    TWOFISH128_CTR,
                    AES256_CBC,
                    AES192_CBC,
                    SERPENT256_CBC,
                    SERPENT256_CTR,
                    SERPENT192_CBC,
                    SERPENT192_CTR,
                    SERPENT128_CBC,
                    SERPENT128_CTR,
                    IDEA_CBC,
                    IDEA_CTR,
                    CAST_CBC,
                    CAST_CTR,
                    ARCFOUR256,
                    ARCFOUR128,
                    ARCFOUR);
    }

    /**
     * Setups all implemented encryption algorithms.
     *
     * @param factory a factory
     *
     * @return a factory
     */
    public static JceEncryptionFactory initAll(JceEncryptionFactory factory) {
        return initOptional(initRecommended(initRequired(factory)));
    }

    public static JceEncryptionFactory initAllClient(JceEncryptionFactory factory) {
        return initAllClient(factory, CryptoServicesProvider.instance());
    }

    public static JceEncryptionFactory initAllClient(JceEncryptionFactory factory, String provider) {
        return init(factory,
                    provider,
                    BLOWFISH_CBC,
                    AES128_CBC,
                    AES192_CBC,
                    AES256_CBC,
                    TWOFISH192_CBC,
                    TWOFISH256_CBC,
                    TWOFISH_CBC,
                    SERPENT128_CBC,
                    SERPENT192_CBC,
                    SERPENT256_CBC,
                    CAST_CBC,
                    IDEA_CBC,
                    THREE_DES_CBC,
                    AES128_CTR,
                    AES192_CTR,
                    AES256_CTR,
                    BLOWFISH_CTR,
                    TWOFISH192_CTR,
                    TWOFISH256_CTR,
                    SERPENT128_CTR,
                    SERPENT192_CTR,
                    SERPENT256_CTR,
                    IDEA_CTR,
                    CAST_CTR,
                    THREE_DES_CTR,
                    ARCFOUR128,
                    ARCFOUR256,
                    ARCFOUR).set(JceEncryptionFactory.Entry.NONE);
    }

    public static JceEncryptionFactory initAllClient(JceEncryptionFactory factory, Provider provider) {
        return init(factory,
                    provider,
                    BLOWFISH_CBC,
                    AES128_CBC,
                    AES192_CBC,
                    AES256_CBC,
                    TWOFISH192_CBC,
                    TWOFISH256_CBC,
                    TWOFISH_CBC,
                    SERPENT128_CBC,
                    SERPENT192_CBC,
                    SERPENT256_CBC,
                    CAST_CBC,
                    IDEA_CBC,
                    THREE_DES_CBC,
                    AES128_CTR,
                    AES192_CTR,
                    AES256_CTR,
                    BLOWFISH_CTR,
                    TWOFISH192_CTR,
                    TWOFISH256_CTR,
                    SERPENT128_CTR,
                    SERPENT192_CTR,
                    SERPENT256_CTR,
                    IDEA_CTR,
                    CAST_CTR,
                    THREE_DES_CTR,
                    ARCFOUR128,
                    ARCFOUR256,
                    ARCFOUR).set(JceEncryptionFactory.Entry.NONE);
    }

    public static JceEncryptionFactory initFipsClient(JceEncryptionFactory factory) {
        return init(factory,
                    CryptoServicesProvider.instance(),
                    AES128_CBC,
                    AES192_CBC,
                    AES256_CBC,
                    AES128_CTR,
                    AES192_CTR,
                    AES256_CTR,
                    THREE_DES_CBC,
                    THREE_DES_CTR);
    }

    private JceEncryptions() {
        // empty
    }

    public static class Entry
            extends JceEncryptionFactory.Entry {

        public Entry(String name, String algorithm, Object provider, int blockLength, int keyLength) {
            super(name, algorithm, provider, blockLength, keyLength);
        }

        public JceEncryptionFactory update(JceEncryptionFactory factory, String provider) {
            JceEncryptionFactory.Entry entry = JceEncryptionFactory.Entry.entryFor(this.name, this.algorithm, provider, this.blockLength, this.keyLength);
            return factory.set(entry);
        }

        public JceEncryptionFactory update(JceEncryptionFactory factory, Provider provider) {
            JceEncryptionFactory.Entry entry = JceEncryptionFactory.Entry.entryFor(this.name, this.algorithm, provider, this.blockLength, this.keyLength);
            return factory.set(entry);
        }
    }
}
