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

import com.jscape.util.crypto.CryptoServicesProvider;

import java.security.Provider;

/**
 * JCE-based MAC algorithms.
 *
 * @author Alex Usun
 */
public class JceMacs {

    public static final Entry HMAC_SHA1 = new Entry("hmac-sha1", "HmacSha1", CryptoServicesProvider.instance(), 20, 20);
    public static final Entry HMAC_SHA1_96 = new Entry("hmac-sha1-96", "HmacSha1", CryptoServicesProvider.instance(), 12, 20);
    public static final Entry HMAC_MD5 = new Entry("hmac-md5", "HmacMD5", CryptoServicesProvider.instance(), 16, 16);
    public static final Entry HMAC_MD5_96 = new Entry("hmac-md5-96", "HmacMD5", CryptoServicesProvider.instance(), 12, 16);

    public static final Entry HMAC_SHA2_256 = new Entry("hmac-sha2-256", "HMACSHA256", CryptoServicesProvider.instance(), 32, 32);
    public static final Entry HMAC_SHA2_256_96 = new Entry("hmac-sha2-256-96", "HMACSHA256", CryptoServicesProvider.instance(), 12, 32);
    public static final Entry HMAC_SHA2_512 = new Entry("hmac-sha2-512", "HMACSHA512", CryptoServicesProvider.instance(), 64, 64);
    public static final Entry HMAC_SHA2_512_96 = new Entry("hmac-sha2-512-96", "HMACSHA512", CryptoServicesProvider.instance(), 12, 64);

    public static final Entry[] ALL = {
            HMAC_SHA1,
            HMAC_SHA1_96,
            HMAC_MD5,
            HMAC_MD5_96,
            HMAC_SHA2_256,
            HMAC_SHA2_256_96,
            HMAC_SHA2_512,
            HMAC_SHA2_512_96
    };

    public static final Entry[] FIPS = {
            HMAC_SHA1,
            HMAC_SHA1_96,
            HMAC_SHA2_256,
            HMAC_SHA2_256_96,
            HMAC_SHA2_512,
            HMAC_SHA2_512_96
    };

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

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

        return factory;
    }

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

        return factory;
    }

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

    public static JceMacFactory initRequired(JceMacFactory factory, String provider) {
        return init(factory, provider, HMAC_SHA1);
    }

    public static JceMacFactory initRequired(JceMacFactory factory, Provider provider) {
        return init(factory, provider, HMAC_SHA1);
    }

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

    public static JceMacFactory initRecommended(JceMacFactory factory, String provider) {
        return init(factory, provider, HMAC_SHA2_256, HMAC_SHA1_96);
    }

    public static JceMacFactory initRecommended(JceMacFactory factory, Provider provider) {
        return init(factory, provider, HMAC_SHA2_256, HMAC_SHA1_96);
    }

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

    public static JceMacFactory initOptional(JceMacFactory factory, String provider) {
        return init(factory, provider, HMAC_SHA2_256_96, HMAC_SHA2_512, HMAC_SHA2_512_96, HMAC_MD5, HMAC_MD5_96);
    }

    public static JceMacFactory initOptional(JceMacFactory factory, Provider provider) {
        return init(factory, provider, HMAC_SHA2_256_96, HMAC_SHA2_512, HMAC_SHA2_512_96, HMAC_MD5, HMAC_MD5_96);
    }

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

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

    public static JceMacFactory initAllClient(JceMacFactory factory, String provider) {
        return init(factory, provider, HMAC_MD5, HMAC_SHA2_256_96, HMAC_SHA2_256, HMAC_SHA2_512_96, HMAC_SHA2_512, HMAC_SHA1_96, HMAC_SHA1, HMAC_MD5_96).set(JceMacFactory.Entry.NONE);
    }

    public static JceMacFactory initAllClient(JceMacFactory factory, Provider provider) {
        return init(factory, provider, HMAC_MD5, HMAC_SHA2_256_96, HMAC_SHA2_256, HMAC_SHA2_512_96, HMAC_SHA2_512, HMAC_SHA1_96, HMAC_SHA1, HMAC_MD5_96).set(JceMacFactory.Entry.NONE);
    }

    public static JceMacFactory initFipsClient(JceMacFactory factory) {
        return init(factory,
                    CryptoServicesProvider.instance(),
                    HMAC_SHA2_256,
                    HMAC_SHA2_256_96,
                    HMAC_SHA2_512,
                    HMAC_SHA2_512_96,
                    HMAC_SHA1,
                    HMAC_SHA1_96);
    }

    private JceMacs() {
        // empty
    }

    public static class Entry
            extends JceMacFactory.Entry {

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

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

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