/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _DECAF_SECURITY_MESSAGEDIGEST_H_
#define _DECAF_SECURITY_MESSAGEDIGEST_H_

#include <decaf/util/Config.h>

#include <decaf/security/MessageDigestSpi.h>
#include <decaf/nio/ByteBuffer.h>

#include <string>

namespace decaf {
namespace security {

    class MessageDigestSpi;
    class MessageDigestImpl;
    class Provider;

    /**
     * This MessageDigest class provides applications the functionality of a message digest
     * algorithm, such as MD5 or SHA. Message digests are secure one-way hash functions that
     * take arbitrary-sized data and output a fixed-length hash value.
     *
     * A MessageDigest object starts out initialized. The data is processed through it using
     * the update methods. At any point reset can be called to reset the digest. Once all the
     * data to be updated has been updated, one of the digest methods should be called to
     * complete the hash computation.
     *
     * The digest method can be called once for a given number of updates. After digest has
     * been called, the MessageDigest object is reset to its initialized state.
     *
     * Implementations are free to implement the clone method.  Client applications can test
     * cloneability by attempting cloning and catching the CloneNotSupportedException:
     *
     *   MessageDigest* md = MessageDigest::getInstance("SHA");
     *
     *   try {
     *       md->update(toChapter1);
     *       MessageDigest* tc1 = md.clone();
     *       byte[] toChapter1Digest = tc1.digest();
     *       md.update(toChapter2);
     *       ...etc.
     *   } catch (CloneNotSupportedException& ex) {
     *       throw DigestException("couldn't make digest of partial content");
     *   }
     *
     * Note that if a given implementation is not cloneable, it is still possible to compute
     * intermediate digests by instantiating several instances, if the number of digests is
     * known in advance.
     *
     * @see MessageDigestSpi
     *
     * @since 1.0
     */
    class DECAF_API MessageDigest {
    private:

        MessageDigestImpl* impl;

        MessageDigestSpi* spi;
        const Provider* provider;
        std::string algorithm;

    private:

        MessageDigest(const MessageDigest&);
        MessageDigest& operator= (const MessageDigest&);

    protected:

        MessageDigest(const std::string& name);

    public:

        virtual ~MessageDigest();

        /**
         * Completes the hash computation by performing final operations such as padding.
         * The digest is reset after this call is made.
         */
        std::vector<unsigned char> digest();

        /**
         * Completes the hash computation by performing final operations such as padding.
         * The digest is reset after this call is made.
         *
         * @param input
         *      The output buffer for the computed digest.
         * @param size
         *      The size of the given input buffer.
         * @param offset
         *      The offset into the output buffer to begin storing the digest.
         * @param length
         *      The number of bytes within buf allotted for the digest.
         *
         * @return the number of bytes placed into buffer.
         *
         * @throws DigestException if an error occurs.
         */
        int digest(unsigned char* input, int size, int offset, int length);

        /**
         * Performs a final update on the digest using the specified array of bytes, then
         * completes the digest computation. That is, this method first calls update(input),
         * passing the input array to the update method, then calls digest().
         *
         * @param input
         *      The input to be updated before the digest is completed.
         * @param size
         *      The length in bytes of the input buffer.
         *
         * @return the array of bytes for the resulting hash value.
         */
        std::vector<unsigned char> digest(const unsigned char* input, int size);

        /**
         * Performs a final update on the digest using the specified array of bytes, then
         * completes the digest computation. That is, this method first calls update(input),
         * passing the input array to the update method, then calls digest().
         *
         * @param input
         *      The input to be updated before the digest is completed.
         *
         * @return the array of bytes for the resulting hash value.
         */
        std::vector<unsigned char> digest(const std::vector<unsigned char>& input);

        /**
         * Returns a string that identifies the algorithm, independent of implementation
         * details. The name should be a standard name (such as "SHA", "MD5", etc).
         *
         * @return the algorithm name.
         */
        std::string getAlgorithmName() const {
            return this->algorithm;
        }

        /**
         * Returns the Provider associated with this MessageDigest.
         *
         * The pointer returned by this method remains the property of the Security framework
         * and should be deleted by the calling application at any time.
         *
         * @return the provider associated with this MessageDigest.
         */
        const Provider* getProvider() const {
            return this->provider;
        }

        /**
         * Returns the length of the digest in bytes, or 0 if this operation is not supported
         * by the provider and the implementation is not cloneable.
         *
         * @return the digest length in bytes, or 0 if this operation is not supported by the
         *          provider and the implementation is not cloneable.
         */
        int getDigestLength() const;

        /**
         * Returns a clone of this MessageDisgest instance if the MessageDigestSpi in use
         * is cloneable.
         *
         * @return a clone of this MessageDigest if possible.
         *
         * @throws CloneNotSupportedException if the SPI in use cannot be cloned.
         */
        MessageDigest* clone();

        /**
         * Resets the digest for further use.
         */
        void reset();

        /**
         * Returns a string representation of this message digest object.
         *
         * @return a string representation of this message digest object.
         */
        std::string toString() const;

        /**
         * Updates the digest using the specified byte.
         *
         * @param input
         *      The byte with which to update the digest.
         */
        void update(unsigned char input);

        /**
         * Updates the digest using the specified array of bytes, starting at the specified offset.
         *
         * @param input
         *      The array of bytes.
         * @param size
         *      The size of the given input buffer.
         * @param offset
         *      The offset to start from in the array of bytes.
         * @param length
         *      The number of bytes to use, starting at offset.
         */
        void update(unsigned char* input, int size, int offset, int length);

        /**
         * Updates the digest using the specified array of bytes.
         *
         * @param input
         *      The array of bytes to use for the update.
         */
        void update(const std::vector<unsigned char>& input);

        /**
         * Update the digest using the specified ByteBuffer. The digest is updated using
         * the input.remaining() bytes starting at input.position(). Upon return, the
         * buffer's position will be equal to its limit; its limit will not have changed.
         *
         * @param input
         *      The input ByteBuffer to use for the update.
         */
        void update(nio::ByteBuffer& input);

    public:

        /**
         * Returns a MessageDigest object that implements the specified digest algorithm.
         *
         * This method traverses the list of registered security Providers, starting with the
         * most preferred Provider. A new MessageDigest object encapsulating the MessageDigestSpi
         * implementation from the first Provider that supports the specified algorithm is returned.
         *
         * Note that the list of registered providers may be retrieved via the
         * Security.getProviders() method.
         *
         * @param algorithm
         *      The name of the algorithm requested. (MD5, SHA-1, etc)
         *
         * @return a Message Digest object that implements the specified algorithm.
         * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation
         *         for the specified algorithm.
         */
        static MessageDigest* getInstance(const std::string& algorithm);

        /**
         * Compares two digests for equality. Does a simple byte compare.
         *
         * @param digesta
         *      The first digest for comparison.
         * @param digestb
         *      The second digest for comparison.
         *
         * @return true if the two digests are equal.
         */
        static bool isEqual(const std::vector<unsigned char>& digesta,
                            const std::vector<unsigned char>& digestb);

    };

}}

#endif /* _DECAF_SECURITY_MESSAGEDIGEST_H_ */
