package com.jscape.inet.sftp;

import com.jscape.crypto.Hash;
import com.jscape.crypto.JceHash;
import com.jscape.util.crypto.CryptoServicesProvider;
import com.jscape.util.io.ByteArrayOutputStream;

import java.io.InputStream;
import java.util.List;
import java.util.Locale;

/**
 * @author Alex Usun
 */
public enum Checksum {

    CRC32("crc32") {
        @Override
        protected Hash hash()
                throws Exception {
            return com.jscape.crypto.CRC32.bigEndianHash();
        }
    },
    MD5("md5") {
        @Override
        protected Hash hash()
                throws Exception {
            return com.jscape.crypto.MD5.defaultHash();
        }
    },
    SHA1("sha1") {
        @Override
        protected Hash hash()
                throws Exception {
            return com.jscape.crypto.SHA1.defaultHash();
        }
    },
    SHA224("sha224") {
        @Override
        protected Hash hash()
                throws Exception {
            return JceHash.hashFor(this.code.toUpperCase(Locale.ENGLISH), CryptoServicesProvider.jsseProvider());
        }
    },
    SHA256("sha256") {
        @Override
        protected Hash hash()
                throws Exception {
            return JceHash.hashFor(this.code.toUpperCase(Locale.ENGLISH), CryptoServicesProvider.jsseProvider());
        }
    },
    SHA384("sha384") {
        @Override
        protected Hash hash()
                throws Exception {
            return JceHash.hashFor(this.code.toUpperCase(Locale.ENGLISH), CryptoServicesProvider.jsseProvider());
        }
    },
    SHA512("sha512") {
        @Override
        protected Hash hash()
                throws Exception {
            return JceHash.hashFor(this.code.toUpperCase(Locale.ENGLISH), CryptoServicesProvider.jsseProvider());
        }
    };

    private static final int BUFFER_SIZE = 8192;

    public final String code;

    public static Checksum firstSupportedFrom(List<String> algorithms) {
        for (String algorithm : algorithms) {
            for (Checksum candidate : values()) {
                if (candidate.code.equalsIgnoreCase(algorithm)) {
                    return candidate;
                }
            }
        }

        throw new IllegalArgumentException("Common hash algorithm not found.");
    }

    Checksum(String code) {
        this.code = code;
    }

    public byte[] of(InputStream in, long length, int blockSize)
            throws Exception {
        Hash hash = hash();
        return of(in, length, blockSize, hash);
    }

    protected abstract Hash hash()
            throws Exception;

    protected byte[] of(InputStream in, long length, int blockSize, Hash hash)
            throws Exception {
        if (blockSize == 0) {
            return of(in, length, hash);
        }

        ByteArrayOutputStream hashBuffer = new ByteArrayOutputStream();

        while (length > 0) {
            int dataLength = (int) Math.min(length, blockSize);
            byte[] blockHash = of(in, dataLength, hash);
            length -= dataLength;
            hashBuffer.write(blockHash);
        }

        return hashBuffer.toByteArray();
    }

    protected byte[] of(InputStream in, long totalLength, Hash hash)
            throws Exception {
        byte[] buf = new byte[BUFFER_SIZE];
        int read;
        int length = (int) Math.min(buf.length, totalLength);

        while ((read = in.read(buf, 0, length)) != -1 && totalLength > 0) {
            totalLength -= read;
            hash.update(buf, 0, read);
            length = (int) Math.min(buf.length, totalLength);
        }

        return hash.compute();
    }
}
